feat: unfiltered product export for the website's catalogue import
This commit is contained in:
1 parent
272c811f4f
commit
1d42565a84
11 files changed
+970
-33
No files matched your search
@@ -5,8 +5,9 @@ declare(strict_types=1);
|
||||
namespace App\Tests\Controller\Api;
|
||||
|
||||
use App\Controller\Api\ExportController;
|
||||
use App\Model\TravelExport;
|
||||
use App\Model\TravelExportQuery;
|
||||
use App\Model\Export;
|
||||
use App\Model\ExportQuery;
|
||||
use App\Service\ProductExportBuilder;
|
||||
use App\Service\TravelExportBuilder;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\Container;
|
||||
@@ -16,7 +17,7 @@ class ExportControllerTest extends TestCase
|
||||
{
|
||||
public function testPaginatesAndReportsWholeExportFigures(): void
|
||||
{
|
||||
$meta = $this->call(new TravelExportQuery(1, 5))['meta'];
|
||||
$meta = $this->call(new ExportQuery(1, 5))['meta'];
|
||||
|
||||
self::assertSame(1, $meta['page']);
|
||||
self::assertSame(5, $meta['perPage']);
|
||||
@@ -27,8 +28,8 @@ class ExportControllerTest extends TestCase
|
||||
public function testCountersAreIdenticalOnEveryPage(): void
|
||||
{
|
||||
// Whole-export figures repeated per page, so a consumer can abort after page 1.
|
||||
$first = $this->call(new TravelExportQuery(1, 5))['meta'];
|
||||
$last = $this->call(new TravelExportQuery(3, 5))['meta'];
|
||||
$first = $this->call(new ExportQuery(1, 5))['meta'];
|
||||
$last = $this->call(new ExportQuery(3, 5))['meta'];
|
||||
|
||||
foreach (['total', 'totalPages', 'skipped', 'pickupsWithoutMasterData', 'sourceGeneratedAt', 'generatedAt'] as $key) {
|
||||
self::assertSame($first[$key], $last[$key], $key.' must not vary by page');
|
||||
@@ -37,8 +38,8 @@ class ExportControllerTest extends TestCase
|
||||
|
||||
public function testConsecutivePagesDoNotOverlap(): void
|
||||
{
|
||||
$first = array_column($this->call(new TravelExportQuery(1, 5))['data'], 'busProId');
|
||||
$second = array_column($this->call(new TravelExportQuery(2, 5))['data'], 'busProId');
|
||||
$first = array_column($this->call(new ExportQuery(1, 5))['data'], 'busProId');
|
||||
$second = array_column($this->call(new ExportQuery(2, 5))['data'], 'busProId');
|
||||
|
||||
self::assertSame([1, 2, 3, 4, 5], $first);
|
||||
self::assertSame([6, 7, 8, 9, 10], $second);
|
||||
@@ -47,7 +48,7 @@ class ExportControllerTest extends TestCase
|
||||
|
||||
public function testTheLastPageIsShort(): void
|
||||
{
|
||||
$data = $this->call(new TravelExportQuery(3, 5))['data'];
|
||||
$data = $this->call(new ExportQuery(3, 5))['data'];
|
||||
|
||||
self::assertCount(2, $data);
|
||||
self::assertSame([11, 12], array_column($data, 'busProId'));
|
||||
@@ -55,7 +56,7 @@ class ExportControllerTest extends TestCase
|
||||
|
||||
public function testAPageBeyondTheEndIsEmptyRatherThanAnError(): void
|
||||
{
|
||||
$body = $this->call(new TravelExportQuery(99, 5));
|
||||
$body = $this->call(new ExportQuery(99, 5));
|
||||
|
||||
self::assertSame([], $body['data']);
|
||||
self::assertSame(12, $body['meta']['total']);
|
||||
@@ -84,9 +85,9 @@ class ExportControllerTest extends TestCase
|
||||
public function testAnUnknownSourceExportIsNullRatherThanOmitted(): void
|
||||
{
|
||||
$builder = $this->createStub(TravelExportBuilder::class);
|
||||
$builder->method('build')->willReturn(new TravelExport([], 0, 0, new \DateTimeImmutable()));
|
||||
$builder->method('build')->willReturn(new Export([], 0, 0, new \DateTimeImmutable()));
|
||||
|
||||
$meta = json_decode((string) $this->controller($builder)->travels(new TravelExportQuery())->getContent(), true)['meta'];
|
||||
$meta = json_decode((string) $this->controller($builder)->travels(new ExportQuery())->getContent(), true)['meta'];
|
||||
|
||||
self::assertArrayHasKey('sourceGeneratedAt', $meta);
|
||||
self::assertNull($meta['sourceGeneratedAt']);
|
||||
@@ -103,13 +104,57 @@ class ExportControllerTest extends TestCase
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
|
||||
$this->controller($builder)->travels(new TravelExportQuery());
|
||||
$this->controller($builder)->travels(new ExportQuery());
|
||||
}
|
||||
|
||||
public function testProductsUseTheSameEnvelopeAndPaging(): void
|
||||
{
|
||||
$rows = [];
|
||||
for ($i = 1; $i <= 3; ++$i) {
|
||||
$rows[] = ['date' => ['busProId' => $i], 'hotel' => ['busProId' => 1]];
|
||||
}
|
||||
|
||||
$productBuilder = $this->createStub(ProductExportBuilder::class);
|
||||
$productBuilder->method('build')->willReturn(new Export(
|
||||
$rows,
|
||||
2,
|
||||
5,
|
||||
new \DateTimeImmutable('2026-09-29 03:20:00', new \DateTimeZone('Europe/Berlin')),
|
||||
new \DateTimeImmutable('2026-09-28 23:10:15', new \DateTimeZone('Europe/Berlin')),
|
||||
));
|
||||
|
||||
$response = $this->controller($this->createStub(TravelExportBuilder::class), $productBuilder)
|
||||
->products(new ExportQuery(2, 2));
|
||||
$body = json_decode((string) $response->getContent(), true);
|
||||
|
||||
self::assertSame(Response::HTTP_OK, $response->getStatusCode());
|
||||
self::assertSame([3], array_column(array_column($body['data'], 'date'), 'busProId'));
|
||||
self::assertSame([
|
||||
'page' => 2,
|
||||
'perPage' => 2,
|
||||
'total' => 3,
|
||||
'totalPages' => 2,
|
||||
'skipped' => 2,
|
||||
'pickupsWithoutMasterData' => 5,
|
||||
'sourceGeneratedAt' => '2026-09-28T23:10:15+02:00',
|
||||
'generatedAt' => '2026-09-29T03:20:00+02:00',
|
||||
], $body['meta']);
|
||||
}
|
||||
|
||||
public function testAProductBuilderFailureIsNotDowngradedToATwoHundred(): void
|
||||
{
|
||||
$productBuilder = $this->createStub(ProductExportBuilder::class);
|
||||
$productBuilder->method('build')->willThrowException(new \RuntimeException('cache exploded'));
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
|
||||
$this->controller($this->createStub(TravelExportBuilder::class), $productBuilder)->products(new ExportQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function call(?TravelExportQuery $query = null, int $skipped = 0, int $withoutMasterData = 0): array
|
||||
private function call(?ExportQuery $query = null, int $skipped = 0, int $withoutMasterData = 0): array
|
||||
{
|
||||
$rows = [];
|
||||
for ($i = 1; $i <= 12; ++$i) {
|
||||
@@ -117,7 +162,7 @@ class ExportControllerTest extends TestCase
|
||||
}
|
||||
|
||||
$builder = $this->createStub(TravelExportBuilder::class);
|
||||
$builder->method('build')->willReturn(new TravelExport(
|
||||
$builder->method('build')->willReturn(new Export(
|
||||
$rows,
|
||||
$skipped,
|
||||
$withoutMasterData,
|
||||
@@ -125,16 +170,16 @@ class ExportControllerTest extends TestCase
|
||||
new \DateTimeImmutable('2026-09-28 23:10:15', new \DateTimeZone('Europe/Berlin')),
|
||||
));
|
||||
|
||||
$response = $this->controller($builder)->travels($query ?? new TravelExportQuery());
|
||||
$response = $this->controller($builder)->travels($query ?? new ExportQuery());
|
||||
|
||||
self::assertSame(Response::HTTP_OK, $response->getStatusCode());
|
||||
|
||||
return json_decode((string) $response->getContent(), true);
|
||||
}
|
||||
|
||||
private function controller(TravelExportBuilder $builder): ExportController
|
||||
private function controller(TravelExportBuilder $builder, ?ProductExportBuilder $productBuilder = null): ExportController
|
||||
{
|
||||
$controller = new ExportController($builder);
|
||||
$controller = new ExportController($builder, $productBuilder ?? $this->createStub(ProductExportBuilder::class));
|
||||
|
||||
// No 'serializer' service registered, so AbstractController::json() falls back to
|
||||
// JsonResponse -- which is what this endpoint produces in production anyway.
|
||||
|
||||
Reference in new issue
Block a user