Files
Starseed/tests/Module/Technique/Api/ProviderListTest.php
T
Matthieu d6ed4f5faf test(technique) : couvrir RG-3.x PHPUnit + capturer le contrat JSON (ProviderSerializationContractTest, ProviderAuditTest, fixtures démo) (ERP-139)
Ajoute provider:read:accounting sur les réfs comptables partagées (TvaMode/PaymentDelay/PaymentType/Bank) pour embarquer {id,code,label} au lieu d un IRI nu (réplique fix ERP-92). Helper seedCompleteProvider, anti-N+1 + pagination=false + filtre typeCode, restauration conflit 409, fixtures démo idempotentes. Captures JSON réelles collées dans spec § 4.0.bis.
2026-06-12 16:28:01 +02:00

171 lines
6.3 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Module\Technique\Api;
/**
* Tests de la liste paginee /api/providers (ProviderProvider) — ERP-134.
* Couvre : envelope Hydra, tri companyName ASC, exclusion des archives,
* ?includeArchived (RG-3.16). Joue en admin (bypass_scope -> pas de cloisonnement).
*
* @internal
*/
final class ProviderListTest extends AbstractProviderApiTestCase
{
public function testListReturnsHydraEnvelopeSortedByName(): void
{
$this->seedProvider('Zeta Services', [self::SITE_86]);
$this->seedProvider('Alpha Nettoyage', [self::SITE_86]);
$this->seedProvider('Mu Maintenance', [self::SITE_86]);
$client = $this->createAdminClient();
$response = $client->request('GET', '/api/providers', [
'headers' => ['Accept' => self::LD],
]);
self::assertSame(200, $response->getStatusCode());
$body = $response->toArray();
// Envelope Hydra : totalItems present + member.
self::assertSame(3, $body['totalItems']);
$names = array_column($body['member'], 'companyName');
// Tri companyName ASC (RG-3.16) — noms normalises en MAJUSCULES.
self::assertSame(['ALPHA NETTOYAGE', 'MU MAINTENANCE', 'ZETA SERVICES'], $names);
}
public function testListExcludesArchivedByDefault(): void
{
$this->seedProvider('Actif Sas', [self::SITE_86]);
$this->seedProvider('Archive Sarl', [self::SITE_86], isArchived: true);
$client = $this->createAdminClient();
$response = $client->request('GET', '/api/providers', [
'headers' => ['Accept' => self::LD],
]);
self::assertSame(200, $response->getStatusCode());
$body = $response->toArray();
self::assertSame(1, $body['totalItems']);
self::assertSame('ACTIF SAS', $body['member'][0]['companyName']);
}
public function testListIncludeArchivedReintegratesArchived(): void
{
$this->seedProvider('Actif Sas', [self::SITE_86]);
$this->seedProvider('Archive Sarl', [self::SITE_86], isArchived: true);
$client = $this->createAdminClient();
$response = $client->request('GET', '/api/providers?includeArchived=true', [
'headers' => ['Accept' => self::LD],
]);
self::assertSame(200, $response->getStatusCode());
self::assertSame(2, $response->toArray()['totalItems']);
}
public function testListFiltersBySiteIdViaDirectRelation(): void
{
$this->seedProvider('Site 86 Only', [self::SITE_86]);
$this->seedProvider('Site 17 Only', [self::SITE_17]);
$client = $this->createAdminClient();
$site17 = $this->site(self::SITE_17);
$response = $client->request('GET', '/api/providers?siteId='.$site17->getId(), [
'headers' => ['Accept' => self::LD],
]);
self::assertSame(200, $response->getStatusCode());
$body = $response->toArray();
self::assertSame(1, $body['totalItems']);
self::assertSame('SITE 17 ONLY', $body['member'][0]['companyName']);
}
public function testPaginationDisabledReturnsFullCollection(): void
{
$token = $this->token();
for ($i = 0; $i < 3; ++$i) {
$this->seedProvider($token.' Item'.$i, [self::SITE_86]);
}
$client = $this->createAdminClient();
// ?pagination=false : echappatoire pour alimenter un <select> (regle n°13).
$data = $client->request('GET', '/api/providers?search='.$token.'&pagination=false', [
'headers' => ['Accept' => self::LD],
])->toArray();
self::assertArrayHasKey('member', $data);
self::assertCount(3, $data['member']);
}
/**
* Anti N+1 (§ 2.12) : le nombre de requetes SQL de la liste ne doit PAS croitre
* avec le nombre de prestataires. On mesure pour N=2 puis N=4 (memes relations
* embarquees : categories + sites directs + adresses.sites) et on exige un
* compte IDENTIQUE — preuve que l'hydratation est batchee (WHERE IN) et non par
* ligne.
*/
public function testListQueryCountDoesNotGrowWithRowCount(): void
{
$this->skipIfSitesModuleDisabled();
$token = $this->token();
$this->seedCompleteProvider($token.' A');
$this->seedCompleteProvider($token.' B');
$countFor2 = $this->countListQueries($token);
$this->seedCompleteProvider($token.' C');
$this->seedCompleteProvider($token.' D');
$countFor4 = $this->countListQueries($token);
self::assertSame(
$countFor2,
$countFor4,
sprintf('Anti N+1 : le nombre de requetes liste doit etre constant (%d pour 2, %d pour 4).', $countFor2, $countFor4),
);
}
/**
* Filtre ?typeCode= (cree au M2, reutilise au M3) : GET /api/categories?typeCode=
* PRESTATAIRE ne renvoie QUE les categories de type PRESTATAIRE — prerequis des
* multi-selects Categorie du prestataire (DoD § 4.7).
*/
public function testCategoriesTypeCodeFilterReturnsOnlyPrestataire(): void
{
$prestataire = $this->providerCategory('NETTOYAGE');
$foreign = $this->foreignCategory();
$client = $this->createAdminClient();
$data = $client->request('GET', '/api/categories?typeCode=PRESTATAIRE&pagination=false', [
'headers' => ['Accept' => self::LD],
])->toArray();
$ids = array_column($data['member'], 'id');
self::assertContains($prestataire->getId(), $ids, 'La categorie PRESTATAIRE doit etre presente.');
self::assertNotContains($foreign->getId(), $ids, 'Une categorie d\'un autre type doit etre filtree.');
}
/**
* Compte les requetes SQL emises par UN GET liste filtre, via le data holder de
* debug Doctrine. Le holder est remis a zero juste avant la requete pour isoler
* ses requetes (hors login).
*/
private function countListQueries(string $token): int
{
$http = $this->createAdminClient();
$holder = self::getContainer()->get('doctrine.debug_data_holder');
$holder->reset();
$http->request('GET', '/api/providers?search='.$token, ['headers' => ['Accept' => self::LD]]);
$data = $holder->getData();
return count($data['default'] ?? []);
}
private function token(): string
{
return 'List'.substr(bin2hex(random_bytes(4)), 0, 8);
}
}