test(catalog) : M7 — tests RG-7.01→7.08 + contrat de sérialisation stockage (ERP-215)
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Module\Catalog\Api;
|
||||
|
||||
use App\Module\Catalog\Domain\Entity\Storage;
|
||||
use DateTimeImmutable;
|
||||
|
||||
/**
|
||||
* Contrat de serialisation du stockage (M7, spec-back § 4.0 / § 4.0.bis).
|
||||
* Jumeau du ProductSerializationContractTest (M6).
|
||||
*
|
||||
* Capture le JSON REEL (liste + detail) via un stockage cree par l'API (POST reel,
|
||||
* normalisation serveur reelle) et reverifie les pieges du RETEX M1 transposes au
|
||||
* M7 :
|
||||
* #1 : `site` sort en OBJET embarque (site:read), jamais en IRI nu.
|
||||
* #2 : `storageType` sort en OBJET embarque (storage_type:read), jamais en IRI nu.
|
||||
* #3 : `states` = tableau de chaines.
|
||||
* #4 : `displayName` present et correct (RG-7.05 : « <label> <numero> »).
|
||||
*
|
||||
* REGLE D'OR : on asserte sur le CORPS JSON reel, jamais sur les annotations.
|
||||
* DoD (§ 4.0.bis) : avec STORAGE_DOD_DUMP positionnee, ecrit les corps liste +
|
||||
* detail sous /tmp pour les coller dans la spec avant les ecrans front.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class StorageSerializationContractTest extends AbstractStorageApiTestCase
|
||||
{
|
||||
public function testListAndDetailSerializationContract(): void
|
||||
{
|
||||
$client = $this->createAdminClient();
|
||||
|
||||
$site = $this->firstSite();
|
||||
$type = $this->seedStorageType('Cellule');
|
||||
$numero = $this->uniqueCode('NUM');
|
||||
|
||||
// Stockage cree par un POST reel (2 etats pour exercer le tableau).
|
||||
$created = $client->request('POST', '/api/storages', [
|
||||
'headers' => ['Content-Type' => self::LD],
|
||||
'json' => [
|
||||
'site' => $this->iri('sites', (int) $site->getId()),
|
||||
'storageType' => $this->iri('storage_types', (int) $type->getId()),
|
||||
'numero' => $numero,
|
||||
'states' => [Storage::STATE_RECEPTION, Storage::STATE_TRIAGE],
|
||||
],
|
||||
])->toArray();
|
||||
|
||||
self::assertResponseStatusCodeSame(201);
|
||||
$id = (int) $created['id'];
|
||||
|
||||
$detail = $client->request('GET', '/api/storages/'.$id, [
|
||||
'headers' => ['Accept' => self::LD],
|
||||
])->toArray();
|
||||
$list = $client->request('GET', '/api/storages?search='.$numero, [
|
||||
'headers' => ['Accept' => self::LD],
|
||||
])->toArray();
|
||||
|
||||
// Enveloppe Hydra AP4 (member/totalItems sans prefixe hydra:).
|
||||
self::assertArrayHasKey('member', $list);
|
||||
self::assertArrayNotHasKey('hydra:member', $list);
|
||||
|
||||
$row = $this->memberById($list, $id);
|
||||
self::assertNotNull($row, 'Le stockage cree doit apparaitre dans la liste filtree.');
|
||||
|
||||
// === Piege #1 : site en OBJET embarque (pas IRI nu) ===
|
||||
self::assertIsArray($row['site'], 'site doit etre un objet embarque (site:read), pas un IRI nu.');
|
||||
self::assertArrayHasKey('name', $row['site']);
|
||||
self::assertArrayHasKey('code', $row['site']);
|
||||
|
||||
// === Piege #2 : storageType en OBJET embarque (pas IRI nu) ===
|
||||
self::assertIsArray($row['storageType'], 'storageType doit etre un objet embarque (storage_type:read), pas un IRI nu.');
|
||||
self::assertArrayHasKey('label', $row['storageType']);
|
||||
self::assertSame('Cellule', $row['storageType']['label']);
|
||||
|
||||
// === Piege #3 : states tableau de chaines ===
|
||||
self::assertSame([Storage::STATE_RECEPTION, Storage::STATE_TRIAGE], $row['states']);
|
||||
|
||||
// === Piege #4 : displayName present + correct (RG-7.05) ===
|
||||
self::assertArrayHasKey('displayName', $row);
|
||||
self::assertSame('Cellule '.$numero, $row['displayName']);
|
||||
|
||||
// === DETAIL : memes garanties d'embarquement ===
|
||||
self::assertIsArray($detail['site']);
|
||||
self::assertArrayHasKey('name', $detail['site']);
|
||||
self::assertIsArray($detail['storageType']);
|
||||
self::assertArrayHasKey('label', $detail['storageType']);
|
||||
self::assertSame([Storage::STATE_RECEPTION, Storage::STATE_TRIAGE], $detail['states']);
|
||||
self::assertSame('Cellule '.$numero, $detail['displayName']);
|
||||
|
||||
$this->dumpDodIfRequested($list, $detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* RG-7.07 : la liste (et le detail) n'exposent JAMAIS un stockage soft-deleted.
|
||||
*/
|
||||
public function testSoftDeletedIsNotExposed(): void
|
||||
{
|
||||
$deleted = $this->seedStorageEntity('SD', deletedAt: new DateTimeImmutable());
|
||||
|
||||
$client = $this->createAdminClient();
|
||||
|
||||
// Item soft-deleted -> 404 (§ 2.8).
|
||||
$client->request('GET', '/api/storages/'.$deleted->getId(), ['headers' => ['Accept' => self::LD]]);
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
|
||||
// … et absent de la collection (RG-7.07).
|
||||
$list = $client->request('GET', '/api/storages', ['headers' => ['Accept' => self::LD]])->toArray();
|
||||
self::assertNull($this->memberById($list, (int) $deleted->getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* DoD (§ 4.0.bis) : ecrit les corps JSON reels sous /tmp si STORAGE_DOD_DUMP est
|
||||
* positionnee (sinon no-op). A coller dans spec-back.md § 4.0.bis.
|
||||
*
|
||||
* @param array<string, mixed> $list
|
||||
* @param array<string, mixed> $detail
|
||||
*/
|
||||
private function dumpDodIfRequested(array $list, array $detail): void
|
||||
{
|
||||
if (false === getenv('STORAGE_DOD_DUMP')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
|
||||
file_put_contents('/tmp/storage-dod-list.json', json_encode($list, $flags));
|
||||
file_put_contents('/tmp/storage-dod-detail.json', json_encode($detail, $flags));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user