130 lines
5.3 KiB
PHP
130 lines
5.3 KiB
PHP
<?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));
|
|
}
|
|
}
|