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
+54
View File
@@ -106,4 +106,58 @@ class SecretaryFieldsTest extends ApiTestCase
$this->assertSame(422, $this->responseCode());
}
/**
* صفحهٔ ادمین نقشهٔ تخت می‌فرستد — بدون envelope. تا پیش از رجیستری، کنترلر
* آن را می‌پذیرفت ولی Entity فقط $patch['resources'] را می‌خواند، پس ویرایش
* دسترسی بی‌صدا هیچ اثری نداشت.
*/
public function testUpdateAcceptsFlatPermissionMapFromAdminPage(): void
{
[$owner, $doctor] = $this->makeDoctor();
$mobile = '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT);
$created = $this->authJson('POST', '/api/v1/secretary', $owner, [
'doctor_uuid' => $doctor->getUuid(),
'mobile_number' => $mobile,
'name' => 'منشی تخت',
]);
$uuid = ($created['data']['data'] ?? $created['data'])['uuid'];
$res = $this->authJson('PATCH', "/api/v1/secretary/{$uuid}", $owner, [
'permissions' => ['staff' => ['view' => true, 'create' => true]],
]);
$this->assertSame(200, $this->responseCode());
$perms = ($res['data']['data'] ?? $res['data'])['permissions'];
$this->assertTrue($perms['staff']['view']);
$this->assertTrue($perms['staff']['create']);
$this->assertTrue($perms['appointments']['view'], 'بقیهٔ منابع نباید دست بخورند');
}
/** منشی تا پیش از این هیچ اعتبارسنجی نداشت و هر کلیدی را ذخیره می‌کرد. */
public function testUpdateDropsResourcesOutsideTheRegistry(): void
{
[$owner, $doctor] = $this->makeDoctor();
$mobile = '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT);
$created = $this->authJson('POST', '/api/v1/secretary', $owner, [
'doctor_uuid' => $doctor->getUuid(),
'mobile_number' => $mobile,
'name' => 'منشی ناشناخته',
]);
$uuid = ($created['data']['data'] ?? $created['data'])['uuid'];
$res = $this->authJson('PATCH', "/api/v1/secretary/{$uuid}", $owner, [
'permissions' => ['resources' => [
'ghost_resource' => ['view' => true],
'tags' => ['view' => true],
]],
]);
$this->assertSame(200, $this->responseCode());
$perms = ($res['data']['data'] ?? $res['data'])['permissions'];
$this->assertArrayNotHasKey('ghost_resource', $perms);
$this->assertTrue($perms['tags']['view']);
}
}