getEm(); // Nettoyage defensif au cas ou un run precedent aurait laisse des restes. $this->cleanupTestPermissions(); // Donnees de test : deux permissions "core" dont une orpheline, // plus une permission d'un autre module pour verifier le filtre. $p1 = new Permission('test.core.users.view', 'View users (test)', 'core'); $p2 = new Permission('test.core.users.manage', 'Manage users (test)', 'core'); $p3 = new Permission('test.commercial.clients.view', 'View clients (test)', 'commercial'); $p2->markOrphan(); $em->persist($p1); $em->persist($p2); $em->persist($p3); $em->flush(); $em->clear(); } protected function tearDown(): void { $this->cleanupTestPermissions(); parent::tearDown(); } public function testGetCollectionAsAdminReturns200(): void { $client = $this->authenticatedClient('admin', 'admin'); $response = $client->request('GET', '/api/permissions'); self::assertResponseIsSuccessful(); $data = $response->toArray(); // API Platform 4 emet du JSON-LD 1.1 avec un @context qui utilise un // @vocab : les cles sortent donc non prefixees (`member`, `totalItems`) // au lieu des anciennes `hydra:member` / `hydra:totalItems`. self::assertArrayHasKey('member', $data); self::assertGreaterThanOrEqual(3, $data['totalItems']); } public function testCollectionFilterByModule(): void { $client = $this->authenticatedClient('admin', 'admin'); $response = $client->request('GET', '/api/permissions', [ 'query' => ['module' => 'core'], ]); self::assertResponseIsSuccessful(); $data = $response->toArray(); foreach ($data['member'] as $item) { self::assertSame('core', $item['module']); } // Doit contenir au moins nos deux permissions core de test. $codes = array_column($data['member'], 'code'); self::assertContains('test.core.users.view', $codes); self::assertContains('test.core.users.manage', $codes); self::assertNotContains('test.commercial.clients.view', $codes); } public function testCollectionFilterByOrphanTrue(): void { $client = $this->authenticatedClient('admin', 'admin'); $response = $client->request('GET', '/api/permissions', [ 'query' => ['orphan' => 'true'], ]); self::assertResponseIsSuccessful(); $data = $response->toArray(); foreach ($data['member'] as $item) { self::assertTrue($item['orphan']); } $codes = array_column($data['member'], 'code'); // La permission marquee orpheline dans setUp() doit remonter... self::assertContains('test.core.users.manage', $codes); // ...et celles non orphelines doivent etre exclues. self::assertNotContains('test.core.users.view', $codes); self::assertNotContains('test.commercial.clients.view', $codes); } public function testCollectionFilterByOrphanFalse(): void { $client = $this->authenticatedClient('admin', 'admin'); $response = $client->request('GET', '/api/permissions', [ 'query' => ['orphan' => 'false'], ]); self::assertResponseIsSuccessful(); $data = $response->toArray(); foreach ($data['member'] as $item) { self::assertFalse($item['orphan']); } $codes = array_column($data['member'], 'code'); self::assertContains('test.core.users.view', $codes); self::assertNotContains('test.core.users.manage', $codes); } public function testGetItemAsAdminReturnsAllReadFields(): void { /** @var null|Permission $permission */ $permission = $this->getEm()->getRepository(Permission::class) ->findOneBy(['code' => 'test.core.users.view']) ; self::assertNotNull($permission); $client = $this->authenticatedClient('admin', 'admin'); $response = $client->request('GET', '/api/permissions/'.$permission->getId()); self::assertResponseIsSuccessful(); $data = $response->toArray(); self::assertSame($permission->getId(), $data['id']); self::assertSame('test.core.users.view', $data['code']); self::assertSame('View users (test)', $data['label']); self::assertSame('core', $data['module']); self::assertFalse($data['orphan']); } public function testPostIsMethodNotAllowed(): void { $client = $this->authenticatedClient('admin', 'admin'); $client->request('POST', '/api/permissions', [ 'headers' => ['Content-Type' => 'application/ld+json'], 'json' => ['code' => 'test.foo.bar.baz', 'label' => 'Foo', 'module' => 'foo'], ]); self::assertResponseStatusCodeSame(405); } public function testUnauthenticatedReturns401(): void { $client = self::createClient(); $client->request('GET', '/api/permissions'); self::assertResponseStatusCodeSame(401); } public function testStandardUserWithoutPermissionIsForbiddenOnCollection(): void { // Le catalogue de permissions est protege par `core.permissions.view` : // un user authentifie sans cette permission (ni flag admin) doit // recevoir un 403. Alice n'a que le role systeme "user" (zero // permission attachee) — elle est le cobaye ideal pour ce test. $client = $this->authenticatedClient('alice', 'alice'); $client->request('GET', '/api/permissions'); self::assertResponseStatusCodeSame(403); } public function testStandardUserWithoutPermissionIsForbiddenOnItem(): void { $permission = $this->getEm()->getRepository(Permission::class) ->findOneBy(['code' => 'test.core.users.view']) ; self::assertNotNull($permission); $client = $this->authenticatedClient('alice', 'alice'); $client->request('GET', '/api/permissions/'.$permission->getId()); self::assertResponseStatusCodeSame(403); } public function testNonAdminWithPermissionViewCanListPermissions(): void { // Chemin positif : un user non-admin qui porte la permission // `core.permissions.view` (via un role dedie) doit recevoir 200 sur // le catalogue. Couvre l'habilitation sans bypass isAdmin. $creds = $this->createUserWithPermission('core.permissions.view'); $client = $this->authenticatedClient($creds['username'], $creds['password']); $client->request('GET', '/api/permissions'); self::assertResponseIsSuccessful(); } public function testNonAdminWithUsersManageCanListPermissions(): void { // Bypass pragmatique : les gestionnaires d'users ont besoin du // catalogue pour les drawers RBAC. Couvre la branche OR de la // security expression `core.users.manage`. $creds = $this->createUserWithPermission('core.users.manage'); $client = $this->authenticatedClient($creds['username'], $creds['password']); $client->request('GET', '/api/permissions'); self::assertResponseIsSuccessful(); } public function testNonAdminWithRolesManageCanListPermissions(): void { // Meme logique que ci-dessus pour les gestionnaires de roles // (branche OR `core.roles.manage` de la security expression). $creds = $this->createUserWithPermission('core.roles.manage'); $client = $this->authenticatedClient($creds['username'], $creds['password']); $client->request('GET', '/api/permissions'); self::assertResponseIsSuccessful(); } private function cleanupTestPermissions(): void { $em = $this->getEm(); // Purge des users et roles jetables crees par createUserWithPermission(). $em->createQuery( 'DELETE FROM '.User::class.' u WHERE u.username LIKE :prefix' )->setParameter('prefix', 'testuser_%')->execute(); $em->createQuery( 'DELETE FROM '.Role::class.' r WHERE r.code LIKE :prefix' )->setParameter('prefix', 'test_%')->execute(); $em->createQuery( 'DELETE FROM '.Permission::class.' p WHERE p.code LIKE :prefix' )->setParameter('prefix', self::TEST_CODE_PREFIX.'%')->execute(); } }