test(commercial) : tests PHPUnit M2 fournisseurs (matrice RG + contrat sérialisation + DoD JSON réel) (ERP-92)
Suite fonctionnelle M2 assertant sur le CORPS JSON (jamais les annotations),
jumelle de la suite clients M1 :
- contrat de sérialisation : 4 régressions M1 re-testées (RIB gaté absent pour
Commerciale, booléens triageProvider/isArchived présents, embed
categories[].code/name, embed sites[].name/postalCode objet) + enveloppe AP4
(member/totalItems/view, archivés exclus) + suppression du contact inline ;
- matrice RBAC réelle (app:seed-rbac) bureau/compta/commerciale/usine 200/403,
gating accounting par omission de clé, mode strict PATCH (RG-2.16) ;
- RG-2.03/2.04/2.05/2.06/2.07/2.08/2.09/2.10/2.11/2.12/2.14/2.15/2.17 ;
- sous-ressources contacts/adresses/ribs (CRUD, sécurité, normalisation) ;
- anti N+1 liste (compte de requêtes constant), audit Supplier + RIB iban/bic.
Fix de contrat découvert et corrigé (sinon DoD figée sur un contrat faux) :
les référentiels comptables (TvaMode/PaymentType/PaymentDelay/Bank) ne portaient
que le groupe client:read:accounting (M1) → sur un fournisseur ils sortaient en
IRI nu. Ajout de supplier:read:accounting → objet {id, code, label} embarqué.
makefile : test-db-setup recrée l'index partiel uq_supplier_company_name_active
(droppé par schema:update comme pour le client) — oubli M2.
DoD § 4.0.bis : réponses JSON RÉELLES (liste + détail admin/commerciale) collées,
capturées via SupplierSerializationContractTest.
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
<?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.
|
||||
$client->request('PATCH', '/api/suppliers/'.$seed->getId(), [
|
||||
'headers' => ['Content-Type' => self::MERGE],
|
||||
'json' => ['isArchived' => true, 'companyName' => 'Renamed While Archiving'],
|
||||
]);
|
||||
|
||||
self::assertResponseStatusCodeSame(422);
|
||||
}
|
||||
|
||||
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.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user