Files
Starseed/tests/Module/Catalog/Api/StorageStatesValidationTest.php
T
tristan 7075f0f95d fix(catalog) : M7 — durcissement stockages (états JSONB séquentiels + Assert\Unique, neutralisation injection formules XLSX partagée, parité listing/export via StorageListFilters, streaming export)
- Storage.setStates() renormalise en liste séquentielle (array_values) : un states posté en objet JSON ne peut plus être persisté en JSONB objet (jsonb_array_length → 500). Doublons rejetés en 422 via Assert\Unique.
- PhpSpreadsheetExporter écrit les cellules chaîne en TYPE_STRING explicite : neutralise l'injection de formules/DDE sur toutes les valeurs saisies (corrige aussi Produit/Client/Logistique/Supplier/Provider/Carrier).
- StorageListFilters : source unique de parsing des filtres (?search, ?siteId[], ?storageTypeId, ?state), consommée par le provider ET l'export → fin des divergences (numéro « 0 » coercé à null, param tableau en 400, id non positif).
- Export en streaming (toIterable + clear par lot) au lieu de getResult() : mémoire bornée.
- Tests : doublon/objet states, normalisation trim RG-7.06, 422 relations nulles, absence de deletedAt, soft-delete liste discriminant, neutralisation formule, parité ?search=0, robustesse param tableau ; garde-fou Assert\Unique enregistré.
2026-06-29 18:01:54 +02:00

112 lines
3.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Module\Catalog\Api;
use App\Module\Catalog\Domain\Entity\Storage;
/**
* RG-7.04 : `states` = multi-select ⊆ {RECEPTION, PRODUCTION, TRIAGE}, au moins 1
* requis. RG-7.08 : le PATCH applique les memes regles que le POST.
*
* Couvre :
* - tableau d'etats vide -> 422 (Assert\Count(min: 1)) sur le champ `states` ;
* - valeur hors enum -> 422 (Assert\Choice) sur le champ `states` ;
* - un seul etat valide -> 201 (borne basse acceptee) ;
* - PATCH vers un tableau d'etats vide -> 422 (RG-7.08).
*
* @internal
*/
final class StorageStatesValidationTest extends AbstractStorageApiTestCase
{
public function testEmptyStatesIsRejected(): void
{
$client = $this->createAdminClient();
$response = $client->request('POST', '/api/storages', [
'headers' => ['Content-Type' => self::LD],
'json' => $this->validStoragePayload(['states' => []]),
]);
self::assertResponseStatusCodeSame(422);
self::assertContains('states', $this->violationPaths($response));
}
public function testUnknownStateValueIsRejected(): void
{
$client = $this->createAdminClient();
$response = $client->request('POST', '/api/storages', [
'headers' => ['Content-Type' => self::LD],
'json' => $this->validStoragePayload(['states' => [Storage::STATE_RECEPTION, 'FOOBAR']]),
]);
self::assertResponseStatusCodeSame(422);
self::assertContains('states', $this->violationPaths($response));
}
public function testSingleValidStateIsAccepted(): void
{
$client = $this->createAdminClient();
$client->request('POST', '/api/storages', [
'headers' => ['Content-Type' => self::LD],
'json' => $this->validStoragePayload(['states' => [Storage::STATE_PRODUCTION]]),
]);
self::assertResponseStatusCodeSame(201);
}
public function testPatchToEmptyStatesIsRejected(): void
{
$storage = $this->seedStorageEntity();
// RG-7.08 : la regle RG-7.04 vaut aussi en edition.
$client = $this->createAdminClient();
$response = $client->request('PATCH', '/api/storages/'.$storage->getId(), [
'headers' => ['Content-Type' => self::MERGE],
'json' => ['states' => []],
]);
self::assertResponseStatusCodeSame(422);
self::assertContains('states', $this->violationPaths($response));
}
public function testDuplicateStatesAreRejected(): void
{
$client = $this->createAdminClient();
// Doublon dans le multi-select : 422 (Assert\Unique), pas un stockage avec un
// tableau d'etats incoherent (RG-7.04 = sous-ensemble).
$response = $client->request('POST', '/api/storages', [
'headers' => ['Content-Type' => self::LD],
'json' => $this->validStoragePayload([
'states' => [Storage::STATE_TRIAGE, Storage::STATE_TRIAGE],
]),
]);
self::assertResponseStatusCodeSame(422);
self::assertContains('states', $this->violationPaths($response));
}
public function testNonSequentialStatesDoNotCrash(): void
{
$client = $this->createAdminClient();
// `states` envoye comme OBJET JSON (cle non sequentielle) : auparavant
// persiste tel quel en JSONB objet -> le CHECK jsonb_array_length plantait en
// 500. Doit desormais etre renormalise en liste sequentielle (array_values du
// setter), donc accepte proprement sans 500.
$created = $client->request('POST', '/api/storages', [
'headers' => ['Content-Type' => self::LD],
'json' => $this->validStoragePayload([
'states' => [7 => Storage::STATE_RECEPTION],
]),
])->toArray();
self::assertResponseStatusCodeSame(201);
self::assertSame([Storage::STATE_RECEPTION], $created['states']);
}
}