ab15452459
Couverture des règles de gestion du M5 (tickets de pesée) et capture de la
réponse JSON réelle (DoD § 4.0.bis) avant les écrans front.
Tests unitaires (Processor/Normalizer/Callback, sans BDD ni HTTP) :
- NetWeightTest (RG-5.05) : net = plein − vide, null si pesée manquante, recalcul PATCH.
- CounterpartyValidationTest (RG-5.03) : présence par branche (propertyPath) + exclusivité.
- ImmatriculationNormalizationTest (RG-5.01/5.10) : masque XX-000-XX, « Tout format », 422.
Tests fonctionnels (API réelle) :
- WeighingTicketNumberingTest (RG-5.02/5.09) : format {siteCode}-TP-{NNNN}, séquence
par site, isolation inter-sites, immuabilité numéro/site au PATCH.
- WeighingTicketSerializationContractTest (DoD § 4.0.bis) : 4 pièges (client embarqué,
plateFreeFormat présent, number formaté, netWeight = full − empty) + dump JSON.
- WeighingTicketRBACMatrixTest (§ 5.2) : admin/bureau/usine OK, compta/commerciale 403,
anonyme 401.
DSD/stub/reading déjà couverts (ERP-184/185). spec-back.md § 4.0.bis : JSON réel collé.
102 lines
4.5 KiB
PHP
102 lines
4.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Module\Logistique\Api;
|
|
|
|
/**
|
|
* Contrat de serialisation du ticket de pesee (M5, spec-back § 4.0 / § 4.0.bis).
|
|
* Jumeau de {@see \App\Tests\Module\Transport\Api\CarrierSerializationContractTest}.
|
|
*
|
|
* Capture le JSON REEL (liste + detail) via un ticket cree par l'API (numerotation
|
|
* serveur reelle) et reverifie les 4 pieges du RETEX M1 transposes au M5 :
|
|
* #1 : `client` (et `supplier`) sortent en OBJET embarque, pas en IRI nu
|
|
* (read-groups client:read / supplier:read).
|
|
* #2 : booleen `plateFreeFormat` present dans le JSON (getter + SerializedName).
|
|
* #3 : `number` present, formate {siteCode}-TP-{NNNN}.
|
|
* #4 : `netWeight` coherent = full - empty (plein - vide, RG-5.05).
|
|
*
|
|
* REGLE D'OR : on asserte sur le CORPS JSON reel, jamais sur les annotations.
|
|
* DoD (§ 4.0.bis) : avec WEIGHING_TICKET_DOD_DUMP positionnee, ecrit les corps
|
|
* liste + detail sous /tmp pour les coller dans la spec avant les ecrans front.
|
|
*
|
|
* @internal
|
|
*/
|
|
final class WeighingTicketSerializationContractTest extends AbstractWeighingTicketApiTestCase
|
|
{
|
|
public function testListAndDetailSerializationContract(): void
|
|
{
|
|
$site = $this->siteByCode('86');
|
|
$http = $this->authManageOnSite($site);
|
|
$clientEntity = $this->seedTestClient('Negoce');
|
|
|
|
$created = $this->postTicket($http, $this->validClientTicketPayload($clientEntity));
|
|
self::assertResponseStatusCodeSame(201);
|
|
$createdBody = $created->toArray();
|
|
|
|
$id = (int) $createdBody['id'];
|
|
$number = (string) $createdBody['number'];
|
|
|
|
$detail = $http->request('GET', '/api/weighing_tickets/'.$id, ['headers' => ['Accept' => self::LD]])->toArray();
|
|
$list = $http->request('GET', '/api/weighing_tickets?search='.$number, ['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 ticket cree doit apparaitre dans la liste filtree.');
|
|
|
|
// === Piege #1 : relations embarquees en OBJET (pas IRI nu) ===
|
|
self::assertIsArray($row['client'], 'client doit etre un objet embarque (client:read), pas un IRI nu.');
|
|
self::assertArrayHasKey('companyName', $row['client']);
|
|
// supplier null sur une contrepartie Client (cle potentiellement omise par
|
|
// skip_null_values — tolerant aux deux cas, jamais un IRI nu).
|
|
self::assertNull($row['supplier'] ?? null);
|
|
|
|
// === Piege #2 : booleen plateFreeFormat present ===
|
|
self::assertArrayHasKey('plateFreeFormat', $row);
|
|
self::assertFalse($row['plateFreeFormat']);
|
|
|
|
// === Piege #3 : number formate {siteCode}-TP-{NNNN} ===
|
|
self::assertArrayHasKey('number', $row);
|
|
self::assertMatchesRegularExpression('/^86-TP-\d{4}$/', $row['number']);
|
|
|
|
// === Piege #4 : netWeight = full - empty (14300 - 7150) ===
|
|
self::assertSame(7150, $row['netWeight']);
|
|
|
|
// displayDate (date du ticket = fullDate ?? emptyDate) expose en liste.
|
|
self::assertArrayHasKey('displayDate', $row);
|
|
|
|
// === DETAIL : site embarque (avec code), immatriculation, les 2 pesees ===
|
|
self::assertIsArray($detail['site']);
|
|
self::assertSame('86', $detail['site']['code']);
|
|
self::assertSame('AB-123-CD', $detail['immatriculation']);
|
|
self::assertSame(7150, $detail['emptyWeight']);
|
|
self::assertSame(14300, $detail['fullWeight']);
|
|
self::assertSame(7150, $detail['netWeight']);
|
|
self::assertIsArray($detail['client']);
|
|
self::assertArrayHasKey('companyName', $detail['client']);
|
|
|
|
$this->dumpDodIfRequested($list, $detail);
|
|
}
|
|
|
|
/**
|
|
* DoD (§ 4.0.bis) : ecrit les corps JSON reels sous /tmp si WEIGHING_TICKET_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('WEIGHING_TICKET_DOD_DUMP')) {
|
|
return;
|
|
}
|
|
|
|
$flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
|
|
file_put_contents('/tmp/weighing-ticket-dod-list.json', json_encode($list, $flags));
|
|
file_put_contents('/tmp/weighing-ticket-dod-detail.json', json_encode($detail, $flags));
|
|
}
|
|
}
|