feat: simplify booking pricing flow
This commit is contained in:
@@ -5,14 +5,12 @@ declare(strict_types=1);
|
||||
namespace App\Tests\Service;
|
||||
|
||||
use App\BusProNet\ApiClient;
|
||||
use App\BusProNet\Model\Insurance;
|
||||
use App\BusProNet\Model\Travel;
|
||||
use App\BusProNet\XmlLoader\HotelLoader;
|
||||
use App\BusProNet\XmlLoader\InsuranceLoader;
|
||||
use App\BusProNet\XmlLoader\PickupLoader;
|
||||
use App\BusProNet\XmlLoader\TravelLoader;
|
||||
use App\Exception\TravelNotFoundException;
|
||||
use App\Service\TravelDataService;
|
||||
use App\Service\TravelEnrichmentService;
|
||||
use App\Service\TravelLookupService;
|
||||
use App\Service\TravelSnapshotService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -22,34 +20,31 @@ class TravelDataServiceTest extends TestCase
|
||||
{
|
||||
private TravelDataService $service;
|
||||
private TravelLoader $travelLoader;
|
||||
private HotelLoader $hotelLoader;
|
||||
private PickupLoader $pickupLoader;
|
||||
private InsuranceLoader $insuranceLoader;
|
||||
private ApiClient $apiClient;
|
||||
private CacheInterface $cache;
|
||||
private LoggerInterface $logger;
|
||||
private TravelSnapshotService $travelSnapshotService;
|
||||
private TravelLookupService $travelLookupService;
|
||||
private TravelEnrichmentService $travelEnrichmentService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->travelLoader = $this->createMock(TravelLoader::class);
|
||||
$this->hotelLoader = $this->createMock(HotelLoader::class);
|
||||
$this->pickupLoader = $this->createMock(PickupLoader::class);
|
||||
$this->insuranceLoader = $this->createMock(InsuranceLoader::class);
|
||||
$this->apiClient = $this->createMock(ApiClient::class);
|
||||
$this->cache = $this->createMock(CacheInterface::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->travelSnapshotService = $this->createMock(TravelSnapshotService::class);
|
||||
$this->travelLookupService = $this->createMock(TravelLookupService::class);
|
||||
$this->travelEnrichmentService = $this->createMock(TravelEnrichmentService::class);
|
||||
|
||||
$this->service = new TravelDataService(
|
||||
$this->travelLoader,
|
||||
$this->hotelLoader,
|
||||
$this->pickupLoader,
|
||||
$this->insuranceLoader,
|
||||
$this->apiClient,
|
||||
$this->cache,
|
||||
$this->logger,
|
||||
$this->travelSnapshotService,
|
||||
$this->travelLookupService,
|
||||
$this->travelEnrichmentService,
|
||||
false, // preferRemote
|
||||
true, // enableFallback
|
||||
);
|
||||
@@ -69,15 +64,45 @@ class TravelDataServiceTest extends TestCase
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn($travel);
|
||||
|
||||
$this->pickupLoader
|
||||
$this->travelEnrichmentService
|
||||
->expects($this->once())
|
||||
->method('patchPickupsDetails')
|
||||
->method('enrichFromXml')
|
||||
->with($travel)
|
||||
->willReturn(true);
|
||||
|
||||
$this->travelSnapshotService
|
||||
->expects($this->once())
|
||||
->method('upsertFromTravel')
|
||||
->with($travel);
|
||||
|
||||
$this->hotelLoader
|
||||
$result = $this->service->getTravelDataFromXml($dateId, $hotelId);
|
||||
|
||||
$this->assertSame($travel, $result);
|
||||
}
|
||||
|
||||
public function testGetTravelDataFromXmlSkipsSnapshotWhenEnrichmentFails(): void
|
||||
{
|
||||
$dateId = 12345;
|
||||
$hotelId = 67890;
|
||||
$travel = new Travel();
|
||||
$travel->id = $dateId;
|
||||
$travel->hotelId = $hotelId;
|
||||
|
||||
$this->travelLoader
|
||||
->expects($this->once())
|
||||
->method('patchHotelDetails')
|
||||
->with($travel);
|
||||
->method('loadById')
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn($travel);
|
||||
|
||||
$this->travelEnrichmentService
|
||||
->expects($this->once())
|
||||
->method('enrichFromXml')
|
||||
->with($travel)
|
||||
->willReturn(false);
|
||||
|
||||
$this->travelSnapshotService
|
||||
->expects($this->never())
|
||||
->method('upsertFromTravel');
|
||||
|
||||
$result = $this->service->getTravelDataFromXml($dateId, $hotelId);
|
||||
|
||||
@@ -95,13 +120,9 @@ class TravelDataServiceTest extends TestCase
|
||||
->with($dateId, $hotelId)
|
||||
->willThrowException(new TravelNotFoundException($dateId));
|
||||
|
||||
$this->pickupLoader
|
||||
$this->travelEnrichmentService
|
||||
->expects($this->never())
|
||||
->method('patchPickupsDetails');
|
||||
|
||||
$this->hotelLoader
|
||||
->expects($this->never())
|
||||
->method('patchHotelDetails');
|
||||
->method('enrichFromXml');
|
||||
|
||||
$this->expectException(TravelNotFoundException::class);
|
||||
$this->service->getTravelDataFromXml($dateId, $hotelId);
|
||||
@@ -116,7 +137,7 @@ class TravelDataServiceTest extends TestCase
|
||||
$travel->id = $dateId;
|
||||
$travel->hotelId = $hotelId;
|
||||
|
||||
$this->travelLoader
|
||||
$this->travelLookupService
|
||||
->expects($this->once())
|
||||
->method('mapDateIdToProductId')
|
||||
->with($dateId)
|
||||
@@ -128,17 +149,60 @@ class TravelDataServiceTest extends TestCase
|
||||
->with($productId, $hotelId)
|
||||
->willReturn($travel);
|
||||
|
||||
$this->travelEnrichmentService
|
||||
->expects($this->once())
|
||||
->method('patchInsurances')
|
||||
->with($travel);
|
||||
|
||||
$this->travelSnapshotService
|
||||
->expects($this->once())
|
||||
->method('upsertFromTravel')
|
||||
->with($travel);
|
||||
|
||||
$result = $this->service->getTravelDataFromApi($dateId, $hotelId);
|
||||
|
||||
$this->assertSame($travel, $result);
|
||||
}
|
||||
|
||||
public function testGetTravelDataFromApiSnapshotFailureDoesNotDiscardApiTravel(): void
|
||||
{
|
||||
$dateId = 12345;
|
||||
$hotelId = 67890;
|
||||
$productId = 555;
|
||||
$travel = new Travel();
|
||||
$travel->id = $dateId;
|
||||
$travel->hotelId = $hotelId;
|
||||
|
||||
$this->travelLookupService
|
||||
->method('mapDateIdToProductId')
|
||||
->willReturn($productId);
|
||||
|
||||
$this->apiClient
|
||||
->method('getTravelData')
|
||||
->willReturn($travel);
|
||||
|
||||
$this->travelSnapshotService
|
||||
->expects($this->once())
|
||||
->method('upsertFromTravel')
|
||||
->willThrowException(new \RuntimeException('DB write failed'));
|
||||
|
||||
$this->logger
|
||||
->expects($this->once())
|
||||
->method('warning')
|
||||
->with('Failed to persist travel snapshot after API load', $this->arrayHasKey('dateId'));
|
||||
|
||||
$result = $this->service->getTravelDataFromApi($dateId, $hotelId);
|
||||
|
||||
$this->assertNotNull($result);
|
||||
$this->assertSame($travel, $result);
|
||||
}
|
||||
|
||||
public function testGetTravelDataFromApiCannotMapDateId(): void
|
||||
{
|
||||
$dateId = 12345;
|
||||
$hotelId = 67890;
|
||||
|
||||
$this->travelLoader
|
||||
$this->travelLookupService
|
||||
->expects($this->once())
|
||||
->method('mapDateIdToProductId')
|
||||
->with($dateId)
|
||||
@@ -153,175 +217,45 @@ class TravelDataServiceTest extends TestCase
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
public function testExistsLocallyTrueFromSnapshot(): void
|
||||
public function testExistsLocallyDelegatesToLookupService(): void
|
||||
{
|
||||
$dateId = 12345;
|
||||
$hotelId = 67890;
|
||||
|
||||
$this->travelSnapshotService
|
||||
$this->travelLookupService
|
||||
->expects($this->once())
|
||||
->method('exists')
|
||||
->method('existsLocally')
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn(true);
|
||||
|
||||
$this->travelLoader
|
||||
->expects($this->never())
|
||||
->method('generateFilesMap');
|
||||
|
||||
$result = $this->service->existsLocally($dateId, $hotelId);
|
||||
|
||||
$this->assertTrue($result);
|
||||
$this->assertTrue($this->service->existsLocally($dateId, $hotelId));
|
||||
}
|
||||
|
||||
public function testExistsLocallyTrueFromXmlMap(): void
|
||||
public function testGetAvailableSourcesDelegatesToLookupService(): void
|
||||
{
|
||||
$dateId = 12345;
|
||||
$hotelId = 67890;
|
||||
$mapping = [
|
||||
$dateId => [
|
||||
'id' => $dateId,
|
||||
'hotels' => [
|
||||
$hotelId => ['id' => $hotelId, 'name' => 'Test Hotel'],
|
||||
],
|
||||
],
|
||||
];
|
||||
$expected = ['local' => true, 'remote' => true];
|
||||
|
||||
$this->travelSnapshotService
|
||||
$this->travelLookupService
|
||||
->expects($this->once())
|
||||
->method('exists')
|
||||
->method('getAvailableSources')
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn(false);
|
||||
->willReturn($expected);
|
||||
|
||||
$this->travelLoader
|
||||
->expects($this->once())
|
||||
->method('generateFilesMap')
|
||||
->willReturn($mapping);
|
||||
|
||||
$result = $this->service->existsLocally($dateId, $hotelId);
|
||||
|
||||
$this->assertTrue($result);
|
||||
$this->assertSame($expected, $this->service->getAvailableSources($dateId, $hotelId));
|
||||
}
|
||||
|
||||
public function testExistsLocallyFalseNoTravel(): void
|
||||
public function testGenerateFilesMapDelegatesToLookupService(): void
|
||||
{
|
||||
$dateId = 12345;
|
||||
$hotelId = 67890;
|
||||
$mapping = [];
|
||||
$mapping = [12345 => ['id' => 12345, 'hotels' => []]];
|
||||
|
||||
$this->travelSnapshotService
|
||||
->expects($this->once())
|
||||
->method('exists')
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn(false);
|
||||
|
||||
$this->travelLoader
|
||||
$this->travelLookupService
|
||||
->expects($this->once())
|
||||
->method('generateFilesMap')
|
||||
->willReturn($mapping);
|
||||
|
||||
$result = $this->service->existsLocally($dateId, $hotelId);
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testExistsLocallyFalseNoHotel(): void
|
||||
{
|
||||
$dateId = 12345;
|
||||
$hotelId = 67890;
|
||||
$mapping = [
|
||||
$dateId => [
|
||||
'id' => $dateId,
|
||||
'hotels' => [],
|
||||
],
|
||||
];
|
||||
|
||||
$this->travelSnapshotService
|
||||
->expects($this->once())
|
||||
->method('exists')
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn(false);
|
||||
|
||||
$this->travelLoader
|
||||
->expects($this->once())
|
||||
->method('generateFilesMap')
|
||||
->willReturn($mapping);
|
||||
|
||||
$result = $this->service->existsLocally($dateId, $hotelId);
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testGetAvailableSources(): void
|
||||
{
|
||||
$dateId = 12345;
|
||||
$hotelId = 67890;
|
||||
$productId = 555;
|
||||
$mapping = [
|
||||
$dateId => [
|
||||
'id' => $dateId,
|
||||
'hotels' => [
|
||||
$hotelId => ['id' => $hotelId, 'name' => 'Test Hotel'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->travelSnapshotService
|
||||
->expects($this->once())
|
||||
->method('exists')
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn(false);
|
||||
|
||||
$this->travelLoader
|
||||
->expects($this->once())
|
||||
->method('generateFilesMap')
|
||||
->willReturn($mapping);
|
||||
|
||||
$this->travelLoader
|
||||
->expects($this->once())
|
||||
->method('mapDateIdToProductId')
|
||||
->with($dateId)
|
||||
->willReturn($productId);
|
||||
|
||||
$result = $this->service->getAvailableSources($dateId, $hotelId);
|
||||
|
||||
$this->assertEquals([
|
||||
'local' => true,
|
||||
'remote' => true,
|
||||
], $result);
|
||||
}
|
||||
|
||||
public function testGenerateFilesMapIsMemoizedWithinRequest(): void
|
||||
{
|
||||
$dateId = 12345;
|
||||
$hotelId = 67890;
|
||||
$mapping = [
|
||||
$dateId => [
|
||||
'id' => $dateId,
|
||||
'hotels' => [
|
||||
$hotelId => ['id' => $hotelId, 'name' => 'Test Hotel'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Both existsLocally() calls go through generateFilesMap(); the underlying
|
||||
// loader and snapshot service must each be invoked only once.
|
||||
$this->travelSnapshotService
|
||||
->expects($this->exactly(2))
|
||||
->method('exists')
|
||||
->willReturn(false);
|
||||
|
||||
$this->travelLoader
|
||||
->expects($this->once())
|
||||
->method('generateFilesMap')
|
||||
->willReturn($mapping);
|
||||
|
||||
$this->travelSnapshotService
|
||||
->expects($this->once())
|
||||
->method('generateMapping')
|
||||
->willReturn([]);
|
||||
|
||||
$this->service->existsLocally($dateId, $hotelId);
|
||||
$this->service->existsLocally($dateId, $hotelId);
|
||||
$this->assertSame($mapping, $this->service->generateFilesMap());
|
||||
}
|
||||
|
||||
public function testGetTravelDataFromLocalPrefersSnapshot(): void
|
||||
@@ -342,53 +276,14 @@ class TravelDataServiceTest extends TestCase
|
||||
->expects($this->never())
|
||||
->method('loadById');
|
||||
|
||||
$result = $this->service->getTravelDataFromLocal($dateId, $hotelId);
|
||||
|
||||
$this->assertSame($travel, $result);
|
||||
}
|
||||
|
||||
public function testGetTravelDataFromLocalReplacesSnapshotInsurancesWithLoaderData(): void
|
||||
{
|
||||
$dateId = 12345;
|
||||
$hotelId = 67890;
|
||||
|
||||
$travel = new Travel();
|
||||
$travel->id = $dateId;
|
||||
$travel->hotelId = $hotelId;
|
||||
|
||||
// Snapshot-provided insurances are intentionally replaced by InsuranceLoader data.
|
||||
$staleSnapshotInsurance = new Insurance();
|
||||
$staleSnapshotInsurance->id = 'stale';
|
||||
$travel->insurances = [$staleSnapshotInsurance];
|
||||
|
||||
$individual = new Insurance();
|
||||
$individual->id = '10';
|
||||
$individual->package = false;
|
||||
|
||||
$package = new Insurance();
|
||||
$package->id = '20';
|
||||
$package->package = true;
|
||||
$package->containedInsuranceIds = ['10'];
|
||||
// InsuranceParser populates containedInsurances at parse time; loadAll() returns hydrated data.
|
||||
$package->containedInsurances = [$individual];
|
||||
|
||||
$this->travelSnapshotService
|
||||
$this->travelEnrichmentService
|
||||
->expects($this->once())
|
||||
->method('loadTravel')
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn($travel);
|
||||
|
||||
$this->insuranceLoader
|
||||
->expects($this->once())
|
||||
->method('loadAll')
|
||||
->willReturn([$individual, $package]);
|
||||
->method('patchInsurances')
|
||||
->with($travel);
|
||||
|
||||
$result = $this->service->getTravelDataFromLocal($dateId, $hotelId);
|
||||
|
||||
$this->assertSame($travel, $result);
|
||||
$this->assertSame([$individual, $package], array_values($travel->insurances));
|
||||
$this->assertCount(1, $package->containedInsurances);
|
||||
$this->assertSame($individual, $package->containedInsurances[0]);
|
||||
}
|
||||
|
||||
public function testGetTravelDataFromLocalFallsBackToXmlWhenSnapshotMissing(): void
|
||||
@@ -411,105 +306,23 @@ class TravelDataServiceTest extends TestCase
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn($travel);
|
||||
|
||||
$this->pickupLoader
|
||||
$this->travelEnrichmentService
|
||||
->expects($this->once())
|
||||
->method('patchPickupsDetails')
|
||||
->with($travel);
|
||||
|
||||
$this->hotelLoader
|
||||
->expects($this->once())
|
||||
->method('patchHotelDetails')
|
||||
->with($travel);
|
||||
->method('enrichFromXml')
|
||||
->with($travel)
|
||||
->willReturn(true);
|
||||
|
||||
$this->travelSnapshotService
|
||||
->expects($this->once())
|
||||
->method('upsertFromTravel')
|
||||
->with($travel);
|
||||
|
||||
$result = $this->service->getTravelDataFromLocal($dateId, $hotelId);
|
||||
|
||||
$this->assertSame($travel, $result);
|
||||
}
|
||||
|
||||
public function testGetTravelDataFromLocalAttachesLoaderInsurancesFromXmlFallback(): void
|
||||
{
|
||||
$dateId = 12345;
|
||||
$hotelId = 67890;
|
||||
|
||||
$individual = new Insurance();
|
||||
$individual->id = '10';
|
||||
$individual->package = false;
|
||||
|
||||
$package = new Insurance();
|
||||
$package->id = '20';
|
||||
$package->package = true;
|
||||
$package->containedInsuranceIds = ['10'];
|
||||
// InsuranceParser populates containedInsurances at parse time; loadAll() returns hydrated data.
|
||||
$package->containedInsurances = [$individual];
|
||||
|
||||
$travel = new Travel();
|
||||
$travel->id = $dateId;
|
||||
$travel->hotelId = $hotelId;
|
||||
|
||||
$this->travelSnapshotService
|
||||
$this->travelEnrichmentService
|
||||
->expects($this->once())
|
||||
->method('loadTravel')
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn(null);
|
||||
|
||||
$this->travelLoader
|
||||
->expects($this->once())
|
||||
->method('loadById')
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn($travel);
|
||||
|
||||
$this->pickupLoader->expects($this->once())->method('patchPickupsDetails')->with($travel);
|
||||
$this->hotelLoader->expects($this->once())->method('patchHotelDetails')->with($travel);
|
||||
$this->insuranceLoader->expects($this->once())->method('loadAll')->willReturn([$individual, $package]);
|
||||
$this->travelSnapshotService->expects($this->once())->method('upsertFromTravel')->with($travel);
|
||||
|
||||
$result = $this->service->getTravelDataFromLocal($dateId, $hotelId);
|
||||
|
||||
$this->assertSame($travel, $result);
|
||||
$this->assertCount(1, $package->containedInsurances);
|
||||
$this->assertSame($individual, $package->containedInsurances[0]);
|
||||
}
|
||||
|
||||
public function testGetTravelDataDelegatesToLoadUncached(): void
|
||||
{
|
||||
$dateId = 12345;
|
||||
$hotelId = 67890;
|
||||
$travel = new Travel();
|
||||
$travel->id = $dateId;
|
||||
$travel->hotelId = $hotelId;
|
||||
|
||||
$this->cache
|
||||
->expects($this->never())
|
||||
->method('get');
|
||||
|
||||
$this->travelSnapshotService
|
||||
->expects($this->once())
|
||||
->method('loadTravel')
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn(null);
|
||||
|
||||
$this->travelLoader
|
||||
->expects($this->once())
|
||||
->method('loadById')
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn($travel);
|
||||
|
||||
$this->pickupLoader
|
||||
->expects($this->once())
|
||||
->method('patchPickupsDetails')
|
||||
->method('patchInsurances')
|
||||
->with($travel);
|
||||
|
||||
$this->hotelLoader
|
||||
->expects($this->once())
|
||||
->method('patchHotelDetails')
|
||||
->with($travel);
|
||||
|
||||
$result = $this->service->getTravelData($dateId, $hotelId, false);
|
||||
$result = $this->service->getTravelDataFromLocal($dateId, $hotelId);
|
||||
|
||||
$this->assertSame($travel, $result);
|
||||
}
|
||||
@@ -579,6 +392,10 @@ class TravelDataServiceTest extends TestCase
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn($travel);
|
||||
|
||||
$this->travelEnrichmentService
|
||||
->method('enrichFromXml')
|
||||
->willReturn(true);
|
||||
|
||||
$this->travelSnapshotService
|
||||
->expects($this->once())
|
||||
->method('upsertFromTravel')
|
||||
@@ -594,4 +411,33 @@ class TravelDataServiceTest extends TestCase
|
||||
$this->assertNotNull($result);
|
||||
$this->assertSame($travel, $result);
|
||||
}
|
||||
|
||||
public function testGetTravelDataDelegatesToLoadUncached(): void
|
||||
{
|
||||
$dateId = 12345;
|
||||
$hotelId = 67890;
|
||||
$travel = new Travel();
|
||||
$travel->id = $dateId;
|
||||
$travel->hotelId = $hotelId;
|
||||
|
||||
$this->cache
|
||||
->expects($this->never())
|
||||
->method('get');
|
||||
|
||||
$this->travelSnapshotService
|
||||
->expects($this->once())
|
||||
->method('loadTravel')
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn(null);
|
||||
|
||||
$this->travelLoader
|
||||
->expects($this->once())
|
||||
->method('loadById')
|
||||
->with($dateId, $hotelId)
|
||||
->willReturn($travel);
|
||||
|
||||
$result = $this->service->getTravelData($dateId, $hotelId, false);
|
||||
|
||||
$this->assertSame($travel, $result);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user