feat(permissions): expose the registry over GET /api/v1/permission-catalog

Both permission forms in the admin panel can now render from the backend
registry instead of their own hardcoded lists. Resources come back as an array
so display order is part of the contract, each carrying its Persian label, its
actions, and the clinic_only flag that used to live in the frontend.

contextPermissions() normalizes the no-row branch through the registry too, so
a doctor whose permission row was never provisioned sees the same shape as one
who has it.

Two existing assertions compared the API response against DEFAULT_PERMISSIONS
by identity. The values are unchanged; only key order moved to the registry's,
so both now compare through PermissionCatalog::merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-07 17:39:45 +03:30
co-authored by Claude Opus 5
parent aff7b7fd4a
commit 5211b34d0e
8 changed files with 264 additions and 3 deletions
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Tests\Shared;
use App\Shared\Security\PermissionCatalog;
use App\Tests\ApiTestCase;
/**
* اندپوینتِ کاتالوگ — منبعی که هر دو فرمِ مجوز از آن رندر می‌شوند.
*/
class PermissionCatalogApiTest extends ApiTestCase
{
public function testAuthenticatedUserGetsEveryRegistryResource(): void
{
$user = $this->createUser(['ROLE_USER']);
$res = $this->authJson('GET', '/api/v1/permission-catalog', $user);
self::assertSame(200, $this->responseCode());
self::assertSame(
array_keys(PermissionCatalog::RESOURCES),
array_column($res['data']['resources'], 'key'),
'کاتالوگ باید همان منابع رجیستری و با همان ترتیب باشد',
);
}
public function testEveryResourceCarriesLabelAndActions(): void
{
$user = $this->createUser(['ROLE_USER']);
$res = $this->authJson('GET', '/api/v1/permission-catalog', $user);
foreach ($res['data']['resources'] as $resource) {
self::assertNotSame('', $resource['label'], "منبع {$resource['key']} برچسب ندارد");
self::assertNotEmpty($resource['actions'], "منبع {$resource['key']} اکشن ندارد");
foreach ($resource['actions'] as $action) {
self::assertArrayHasKey('key', $action);
self::assertNotSame('', $action['label']);
}
}
}
public function testClinicOnlyFlagIsExposed(): void
{
$user = $this->createUser(['ROLE_USER']);
$res = $this->authJson('GET', '/api/v1/permission-catalog', $user);
$byKey = array_column($res['data']['resources'], 'clinic_only', 'key');
self::assertTrue($byKey['clinic_doctors'], 'مدیریت پزشکان کلینیک فقط برای مالکِ کلینیک است');
self::assertFalse($byKey['appointments']);
}
public function testAnonymousIsRejected(): void
{
$this->client->request('GET', '/api/v1/permission-catalog');
self::assertSame(401, $this->responseCode());
}
}