422 ; * - RG-4.05 : transporteur affrete + adresse incomplete -> 422 (par champ) ; * - RG-4.05 : transporteur affrete + adresse complete -> 201 ; * - PATCH / DELETE OK avec transport.carriers.manage, 403 sans (view seul). * * @internal */ final class CarrierAddressApiTest extends AbstractCarrierApiTestCase { private const string PWD = RbacDemoFixtures::DEMO_PASSWORD; protected function setUp(): void { parent::setUp(); // Seed idempotent des roles + matrice § 5.2 + comptes demo (meme chemin // qu'en recette), requis pour les tests de permission (bureau/commerciale). self::bootKernel(); $application = new Application(self::$kernel); $application->setAutoExit(false); $exit = $application->run( new ArrayInput([ 'command' => 'app:seed-rbac', '--with-demo-users' => true, '--password' => self::PWD, ]), new NullOutput(), ); self::assertSame(0, $exit, 'app:seed-rbac a echoue (permissions transport.carriers.* synchronisees ?).'); self::ensureKernelShutdown(); } public function testInvalidPostalCodeReturns422(): void { // Transporteur NON affrete : RG-4.05 ne s'applique pas, seule RG-4.06 joue. $carrier = $this->seedCarrierWithChartered('Cp Invalide', false); $client = $this->createAdminClient(); $client->request('POST', '/api/carriers/'.$carrier->getId().'/addresses', [ 'headers' => ['Content-Type' => self::LD], 'json' => ['postalCode' => '123'], // 3 chiffres -> Regex KO ]); self::assertResponseStatusCodeSame(422); } public function testCharteredCarrierIncompleteAddressReturns422(): void { // Transporteur affrete : RG-4.05 exige Pays/CP/Ville/Adresse. CP valide mais // ville + rue manquantes -> 422 conditionnelle (CarrierAddressProcessor). $carrier = $this->seedCarrierWithChartered('Affrete Incomplet', true); $client = $this->createAdminClient(); $client->request('POST', '/api/carriers/'.$carrier->getId().'/addresses', [ 'headers' => ['Content-Type' => self::LD], 'json' => ['postalCode' => '86000'], ]); self::assertResponseStatusCodeSame(422); } public function testCharteredCarrierCompleteAddressIsCreated(): void { $carrier = $this->seedCarrierWithChartered('Affrete Complet', true); $client = $this->createAdminClient(); $client->request('POST', '/api/carriers/'.$carrier->getId().'/addresses', [ 'headers' => ['Content-Type' => self::LD], 'json' => [ 'country' => 'France', 'postalCode' => '86000', 'city' => 'Poitiers', 'street' => '12 rue des Acacias', ], ]); self::assertResponseStatusCodeSame(201); } public function testPatchAndDeleteSucceedWithManage(): void { $address = $this->seedAddress('Patch Delete', false); $client = $this->authenticatedClient('bureau', self::PWD); // manage (matrice § 5.2) // PATCH (manage) -> 200 $client->request('PATCH', '/api/carrier_addresses/'.$address->getId(), [ 'headers' => ['Content-Type' => self::MERGE], 'json' => ['city' => 'Lyon'], ]); self::assertResponseStatusCodeSame(200); // DELETE (manage) -> 204 $client->request('DELETE', '/api/carrier_addresses/'.$address->getId()); self::assertResponseStatusCodeSame(204); } public function testWriteForbiddenWithoutManage(): void { $address = $this->seedAddress('Forbidden', false); $carrier = $address->getCarrier(); self::assertNotNull($carrier); $client = $this->authenticatedClient('commerciale', self::PWD); // view seul $client->request('POST', '/api/carriers/'.$carrier->getId().'/addresses', [ 'headers' => ['Content-Type' => self::LD], 'json' => ['postalCode' => '86000', 'city' => 'Poitiers', 'street' => '1 rue X'], ]); self::assertResponseStatusCodeSame(403); $client->request('PATCH', '/api/carrier_addresses/'.$address->getId(), [ 'headers' => ['Content-Type' => self::MERGE], 'json' => ['city' => 'Lyon'], ]); self::assertResponseStatusCodeSame(403); $client->request('DELETE', '/api/carrier_addresses/'.$address->getId()); self::assertResponseStatusCodeSame(403); } /** * Seede un transporteur minimal en controlant le flag affrete (RG-4.05). */ private function seedCarrierWithChartered(string $name, bool $isChartered): Carrier { $em = $this->getEm(); $carrier = new Carrier(); $carrier->setName(mb_strtoupper($name, 'UTF-8')); $carrier->setCertificationType('GMP_PLUS'); $carrier->setIsChartered($isChartered); $em->persist($carrier); $em->flush(); return $carrier; } /** * Seede un transporteur + une adresse rattachee (pour les tests PATCH/DELETE). */ private function seedAddress(string $name, bool $isChartered): CarrierAddress { $em = $this->getEm(); $carrier = $this->seedCarrierWithChartered($name, $isChartered); $address = new CarrierAddress(); $address->setCarrier($carrier); $address->setPostalCode('86000'); $address->setCity('Poitiers'); $address->setStreet('12 rue des Acacias'); $carrier->addAddress($address); $em->persist($address); $em->flush(); return $address; } }