assertSame( array_keys(PermissionCatalog::RESOURCES), array_keys($blank['resources']), 'blank() باید دقیقاً همان منابع رجیستری را داشته باشد', ); foreach (PermissionCatalog::RESOURCES as $resource => $meta) { foreach (array_keys($meta['actions']) as $action) { $this->assertFalse( $blank['resources'][$resource][$action], "{$resource}.{$action} باید در blank خاموش باشد", ); } } } public function testMergeKeepsStoredValuesUntouched(): void { $stored = ['version' => 1, 'resources' => [ 'appointments' => ['view' => true, 'create' => false], ]]; $defaults = ['version' => 1, 'resources' => [ 'appointments' => ['view' => false, 'create' => true], ]]; $merged = PermissionCatalog::merge($stored, $defaults); $this->assertTrue($merged['resources']['appointments']['view']); $this->assertFalse( $merged['resources']['appointments']['create'], 'مقدارِ صریحِ false در stored نباید با پیش‌فرضِ true بازنویسی شود', ); } public function testMergeFillsResourceMissingFromStoredWithRoleDefault(): void { $stored = ['version' => 1, 'resources' => ['appointments' => ['view' => true]]]; $defaults = ['version' => 1, 'resources' => ['treatment' => ['view' => true, 'update' => false]]]; $merged = PermissionCatalog::merge($stored, $defaults); $this->assertTrue( $merged['resources']['treatment']['view'], 'منبعی که بعد از ساختِ ردیف به رجیستری اضافه شده باید پیش‌فرضِ نقش را بگیرد', ); $this->assertFalse($merged['resources']['treatment']['update']); } public function testMergeFallsBackToFalseWhenNeitherStoredNorDefaultHasIt(): void { $merged = PermissionCatalog::merge([], []); foreach (PermissionCatalog::RESOURCES as $resource => $meta) { foreach (array_keys($meta['actions']) as $action) { $this->assertFalse($merged['resources'][$resource][$action]); } } } public function testFilterPatchDropsUnknownResourcesAndActions(): void { $clean = PermissionCatalog::filterPatch([ 'appointments' => ['view' => true, 'teleport' => true], 'unknown_thing' => ['view' => true], 'patients' => 'not-an-array', ]); $this->assertSame(['appointments' => ['view' => true]], $clean); } /** پیش‌فرضِ هر نقش فقط حق دارد از منابعِ رجیستری حرف بزند. */ public function testRoleDefaultsUseOnlyKnownResourcesAndActions(): void { $roles = [ 'secretary' => DoctorSecretary::DEFAULT_PERMISSIONS, 'clinic_doctor' => ClinicDoctorPermission::DEFAULT_PERMISSIONS, ]; foreach ($roles as $role => $defaults) { foreach ($defaults['resources'] as $resource => $actions) { $this->assertTrue( PermissionCatalog::hasResource($resource), "منبع «{$resource}» در پیش‌فرضِ {$role} هست ولی در رجیستری نیست", ); foreach (array_keys($actions) as $action) { $this->assertTrue( PermissionCatalog::hasAction($resource, $action), "اکشن «{$resource}.{$action}» در پیش‌فرضِ {$role} هست ولی در رجیستری نیست", ); } } } } public function testApiArrayKeepsRegistryOrder(): void { $api = PermissionCatalog::toApiArray(); $this->assertSame( array_keys(PermissionCatalog::RESOURCES), array_column($api, 'key'), ); $this->assertTrue( $api[array_search('clinic_doctors', array_column($api, 'key'), true)]['clinic_only'], ); } }