test(commercial) : fix CI anti-N+1 (profiling test) + durcissement 422/gating M2 fournisseurs (ERP-92)
- config/packages/test/doctrine.yaml : force dbal profiling en test pour que doctrine.debug_data_holder existe sous APP_DEBUG=0 (CI). Le test anti-N+1 SupplierListTest passait en local (debug=1) mais cassait en CI. - RBACMatrix/SupplierApi : les 422 RG-2.03 et RG-2.14 assertent desormais le propertyPath / message (plus seulement le code) — un 422 orthogonal ne peut plus faire passer le test. - RBACMatrix : gating bureau/commerciale verifie l'ensemble des champs comptables (accountNumber/nTva/tvaMode/paymentType), plus seulement siren/ribs. - violationsByPath() mutualise dans AbstractSupplierApiTestCase (dedup).
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
doctrine:
|
||||
dbal:
|
||||
connections:
|
||||
# Force le profiling DBAL en environnement de test independamment de
|
||||
# APP_DEBUG. Sans cela, la CI tourne en APP_DEBUG=0 (prod-like) et le
|
||||
# service `doctrine.debug_data_holder` n'est pas enregistre : le test
|
||||
# anti-N+1 (SupplierListTest::testListQueryCountDoesNotGrowWithRowCount)
|
||||
# qui compte les requetes via ce holder echoue alors en CI alors qu'il
|
||||
# passe en local (APP_DEBUG=1). Activer le profiling ici garde le test
|
||||
# actif precisement la ou il compte (CI), sans impacter la prod.
|
||||
default:
|
||||
profiling: true
|
||||
@@ -316,4 +316,24 @@ abstract class AbstractSupplierApiTestCase extends AbstractCommercialApiTestCase
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexe les violations d'un corps de reponse 422 par propertyPath. Permet
|
||||
* d'asserter qu'un 422 porte bien sur le champ attendu (et n'est pas un 422
|
||||
* orthogonal) : un test qui se contente du code 422 passerait meme si la RG
|
||||
* visee etait cassee pour une autre raison.
|
||||
*
|
||||
* @param array<string, mixed> $body corps decode de la reponse (toArray(false))
|
||||
*
|
||||
* @return array<string, string> propertyPath => message
|
||||
*/
|
||||
protected function violationsByPath(array $body): array
|
||||
{
|
||||
$byPath = [];
|
||||
foreach ($body['violations'] ?? [] as $v) {
|
||||
$byPath[$v['propertyPath']] = $v['message'];
|
||||
}
|
||||
|
||||
return $byPath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,18 +77,5 @@ final class SupplierAccountingApiTest extends AbstractSupplierApiTestCase
|
||||
self::assertResponseStatusCodeSame(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $body
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function violationsByPath(array $body): array
|
||||
{
|
||||
$byPath = [];
|
||||
foreach ($body['violations'] ?? [] as $v) {
|
||||
$byPath[$v['propertyPath']] = $v['message'];
|
||||
}
|
||||
|
||||
return $byPath;
|
||||
}
|
||||
// violationsByPath() : helper mutualise dans AbstractSupplierApiTestCase.
|
||||
}
|
||||
|
||||
@@ -147,12 +147,15 @@ final class SupplierApiTest extends AbstractSupplierApiTestCase
|
||||
$seed = $this->seedSupplier('Archive Plus Field');
|
||||
|
||||
// RG-2.14 : une requete d'archivage ne modifie aucun autre champ.
|
||||
$client->request('PATCH', '/api/suppliers/'.$seed->getId(), [
|
||||
$response = $client->request('PATCH', '/api/suppliers/'.$seed->getId(), [
|
||||
'headers' => ['Content-Type' => self::MERGE],
|
||||
'json' => ['isArchived' => true, 'companyName' => 'Renamed While Archiving'],
|
||||
]);
|
||||
|
||||
self::assertResponseStatusCodeSame(422);
|
||||
// Le 422 doit etre celui de RG-2.14 (archivage exclusif) et non un 422
|
||||
// orthogonal : on verifie le message porte par l'exception.
|
||||
self::assertStringContainsString('archivage', $response->getContent(false));
|
||||
}
|
||||
|
||||
public function testRestoreSetsArchivedAtNull(): void
|
||||
|
||||
@@ -131,7 +131,14 @@ final class SupplierRBACMatrixTest extends AbstractSupplierApiTestCase
|
||||
|
||||
$data = $client->request('GET', '/api/suppliers/'.$supplier->getId(), ['headers' => ['Accept' => self::LD]])->toArray();
|
||||
|
||||
// Gating par omission sur l'ensemble des champs comptables (pas seulement
|
||||
// siren/ribs) : une regression reintroduisant accountNumber/nTva/tvaMode/
|
||||
// paymentType dans le groupe bureau serait sinon invisible.
|
||||
self::assertArrayNotHasKey('siren', $data);
|
||||
self::assertArrayNotHasKey('accountNumber', $data);
|
||||
self::assertArrayNotHasKey('nTva', $data);
|
||||
self::assertArrayNotHasKey('tvaMode', $data);
|
||||
self::assertArrayNotHasKey('paymentType', $data);
|
||||
self::assertArrayNotHasKey('ribs', $data);
|
||||
}
|
||||
|
||||
@@ -205,11 +212,14 @@ final class SupplierRBACMatrixTest extends AbstractSupplierApiTestCase
|
||||
|
||||
// manage : la creation passe la security d'operation (pas un 403 comme
|
||||
// Compta) mais bute sur RG-2.03 (onglet Information incomplet) -> 422.
|
||||
$client->request('POST', '/api/suppliers', [
|
||||
$response = $client->request('POST', '/api/suppliers', [
|
||||
'headers' => ['Content-Type' => self::LD],
|
||||
'json' => $this->validMainPayload('Commerciale Post'),
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(422);
|
||||
// Le 422 doit bien etre celui de RG-2.03 (onglet Information) et non un
|
||||
// 422 orthogonal : on exige une violation sur un champ de completude.
|
||||
self::assertArrayHasKey('description', $this->violationsByPath($response->toArray(false)));
|
||||
|
||||
// PAS accounting : edition onglet Comptabilite refusee
|
||||
$client->request('PATCH', '/api/suppliers/'.$seed->getId(), [
|
||||
@@ -234,8 +244,11 @@ final class SupplierRBACMatrixTest extends AbstractSupplierApiTestCase
|
||||
$data = $client->request('GET', '/api/suppliers/'.$supplier->getId(), ['headers' => ['Accept' => self::LD]])->toArray();
|
||||
|
||||
self::assertArrayNotHasKey('siren', $data);
|
||||
self::assertArrayNotHasKey('ribs', $data);
|
||||
self::assertArrayNotHasKey('accountNumber', $data);
|
||||
self::assertArrayNotHasKey('nTva', $data);
|
||||
self::assertArrayNotHasKey('tvaMode', $data);
|
||||
self::assertArrayNotHasKey('paymentType', $data);
|
||||
self::assertArrayNotHasKey('ribs', $data);
|
||||
}
|
||||
|
||||
public function testRG203CommercialePostIncompleteIs422AdminIs201(): void
|
||||
@@ -244,11 +257,12 @@ final class SupplierRBACMatrixTest extends AbstractSupplierApiTestCase
|
||||
|
||||
// RG-2.03 : Commerciale POST sans onglet Information complet -> 422.
|
||||
$commerciale = $this->authAs('commerciale');
|
||||
$commerciale->request('POST', '/api/suppliers', [
|
||||
$response = $commerciale->request('POST', '/api/suppliers', [
|
||||
'headers' => ['Content-Type' => self::LD],
|
||||
'json' => $this->validMainPayload('RG203 Commerciale', $cat->getId()),
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(422);
|
||||
self::assertArrayHasKey('description', $this->violationsByPath($response->toArray(false)));
|
||||
|
||||
// Meme payload par un Admin (non gate par RG-2.03) -> 201.
|
||||
$admin = $this->createAdminClient();
|
||||
@@ -266,11 +280,12 @@ final class SupplierRBACMatrixTest extends AbstractSupplierApiTestCase
|
||||
$seed = $this->seedSupplier('Commerciale Patch Incomplete');
|
||||
$commerciale = $this->authAs('commerciale');
|
||||
|
||||
$commerciale->request('PATCH', '/api/suppliers/'.$seed->getId(), [
|
||||
$response = $commerciale->request('PATCH', '/api/suppliers/'.$seed->getId(), [
|
||||
'headers' => ['Content-Type' => self::MERGE],
|
||||
'json' => ['companyName' => 'Commerciale Renamed'],
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(422);
|
||||
self::assertArrayHasKey('description', $this->violationsByPath($response->toArray(false)));
|
||||
|
||||
// Le meme PATCH par un Admin passe (non gate par RG-2.03) -> 200.
|
||||
$admin = $this->createAdminClient();
|
||||
|
||||
@@ -345,20 +345,7 @@ final class SupplierSubResourceApiTest extends AbstractSupplierApiTestCase
|
||||
|
||||
// === Helpers ===
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $body
|
||||
*
|
||||
* @return array<string, string> propertyPath => message
|
||||
*/
|
||||
private function violationsByPath(array $body): array
|
||||
{
|
||||
$byPath = [];
|
||||
foreach ($body['violations'] ?? [] as $v) {
|
||||
$byPath[$v['propertyPath']] = $v['message'];
|
||||
}
|
||||
|
||||
return $byPath;
|
||||
}
|
||||
// violationsByPath() : helper mutualise dans AbstractSupplierApiTestCase.
|
||||
|
||||
private function firstSiteIri(): string
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user