4207a4ae12
Auto Tag Develop / tag (push) Successful in 11s
Module **M6 — Catalogue produits** (ERP-197 → ERP-203), pile consolidée en une seule MR vers `develop` pour un CI unique. Contenu (commits) : - ERP-197 — permissions `catalog.products.*` + sidebar + 3 miroirs RBAC - ERP-198 — migration schéma M6 (storage_type, product, jonctions, type PRODUIT) - ERP-199 — entités Product + StorageType + repositories + contrat de sérialisation - ERP-200 — ProductProvider + ProductProcessor (unicité code, RG-6.03/05/06, normalisation) - ERP-201 — référentiel StorageType exposé (filtre site) + seed Figma + catégories PRODUIT - ERP-202 — export XLSX du catalogue produits (filtres liste) - ERP-203 — tests PHPUnit RG-6.01→6.10 + capture du contrat JSON produit - fix review M6 — default jsonb mort (states) + constante préfixe storage-type de test Remplace et clôt les MR #148, #149, #150, #151, #152, #153 (commits intégralement inclus ici). --------- Co-authored-by: admin malio <malio@yuno.malio.fr> Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #154
83 lines
2.9 KiB
PHP
83 lines
2.9 KiB
PHP
<?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);
|
|
}
|
|
}
|