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é.
This commit is contained in:
@@ -138,6 +138,50 @@ final class StorageExportControllerTest extends AbstractStorageApiTestCase
|
||||
self::assertMatchesRegularExpression('#^\d{2}/\d{2}/\d{4} \d{2}:\d{2}$#', (string) $row[6]);
|
||||
}
|
||||
|
||||
public function testFormulaInjectionIsNeutralized(): void
|
||||
{
|
||||
$client = $this->createAdminClient();
|
||||
|
||||
// Numero malicieux commencant par « = » (injection de formule / DDE). Seede en
|
||||
// direct (le numero contournerait de toute facon le normalizer, qui ne fait
|
||||
// qu'un trim). L'export doit le restituer comme TEXTE litteral, jamais comme
|
||||
// une formule evaluee : si la cellule etait une formule, IOFactory::load la
|
||||
// calculerait (resultat 3 ou erreur) et « =1+2 » serait absent de la colonne.
|
||||
$this->seedStorageEntity('=1+2');
|
||||
|
||||
$numeros = $this->numeros($client->request('GET', self::EXPORT_URL)->getContent());
|
||||
|
||||
self::assertContains('=1+2', $numeros, 'Le numero « =1+2 » doit etre stocke en texte, pas evalue.');
|
||||
}
|
||||
|
||||
public function testExportKeepsSearchTermZero(): void
|
||||
{
|
||||
$client = $this->createAdminClient();
|
||||
$this->seedStorageEntity('0');
|
||||
$this->seedStorageEntity('X1');
|
||||
|
||||
// « 0 » est un numero valide : le filtre ?search=0 NE DOIT PAS etre coerce a
|
||||
// null (parite stricte avec la liste a l'ecran via StorageListFilters).
|
||||
$numeros = $this->numeros($client->request('GET', self::EXPORT_URL.'?search=0')->getContent());
|
||||
|
||||
self::assertContains('0', $numeros);
|
||||
self::assertNotContains('X1', $numeros);
|
||||
}
|
||||
|
||||
public function testExportToleratesArrayShapedScalarParam(): void
|
||||
{
|
||||
$client = $this->createAdminClient();
|
||||
$this->seedStorageEntity('NUM-ARR');
|
||||
|
||||
// ?search[]=foo : parametre tableau la ou un scalaire est attendu. L'export ne
|
||||
// doit pas planter en 400 (la liste le tolere) : la valeur est simplement
|
||||
// ignoree -> 200 avec tous les stockages.
|
||||
$response = $client->request('GET', self::EXPORT_URL.'?search[]=foo');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertContains('NUM-ARR', $this->numeros($response->getContent()));
|
||||
}
|
||||
|
||||
public function testForbiddenWithoutStoragesViewPermission(): void
|
||||
{
|
||||
$creds = $this->createUserWithPermission('core.users.view');
|
||||
|
||||
Reference in New Issue
Block a user