[ERP-72] Paginer toutes les collections API + regle pagination obligatoire #28

Merged
tristan merged 8 commits from feature/ERP-72-backend-l-paginer-toutes-les-collections-api-exist into develop 2026-05-29 14:15:42 +00:00
2 changed files with 242 additions and 0 deletions
Showing only changes of commit 0430d7dc1e - Show all commits
@@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
namespace App\Tests\Module\Catalog\Api;
use DateTimeImmutable;
/**
* Tests du contrat de pagination sur GET /api/categories (ERP-72).
*
* Invariants testes :
* - la collection expose les metadonnees Hydra (totalItems, view, member) ;
* - itemsPerPage est plafonne au maximum global (50) ;
* - une page hors limites retourne une collection vide, pas une 500 ;
* - ?pagination=false retourne tous les items sans troncature (select-box) ;
* - la pagination est compatible avec le flag ?includeDeleted=true.
*
* @internal
*/
final class CategoryPaginationTest extends AbstractCatalogApiTestCase
{
/**
* La collection expose les metadonnees de pagination JSON-LD sans prefixe :
* `totalItems`, `view`, `member` (convention API Platform 4, pas hydra:*).
*
* On cree 12 categories pour depasser la limite par page (10) : la cle
* `view` n'est presente que lorsqu'il y a plus d'items que la taille de page.
*/
public function testCollectionExposesHydraPaginationMetadata(): void
{
$type = $this->createCategoryType();
for ($i = 1; $i <= 12; ++$i) {
$this->createCategory(self::TEST_CATEGORY_PREFIX.'meta_'.$i, $type);
}
$client = $this->createAdminClient();
$response = $client->request('GET', '/api/categories');
self::assertSame(200, $response->getStatusCode());
$data = $response->toArray();
self::assertArrayHasKey('totalItems', $data, 'La collection doit exposer totalItems.');
self::assertArrayHasKey('view', $data, 'La collection doit exposer view (pagination) quand totalItems > itemsPerPage.');
self::assertIsArray($data['member'], 'member doit etre un tableau.');
}
/**
* Un itemsPerPage arbitrairement grand (99999) doit etre plafonne au
* maximum global configure (50). On cree 12 categories pour etre certain
* de disposer de donnees ; le cap doit s'appliquer quelle que soit la taille
* reelle de la collection.
*/
public function testItemsPerPageIsCappedAtMaximum(): void
{
$type = $this->createCategoryType();
for ($i = 1; $i <= 12; ++$i) {
$this->createCategory(self::TEST_CATEGORY_PREFIX.'cap_'.$i, $type);
}
$client = $this->createAdminClient();
$response = $client->request('GET', '/api/categories?itemsPerPage=99999');
self::assertSame(200, $response->getStatusCode());
// Le cap global est 50 : jamais plus d'items par page que le maximum.
self::assertLessThanOrEqual(
50,
count($response->toArray()['member']),
'itemsPerPage doit etre plafonne au maximum global (50).',
);
}
/**
* Une page tres elevee (99999) sur une petite collection ne doit pas
* produire une 500 PG (OFFSET negatif ou depassement de capacite) mais
* retourner 200 avec un tableau member vide.
*/
public function testOutOfBoundPageReturnsEmptyCollectionNot500(): void
{
$type = $this->createCategoryType();
$this->createCategory(self::TEST_CATEGORY_PREFIX.'oob', $type);
$client = $this->createAdminClient();
$response = $client->request('GET', '/api/categories?page=99999');
self::assertSame(200, $response->getStatusCode());
// La page 99999 est forcement vide (on a bien moins que 99999*10 items).
self::assertSame(
[],
$response->toArray()['member'],
'Une page hors limites doit retourner un member vide, jamais une 500.',
);
}
/**
* ?pagination=false permet au frontend de desactiver la pagination pour
* alimenter un select-box. On cree exactement 12 categories dont les noms
* commencent par `test_cat_select_` : le filtre sur ce prefixe isole nos
* entrees des donnees concurrentes et prouve que les 12 items sont tous
* retournes (et pas seulement les 10 premiers de la page 1).
*/
public function testClientCanDisablePaginationToFeedASelect(): void
{
$type = $this->createCategoryType();
for ($i = 1; $i <= 12; ++$i) {
$this->createCategory(self::TEST_CATEGORY_PREFIX.'select_'.$i, $type);
}
$client = $this->createAdminClient();
$response = $client->request('GET', '/api/categories?pagination=false');
self::assertSame(200, $response->getStatusCode());
$members = $response->toArray()['member'];
// Filtre sur le sous-prefixe pour ne pas comptabiliser les categories
// d'autres tests qui partagent la meme base de donnees.
$selectItems = array_values(array_filter(
$members,
fn (array $m): bool => str_starts_with($m['name'], self::TEST_CATEGORY_PREFIX.'select_'),
));
self::assertCount(
12,
$selectItems,
'?pagination=false doit retourner toutes les categories (pas seulement la page 1).',
);
}
/**
* La pagination doit fonctionner conjointement avec le flag ?includeDeleted=true.
* On seed 3 categories actives + 2 soft-deleted, on demande itemsPerPage=5 :
* la page 1 doit contenir exactement 5 items et totalItems doit valoir >= 5.
*/
public function testPaginationCombinedWithIncludeDeletedFlag(): void
{
$type = $this->createCategoryType();
// 3 categories actives.
for ($i = 1; $i <= 3; ++$i) {
$this->createCategory(self::TEST_CATEGORY_PREFIX.'pag_active_'.$i, $type);
}
// 2 categories soft-deleted.
for ($i = 1; $i <= 2; ++$i) {
$this->createCategory(
self::TEST_CATEGORY_PREFIX.'pag_deleted_'.$i,
$type,
new DateTimeImmutable(),
);
}
$client = $this->createAdminClient();
$response = $client->request('GET', '/api/categories?includeDeleted=true&itemsPerPage=5');
self::assertSame(200, $response->getStatusCode());
$data = $response->toArray();
// La page retournee ne doit pas exceder itemsPerPage=5.
self::assertCount(
5,
$data['member'],
'La page 1 doit contenir exactement 5 items (itemsPerPage=5 avec >= 5 categories disponibles).',
);
self::assertGreaterThanOrEqual(
5,
$data['totalItems'],
'totalItems doit refleter au moins les 5 categories seedees (actives + soft-deleted).',
);
}
}
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace App\Tests\Module\Core\Api;
/**
* Regression test de pagination sur GET /api/audit-logs (ERP-72).
*
* Avant ce ticket, `paginationItemsPerPage` etait fixe a 30 dans
* AuditLogResource. Apres migration vers les defaults globaux (10/50),
* ce fichier verrouille le nouveau contrat :
* - la reponse est paginee (max 10 items par page par defaut) ;
* - un itemsPerPage excessif est plafonne a 50.
*
* Pas de seed : la table audit_log contient deja des lignes issues des
* fixtures / autres tests. Les assertions utilisent des inegalites pour
* rester robustes quelle que soit la quantite exacte de donnees presentes.
*
* @internal
*/
final class AuditLogPaginationRegressionTest extends AbstractApiTestCase
{
/**
* La collection /api/audit-logs doit etre paginee avec les defaults globaux :
* - `member`, `totalItems`, `view` presentes dans la reponse JSON-LD ;
* - au plus 10 items par page (nouveau defaut, etait 30 avant ce ticket).
*/
public function testAuditLogCollectionStillPaginated(): void
{
$client = $this->authenticatedClient('admin', 'admin');
$response = $client->request('GET', '/api/audit-logs');
self::assertSame(200, $response->getStatusCode());
$data = $response->toArray();
self::assertArrayHasKey('totalItems', $data, 'La collection audit-logs doit exposer totalItems.');
self::assertArrayHasKey('view', $data, 'La collection audit-logs doit exposer view (pagination active).');
self::assertIsArray($data['member'], 'member doit etre un tableau.');
// Le nouveau defaut global est 10 (etait 30 dans AuditLogResource avant ERP-72).
self::assertLessThanOrEqual(
10,
count($data['member']),
'La page par defaut ne doit pas depasser 10 items (default global ERP-72).',
);
}
/**
* Un itemsPerPage excessif (99999) doit etre plafonne au maximum global (50).
* Teste la regression specifique du paginator DBAL custom (DbalPaginator) qui
* pourrait ignorer la limite si la logique de cap n'est pas appliquee cote provider.
*/
public function testAuditLogItemsPerPageCappedAt50(): void
{
$client = $this->authenticatedClient('admin', 'admin');
$response = $client->request('GET', '/api/audit-logs?itemsPerPage=99999');
self::assertSame(200, $response->getStatusCode());
$data = $response->toArray();
self::assertIsArray($data['member'], 'member doit etre un tableau.');
// Le cap global est 50 : jamais plus d'items par page que le maximum.
self::assertLessThanOrEqual(
50,
count($data['member']),
'itemsPerPage=99999 doit etre plafonne a 50 (maximum global ERP-72).',
);
}
}