c1fcd9a7c8
Repond aux retours de review (rigueur d'assertion transversale) :
- mutualise assertViolationOnPath dans AbstractCarrierApiTestCase (au lieu d'un
duplicata local a CarrierWriteApiTest) ;
- asserte le propertyPath des 422 des sous-ressources (adresses city/street/postalCode,
contacts firstName/phones/email, prix clientDeliveryAddress/supplierSupplyAddress/price)
-> evite les faux-verts du mapping inline (ERP-101) ;
- 404 parent (POST sur /carriers/999999/{addresses,contacts,prices}) ;
- 401 anonyme + filtre ?certificationType= sur la collection (trous releves sur le
contrat de lecture).
211 lines
8.2 KiB
PHP
211 lines
8.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Module\Transport\Api;
|
|
|
|
use App\Module\Core\Infrastructure\DataFixtures\RbacDemoFixtures;
|
|
use App\Module\Transport\Domain\Entity\Carrier;
|
|
use App\Module\Transport\Domain\Entity\CarrierAddress;
|
|
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
|
use Symfony\Component\Console\Input\ArrayInput;
|
|
use Symfony\Component\Console\Output\NullOutput;
|
|
|
|
/**
|
|
* Sous-ressource Adresse d'un transporteur (spec-back M4 § 4.5, ERP-159).
|
|
* POST /api/carriers/{id}/addresses, PATCH/DELETE /api/carrier_addresses/{id}.
|
|
*
|
|
* Contrat verifie :
|
|
* - RG-4.06 : code postal hors ^[0-9]{4,5}$ -> 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();
|
|
|
|
$response = $client->request('POST', '/api/carriers/'.$carrier->getId().'/addresses', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => ['postalCode' => '123'], // 3 chiffres -> Regex KO
|
|
]);
|
|
self::assertResponseStatusCodeSame(422);
|
|
// La 422 doit cibler le champ fautif (mapping inline ERP-101), pas juste le code HTTP.
|
|
self::assertViolationOnPath($response, 'postalCode');
|
|
}
|
|
|
|
public function testInconsistentPostalCodeAndCityIsAccepted(): void
|
|
{
|
|
// RG-4.06 : la validation serveur borne le FORMAT du code postal
|
|
// (^[0-9]{4,5}$) mais ne controle PAS la coherence CP <-> ville (deleguee
|
|
// a l'autocomplete BAN cote front). Un CP valide avec une ville qui ne lui
|
|
// correspond pas est donc accepte (201).
|
|
$carrier = $this->seedCarrierWithChartered('Cp Ville Incoherents', false);
|
|
$client = $this->createAdminClient();
|
|
|
|
$client->request('POST', '/api/carriers/'.$carrier->getId().'/addresses', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => [
|
|
'postalCode' => '86000', // Poitiers
|
|
'city' => 'Marseille', // incoherent, mais non controle
|
|
'street' => '1 rue de la Coherence',
|
|
],
|
|
]);
|
|
self::assertResponseStatusCodeSame(201);
|
|
}
|
|
|
|
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();
|
|
|
|
$response = $client->request('POST', '/api/carriers/'.$carrier->getId().'/addresses', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => ['postalCode' => '86000'],
|
|
]);
|
|
self::assertResponseStatusCodeSame(422);
|
|
// RG-4.05 mappe une violation PAR champ manquant (ville + rue ici) -> chaque
|
|
// erreur s'affiche inline sous son champ (ERP-101).
|
|
self::assertViolationOnPath($response, 'city');
|
|
self::assertViolationOnPath($response, 'street');
|
|
}
|
|
|
|
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 testPostAddressOnUnknownCarrierReturns404(): void
|
|
{
|
|
// Sous-ressource en read:false : le parent introuvable n'est plus intercepte
|
|
// en amont -> le processor doit lever un 404 explicite (sinon 500 au persist).
|
|
$client = $this->createAdminClient();
|
|
|
|
$client->request('POST', '/api/carriers/999999/addresses', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => ['postalCode' => '86000', 'city' => 'Poitiers', 'street' => '1 rue X'],
|
|
]);
|
|
self::assertResponseStatusCodeSame(404);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|