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).
225 lines
8.5 KiB
PHP
225 lines
8.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Module\Transport\Api;
|
|
|
|
/**
|
|
* Ecriture du formulaire principal transporteur (M4, WT4 — ERP-158). Couvre les
|
|
* RG du CarrierProcessor + contraintes conditionnelles : RG-4.01 (QUALIMAT + cas
|
|
* LIOT), RG-4.02 (AUTRE -> decharge), RG-4.03 (affrete -> indexation/benne/volume),
|
|
* RG-4.12 (doublon de nom -> 409), RG-4.13 (normalisation), RG-4.14 (archivage +
|
|
* mode strict). Jumeau des SupplierApiTest / SupplierPatchStrictTest (M2).
|
|
*
|
|
* @internal
|
|
*/
|
|
final class CarrierWriteApiTest extends AbstractCarrierApiTestCase
|
|
{
|
|
/**
|
|
* RG-4.01 : POST avec qualimatCarrier -> certificationType=QUALIMAT accepte et
|
|
* FK persistee (verifiee au detail, qualimatCarrier embarque).
|
|
*/
|
|
public function testPostQualimatPersistsCertificationAndForeignKey(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
$qualimat = $this->seedQualimatCarrier('Transports Grelillier');
|
|
|
|
$created = $client->request('POST', '/api/carriers', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => [
|
|
'name' => 'Transports Grelillier',
|
|
'qualimatCarrier' => '/api/qualimat_carriers/'.$qualimat->getId(),
|
|
'certificationType' => 'QUALIMAT',
|
|
'isChartered' => false,
|
|
],
|
|
])->toArray();
|
|
|
|
self::assertResponseStatusCodeSame(201);
|
|
self::assertSame('QUALIMAT', $created['certificationType']);
|
|
|
|
$detail = $client->request('GET', $created['@id'], ['headers' => ['Accept' => self::LD]])->toArray();
|
|
self::assertIsArray($detail['qualimatCarrier']);
|
|
self::assertSame((int) $qualimat->getId(), (int) $detail['qualimatCarrier']['id']);
|
|
}
|
|
|
|
/**
|
|
* RG-4.01 (cas LIOT) : nom = LIOT -> certificationType non requis (champ masque)
|
|
* et liotPlates accepte (et normalise, RG-4.13).
|
|
*/
|
|
public function testPostLiotAcceptsPlatesWithoutCertification(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
|
|
$created = $client->request('POST', '/api/carriers', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => [
|
|
'name' => 'LIOT',
|
|
'liotPlates' => 'ab-123-cd ; ef-456-gh',
|
|
'isChartered' => false,
|
|
],
|
|
])->toArray();
|
|
|
|
self::assertResponseStatusCodeSame(201);
|
|
self::assertNull($created['certificationType']);
|
|
self::assertSame('AB-123-CD; EF-456-GH', $created['liotPlates']);
|
|
}
|
|
|
|
/**
|
|
* RG-4.01 : hors cas LIOT, l'absence de certification est rejetee (422 cible
|
|
* sur certificationType).
|
|
*/
|
|
public function testPostWithoutCertificationOutsideLiotIsRejected(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
|
|
$response = $client->request('POST', '/api/carriers', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => ['name' => 'Sans Certif', 'isChartered' => false],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(422);
|
|
self::assertViolationOnPath($response, 'certificationType');
|
|
}
|
|
|
|
/**
|
|
* RG-4.02 : certificationType=AUTRE sans dischargeDocument -> 422 cible ; une
|
|
* certification != AUTRE sans decharge passe (201).
|
|
*/
|
|
public function testAutreCertificationRequiresDischarge(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
|
|
$response = $client->request('POST', '/api/carriers', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => ['name' => 'Sans Decharge', 'certificationType' => 'AUTRE', 'isChartered' => false],
|
|
]);
|
|
self::assertResponseStatusCodeSame(422);
|
|
self::assertViolationOnPath($response, 'dischargeDocument');
|
|
|
|
// Certification != AUTRE : pas de decharge requise.
|
|
$client->request('POST', '/api/carriers', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => ['name' => 'Avec GmpPlus', 'certificationType' => 'GMP_PLUS', 'isChartered' => false],
|
|
]);
|
|
self::assertResponseStatusCodeSame(201);
|
|
}
|
|
|
|
/**
|
|
* RG-4.03 : isChartered=true sans indexationRate / containerType / volumeM3 ->
|
|
* 422 (violations ciblees) ; complet -> 201.
|
|
*/
|
|
public function testCharteredRequiresIndexationContainerAndVolume(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
|
|
$response = $client->request('POST', '/api/carriers', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => ['name' => 'Affrete Incomplet', 'certificationType' => 'GMP_PLUS', 'isChartered' => true],
|
|
]);
|
|
self::assertResponseStatusCodeSame(422);
|
|
self::assertViolationOnPath($response, 'indexationRate');
|
|
self::assertViolationOnPath($response, 'containerType');
|
|
self::assertViolationOnPath($response, 'volumeM3');
|
|
|
|
$client->request('POST', '/api/carriers', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => [
|
|
'name' => 'Affrete Complet',
|
|
'certificationType' => 'GMP_PLUS',
|
|
'isChartered' => true,
|
|
'indexationRate' => '5.00',
|
|
'containerType' => 'BENNE',
|
|
'volumeM3' => '90.00',
|
|
],
|
|
]);
|
|
self::assertResponseStatusCodeSame(201);
|
|
}
|
|
|
|
/**
|
|
* RG-4.12 : nom deja pris (parmi actifs) -> 409. Le meme nom redevient
|
|
* disponible apres archivage de l'ancien -> 201.
|
|
*/
|
|
public function testDuplicateNameReturns409AndIsFreedAfterArchive(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
$existing = $this->seedCarrier('Doublon Co');
|
|
|
|
$client->request('POST', '/api/carriers', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => $this->validMainPayload('Doublon Co'),
|
|
]);
|
|
self::assertResponseStatusCodeSame(409);
|
|
|
|
// Archivage de l'ancien -> le nom se libere (index partiel sur actifs).
|
|
$client->request('PATCH', '/api/carriers/'.$existing->getId(), [
|
|
'headers' => ['Content-Type' => self::MERGE],
|
|
'json' => ['isArchived' => true],
|
|
]);
|
|
self::assertResponseStatusCodeSame(200);
|
|
|
|
$client->request('POST', '/api/carriers', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => $this->validMainPayload('Doublon Co'),
|
|
]);
|
|
self::assertResponseStatusCodeSame(201);
|
|
}
|
|
|
|
/**
|
|
* RG-4.13 : le nom est persiste en MAJUSCULES (normalisation serveur).
|
|
*/
|
|
public function testNameIsUpperCasedOnPersist(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
|
|
$created = $client->request('POST', '/api/carriers', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => $this->validMainPayload('transports x'),
|
|
])->toArray();
|
|
|
|
self::assertResponseStatusCodeSame(201);
|
|
self::assertSame('TRANSPORTS X', $created['name']);
|
|
}
|
|
|
|
/**
|
|
* RG-4.14 : PATCH isArchived=true par Admin -> 200 + archivedAt rempli ;
|
|
* restauration -> archivedAt remis a null.
|
|
*/
|
|
public function testAdminArchiveSetsArchivedAtAndRestoreClearsIt(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
$carrier = $this->seedCarrier('A Archiver');
|
|
|
|
$archived = $client->request('PATCH', '/api/carriers/'.$carrier->getId(), [
|
|
'headers' => ['Content-Type' => self::MERGE],
|
|
'json' => ['isArchived' => true],
|
|
])->toArray();
|
|
self::assertResponseStatusCodeSame(200);
|
|
self::assertTrue($archived['isArchived']);
|
|
self::assertNotNull($archived['archivedAt']);
|
|
|
|
$restored = $client->request('PATCH', '/api/carriers/'.$carrier->getId(), [
|
|
'headers' => ['Content-Type' => self::MERGE],
|
|
'json' => ['isArchived' => false],
|
|
])->toArray();
|
|
self::assertResponseStatusCodeSame(200);
|
|
self::assertFalse($restored['isArchived']);
|
|
self::assertNull($restored['archivedAt']);
|
|
}
|
|
|
|
/**
|
|
* RG-4.14 (mode strict) : une requete d'archivage ne peut modifier aucun autre
|
|
* champ ecrivable -> 422.
|
|
*/
|
|
public function testArchiveRequestMixingOtherFieldIsRejected(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
$carrier = $this->seedCarrier('Strict Co');
|
|
|
|
$client->request('PATCH', '/api/carriers/'.$carrier->getId(), [
|
|
'headers' => ['Content-Type' => self::MERGE],
|
|
'json' => ['isArchived' => true, 'name' => 'Renamed While Archiving'],
|
|
]);
|
|
self::assertResponseStatusCodeSame(422);
|
|
}
|
|
}
|