newInstanceWithoutConstructor(); (new \ReflectionProperty(ClinicDoctorPermission::class, 'permissions')) ->setValue($perm, ClinicDoctorPermission::DEFAULT_PERMISSIONS); return $perm; } private function secretary(): DoctorSecretary { $ref = new \ReflectionClass(DoctorSecretary::class); return $ref->newInstanceWithoutConstructor(); } // ── پزشکِ عضو کلینیک ────────────────────────────────────────────────── /** تا پیش از این services فقط view/update داشت و create بی‌صدا دور ریخته می‌شد. */ public function testClinicDoctorCanNowStoreServicesCreate(): void { $perm = $this->clinicPermission(); $perm->mergePermissions(['resources' => ['services' => ['create' => true, 'delete' => true]]]); $this->assertTrue($perm->can('services', 'create')); $this->assertTrue($perm->can('services', 'delete')); } public function testClinicDoctorDropsUnknownResourceWithoutTouchingTheRest(): void { $perm = $this->clinicPermission(); $perm->mergePermissions(['resources' => [ 'not_a_resource' => ['view' => true], 'patients' => ['delete' => true], ]]); $this->assertArrayNotHasKey('not_a_resource', $perm->getPermissions()['resources']); $this->assertTrue($perm->can('patients', 'delete')); } public function testClinicDoctorPermissionsAlwaysExposeFullRegistry(): void { $resources = $this->clinicPermission()->getPermissions()['resources']; $this->assertSame(array_keys(PermissionCatalog::RESOURCES), array_keys($resources)); } // ── منشی ───────────────────────────────────────────────────────────── /** صفحهٔ ادمین نقشهٔ تخت می‌فرستد؛ تا پیش از این کل patch نادیده می‌رفت. */ public function testSecretaryAcceptsFlatPatchWithoutEnvelope(): void { $secretary = $this->secretary(); $secretary->mergePermissions(['patients' => ['update' => true]]); $this->assertTrue($secretary->getPermissions()['resources']['patients']['update']); } public function testSecretaryAcceptsEnvelopePatch(): void { $secretary = $this->secretary(); $secretary->mergePermissions(['version' => 1, 'resources' => ['sms' => ['view' => true]]]); $this->assertTrue($secretary->getPermissions()['resources']['sms']['view']); } /** تا پیش از این منشی هیچ اعتبارسنجی نداشت و هر کلیدی ذخیره می‌شد. */ public function testSecretaryDropsUnknownKeys(): void { $secretary = $this->secretary(); $secretary->mergePermissions(['resources' => [ 'ghost_resource' => ['view' => true], 'patients' => ['teleport' => true], ]]); $resources = $secretary->getPermissions()['resources']; $this->assertArrayNotHasKey('ghost_resource', $resources); $this->assertArrayNotHasKey('teleport', $resources['patients']); } /** * ردیفِ ساخته‌شده پیش از افزودن treatment به رجیستری — نباید null یا خطا بدهد * و باید پیش‌فرضِ نقش را بگیرد. */ public function testLegacyStoredJsonGetsRegistryDefaultsForNewResources(): void { $secretary = $this->secretary(); $legacy = new \ReflectionProperty(DoctorSecretary::class, 'permissions'); $legacy->setValue($secretary, ['version' => 1, 'resources' => [ 'appointments' => ['view' => true, 'create' => true, 'cancel' => false, 'update_status' => true], ]]); $resources = $secretary->getPermissions()['resources']; $this->assertTrue($resources['treatment']['view'], 'treatment باید پیش‌فرضِ نقش منشی را بگیرد'); $this->assertFalse($resources['resources']['view']); $this->assertTrue($resources['appointments']['view'], 'مقدارِ ذخیره‌شده نباید عوض شود'); } public function testExplicitlyDisabledStoredValueSurvivesMerge(): void { $secretary = $this->secretary(); $stored = new \ReflectionProperty(DoctorSecretary::class, 'permissions'); $stored->setValue($secretary, ['version' => 1, 'resources' => [ 'appointments' => ['view' => false], ]]); $this->assertFalse( $secretary->getPermissions()['resources']['appointments']['view'], 'خاموشیِ صریح نباید با پیش‌فرضِ روشنِ نقش بازنویسی شود', ); } }