chore: remote obsolete contingents-related plumbing
This commit is contained in:
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BusProNet\Utility;
|
||||
|
||||
use App\BusProNet\Utility\BookingUrlUtility;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class BookingUrlUtilityTest extends TestCase
|
||||
{
|
||||
public function testBuildUsesConfiguredDefaultBaseUrlAsAbsoluteFallback(): void
|
||||
{
|
||||
$utility = new BookingUrlUtility('https://my.ep-reisen.de');
|
||||
|
||||
$url = $utility->build(157047, 11606);
|
||||
|
||||
$this->assertSame(
|
||||
'https://my.ep-reisen.de/bookings/create?date_id=11606&hotel_id=157047',
|
||||
$url
|
||||
);
|
||||
}
|
||||
|
||||
public function testBuildUsesCustomBaseUrlWhenProvided(): void
|
||||
{
|
||||
$utility = new BookingUrlUtility('https://my.ep-reisen.de');
|
||||
|
||||
$url = $utility->build(157047, 11606, 'https://my.example.test/');
|
||||
|
||||
$this->assertSame(
|
||||
'https://my.example.test/bookings/create?date_id=11606&hotel_id=157047',
|
||||
$url
|
||||
);
|
||||
}
|
||||
|
||||
public function testBuildFallsBackToConfiguredDefaultWhenCustomBaseIsBlank(): void
|
||||
{
|
||||
$utility = new BookingUrlUtility('https://fallback.test');
|
||||
|
||||
$url = $utility->build(157047, 11606, ' ');
|
||||
|
||||
$this->assertSame(
|
||||
'https://fallback.test/bookings/create?date_id=11606&hotel_id=157047',
|
||||
$url
|
||||
);
|
||||
}
|
||||
|
||||
public function testBuildReturnsRelativeUrlWhenNoBaseUrlAvailable(): void
|
||||
{
|
||||
$utility = new BookingUrlUtility('');
|
||||
|
||||
$url = $utility->build(157047, 11606);
|
||||
|
||||
$this->assertSame('/bookings/create?date_id=11606&hotel_id=157047', $url);
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\BusProNet\XmlLoader;
|
||||
|
||||
use App\BusProNet\XmlLoader\ContingentLoader;
|
||||
use App\BusProNet\XmlParser\ContingentParser;
|
||||
use League\Flysystem\DirectoryListing;
|
||||
use League\Flysystem\FileAttributes;
|
||||
use League\Flysystem\FilesystemOperator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||
|
||||
class ContingentLoaderTest extends TestCase
|
||||
{
|
||||
public function testGenerateFilesMapBuildsMappingAndTagsCache(): void
|
||||
{
|
||||
$cache = $this->createMock(TagAwareCacheInterface::class);
|
||||
$filesystem = $this->createMock(FilesystemOperator::class);
|
||||
$parser = $this->createMock(ContingentParser::class);
|
||||
|
||||
$filesystem->method('listContents')->willReturn(new DirectoryListing([
|
||||
new FileAttributes('HotelZimmer_100.xml', 10),
|
||||
new FileAttributes('HotelZimmer_200.xml', 10),
|
||||
new FileAttributes('other.xml', 10),
|
||||
]));
|
||||
|
||||
$item = $this->createMock(ItemInterface::class);
|
||||
$item->expects($this->once())->method('expiresAfter')->with(3 * 60 * 60);
|
||||
$item->expects($this->once())->method('tag')->with(['xml-sync']);
|
||||
|
||||
$cache->expects($this->once())
|
||||
->method('get')
|
||||
->with('bpn_contingent_files', $this->isType('callable'))
|
||||
->willReturnCallback(fn (string $key, callable $callback) => $callback($item));
|
||||
|
||||
$loader = new ContingentLoader($parser, $cache, $filesystem);
|
||||
$map = $loader->generateFilesMap();
|
||||
|
||||
$this->assertSame([
|
||||
100 => 'HotelZimmer_100.xml',
|
||||
200 => 'HotelZimmer_200.xml',
|
||||
], $map);
|
||||
}
|
||||
|
||||
public function testLoadByHotelIdUsesRootLinkOneHopAndTagsCache(): void
|
||||
{
|
||||
$cache = $this->createMock(TagAwareCacheInterface::class);
|
||||
$filesystem = $this->createMock(FilesystemOperator::class);
|
||||
$parser = $this->createMock(ContingentParser::class);
|
||||
|
||||
$originalXml = '<hotelzimmer idbuspro_kontingent_aus="200"><unterbringungen /></hotelzimmer>';
|
||||
$linkedXml = '<hotelzimmer><unterbringungen /></hotelzimmer>';
|
||||
$parsed = ['roomTypes' => [], 'rows' => [['roomCode' => 'DZ']]];
|
||||
|
||||
$filesystem->method('listContents')->willReturn(new DirectoryListing([
|
||||
new FileAttributes('HotelZimmer_100.xml', 10),
|
||||
new FileAttributes('HotelZimmer_200.xml', 10),
|
||||
]));
|
||||
$filesystem->method('read')->willReturnMap([
|
||||
['HotelZimmer_100.xml', $originalXml],
|
||||
['HotelZimmer_200.xml', $linkedXml],
|
||||
]);
|
||||
|
||||
$mapItem = $this->createMock(ItemInterface::class);
|
||||
$mapItem->method('expiresAfter')->with(3 * 60 * 60);
|
||||
$mapItem->method('tag')->with(['xml-sync']);
|
||||
|
||||
$hotelItem = $this->createMock(ItemInterface::class);
|
||||
$hotelItem->method('expiresAfter')->with(3600);
|
||||
$hotelItem->method('tag')->with(['xml-sync']);
|
||||
|
||||
$cache->method('get')->willReturnCallback(
|
||||
function (string $key, callable $callback) use ($mapItem, $hotelItem) {
|
||||
if ('bpn_contingent_files' === $key) {
|
||||
return $callback($mapItem);
|
||||
}
|
||||
if ('contingent_hotel_100' === $key) {
|
||||
return $callback($hotelItem);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
$parser->expects($this->once())->method('parse')->with($linkedXml)->willReturn($parsed);
|
||||
|
||||
$loader = new ContingentLoader($parser, $cache, $filesystem);
|
||||
$result = $loader->loadByHotelId(100);
|
||||
|
||||
$this->assertSame($parsed, $result);
|
||||
}
|
||||
|
||||
public function testLoadByHotelIdReturnsEmptyWhenLinkedTargetIsMissing(): void
|
||||
{
|
||||
$cache = $this->createMock(TagAwareCacheInterface::class);
|
||||
$filesystem = $this->createMock(FilesystemOperator::class);
|
||||
$parser = $this->createMock(ContingentParser::class);
|
||||
|
||||
$xml = '<hotelzimmer idbuspro_kontingent_aus="999"><unterbringungen /></hotelzimmer>';
|
||||
|
||||
$filesystem->method('listContents')->willReturn(new DirectoryListing([
|
||||
new FileAttributes('HotelZimmer_100.xml', 10),
|
||||
]));
|
||||
$filesystem->method('read')->willReturn($xml);
|
||||
|
||||
$mapItem = $this->createMock(ItemInterface::class);
|
||||
$mapItem->method('expiresAfter')->with(3 * 60 * 60);
|
||||
$mapItem->method('tag')->with(['xml-sync']);
|
||||
|
||||
$hotelItem = $this->createMock(ItemInterface::class);
|
||||
$hotelItem->method('expiresAfter')->with(3600);
|
||||
$hotelItem->method('tag')->with(['xml-sync']);
|
||||
|
||||
$cache->method('get')->willReturnCallback(
|
||||
function (string $key, callable $callback) use ($mapItem, $hotelItem) {
|
||||
if ('bpn_contingent_files' === $key) {
|
||||
return $callback($mapItem);
|
||||
}
|
||||
if ('contingent_hotel_100' === $key) {
|
||||
return $callback($hotelItem);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
$parser->expects($this->never())->method('parse');
|
||||
|
||||
$loader = new ContingentLoader($parser, $cache, $filesystem);
|
||||
$result = $loader->loadByHotelId(100);
|
||||
|
||||
$this->assertSame(['roomTypes' => [], 'rows' => []], $result);
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\BusProNet\XmlParser;
|
||||
|
||||
use App\BusProNet\XmlParser\ContingentParser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ContingentParserTest extends TestCase
|
||||
{
|
||||
private ContingentParser $parser;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->parser = new ContingentParser();
|
||||
}
|
||||
|
||||
public function testParseBuildsRoomTypesAndRowsWithLinksAndControlRoom(): void
|
||||
{
|
||||
$xml = <<<'XML'
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<hotelzimmer>
|
||||
<unterbringungen>
|
||||
<unterbringung idbuspro="10" code="DZ" zimmerbezeichnung="Double Room" pax_max="2" />
|
||||
<unterbringung idbuspro="11" code="BelKal" zimmerbezeichnung="Control" pax_max="1" />
|
||||
<unterbringung idbuspro="12" code="PDGS" zimmerbezeichnung="Pseudo" pax_max="1" />
|
||||
<unterbringung idbuspro="13" code="EZ" zimmerbezeichnung="Single Room" pax_max="1" idbuspro_kontingent_aus="10" />
|
||||
</unterbringungen>
|
||||
<kapazitaeten>
|
||||
<kapazitaet termin="24.02.2026">
|
||||
<zimmerliste>
|
||||
<zimmer idbuspro="10" kontingent="3" frei="1" status="" abpreis="100,00" abpreis_naechte="2" abpreis_verlaengerung="15,00" abpreis_verlaengerung_naechte="1" />
|
||||
<zimmer idbuspro="11" kontingent="1" frei="1" status="A" abpreis="0" abpreis_naechte="0" abpreis_verlaengerung="0" abpreis_verlaengerung_naechte="0" />
|
||||
</zimmerliste>
|
||||
</kapazitaet>
|
||||
</kapazitaeten>
|
||||
</hotelzimmer>
|
||||
XML;
|
||||
|
||||
$result = $this->parser->parse($xml);
|
||||
|
||||
$this->assertArrayHasKey('roomTypes', $result);
|
||||
$this->assertArrayHasKey('rows', $result);
|
||||
$this->assertCount(3, $result['roomTypes']); // PDGS skipped
|
||||
$this->assertCount(3, $result['rows']);
|
||||
|
||||
$rowsByCode = [];
|
||||
foreach ($result['rows'] as $row) {
|
||||
$rowsByCode[$row['roomCode']] = $row;
|
||||
}
|
||||
|
||||
$this->assertSame('OK', $rowsByCode['DZ']['status']);
|
||||
$this->assertSame(6, $rowsByCode['DZ']['pax']);
|
||||
$this->assertSame(2, $rowsByCode['DZ']['available']);
|
||||
$this->assertSame(100.0, $rowsByCode['DZ']['minPrice']);
|
||||
$this->assertSame(2, $rowsByCode['DZ']['minNights']);
|
||||
$this->assertSame(15.0, $rowsByCode['DZ']['additionalNightMinPrice']);
|
||||
$this->assertSame(1, $rowsByCode['DZ']['additionalNightMinNights']);
|
||||
$this->assertFalse($rowsByCode['DZ']['isControlRoom']);
|
||||
|
||||
// Linked room uses DZ contingent node but its own pax
|
||||
$this->assertSame('Single Room', $rowsByCode['EZ']['roomLabel']);
|
||||
$this->assertSame(3, $rowsByCode['EZ']['pax']);
|
||||
$this->assertSame(1, $rowsByCode['EZ']['available']);
|
||||
|
||||
$this->assertSame('ON_REQUEST', $rowsByCode['BelKal']['status']);
|
||||
$this->assertSame(0, $rowsByCode['BelKal']['pax']);
|
||||
$this->assertSame(0, $rowsByCode['BelKal']['available']);
|
||||
$this->assertTrue($rowsByCode['BelKal']['isControlRoom']);
|
||||
}
|
||||
|
||||
public function testParseHandlesZeroValuesAndBlockedStatus(): void
|
||||
{
|
||||
$xml = <<<'XML'
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<hotelzimmer>
|
||||
<unterbringungen>
|
||||
<unterbringung idbuspro="20" code="DZ" zimmerbezeichnung="Double Room" pax_max="2" />
|
||||
</unterbringungen>
|
||||
<kapazitaeten>
|
||||
<kapazitaet termin="25.02.2026">
|
||||
<zimmerliste>
|
||||
<zimmer idbuspro="20" kontingent="0" frei="0" status="S" abpreis="0" abpreis_naechte="0" abpreis_verlaengerung="0" abpreis_verlaengerung_naechte="0" />
|
||||
</zimmerliste>
|
||||
</kapazitaet>
|
||||
</kapazitaeten>
|
||||
</hotelzimmer>
|
||||
XML;
|
||||
|
||||
$result = $this->parser->parse($xml);
|
||||
$row = $result['rows'][0];
|
||||
|
||||
$this->assertSame('BLOCKED', $row['status']);
|
||||
$this->assertSame(0, $row['pax']);
|
||||
$this->assertSame(0, $row['available']);
|
||||
$this->assertSame(0.0, $row['minPrice']);
|
||||
$this->assertSame(0, $row['minNights']);
|
||||
$this->assertSame(0.0, $row['additionalNightMinPrice']);
|
||||
$this->assertSame(0, $row['additionalNightMinNights']);
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,12 @@ namespace App\Tests\Command;
|
||||
|
||||
use App\BusProNet\XmlLoader\TravelLoader;
|
||||
use App\Command\BpnXmlSyncCommand;
|
||||
use App\Service\BpnXmlSyncManager;
|
||||
use App\Service\BpnXmlSnapshotRefreshManager;
|
||||
use App\Service\BpnXmlSyncManager;
|
||||
use App\Service\TravelDataProvider;
|
||||
use App\Service\TravelSnapshotManager;
|
||||
use League\Flysystem\DirectoryListing;
|
||||
use League\Flysystem\FilesystemOperator;
|
||||
use League\Flysystem\UnableToListContents;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
@@ -23,8 +22,6 @@ class BpnXmlSyncCommandTest extends TestCase
|
||||
{
|
||||
private FilesystemOperator $xmlSource;
|
||||
private FilesystemOperator $xmlExport;
|
||||
private FilesystemOperator $xmlSourceContingents;
|
||||
private FilesystemOperator $xmlExportContingents;
|
||||
private TagAwareCacheInterface $cache;
|
||||
private LoggerInterface $logger;
|
||||
private TravelLoader $travelLoader;
|
||||
@@ -36,7 +33,6 @@ class BpnXmlSyncCommandTest extends TestCase
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// Release the filesystem lock held by LockableTrait so subsequent tests can acquire it.
|
||||
$release = new \ReflectionMethod($this->command, 'release');
|
||||
$release->setAccessible(true);
|
||||
$release->invoke($this->command);
|
||||
@@ -46,8 +42,6 @@ class BpnXmlSyncCommandTest extends TestCase
|
||||
{
|
||||
$this->xmlSource = $this->createMock(FilesystemOperator::class);
|
||||
$this->xmlExport = $this->createMock(FilesystemOperator::class);
|
||||
$this->xmlSourceContingents = $this->createMock(FilesystemOperator::class);
|
||||
$this->xmlExportContingents = $this->createMock(FilesystemOperator::class);
|
||||
$this->cache = $this->createMock(TagAwareCacheInterface::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->travelLoader = $this->createMock(TravelLoader::class);
|
||||
@@ -63,8 +57,6 @@ class BpnXmlSyncCommandTest extends TestCase
|
||||
$this->syncManager = new BpnXmlSyncManager(
|
||||
$this->xmlSource,
|
||||
$this->xmlExport,
|
||||
$this->xmlSourceContingents,
|
||||
$this->xmlExportContingents,
|
||||
$this->cache,
|
||||
$this->logger,
|
||||
);
|
||||
@@ -76,9 +68,6 @@ class BpnXmlSyncCommandTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote newer than local -> files downloaded -> syncSnapshotsFromXml() called once.
|
||||
*/
|
||||
public function testSnapshotSyncIsTriggeredAfterSuccessfulFileDownload(): void
|
||||
{
|
||||
$newerTimestamp = "24.03.2026 12:00:00\nExport\n3 Dateien\n";
|
||||
@@ -90,12 +79,6 @@ class BpnXmlSyncCommandTest extends TestCase
|
||||
$this->xmlSource->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
$this->xmlExport->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
|
||||
$this->xmlSourceContingents->method('read')->willReturn($olderTimestamp);
|
||||
$this->xmlExportContingents->method('fileExists')->willReturn(true);
|
||||
$this->xmlExportContingents->method('read')->willReturn($olderTimestamp);
|
||||
$this->xmlSourceContingents->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
$this->xmlExportContingents->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
|
||||
$fileMap = [
|
||||
101 => ['hotels' => ['H1' => null, 'H2' => null]],
|
||||
102 => ['hotels' => ['H3' => null]],
|
||||
@@ -124,9 +107,6 @@ class BpnXmlSyncCommandTest extends TestCase
|
||||
$this->assertSame(Command::SUCCESS, $tester->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Local already up to date -> early return before snapshot code is reached.
|
||||
*/
|
||||
public function testSnapshotSyncIsSkippedWhenLocalDataIsUpToDate(): void
|
||||
{
|
||||
$sameTimestamp = "24.03.2026 10:00:00\nExport\n3 Dateien\n";
|
||||
@@ -136,12 +116,6 @@ class BpnXmlSyncCommandTest extends TestCase
|
||||
$this->xmlSource->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
$this->xmlExport->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
|
||||
$this->xmlSourceContingents->method('read')->willReturn($sameTimestamp);
|
||||
$this->xmlExportContingents->method('fileExists')->willReturn(true);
|
||||
$this->xmlExportContingents->method('read')->willReturn($sameTimestamp);
|
||||
$this->xmlSourceContingents->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
$this->xmlExportContingents->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
|
||||
$this->travelLoader->expects($this->never())->method('generateFilesMap');
|
||||
$this->travelDataService->expects($this->never())->method('syncSnapshotsFromXml');
|
||||
$this->travelSnapshotService->expects($this->never())->method('purgeOrphanedFutureSnapshots');
|
||||
@@ -153,10 +127,6 @@ class BpnXmlSyncCommandTest extends TestCase
|
||||
$this->assertSame(Command::SUCCESS, $tester->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* generateFilesMap() throws -> warning logged, syncSnapshotsFromXml() never called,
|
||||
* command still returns SUCCESS.
|
||||
*/
|
||||
public function testSnapshotSyncIsSkippedAndWarningLoggedWhenFileMapGenerationFails(): void
|
||||
{
|
||||
$newerTimestamp = "24.03.2026 12:00:00\nExport\n3 Dateien\n";
|
||||
@@ -168,12 +138,6 @@ class BpnXmlSyncCommandTest extends TestCase
|
||||
$this->xmlSource->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
$this->xmlExport->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
|
||||
$this->xmlSourceContingents->method('read')->willReturn($olderTimestamp);
|
||||
$this->xmlExportContingents->method('fileExists')->willReturn(true);
|
||||
$this->xmlExportContingents->method('read')->willReturn($olderTimestamp);
|
||||
$this->xmlSourceContingents->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
$this->xmlExportContingents->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
|
||||
$this->travelLoader->expects($this->once())
|
||||
->method('generateFilesMap')
|
||||
->willThrowException(new \RuntimeException('Storage unavailable'));
|
||||
@@ -194,84 +158,4 @@ class BpnXmlSyncCommandTest extends TestCase
|
||||
|
||||
$this->assertSame(Command::SUCCESS, $tester->getStatusCode());
|
||||
}
|
||||
|
||||
public function testContingentTransferFailureDoesNotFailSuccessfulTravelSync(): void
|
||||
{
|
||||
$newerTimestamp = "24.03.2026 12:00:00\nExport\n3 Dateien\n";
|
||||
$olderTimestamp = "24.03.2026 10:00:00\nExport\n3 Dateien\n";
|
||||
|
||||
$this->xmlSource->method('read')->willReturn($newerTimestamp);
|
||||
$this->xmlExport->method('fileExists')->willReturn(true);
|
||||
$this->xmlExport->method('read')->willReturn($olderTimestamp);
|
||||
$this->xmlSource->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
$this->xmlExport->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
|
||||
$this->xmlSourceContingents->method('read')->willReturn($newerTimestamp);
|
||||
$this->xmlExportContingents->method('fileExists')->willReturn(true);
|
||||
$this->xmlExportContingents->method('read')->willReturn($olderTimestamp);
|
||||
$this->xmlSourceContingents->method('listContents')
|
||||
->willThrowException(UnableToListContents::atLocation('.', false, new \RuntimeException('SFTP unavailable')));
|
||||
|
||||
$fileMap = [
|
||||
101 => ['hotels' => ['H1' => null]],
|
||||
];
|
||||
$this->travelLoader->expects($this->once())
|
||||
->method('generateFilesMap')
|
||||
->willReturn($fileMap);
|
||||
|
||||
$this->travelDataService->expects($this->once())
|
||||
->method('syncSnapshotsFromXml')
|
||||
->with($fileMap, $this->isInstanceOf(\Closure::class))
|
||||
->willReturn(['processed' => 1, 'failed' => 0]);
|
||||
|
||||
$this->travelSnapshotService->expects($this->once())
|
||||
->method('purgeOrphanedFutureSnapshots')
|
||||
->with([101])
|
||||
->willReturn(0);
|
||||
|
||||
$this->cache->expects($this->once())
|
||||
->method('invalidateTags')
|
||||
->with(['xml-sync']);
|
||||
|
||||
$tester = new CommandTester($this->command);
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(Command::SUCCESS, $tester->getStatusCode());
|
||||
$this->assertStringContainsString('[contingents] Sync failed', $tester->getDisplay());
|
||||
$this->assertMatchesRegularExpression('/failed datasets:\s+contingents/', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testTravelTransferFailureDoesNotPreventContingentsSync(): void
|
||||
{
|
||||
$newerTimestamp = "24.03.2026 12:00:00\nExport\n3 Dateien\n";
|
||||
$olderTimestamp = "24.03.2026 10:00:00\nExport\n3 Dateien\n";
|
||||
|
||||
$this->xmlSource->method('read')->willReturn($newerTimestamp);
|
||||
$this->xmlExport->method('fileExists')->willReturn(true);
|
||||
$this->xmlExport->method('read')->willReturn($olderTimestamp);
|
||||
$this->xmlSource->method('listContents')
|
||||
->willThrowException(UnableToListContents::atLocation('.', false, new \RuntimeException('SFTP unavailable')));
|
||||
|
||||
$this->xmlSourceContingents->method('read')->willReturn($newerTimestamp);
|
||||
$this->xmlExportContingents->method('fileExists')->willReturn(true);
|
||||
$this->xmlExportContingents->method('read')->willReturn($olderTimestamp);
|
||||
$this->xmlSourceContingents->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
$this->xmlExportContingents->method('listContents')->willReturn(new DirectoryListing([]));
|
||||
|
||||
$this->travelLoader->expects($this->never())->method('generateFilesMap');
|
||||
$this->travelDataService->expects($this->never())->method('syncSnapshotsFromXml');
|
||||
$this->travelSnapshotService->expects($this->never())->method('purgeOrphanedFutureSnapshots');
|
||||
|
||||
$this->cache->expects($this->once())
|
||||
->method('invalidateTags')
|
||||
->with(['xml-sync']);
|
||||
|
||||
$tester = new CommandTester($this->command);
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(Command::SUCCESS, $tester->getStatusCode());
|
||||
$this->assertStringContainsString('[travel] Sync failed', $tester->getDisplay());
|
||||
$this->assertStringContainsString('[contingents] Synced 0 files, deleted 0', $tester->getDisplay());
|
||||
$this->assertStringContainsString('datasets: travel', $tester->getDisplay());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Controller\Api;
|
||||
|
||||
use App\Controller\Api\ContingentController;
|
||||
use App\Service\ContingentDataService;
|
||||
use App\Service\TravelDataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\Container;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
|
||||
class ContingentControllerTest extends TestCase
|
||||
{
|
||||
public function testByDateQueryResolvesCodeReferences(): void
|
||||
{
|
||||
$contingentDataService = $this->createMock(ContingentDataService::class);
|
||||
$travelDataProvider = $this->createMock(TravelDataProvider::class);
|
||||
|
||||
$travelDataProvider
|
||||
->expects($this->once())
|
||||
->method('mapHotelCodeToId')
|
||||
->with('SBW-HOTEL')
|
||||
->willReturn(157047);
|
||||
|
||||
$travelDataProvider
|
||||
->expects($this->once())
|
||||
->method('mapDateCodeToId')
|
||||
->with('SBW131225')
|
||||
->willReturn(11606);
|
||||
|
||||
$contingentDataService
|
||||
->expects($this->once())
|
||||
->method('getAvailableContingents')
|
||||
->with(157047, 11606)
|
||||
->willReturn([]);
|
||||
|
||||
$controller = $this->createController($contingentDataService, $travelDataProvider);
|
||||
$request = Request::create('/api/contingents', 'GET', [
|
||||
'hotelRef' => 'SBW-HOTEL',
|
||||
'dateRef' => 'sbw-13/12/25',
|
||||
]);
|
||||
|
||||
$response = $controller->byDate($request);
|
||||
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testByDateQueryRequiresDateReference(): void
|
||||
{
|
||||
$contingentDataService = $this->createMock(ContingentDataService::class);
|
||||
$travelDataProvider = $this->createMock(TravelDataProvider::class);
|
||||
|
||||
$controller = $this->createController($contingentDataService, $travelDataProvider);
|
||||
$request = Request::create('/api/contingents', 'GET', [
|
||||
'hotelRef' => '157047',
|
||||
]);
|
||||
|
||||
$response = $controller->byDate($request);
|
||||
$payload = json_decode((string) $response->getContent(), true);
|
||||
|
||||
$this->assertSame(400, $response->getStatusCode());
|
||||
$this->assertSame('dateRef is required', $payload['error']);
|
||||
}
|
||||
|
||||
public function testByDateQueryReturnsNotFoundForUnknownDateReference(): void
|
||||
{
|
||||
$contingentDataService = $this->createMock(ContingentDataService::class);
|
||||
$travelDataProvider = $this->createMock(TravelDataProvider::class);
|
||||
|
||||
$travelDataProvider
|
||||
->expects($this->once())
|
||||
->method('mapHotelCodeToId')
|
||||
->with('SBW-HOTEL')
|
||||
->willReturn(157047);
|
||||
|
||||
$travelDataProvider
|
||||
->expects($this->once())
|
||||
->method('mapDateCodeToId')
|
||||
->with('UNKNOWN')
|
||||
->willReturn(null);
|
||||
|
||||
$contingentDataService
|
||||
->expects($this->never())
|
||||
->method('getAvailableContingents');
|
||||
|
||||
$controller = $this->createController($contingentDataService, $travelDataProvider);
|
||||
$request = Request::create('/api/contingents', 'GET', [
|
||||
'hotelRef' => 'SBW-HOTEL',
|
||||
'dateRef' => 'unknown',
|
||||
]);
|
||||
|
||||
$response = $controller->byDate($request);
|
||||
$payload = json_decode((string) $response->getContent(), true);
|
||||
|
||||
$this->assertSame(404, $response->getStatusCode());
|
||||
$this->assertSame('Not found', $payload['message']);
|
||||
}
|
||||
|
||||
public function testRoomsQueryRequiresDateRange(): void
|
||||
{
|
||||
$contingentDataService = $this->createMock(ContingentDataService::class);
|
||||
$travelDataProvider = $this->createMock(TravelDataProvider::class);
|
||||
|
||||
$controller = $this->createController($contingentDataService, $travelDataProvider);
|
||||
$request = Request::create('/api/contingents/rooms', 'GET', [
|
||||
'hotelRef' => '157047',
|
||||
'dateRef' => '11606',
|
||||
]);
|
||||
|
||||
$response = $controller->rooms($request);
|
||||
$payload = json_decode((string) $response->getContent(), true);
|
||||
|
||||
$this->assertSame(400, $response->getStatusCode());
|
||||
$this->assertSame('dateFrom and dateTo are required', $payload['error']);
|
||||
}
|
||||
|
||||
public function testByDateQueryUsesNumericReferencesAsIdentifiers(): void
|
||||
{
|
||||
$contingentDataService = $this->createMock(ContingentDataService::class);
|
||||
$travelDataProvider = $this->createMock(TravelDataProvider::class);
|
||||
|
||||
$travelDataProvider
|
||||
->expects($this->never())
|
||||
->method('mapHotelCodeToId');
|
||||
|
||||
$travelDataProvider
|
||||
->expects($this->never())
|
||||
->method('mapDateCodeToId');
|
||||
|
||||
$contingentDataService
|
||||
->expects($this->once())
|
||||
->method('getAvailableContingents')
|
||||
->with(157047, 11606)
|
||||
->willReturn([]);
|
||||
|
||||
$controller = $this->createController($contingentDataService, $travelDataProvider);
|
||||
$request = Request::create('/api/contingents', 'GET', [
|
||||
'hotelRef' => '157047',
|
||||
'dateRef' => '11606',
|
||||
]);
|
||||
$response = $controller->byDate($request);
|
||||
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testRoomsQueryForwardsOptionalMyEpUrl(): void
|
||||
{
|
||||
$contingentDataService = $this->createMock(ContingentDataService::class);
|
||||
$travelDataProvider = $this->createMock(TravelDataProvider::class);
|
||||
|
||||
$contingentDataService
|
||||
->expects($this->once())
|
||||
->method('getAvailableRooms')
|
||||
->with('2026-03-01', '2026-03-08', 157047, 11606, 'https://my.ep-reisen.de')
|
||||
->willReturn([]);
|
||||
|
||||
$controller = $this->createController($contingentDataService, $travelDataProvider);
|
||||
$request = Request::create('/api/contingents/rooms', 'GET', [
|
||||
'hotelRef' => '157047',
|
||||
'dateRef' => '11606',
|
||||
'dateFrom' => '2026-03-01',
|
||||
'dateTo' => '2026-03-08',
|
||||
'my_ep_url' => 'https://my.ep-reisen.de',
|
||||
]);
|
||||
|
||||
$response = $controller->rooms($request);
|
||||
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
private function createController(
|
||||
ContingentDataService $contingentDataService,
|
||||
TravelDataProvider $travelDataProvider,
|
||||
): ContingentController {
|
||||
$controller = new ContingentController($contingentDataService, $travelDataProvider);
|
||||
|
||||
$container = new Container();
|
||||
$container->set('serializer', new Serializer([new ObjectNormalizer()], [new JsonEncoder()]));
|
||||
$controller->setContainer($container);
|
||||
|
||||
return $controller;
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service;
|
||||
|
||||
use App\BusProNet\Model\Travel;
|
||||
use App\BusProNet\Utility\BookingUrlUtility;
|
||||
use App\BusProNet\XmlLoader\ContingentLoader;
|
||||
use App\BusProNet\XmlLoader\TravelLoader;
|
||||
use App\Service\ContingentDataService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ContingentDataServiceTest extends TestCase
|
||||
{
|
||||
private ContingentLoader $contingentLoader;
|
||||
private TravelLoader $travelLoader;
|
||||
private ContingentDataService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->contingentLoader = $this->createMock(ContingentLoader::class);
|
||||
$this->travelLoader = $this->createMock(TravelLoader::class);
|
||||
|
||||
$this->service = new ContingentDataService(
|
||||
$this->contingentLoader,
|
||||
$this->travelLoader,
|
||||
new BookingUrlUtility('https://my.ep-reisen.de'),
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetAvailableContingentsAggregatesAndAppliesBelKalOverride(): void
|
||||
{
|
||||
$travel = new Travel();
|
||||
$travel->dateFrom = new \DateTimeImmutable('2026-02-01');
|
||||
$travel->dateTo = new \DateTimeImmutable('2026-02-03');
|
||||
|
||||
$this->travelLoader->method('loadById')->with(123, 10)->willReturn($travel);
|
||||
$this->contingentLoader->method('loadByHotelId')->with(10)->willReturn([
|
||||
'rows' => [
|
||||
[
|
||||
'date' => new \DateTimeImmutable('2026-02-01'),
|
||||
'isControlRoom' => false,
|
||||
'available' => 4,
|
||||
'pax' => 8,
|
||||
'minNights' => 3,
|
||||
'status' => 'OK',
|
||||
],
|
||||
[
|
||||
'date' => new \DateTimeImmutable('2026-02-01'),
|
||||
'isControlRoom' => false,
|
||||
'available' => 2,
|
||||
'pax' => 4,
|
||||
'minNights' => 2,
|
||||
'status' => 'OK',
|
||||
],
|
||||
[
|
||||
'date' => new \DateTimeImmutable('2026-02-01'),
|
||||
'isControlRoom' => true,
|
||||
'available' => 0,
|
||||
'pax' => 0,
|
||||
'minNights' => null,
|
||||
'status' => 'BLOCKED',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$result = $this->service->getAvailableContingents(10, 123);
|
||||
|
||||
$this->assertArrayHasKey('2026-02-01', $result);
|
||||
$summary = $result['2026-02-01'];
|
||||
$this->assertSame('2026-02-01', $summary->date);
|
||||
$this->assertSame(2, $summary->minNights);
|
||||
$this->assertSame(12, $summary->capacity);
|
||||
$this->assertSame(0, $summary->total); // BelKal override
|
||||
}
|
||||
|
||||
public function testGetAvailableRoomsRejectsRangeOutsideTravelPeriod(): void
|
||||
{
|
||||
$travel = new Travel();
|
||||
$travel->dateFrom = new \DateTimeImmutable('2026-02-10');
|
||||
$travel->dateTo = new \DateTimeImmutable('2026-02-20');
|
||||
|
||||
$this->travelLoader->method('loadById')->with(123, 10)->willReturn($travel);
|
||||
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Requested range must be within the travel date range');
|
||||
|
||||
$this->service->getAvailableRooms('2026-02-01', '2026-02-05', 10, 123);
|
||||
}
|
||||
|
||||
public function testGetAvailableRoomsCalculatesPriceAndAppliesBelKalStatusOverride(): void
|
||||
{
|
||||
$travel = new Travel();
|
||||
$travel->dateFrom = new \DateTimeImmutable('2026-02-01');
|
||||
$travel->dateTo = new \DateTimeImmutable('2026-02-10');
|
||||
|
||||
$this->travelLoader->method('loadById')->with(123, 10)->willReturn($travel);
|
||||
$this->contingentLoader->method('loadByHotelId')->with(10)->willReturn([
|
||||
'rows' => [
|
||||
[
|
||||
'date' => new \DateTimeImmutable('2026-02-03'),
|
||||
'isControlRoom' => true,
|
||||
'status' => 'ON_REQUEST',
|
||||
],
|
||||
[
|
||||
'date' => new \DateTimeImmutable('2026-02-03'),
|
||||
'isControlRoom' => false,
|
||||
'roomCode' => 'DZ',
|
||||
'roomLabel' => 'Double',
|
||||
'pax' => 4,
|
||||
'available' => 2,
|
||||
'status' => 'OK',
|
||||
'minPrice' => 100.0,
|
||||
'minNights' => 2,
|
||||
'additionalNightMinPrice' => 30.0,
|
||||
'additionalNightMinNights' => 1,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$result = $this->service->getAvailableRooms('2026-02-03', '2026-02-06', 10, 123);
|
||||
|
||||
$this->assertCount(1, $result);
|
||||
$room = $result[0];
|
||||
$this->assertSame('2026-02-03', $room->date);
|
||||
$this->assertSame('ON_REQUEST', $room->status);
|
||||
$this->assertSame(0, $room->available); // overridden
|
||||
$this->assertSame(130.0, $room->priceForSelection); // 100 + (3-2)*30
|
||||
$this->assertSame('https://my.ep-reisen.de/bookings/create?date_id=123&hotel_id=10', $room->bookingUrl);
|
||||
}
|
||||
|
||||
public function testGetCalendarEventsUsesWorstStatusAndExcludesControlRoomFromSums(): void
|
||||
{
|
||||
$this->contingentLoader->method('loadByHotelId')->with(10)->willReturn([
|
||||
'rows' => [
|
||||
[
|
||||
'date' => new \DateTimeImmutable('2026-02-03'),
|
||||
'isControlRoom' => false,
|
||||
'status' => 'OK',
|
||||
'pax' => 6,
|
||||
'available' => 2,
|
||||
],
|
||||
[
|
||||
'date' => new \DateTimeImmutable('2026-02-03'),
|
||||
'isControlRoom' => true,
|
||||
'status' => 'BLOCKED',
|
||||
'pax' => 0,
|
||||
'available' => 0,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$events = $this->service->getCalendarEvents(10, '2026-02-01', '2026-02-10');
|
||||
|
||||
$this->assertCount(1, $events);
|
||||
$event = $events[0];
|
||||
$this->assertSame('2026-02-03', $event->date);
|
||||
$this->assertSame('BLOCKED', $event->status);
|
||||
$this->assertSame(6, $event->pax);
|
||||
$this->assertSame(2, $event->available);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user