Files
Starseed/tests/Module/Catalog/Api/ProductCodeUniquenessTest.php
T
matthieu 4207a4ae12
Auto Tag Develop / tag (push) Successful in 11s
feat(catalog) : M6 — Catalogue produits (ERP-197 → ERP-203) (#154)
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
2026-06-25 12:50:14 +00:00

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);
}
}