cleanupTestSites(); } protected function tearDown(): void { $this->cleanupTestSites(); parent::tearDown(); } public function testAdminCanListSites(): void { $client = $this->authenticatedClient('admin', 'admin'); $response = $client->request('GET', '/api/sites'); self::assertResponseIsSuccessful(); $data = $response->toArray(); self::assertGreaterThanOrEqual(3, $data['totalItems']); } public function testUserWithSitesViewCanListSites(): void { $this->skipIfSitesModuleDisabled(); $credentials = $this->createUserWithPermission('sites.view'); $client = $this->authenticatedClient($credentials['username'], $credentials['password']); $client->request('GET', '/api/sites'); self::assertResponseIsSuccessful(); } public function testUserWithoutPermissionGetsForbidden(): void { // alice a la permission via son role "user" ? Non : le role user par // defaut n'a aucune permission. Elle ne peut donc pas lister. $client = $this->authenticatedClient('alice', 'alice'); $client->request('GET', '/api/sites'); self::assertResponseStatusCodeSame(403); } public function testUnauthenticatedGetCollectionReturns401(): void { $client = self::createClient(); $client->request('GET', '/api/sites'); self::assertResponseStatusCodeSame(401); } public function testAdminCanCreateSite(): void { $client = $this->authenticatedClient('admin', 'admin'); $response = $client->request('POST', '/api/sites', [ 'headers' => ['Content-Type' => 'application/ld+json'], 'json' => [ 'name' => 'Test-New-Site', 'street' => '1 rue du Test', 'complement' => null, 'postalCode' => '86000', 'city' => 'Poitiers', 'color' => '#AABBCC', ], ]); self::assertResponseStatusCodeSame(201); $data = $response->toArray(); self::assertSame('Test-New-Site', $data['name']); self::assertSame('#AABBCC', $data['color']); } public function testAdminCanPatchSite(): void { $em = $this->getEm(); $site = new Site('Test-Patch-Site', '1 rue Test', null, '86000', 'Poitiers', '#000000'); $em->persist($site); $em->flush(); $client = $this->authenticatedClient('admin', 'admin'); $response = $client->request('PATCH', '/api/sites/'.$site->getId(), [ 'headers' => ['Content-Type' => 'application/merge-patch+json'], 'json' => ['color' => '#FF0000'], ]); self::assertResponseIsSuccessful(); $data = $response->toArray(); self::assertSame('#FF0000', $data['color']); } public function testAdminCanDeleteSite(): void { $em = $this->getEm(); $site = new Site('Test-Delete-Site', '1 rue Test', null, '86000', 'Poitiers', '#000000'); $em->persist($site); $em->flush(); $siteId = $site->getId(); $client = $this->authenticatedClient('admin', 'admin'); $client->request('DELETE', '/api/sites/'.$siteId); self::assertResponseStatusCodeSame(204); $em->clear(); self::assertNull($em->getRepository(Site::class)->find($siteId)); } public function testUserWithViewButNotManageCannotDelete(): void { $em = $this->getEm(); $site = new Site('Test-Protected', '1 rue Test', null, '86000', 'Poitiers', '#000000'); $em->persist($site); $em->flush(); $this->skipIfSitesModuleDisabled(); $credentials = $this->createUserWithPermission('sites.view'); $client = $this->authenticatedClient($credentials['username'], $credentials['password']); $client->request('DELETE', '/api/sites/'.$site->getId()); self::assertResponseStatusCodeSame(403); } public function testCreateSiteWithDuplicateNameReturns422(): void { $client = $this->authenticatedClient('admin', 'admin'); $client->request('POST', '/api/sites', [ 'headers' => ['Content-Type' => 'application/ld+json'], 'json' => [ 'name' => 'Chatellerault', 'street' => 'Autre rue', 'postalCode' => '75001', 'city' => 'Autre ville', 'color' => '#FF0000', ], ]); self::assertResponseStatusCodeSame(422); } public function testCreateSiteWithInvalidColorReturns422(): void { $client = $this->authenticatedClient('admin', 'admin'); $client->request('POST', '/api/sites', [ 'headers' => ['Content-Type' => 'application/ld+json'], 'json' => [ 'name' => 'Test-Invalid-Color', 'street' => '1 rue Test', 'postalCode' => '86000', 'city' => 'Poitiers', 'color' => 'red', ], ]); self::assertResponseStatusCodeSame(422); } public function testCreateSiteIgnoresFullAddressInPayload(): void { // Garde structurelle : `fullAddress` est un getter computed cote // backend (Site::getFullAddress, groupe site:read uniquement). Si un // client envoie ce champ en POST, API Platform doit l'ignorer // silencieusement car il n'est pas dans le groupe site:write. On // grave ce comportement pour qu'un futur dev qui ajouterait un // setter casse ce test au lieu de casser l'invariant en silence. $client = $this->authenticatedClient('admin', 'admin'); $response = $client->request('POST', '/api/sites', [ 'headers' => ['Content-Type' => 'application/ld+json'], 'json' => [ 'name' => 'Test-FullAddress-Ignored', 'street' => '1 rue Test', 'postalCode' => '86000', 'city' => 'Poitiers', 'color' => '#000000', 'fullAddress' => 'Adresse arbitraire envoyee par le client', ], ]); self::assertResponseStatusCodeSame(201); $data = $response->toArray(); // Le getter computed prevaut sur ce qu'envoie le client : street // determine la 1re ligne, jamais la valeur "Adresse arbitraire...". self::assertSame("1 rue Test\n86000 Poitiers", $data['fullAddress']); } public function testCreateSiteWithInvalidPostalCodeReturns422(): void { $client = $this->authenticatedClient('admin', 'admin'); $client->request('POST', '/api/sites', [ 'headers' => ['Content-Type' => 'application/ld+json'], 'json' => [ 'name' => 'Test-Invalid-CP', 'street' => '1 rue Test', 'postalCode' => '123', 'city' => 'Poitiers', 'color' => '#000000', ], ]); self::assertResponseStatusCodeSame(422); } private function cleanupTestSites(): void { if (!self::$kernel) { self::bootKernel(); } $em = $this->getEm(); $em->createQuery('DELETE FROM '.Site::class.' s WHERE s.name LIKE :prefix') ->setParameter('prefix', self::TEST_NAME_PREFIX.'%') ->execute() ; $em->clear(); } }