1343c1b797
- 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).
181 lines
6.9 KiB
PHP
181 lines
6.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Module\Commercial\Api;
|
|
|
|
use App\Module\Commercial\Domain\Entity\Supplier;
|
|
|
|
/**
|
|
* Tests fonctionnels du formulaire principal fournisseur (M2, spec § 4.3 / § 4.4)
|
|
* sur le CORPS JSON : creation (companyName + categories), normalisation serveur
|
|
* (RG-2.12 UPPERCASE), categorie de type FOURNISSEUR (RG-2.10), unicite du nom
|
|
* (RG-2.11) et archivage nominal (RG-2.14). Jumeau de ClientApiTest (M1).
|
|
*
|
|
* @internal
|
|
*/
|
|
final class SupplierApiTest extends AbstractSupplierApiTestCase
|
|
{
|
|
// === POST formulaire principal ===
|
|
|
|
public function testPostMainFormUppercasesCompanyName(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
$cat = $this->supplierCategory('NEGOCIANT');
|
|
|
|
$data = $client->request('POST', '/api/suppliers', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => [
|
|
'companyName' => 'recycla sas',
|
|
'categories' => ['/api/categories/'.$cat->getId()],
|
|
],
|
|
])->toArray();
|
|
|
|
self::assertResponseStatusCodeSame(201);
|
|
// RG-2.12 : companyName normalise en MAJUSCULES sur la valeur RENVOYEE.
|
|
self::assertSame('RECYCLA SAS', $data['companyName']);
|
|
// Embed categorie : code/name presents (category:read dans le contexte).
|
|
self::assertSame('NEGOCIANT', $data['categories'][0]['code']);
|
|
}
|
|
|
|
public function testPostMainFormHasNoInlineContactFields(): void
|
|
{
|
|
// refonte-contact V0.2 : plus aucun champ de contact inline au POST.
|
|
$client = $this->createAdminClient();
|
|
$cat = $this->supplierCategory('NEGOCIANT');
|
|
|
|
$data = $client->request('POST', '/api/suppliers', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => [
|
|
'companyName' => 'No Inline Co',
|
|
// Champs historiques : ignores par le denormaliseur.
|
|
'firstName' => 'Ignored',
|
|
'lastName' => 'Ignored',
|
|
'phonePrimary' => '0612345678',
|
|
'email' => 'ignored@test.fr',
|
|
'categories' => ['/api/categories/'.$cat->getId()],
|
|
],
|
|
])->toArray();
|
|
|
|
self::assertResponseStatusCodeSame(201);
|
|
foreach (['firstName', 'lastName', 'phonePrimary', 'phoneSecondary', 'email'] as $key) {
|
|
self::assertArrayNotHasKey($key, $data);
|
|
}
|
|
}
|
|
|
|
// === RG-2.10 : categorie de type FOURNISSEUR ===
|
|
|
|
public function testPostWithNonFournisseurCategoryReturns422OnCategoriesPath(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
// createCategory() (parent) cree une categorie de type CLIENT -> interdite.
|
|
$clientTypedCategory = $this->createCategory('SECTEUR');
|
|
|
|
$response = $client->request('POST', '/api/suppliers', [
|
|
'headers' => ['Content-Type' => self::LD, 'Accept' => self::LD],
|
|
'json' => [
|
|
'companyName' => 'Wrong Cat Type',
|
|
'categories' => ['/api/categories/'.$clientTypedCategory->getId()],
|
|
],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(422);
|
|
$byPath = [];
|
|
foreach ($response->toArray(false)['violations'] ?? [] as $v) {
|
|
$byPath[$v['propertyPath']] = $v['message'];
|
|
}
|
|
// ERP-101 : la violation porte propertyPath=categories (mapping inline front).
|
|
self::assertArrayHasKey('categories', $byPath);
|
|
self::assertSame('Type de catégorie non autorisé (FOURNISSEUR attendu).', $byPath['categories']);
|
|
}
|
|
|
|
// === RG-2.11 : unicite du nom de societe ===
|
|
|
|
public function testPostDuplicateCompanyNameReturns409(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
$this->seedSupplier('Dup Name Co');
|
|
|
|
$client->request('POST', '/api/suppliers', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => $this->validMainPayload('Dup Name Co'),
|
|
]);
|
|
|
|
// RG-2.11 : doublon parmi les actifs -> 409 (index uq_supplier_company_name_active).
|
|
self::assertResponseStatusCodeSame(409);
|
|
}
|
|
|
|
public function testPostSameNameAfterArchivingPreviousReturns201(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
// L'homonyme est archive -> hors index partiel : le nom redevient disponible.
|
|
$this->seedSupplier('Reuse After Archive', true);
|
|
|
|
$client->request('POST', '/api/suppliers', [
|
|
'headers' => ['Content-Type' => self::LD],
|
|
'json' => $this->validMainPayload('Reuse After Archive'),
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(201);
|
|
}
|
|
|
|
// === RG-2.14 : archivage (admin) ===
|
|
|
|
public function testAdminArchiveSetsArchivedAt(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
$seed = $this->seedSupplier('Archive Me');
|
|
|
|
$client->request('PATCH', '/api/suppliers/'.$seed->getId(), [
|
|
'headers' => ['Content-Type' => self::MERGE],
|
|
'json' => ['isArchived' => true],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(200);
|
|
|
|
$em = $this->getEm();
|
|
$em->clear();
|
|
$reloaded = $em->getRepository(Supplier::class)->find($seed->getId());
|
|
self::assertNotNull($reloaded);
|
|
self::assertTrue($reloaded->isArchived());
|
|
self::assertNotNull($reloaded->getArchivedAt(), 'RG-2.14 : archivedAt doit etre rempli a l\'archivage.');
|
|
}
|
|
|
|
public function testArchiveWithOtherFieldReturns422(): void
|
|
{
|
|
$client = $this->createAdminClient();
|
|
$seed = $this->seedSupplier('Archive Plus Field');
|
|
|
|
// RG-2.14 : une requete d'archivage ne modifie aucun autre champ.
|
|
$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
|
|
{
|
|
$client = $this->createAdminClient();
|
|
$seed = $this->seedSupplier('Restore Me', true);
|
|
|
|
$client->request('PATCH', '/api/suppliers/'.$seed->getId(), [
|
|
'headers' => ['Content-Type' => self::MERGE],
|
|
'json' => ['isArchived' => false],
|
|
]);
|
|
|
|
self::assertResponseStatusCodeSame(200);
|
|
|
|
$em = $this->getEm();
|
|
$em->clear();
|
|
$reloaded = $em->getRepository(Supplier::class)->find($seed->getId());
|
|
self::assertNotNull($reloaded);
|
|
self::assertFalse($reloaded->isArchived());
|
|
self::assertNull($reloaded->getArchivedAt(), 'RG-2.15 : archivedAt repasse a null a la restauration.');
|
|
}
|
|
}
|