feat: sync contingents for TYPO3 calendar hotels
This commit is contained in:
1 parent
ec36b61275
commit
97c6330a9a
24 files changed
+1067
-180
No files matched your search
@@ -8,10 +8,16 @@ use App\BpnConnect\Model\ContingentStatus;
|
||||
use App\Controller\Api\ContingentController;
|
||||
use App\Entity\Groups\Accommodation;
|
||||
use App\Entity\Groups\AccommodationPrice;
|
||||
use App\Entity\Groups\CalendarHotel;
|
||||
use App\Entity\Groups\ContingentSyncState;
|
||||
use App\Model\ContingentCalendarQuery;
|
||||
use App\Model\ContingentHotel;
|
||||
use App\Repository\Groups\AccommodationPriceRepository;
|
||||
use App\Repository\Groups\AccommodationRepository;
|
||||
use App\Repository\Groups\ContingentDayRepository;
|
||||
use App\Repository\Groups\ContingentSyncStateRepository;
|
||||
use App\Service\AccommodationPriceCoverage;
|
||||
use App\Service\ContingentHotelDirectory;
|
||||
use App\Service\ContingentSnapshotReader;
|
||||
use App\Service\PriceTimelineBuilder;
|
||||
use Carbon\CarbonImmutable;
|
||||
@@ -216,15 +222,114 @@ class ContingentControllerTest extends TestCase
|
||||
|
||||
public function testAvailabilityRejectsAnUnknownHotel(): void
|
||||
{
|
||||
$accommodationRepository = $this->createStub(AccommodationRepository::class);
|
||||
$accommodationRepository->method('findOneBy')->willReturn(null);
|
||||
$hotelDirectory = $this->createStub(ContingentHotelDirectory::class);
|
||||
$hotelDirectory->method('find')->willReturn(null);
|
||||
|
||||
$response = $this->callAvailability([], accommodationRepository: $accommodationRepository);
|
||||
$response = $this->callAvailability([], hotelDirectory: $hotelDirectory);
|
||||
|
||||
self::assertSame(Response::HTTP_BAD_REQUEST, $response->getStatusCode());
|
||||
self::assertSame('{"error":"Hotel not found for hotelCode."}', $response->getContent());
|
||||
}
|
||||
|
||||
public function testAvailabilityServesATypo3HotelWithoutAnAccommodation(): void
|
||||
{
|
||||
$hotelDirectory = $this->createStub(ContingentHotelDirectory::class);
|
||||
$hotelDirectory->method('find')->willReturn(new ContingentHotel('DKI', null, (new CalendarHotel())->setCode('DKI')->setName('Sportclub Schwendi')));
|
||||
|
||||
$snapshotReader = $this->createMock(ContingentSnapshotReader::class);
|
||||
$snapshotReader->expects(self::once())
|
||||
->method('statusesFor')
|
||||
->with('DKI', self::anything(), self::anything())
|
||||
->willReturn(['2026-09-01' => ContingentStatus::Ok]);
|
||||
|
||||
$response = $this->callAvailability(null, new ContingentCalendarQuery('DKI', '2026-09-01', '2026-09-03'), $snapshotReader, $hotelDirectory);
|
||||
|
||||
self::assertSame(Response::HTTP_OK, $response->getStatusCode());
|
||||
self::assertSame('[{"date":"2026-09-01","status":"OK"}]', $response->getContent());
|
||||
}
|
||||
|
||||
public function testStatusListsEverySyncedHotel(): void
|
||||
{
|
||||
CarbonImmutable::setTestNow('2026-10-29 10:30:00');
|
||||
|
||||
$accommodation = (new Accommodation())->setName('Gruppenhaus Spinabad')->setCalendarCode('DGS')->setCmsCode('SERDGS');
|
||||
$hotelDirectory = $this->createStub(ContingentHotelDirectory::class);
|
||||
$hotelDirectory->method('all')->willReturn([
|
||||
'DGS' => new ContingentHotel('DGS', $accommodation, (new CalendarHotel())->setCode('DGS')->setName('Sportclub Spinabad')),
|
||||
'DKI' => new ContingentHotel('DKI', null, (new CalendarHotel())->setCode('DKI')->setName('Sportclub Schwendi')),
|
||||
'DWW' => new ContingentHotel('DWW', null, (new CalendarHotel())->setCode('DWW')->setName('Sportclub Weissfluh')),
|
||||
]);
|
||||
|
||||
$syncStateRepository = $this->createStub(ContingentSyncStateRepository::class);
|
||||
$syncStateRepository->method('findAllIndexedByHotelCode')->willReturn([
|
||||
'DGS' => (new ContingentSyncState('DGS'))->recordSuccess(new \DateTimeImmutable('2026-10-29 10:15:00+01:00'), new \DateTimeImmutable('2028-10-29')),
|
||||
'DKI' => (new ContingentSyncState('DKI'))->recordFailure('unknown hotel code'),
|
||||
]);
|
||||
|
||||
$priceRepository = $this->createMock(AccommodationPriceRepository::class);
|
||||
$priceRepository->expects(self::once())
|
||||
->method('findPricedDateBoundsByHotelCode')
|
||||
->with('DGS')
|
||||
->willReturn(['from' => new \DateTimeImmutable('2027-06-01'), 'to' => new \DateTimeImmutable('2027-09-30')]);
|
||||
|
||||
$controller = new ContingentController(
|
||||
$this->createStub(AccommodationRepository::class),
|
||||
$priceRepository,
|
||||
new ContingentSnapshotReader(
|
||||
$this->createStub(ContingentDayRepository::class),
|
||||
$syncStateRepository,
|
||||
$this->createStub(LoggerInterface::class),
|
||||
),
|
||||
new PriceTimelineBuilder(),
|
||||
new AccommodationPriceCoverage($this->createStub(AccommodationPriceRepository::class)),
|
||||
$hotelDirectory,
|
||||
$syncStateRepository,
|
||||
$this->createStub(LoggerInterface::class),
|
||||
);
|
||||
$controller->setContainer(new Container());
|
||||
|
||||
$data = json_decode((string) $controller->status()->getContent(), true);
|
||||
|
||||
self::assertSame([
|
||||
[
|
||||
'hotelCode' => 'DGS',
|
||||
'name' => 'Sportclub Spinabad',
|
||||
'sources' => ['accommodation', 'typo3'],
|
||||
'accommodation' => ['name' => 'Gruppenhaus Spinabad', 'cmsCode' => 'SERDGS'],
|
||||
'pricedFrom' => '2027-06-01',
|
||||
'pricedTo' => '2027-09-30',
|
||||
'syncedAt' => '2026-10-29T10:15:00+01:00',
|
||||
'lastError' => null,
|
||||
'failureCount' => 0,
|
||||
'available' => true,
|
||||
],
|
||||
[
|
||||
'hotelCode' => 'DKI',
|
||||
'name' => 'Sportclub Schwendi',
|
||||
'sources' => ['typo3'],
|
||||
'accommodation' => null,
|
||||
'pricedFrom' => null,
|
||||
'pricedTo' => null,
|
||||
'syncedAt' => null,
|
||||
'lastError' => 'unknown hotel code',
|
||||
'failureCount' => 1,
|
||||
'available' => false,
|
||||
],
|
||||
[
|
||||
'hotelCode' => 'DWW',
|
||||
'name' => 'Sportclub Weissfluh',
|
||||
'sources' => ['typo3'],
|
||||
'accommodation' => null,
|
||||
'pricedFrom' => null,
|
||||
'pricedTo' => null,
|
||||
'syncedAt' => null,
|
||||
'lastError' => null,
|
||||
'failureCount' => 0,
|
||||
'available' => false,
|
||||
],
|
||||
], $data);
|
||||
}
|
||||
|
||||
public function testAvailabilityWithoutARangeReadsFromTodayToTheEndOfTheSnapshot(): void
|
||||
{
|
||||
CarbonImmutable::setTestNow('2026-10-29 09:00:00');
|
||||
@@ -263,13 +368,17 @@ class ContingentControllerTest extends TestCase
|
||||
?array $statuses,
|
||||
?ContingentCalendarQuery $query = null,
|
||||
?ContingentSnapshotReader $snapshotReader = null,
|
||||
?AccommodationRepository $accommodationRepository = null,
|
||||
?ContingentHotelDirectory $hotelDirectory = null,
|
||||
): Response {
|
||||
if (null === $accommodationRepository) {
|
||||
$accommodationRepository = $this->createStub(AccommodationRepository::class);
|
||||
$accommodationRepository->method('findOneBy')->willReturn((new Accommodation())->setCalendarCode('HOTEL1')->setCurrency('EUR'));
|
||||
if (null === $hotelDirectory) {
|
||||
$hotelDirectory = $this->createStub(ContingentHotelDirectory::class);
|
||||
$hotelDirectory->method('find')->willReturn(new ContingentHotel('HOTEL1', (new Accommodation())->setCalendarCode('HOTEL1'), null));
|
||||
}
|
||||
|
||||
// The hotel is looked up in the directory: an accommodation is not required here.
|
||||
$accommodationRepository = $this->createMock(AccommodationRepository::class);
|
||||
$accommodationRepository->expects(self::never())->method(self::anything());
|
||||
|
||||
if (null === $snapshotReader) {
|
||||
$snapshotReader = $this->createStub(ContingentSnapshotReader::class);
|
||||
$snapshotReader->method('statusesFor')->willReturn($statuses);
|
||||
@@ -285,6 +394,8 @@ class ContingentControllerTest extends TestCase
|
||||
$snapshotReader,
|
||||
new PriceTimelineBuilder(),
|
||||
new AccommodationPriceCoverage($this->createStub(AccommodationPriceRepository::class)),
|
||||
$hotelDirectory,
|
||||
$this->createStub(ContingentSyncStateRepository::class),
|
||||
$this->createStub(LoggerInterface::class),
|
||||
);
|
||||
$controller->setContainer(new Container());
|
||||
@@ -343,6 +454,8 @@ class ContingentControllerTest extends TestCase
|
||||
$snapshotReader,
|
||||
new PriceTimelineBuilder(),
|
||||
new AccommodationPriceCoverage($this->createStub(AccommodationPriceRepository::class)),
|
||||
$this->createStub(ContingentHotelDirectory::class),
|
||||
$this->createStub(ContingentSyncStateRepository::class),
|
||||
$this->createStub(LoggerInterface::class),
|
||||
);
|
||||
|
||||
@@ -361,6 +474,8 @@ class ContingentControllerTest extends TestCase
|
||||
$this->createStub(ContingentSnapshotReader::class),
|
||||
new PriceTimelineBuilder(),
|
||||
new AccommodationPriceCoverage($this->createStub(AccommodationPriceRepository::class)),
|
||||
$this->createStub(ContingentHotelDirectory::class),
|
||||
$this->createStub(ContingentSyncStateRepository::class),
|
||||
$this->createStub(LoggerInterface::class),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user