Newer
Older
<?php
namespace App\Tests\Api;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use Doctrine\DBAL\Connection;
class StatsEndpointsTest extends ApiTestCase
{
public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();
static::bootKernel();
/** @var Connection $connection */
$connection = static::getContainer()->get(Connection::class);
$connection->executeStatement('DELETE FROM tax_data');
// Jeu de donnees minimal pour eviter la dependance aux fixtures lourdes.
$rows = [
[2019, '11', 'ILE-DE-FRANCE', '75', '75056', 12.5, 20.0, 11.0, 3.2, 100000, 200000, 300000, 400000, 1000000],
[2020, '11', 'ILE-DE-FRANCE', '75', '75056', 12.7, 20.4, 11.1, 3.4, 110000, 210000, 310000, 410000, 1040000],
[2022, '32', 'LES-HAUTS-DE-FRANCE', '59', '59350', 22.1, 31.5, 17.3, 7.8, 120000, 220000, 320000, 420000, 1080000],
// Deuxieme commune de la meme region/annee pour verifier l'agregation SQL.
[2022, '32', 'LES-HAUTS-DE-FRANCE', '62', '62041', 24.4, 0.0, 19.5, 9.2, 90000, 160000, 250000, 380000, 880000],
// Region distincte pour verifier l'ordre et les aggregations de repartition.
[2022, '84', 'AUVERGNE-RHONE-ALPES', '69', '69123', 18.2, 27.0, 14.2, 6.1, 150000, 520000, 410000, 300000, 1380000],
];
foreach ($rows as $row) {
$connection->executeStatement(
'INSERT INTO tax_data (year, region_code, region_name, department_code, commune_code, rate_tfpnb, rate_tfpb, rate_th, rate_cfe, volume_tfpnb, volume_tfpb, volume_th, volume_cfe, collected_volume)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
$row
);
}
}
public function testRegionEvolutionEndpointReturnsData(): void
{
$response = static::createClient()->request('GET', '/api/stats/region-evolution?tax=tfpb&startYear=2019&endYear=2022');
$this->assertResponseIsSuccessful();
$data = $response->toArray(false);
$members = $this->extractMembers($data);
$this->assertNotEmpty($members, 'Le endpoint region-evolution doit retourner des données.');
$this->assertArrayHasKey('year', $members[0]);
$this->assertArrayHasKey('regionCode', $members[0]);
$this->assertArrayHasKey('regionName', $members[0]);
$this->assertArrayHasKey('rate', $members[0]);
$first = $this->findMember($members, static fn (array $item): bool => (int) $item['year'] === 2019 && $item['regionCode'] === '11');
$this->assertNotNull($first);
$this->assertSame('ILE-DE-FRANCE', $first['regionName']);
$this->assertEquals(20.0, (float) $first['rate']);
}
public function testDepartmentCorrelationEndpointReturnsData(): void
{
$response = static::createClient()->request('GET', '/api/stats/department-correlation?tax=tfpb&year=2022&department=59');
$this->assertResponseIsSuccessful();
$data = $response->toArray(false);
$members = $this->extractMembers($data);
$this->assertNotEmpty($members, 'Le endpoint department-correlation doit retourner des données.');
$this->assertArrayHasKey('communeCode', $members[0]);
$this->assertArrayHasKey('rate', $members[0]);
$this->assertArrayHasKey('collectedVolume', $members[0]);
$this->assertSame('59350', $members[0]['communeCode']);
$this->assertEquals(220000.0, (float) $members[0]['collectedVolume']);
public function testDepartmentCorrelationReturnsTaxSpecificVolume(): void
{
$responseTfpb = static::createClient()->request('GET', '/api/stats/department-correlation?tax=tfpb&year=2022&department=59');
$responseCfe = static::createClient()->request('GET', '/api/stats/department-correlation?tax=cfe&year=2022&department=59');
$this->assertResponseIsSuccessful();
$membersTfpb = $this->extractMembers($responseTfpb->toArray(false));
$membersCfe = $this->extractMembers($responseCfe->toArray(false));
$this->assertNotEmpty($membersTfpb);
$this->assertNotEmpty($membersCfe);
$this->assertSame('59350', $membersTfpb[0]['communeCode']);
$this->assertSame('59350', $membersCfe[0]['communeCode']);
$this->assertEquals(220000.0, (float) $membersTfpb[0]['collectedVolume']);
$this->assertEquals(420000.0, (float) $membersCfe[0]['collectedVolume']);
}
public function testRegionalDistributionEndpointReturnsData(): void
{
$response = static::createClient()->request('GET', '/api/stats/regional-distribution?year=2022');
$this->assertResponseIsSuccessful();
$data = $response->toArray(false);
$members = $this->extractMembers($data);
$this->assertNotEmpty($members, 'Le endpoint regional-distribution doit retourner des données.');
$this->assertArrayHasKey('regionCode', $members[0]);
$this->assertArrayHasKey('regionName', $members[0]);
$this->assertArrayHasKey('collectedVolume', $members[0]);
// En default tax=tfpb, la region 84 est devant avec 520000.
$this->assertSame('84', $members[0]['regionCode']);
$this->assertEquals(520000.0, (float) $members[0]['collectedVolume']);
}
public function testDepartmentCorrelationReturnsEmptyForUnknownDepartment(): void
{
$response = static::createClient()->request('GET', '/api/stats/department-correlation?tax=tfpb&year=2022&department=00');
$this->assertResponseIsSuccessful();
$data = $response->toArray(false);
$members = $this->extractMembers($data);
$this->assertSame([], $members);
}
public function testRegionEvolutionFallsBackWhenTaxIsInvalid(): void
{
$response = static::createClient()->request('GET', '/api/stats/region-evolution?tax=invalid&startYear=2019&endYear=2022');
$this->assertResponseIsSuccessful();
$data = $response->toArray(false);
$members = $this->extractMembers($data);
$this->assertNotEmpty($members);
$member = $this->findMember($members, static fn (array $item): bool => (int) $item['year'] === 2019 && $item['regionCode'] === '11');
$this->assertNotNull($member);
public function testRegionEvolutionReturnsEmptyWhenStartYearIsGreaterThanEndYear(): void
{
$response = static::createClient()->request('GET', '/api/stats/region-evolution?tax=tfpb&startYear=2022&endYear=2019');
$this->assertResponseIsSuccessful();
$data = $response->toArray(false);
$members = $this->extractMembers($data);
$this->assertSame([], $members);
}
public function testRegionEvolutionUsesDefaultParameters(): void
{
$response = static::createClient()->request('GET', '/api/stats/region-evolution');
$this->assertResponseIsSuccessful();
$data = $response->toArray(false);
$members = $this->extractMembers($data);
$this->assertNotEmpty($members);
// Par defaut: tax=tfpb, startYear=2019, endYear=2022.
$first = $this->findMember($members, static fn (array $item): bool => (int) $item['year'] === 2019 && $item['regionCode'] === '11');
$this->assertNotNull($first);
$this->assertEquals(20.0, (float) $first['rate']);
$years = array_values(array_unique(array_map(
static fn (array $item): int => (int) $item['year'],
$members
)));
sort($years);
$this->assertSame([2019, 2020, 2022], $years);
}
public function testRegionEvolutionIgnoresZeroRatesInAverage(): void
{
$response = static::createClient()->request('GET', '/api/stats/region-evolution?tax=tfpb&startYear=2022&endYear=2022');
$this->assertResponseIsSuccessful();
$members = $this->extractMembers($response->toArray(false));
$target = $this->findMember($members, static fn (array $item): bool => $item['regionCode'] === '32');
// Region 32 a deux lignes en 2022: 31.5 et 0.0, la requete doit ignorer 0.
$this->assertNotNull($target);
$this->assertEquals(31.5, (float) $target['rate']);
}
public function testDepartmentCorrelationFallsBackWhenTaxIsInvalid(): void
{
$fallbackResponse = static::createClient()->request('GET', '/api/stats/department-correlation?tax=invalid&year=2022&department=59');
$expectedResponse = static::createClient()->request('GET', '/api/stats/department-correlation?tax=tfpb&year=2022&department=59');
$this->assertSame(200, $fallbackResponse->getStatusCode());
$this->assertSame(200, $expectedResponse->getStatusCode());
$fallbackMembers = $this->extractMembers($fallbackResponse->toArray(false));
$expectedMembers = $this->extractMembers($expectedResponse->toArray(false));
$this->assertSame($expectedMembers, $fallbackMembers);
}
public function testDepartmentCorrelationReturnsEmptyWhenDepartmentIsMissing(): void
{
$response = static::createClient()->request('GET', '/api/stats/department-correlation?tax=tfpb&year=2022');
$this->assertResponseIsSuccessful();
$members = $this->extractMembers($response->toArray(false));
$this->assertSame([], $members);
}
public function testDepartmentCorrelationUsesDefaultParametersWhenMissing(): void
{
$response = static::createClient()->request('GET', '/api/stats/department-correlation');
$this->assertResponseIsSuccessful();
$members = $this->extractMembers($response->toArray(false));
// Default provider: tax=tfpb, year=2022, department=73 (absent du jeu minimal).
$this->assertSame([], $members);
}
public function testRegionalDistributionReturnsDifferentVolumesDependingOnTax(): void
{
$responseTfpb = static::createClient()->request('GET', '/api/stats/regional-distribution?tax=tfpb&year=2022');
$responseCfe = static::createClient()->request('GET', '/api/stats/regional-distribution?tax=cfe&year=2022');
$this->assertSame(200, $responseTfpb->getStatusCode());
$this->assertSame(200, $responseCfe->getStatusCode());
$tfpbMembers = $this->extractMembers($responseTfpb->toArray(false));
$cfeMembers = $this->extractMembers($responseCfe->toArray(false));
$this->assertNotEmpty($tfpbMembers);
$this->assertNotEmpty($cfeMembers);
$targetTfpb = $this->findMember($tfpbMembers, static fn (array $item): bool => $item['regionCode'] === '32');
$targetCfe = $this->findMember($cfeMembers, static fn (array $item): bool => $item['regionCode'] === '32');
$this->assertNotNull($targetTfpb);
$this->assertNotNull($targetCfe);
$this->assertEquals(380000.0, (float) $targetTfpb['collectedVolume']);
$this->assertEquals(800000.0, (float) $targetCfe['collectedVolume']);
$this->assertNotSame((float) $targetTfpb['collectedVolume'], (float) $targetCfe['collectedVolume']);
}
public function testRegionalDistributionFallsBackWhenTaxIsInvalid(): void
{
$fallbackResponse = static::createClient()->request('GET', '/api/stats/regional-distribution?tax=invalid&year=2022');
$expectedResponse = static::createClient()->request('GET', '/api/stats/regional-distribution?tax=tfpb&year=2022');
$this->assertSame(200, $fallbackResponse->getStatusCode());
$this->assertSame(200, $expectedResponse->getStatusCode());
$fallbackMembers = $this->extractMembers($fallbackResponse->toArray(false));
$expectedMembers = $this->extractMembers($expectedResponse->toArray(false));
$this->assertSame($expectedMembers, $fallbackMembers);
}
public function testRegionalDistributionReturnsEmptyForUnknownYear(): void
{
$response = static::createClient()->request('GET', '/api/stats/regional-distribution?tax=tfpb&year=1990');
$this->assertResponseIsSuccessful();
$members = $this->extractMembers($response->toArray(false));
$this->assertSame([], $members);
}
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
public function testRegionalDistributionUsesDefaultParameters(): void
{
$response = static::createClient()->request('GET', '/api/stats/regional-distribution');
$this->assertResponseIsSuccessful();
$members = $this->extractMembers($response->toArray(false));
// Default year=2019, tax=tfpb sur le jeu minimal => seulement region 11.
$this->assertCount(1, $members);
$this->assertSame('11', $members[0]['regionCode']);
$this->assertEquals(200000.0, (float) $members[0]['collectedVolume']);
}
public function testRegionalDistributionIsSortedDescendingByCollectedVolume(): void
{
$response = static::createClient()->request('GET', '/api/stats/regional-distribution?tax=tfpb&year=2022');
$this->assertResponseIsSuccessful();
$members = $this->extractMembers($response->toArray(false));
$this->assertGreaterThanOrEqual(2, count($members));
$this->assertGreaterThanOrEqual((float) $members[1]['collectedVolume'], (float) $members[0]['collectedVolume']);
}
public function testRegionEvolutionSupportsDifferentTaxes(): void
{
$responseTh = static::createClient()->request('GET', '/api/stats/region-evolution?tax=th&startYear=2022&endYear=2022');
$responseCfe = static::createClient()->request('GET', '/api/stats/region-evolution?tax=cfe&startYear=2022&endYear=2022');
$this->assertSame(200, $responseTh->getStatusCode());
$this->assertSame(200, $responseCfe->getStatusCode());
$thMembers = $this->extractMembers($responseTh->toArray(false));
$cfeMembers = $this->extractMembers($responseCfe->toArray(false));
$this->assertNotEmpty($thMembers);
$this->assertNotEmpty($cfeMembers);
$thRegion32 = $this->findMember($thMembers, static fn (array $item): bool => $item['regionCode'] === '32');
$cfeRegion32 = $this->findMember($cfeMembers, static fn (array $item): bool => $item['regionCode'] === '32');
$this->assertNotNull($thRegion32);
$this->assertNotNull($cfeRegion32);
$this->assertNotSame((float) $thRegion32['rate'], (float) $cfeRegion32['rate']);
}
/**
* API Platform peut renvoyer "member" ou "hydra:member" selon la config.
*
* @param array<string, mixed> $data
* @return array<int, array<string, mixed>>
*/
private function extractMembers(array $data): array
{
if (isset($data['member']) && is_array($data['member'])) {
return $data['member'];
}
if (isset($data['hydra:member']) && is_array($data['hydra:member'])) {
return $data['hydra:member'];
}
return [];
}
/**
* @param array<int, array<string, mixed>> $members
* @param callable(array<string, mixed>): bool $predicate
* @return array<string, mixed>|null
*/
private function findMember(array $members, callable $predicate): ?array
{
foreach ($members as $member) {
if ($predicate($member)) {
return $member;
}
}
return null;
}