test(catalog) : ERP-203 — tests PHPUnit RG-6.01→6.10 + capture du contrat JSON produit
Couvre les RG produit (M6) en tests fonctionnels API et capture le contrat de serialisation reel (DoD spec-back § 4.0.bis). Tests (tests/Module/Catalog/Api/) : - AbstractProductApiTestCase : base commune (productType find-or-create PRODUIT, productCategory, seedStorageType par sites, validProductPayload, dump DoD). - ProductSerializationContractTest : POST reel + GET liste/detail ; category en objet embarque, sites/storageTypes en tableaux d'objets, states en tableau, manufactured/containsMolasses booleens presents. Dump regenerable via PRODUCT_DOD_DUMP=1. - ProductCodeUniquenessTest (RG-6.01) : 409 doublon actif, collision sur forme normalisee, reutilisation d'un code soft-deleted (index partiel). - ProductStatesValidationTest (RG-6.02) : >=1 etat requis, valeur hors enum 422. - ProductConditionalFieldsTest (RG-6.03) : manufactured/containsMolasses forces false sans SALE (POST et PATCH), conserves avec SALE. - ProductCategoryTypeTest (RG-6.05) : 422 si categorie non-PRODUIT. - ProductStorageTypeBySiteTest (RG-6.06) : 422 si storageType hors sites choisis. - ProductRBACMatrixTest : Admin 200/201 ; Bureau/Compta/Commerciale/Usine 403 (view + manage) ; view lit mais ne gere pas. Fix contrat de serialisation : le getter containsMolasses() ne respectait pas la convention d'accesseur (get/is/has) -> le serialiseur n'exposait PAS le champ dans le JSON, alors que le DoD l'exige. Renomme en isContainsMolasses() (+ unique appelant dans ProductExportController). Defaut capte par le test de contrat. Spec : JSON reel colle dans spec-back § 4.0.bis (remplace l'esquisse). Le contrat reel est plus riche : la liste porte deja sites/storageTypes embarques, category embarque categoryTypes + audit, createdBy/updatedBy en IRI, sites avec adresse. make test vert (897 tests) ; php-cs-fixer conforme.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Module\Catalog\Api;
|
||||
|
||||
use App\Module\Catalog\Domain\Entity\Product;
|
||||
use DateTimeImmutable;
|
||||
|
||||
/**
|
||||
* RG-6.01 : unicite GLOBALE du code produit parmi les ACTIFS.
|
||||
*
|
||||
* Couvre :
|
||||
* - 409 sur doublon de code actif (pre-check deterministe du Processor) ;
|
||||
* - normalisation (trim + UPPER, RG-6.07) prise en compte par l'unicite : un
|
||||
* code casse / entoure d'espaces collisionne avec sa forme normalisee ;
|
||||
* - reutilisation possible d'un code porte par un produit soft-deleted (l'index
|
||||
* partiel uq_product_code_active ne contraint que les actifs).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ProductCodeUniquenessTest extends AbstractProductApiTestCase
|
||||
{
|
||||
public function testDuplicateActiveCodeReturns409(): void
|
||||
{
|
||||
$client = $this->createAdminClient();
|
||||
$code = $this->uniqueCode('TESTPRD');
|
||||
|
||||
$client->request('POST', '/api/products', [
|
||||
'headers' => ['Content-Type' => self::LD],
|
||||
'json' => $this->validProductPayload(['code' => $code]),
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(201);
|
||||
|
||||
// Meme code -> conflit (RG-6.01).
|
||||
$client->request('POST', '/api/products', [
|
||||
'headers' => ['Content-Type' => self::LD],
|
||||
'json' => $this->validProductPayload(['code' => $code]),
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(409);
|
||||
}
|
||||
|
||||
public function testNormalizedCodeCollides(): void
|
||||
{
|
||||
$client = $this->createAdminClient();
|
||||
$code = $this->uniqueCode('TESTPRD');
|
||||
|
||||
$client->request('POST', '/api/products', [
|
||||
'headers' => ['Content-Type' => self::LD],
|
||||
'json' => $this->validProductPayload(['code' => $code]),
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(201);
|
||||
|
||||
// Variante minuscule + espaces : trim + UPPER serveur (RG-6.07) la ramene
|
||||
// a la meme forme normalisee -> meme collision 409.
|
||||
$client->request('POST', '/api/products', [
|
||||
'headers' => ['Content-Type' => self::LD],
|
||||
'json' => $this->validProductPayload(['code' => ' '.strtolower($code).' ']),
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(409);
|
||||
}
|
||||
|
||||
public function testSoftDeletedCodeCanBeReused(): void
|
||||
{
|
||||
$client = $this->createAdminClient();
|
||||
$code = $this->uniqueCode('TESTPRD');
|
||||
|
||||
// Produit soft-deleted portant le code (seede directement, hors index actif).
|
||||
$this->seedProductEntity(
|
||||
code: $code,
|
||||
states: [Product::STATE_PURCHASE],
|
||||
deletedAt: new DateTimeImmutable(),
|
||||
);
|
||||
|
||||
// Le meme code est libre cote actifs -> creation acceptee (201).
|
||||
$client->request('POST', '/api/products', [
|
||||
'headers' => ['Content-Type' => self::LD],
|
||||
'json' => $this->validProductPayload(['code' => $code]),
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(201);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user