feat: indicate family insurance eligibility
This commit is contained in:
@@ -10,6 +10,7 @@ use App\Form\Model\BookingDto;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
use App\Form\Service\ParticipantInsuranceFieldHandler;
|
||||
use App\Service\BookingPriceCalculator;
|
||||
use App\Service\FamilyInsuranceAvailabilityChecker;
|
||||
use App\Service\InsuranceManager;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@@ -34,7 +35,13 @@ class ParticipantInsuranceFieldHandlerTest extends TestCase
|
||||
$this->priceCalculatorService->method('resolveInsuranceTravelPrice')
|
||||
->willReturn(500.0);
|
||||
|
||||
$this->handler = new ParticipantInsuranceFieldHandler($this->insuranceService, $this->priceCalculatorService);
|
||||
// Real checker over the mocked collaborators - the selection-time recording below is
|
||||
// only meaningful against the actual "is a family insurance available" definition
|
||||
$this->handler = new ParticipantInsuranceFieldHandler(
|
||||
$this->insuranceService,
|
||||
$this->priceCalculatorService,
|
||||
new FamilyInsuranceAvailabilityChecker($this->insuranceService, $this->priceCalculatorService),
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetFieldName(): void
|
||||
@@ -279,21 +286,6 @@ class ParticipantInsuranceFieldHandlerTest extends TestCase
|
||||
$this->assertNull($participant->insurance);
|
||||
}
|
||||
|
||||
public function testProcessFieldNotifiesApplicantWhenFamilyInsuranceNewlyAvailable(): void
|
||||
{
|
||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||
$applicant = $bookingDto->getParticipant(0);
|
||||
|
||||
$this->priceCalculatorService->method('calculateTotalBookingPriceExcludingInsurance')->willReturn(500.0);
|
||||
$this->insuranceService->method('getEligibleInsurances')->willReturn([$this->createInsurance('2', 'Reise-Rücktritt Familie')]);
|
||||
|
||||
// Dependent's own card is submitted (e.g. just their date of birth) - no insurance selection
|
||||
$this->handler->processField([], $bookingDto, 1);
|
||||
|
||||
$this->assertNotEmpty($applicant->notifications, 'Applicant should be notified that family insurance is now available');
|
||||
$this->assertTrue($bookingDto->familyInsuranceHintShown);
|
||||
}
|
||||
|
||||
public function testProcessFieldRecordsIneligibilityWhenApplicantSelectsInsuranceBeforeFamilyEligible(): void
|
||||
{
|
||||
$nonFamilyInsurance = $this->createInsurance('1');
|
||||
@@ -405,124 +397,6 @@ class ParticipantInsuranceFieldHandlerTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testProcessFieldDoesNotNotifyWhenApplicantSwitchesToFamilyInsuranceInSameSubmission(): void
|
||||
{
|
||||
$familyInsurance = $this->createInsurance('2');
|
||||
$familyInsurance->familyInsurance = true;
|
||||
|
||||
$nonFamilyInsurance = $this->createInsurance('1');
|
||||
$nonFamilyInsurance->familyInsurance = false;
|
||||
|
||||
$travel = new Travel();
|
||||
$travel->dateFrom = new \DateTimeImmutable('2025-08-01');
|
||||
$travel->dateTo = new \DateTimeImmutable('2025-08-08');
|
||||
$travel->insurances = [$nonFamilyInsurance, $familyInsurance];
|
||||
|
||||
$applicant = new ParticipantDto();
|
||||
$applicant->index = 0;
|
||||
$applicant->dateOfBirth = new \DateTimeImmutable('1990-06-15');
|
||||
$applicant->insurance = $nonFamilyInsurance;
|
||||
|
||||
$child = new ParticipantDto();
|
||||
$child->index = 1;
|
||||
$child->dateOfBirth = new \DateTimeImmutable('2015-01-01');
|
||||
|
||||
$bookingDto = new BookingDto($travel, 1);
|
||||
$bookingDto->participants = [$applicant, $child];
|
||||
$bookingDto->applicantInsuranceChosenWhileFamilyIneligible = true;
|
||||
|
||||
$this->insuranceService->method('getEligibleInsurances')->willReturn([$familyInsurance]);
|
||||
|
||||
// Applicant's own submission switches them to family insurance in this same request
|
||||
$this->handler->processField(['insurance' => '2'], $bookingDto, 0);
|
||||
|
||||
$this->assertSame($familyInsurance, $applicant->insurance);
|
||||
$this->assertEmpty($applicant->notifications, 'Must not show a stale "please recheck" hint for a choice this submission already made');
|
||||
}
|
||||
|
||||
public function testProcessFieldDoesNotNotifyWhenHintAlreadyShown(): void
|
||||
{
|
||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||
$bookingDto->familyInsuranceHintShown = true;
|
||||
$applicant = $bookingDto->getParticipant(0);
|
||||
|
||||
$this->priceCalculatorService->method('calculateTotalBookingPriceExcludingInsurance')->willReturn(500.0);
|
||||
$this->insuranceService->method('getEligibleInsurances')->willReturn([$this->createInsurance('2')]);
|
||||
|
||||
$this->handler->processField([], $bookingDto, 1);
|
||||
|
||||
$this->assertEmpty($applicant->notifications, 'Hint must not repeat once already shown');
|
||||
}
|
||||
|
||||
public function testProcessFieldDoesNotNotifyWhenApplicantHasNoInsurance(): void
|
||||
{
|
||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||
$applicant = $bookingDto->getParticipant(0);
|
||||
$applicant->insurance = null;
|
||||
|
||||
$this->handler->processField([], $bookingDto, 1);
|
||||
|
||||
$this->assertEmpty($applicant->notifications, 'Nothing to alert about when applicant has not chosen insurance yet');
|
||||
$this->assertFalse($bookingDto->familyInsuranceHintShown);
|
||||
}
|
||||
|
||||
public function testProcessFieldDoesNotNotifyWhenApplicantAlreadyHasFamilyInsurance(): void
|
||||
{
|
||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||
$applicant = $bookingDto->getParticipant(0);
|
||||
$familyInsurance = $this->createInsurance('2');
|
||||
$familyInsurance->familyInsurance = true;
|
||||
$applicant->insurance = $familyInsurance;
|
||||
|
||||
$this->handler->processField([], $bookingDto, 1);
|
||||
|
||||
$this->assertEmpty($applicant->notifications, 'Nothing to alert about when applicant already has family insurance');
|
||||
$this->assertFalse($bookingDto->familyInsuranceHintShown);
|
||||
}
|
||||
|
||||
public function testProcessFieldDoesNotNotifyWhenNoEligibleFamilyInsuranceExists(): void
|
||||
{
|
||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||
$applicant = $bookingDto->getParticipant(0);
|
||||
|
||||
$this->priceCalculatorService->method('calculateTotalBookingPriceExcludingInsurance')->willReturn(500.0);
|
||||
$this->insuranceService->method('getEligibleInsurances')->willReturn([]); // No eligible family insurance
|
||||
|
||||
$this->handler->processField([], $bookingDto, 1);
|
||||
|
||||
$this->assertEmpty($applicant->notifications, 'Nothing to alert about when no family insurance is actually eligible');
|
||||
$this->assertFalse($bookingDto->familyInsuranceHintShown);
|
||||
}
|
||||
|
||||
private function createFamilyBookingDtoWithNonFamilyApplicantInsurance(): BookingDto
|
||||
{
|
||||
$nonFamilyInsurance = $this->createInsurance('1');
|
||||
$nonFamilyInsurance->familyInsurance = false;
|
||||
|
||||
$familyInsurance = $this->createInsurance('2');
|
||||
$familyInsurance->familyInsurance = true;
|
||||
|
||||
$travel = new Travel();
|
||||
$travel->dateFrom = new \DateTimeImmutable('2025-08-01');
|
||||
$travel->dateTo = new \DateTimeImmutable('2025-08-08');
|
||||
$travel->insurances = [$nonFamilyInsurance, $familyInsurance];
|
||||
|
||||
$applicant = new ParticipantDto();
|
||||
$applicant->index = 0;
|
||||
$applicant->dateOfBirth = new \DateTimeImmutable('1990-06-15');
|
||||
$applicant->insurance = $nonFamilyInsurance;
|
||||
|
||||
$child = new ParticipantDto();
|
||||
$child->index = 1;
|
||||
$child->dateOfBirth = new \DateTimeImmutable('2015-01-01');
|
||||
|
||||
$bookingDto = new BookingDto($travel, 1);
|
||||
$bookingDto->participants = [$applicant, $child];
|
||||
$bookingDto->applicantInsuranceChosenWhileFamilyIneligible = true;
|
||||
|
||||
return $bookingDto;
|
||||
}
|
||||
|
||||
public function testGetFieldStateModificationsReturnsEmptyArray(): void
|
||||
{
|
||||
$bookingDto = $this->createMockBookingDto();
|
||||
|
||||
@@ -14,6 +14,7 @@ use App\Form\Model\ParticipantCardPriceDto;
|
||||
use App\Service\BookingCreateContextFactory;
|
||||
use App\Service\BookingPriceCalculator;
|
||||
use App\Service\BookingSummaryAssembler;
|
||||
use App\Service\FamilyInsuranceAvailabilityChecker;
|
||||
use App\Service\ParticipantCardAssembler;
|
||||
use App\Service\RoomPricingCalculator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -62,10 +63,14 @@ class BookingCreateContextFactoryTest extends TestCase
|
||||
$priceCalculator->expects($this->never())
|
||||
->method('calculateAllParticipantIndividualPrices');
|
||||
|
||||
$familyInsuranceService = $this->createMock(FamilyInsuranceAvailabilityChecker::class);
|
||||
$familyInsuranceService->method('isFamilyInsuranceUpgradeAvailable')->willReturn(true);
|
||||
|
||||
$service = new BookingCreateContextFactory(
|
||||
$participantCardDataService,
|
||||
$summaryDataService,
|
||||
$priceCalculator,
|
||||
$familyInsuranceService,
|
||||
);
|
||||
|
||||
$context = $service->create($bookingDto, RoomPricingCalculator::PRICING_MODE_SELECTION);
|
||||
@@ -73,6 +78,7 @@ class BookingCreateContextFactoryTest extends TestCase
|
||||
$this->assertInstanceOf(BookingCreateContext::class, $context);
|
||||
$this->assertSame($bookingDto, $context->bookingDto);
|
||||
$this->assertSame($summaryData, $context->summaryData);
|
||||
$this->assertTrue($context->familyInsuranceUpgradeAvailable);
|
||||
$this->assertNull($context->cardsData);
|
||||
$this->assertFalse($context->isSubmitted);
|
||||
$this->assertSame([11 => $roomByPax], $context->groupedRooms->byPax);
|
||||
@@ -120,10 +126,14 @@ class BookingCreateContextFactoryTest extends TestCase
|
||||
$priceCalculator->expects($this->never())
|
||||
->method('calculateAllParticipantIndividualPrices');
|
||||
|
||||
$familyInsuranceService = $this->createMock(FamilyInsuranceAvailabilityChecker::class);
|
||||
$familyInsuranceService->method('isFamilyInsuranceUpgradeAvailable')->willReturn(true);
|
||||
|
||||
$service = new BookingCreateContextFactory(
|
||||
$participantCardDataService,
|
||||
$summaryDataService,
|
||||
$priceCalculator,
|
||||
$familyInsuranceService,
|
||||
);
|
||||
|
||||
$context = $service->createWithParticipantCards($bookingDto, true);
|
||||
@@ -131,6 +141,7 @@ class BookingCreateContextFactoryTest extends TestCase
|
||||
$this->assertInstanceOf(BookingCreateContext::class, $context);
|
||||
$this->assertSame([$cardData], $context->cardsData);
|
||||
$this->assertTrue($context->isSubmitted);
|
||||
$this->assertTrue($context->familyInsuranceUpgradeAvailable);
|
||||
$this->assertSame([], $context->groupedRooms->byPax);
|
||||
$this->assertSame([10 => $room], $context->groupedRooms->byRoom);
|
||||
}
|
||||
@@ -169,16 +180,21 @@ class BookingCreateContextFactoryTest extends TestCase
|
||||
->with($bookingDto)
|
||||
->willReturn([123.45]);
|
||||
|
||||
$familyInsuranceService = $this->createMock(FamilyInsuranceAvailabilityChecker::class);
|
||||
$familyInsuranceService->method('isFamilyInsuranceUpgradeAvailable')->willReturn(true);
|
||||
|
||||
$service = new BookingCreateContextFactory(
|
||||
$participantCardDataService,
|
||||
$summaryDataService,
|
||||
$priceCalculator,
|
||||
$familyInsuranceService,
|
||||
);
|
||||
|
||||
$context = $service->createWithParticipantPrices($bookingDto);
|
||||
|
||||
$this->assertInstanceOf(BookingCreateContext::class, $context);
|
||||
$this->assertSame([123.45], $context->participantPrices);
|
||||
$this->assertTrue($context->familyInsuranceUpgradeAvailable);
|
||||
$this->assertNull($context->cardsData);
|
||||
$this->assertSame([10 => $room], $context->groupedRooms->byRoom);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service;
|
||||
|
||||
use App\BusProNet\Model\Insurance;
|
||||
use App\BusProNet\Model\Travel;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
use App\Service\BookingPriceCalculator;
|
||||
use App\Service\FamilyInsuranceAvailabilityChecker;
|
||||
use App\Service\InsuranceManager;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class FamilyInsuranceAvailabilityCheckerTest extends TestCase
|
||||
{
|
||||
private FamilyInsuranceAvailabilityChecker $checker;
|
||||
private InsuranceManager $insuranceService;
|
||||
private BookingPriceCalculator $priceCalculatorService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->insuranceService = $this->createMock(InsuranceManager::class);
|
||||
$this->priceCalculatorService = $this->createMock(BookingPriceCalculator::class);
|
||||
|
||||
$this->insuranceService->method('getSelectableInsurances')
|
||||
->willReturnCallback(fn (Travel $travel) => $travel->insurances ?? []);
|
||||
|
||||
$this->priceCalculatorService->method('calculateTotalBookingPriceExcludingInsurance')
|
||||
->willReturn(500.0);
|
||||
|
||||
$this->checker = new FamilyInsuranceAvailabilityChecker($this->insuranceService, $this->priceCalculatorService);
|
||||
}
|
||||
|
||||
public function testUpgradeIsAvailableWhenFamilyBecameEligibleAfterTheApplicantChose(): void
|
||||
{
|
||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||
|
||||
$this->allowAllInsurancesAsEligible();
|
||||
|
||||
$this->assertTrue($this->checker->isFamilyInsuranceUpgradeAvailable($bookingDto));
|
||||
}
|
||||
|
||||
public function testUpgradeIsNotAvailableWhenTheApplicantChoseWhileFamilyWasAlreadyEligible(): void
|
||||
{
|
||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||
$bookingDto->applicantInsuranceChosenWhileFamilyIneligible = false;
|
||||
|
||||
$this->allowAllInsurancesAsEligible();
|
||||
|
||||
$this->assertFalse(
|
||||
$this->checker->isFamilyInsuranceUpgradeAvailable($bookingDto),
|
||||
'An applicant who was offered family insurance and picked something else must not be nagged'
|
||||
);
|
||||
}
|
||||
|
||||
public function testUpgradeIsNotAvailableWhenApplicantHasNoInsuranceYet(): void
|
||||
{
|
||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||
$bookingDto->getParticipant(0)->insurance = null;
|
||||
|
||||
$this->allowAllInsurancesAsEligible();
|
||||
|
||||
$this->assertFalse($this->checker->isFamilyInsuranceUpgradeAvailable($bookingDto));
|
||||
}
|
||||
|
||||
public function testUpgradeIsNotAvailableWhenApplicantAlreadyHasFamilyInsurance(): void
|
||||
{
|
||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||
$familyInsurance = $this->createInsurance('2');
|
||||
$familyInsurance->familyInsurance = true;
|
||||
$bookingDto->getParticipant(0)->insurance = $familyInsurance;
|
||||
|
||||
$this->allowAllInsurancesAsEligible();
|
||||
|
||||
$this->assertFalse($this->checker->isFamilyInsuranceUpgradeAvailable($bookingDto));
|
||||
}
|
||||
|
||||
public function testUpgradeIsNotAvailableWhenNoFamilyInsuranceProductExists(): void
|
||||
{
|
||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||
$bookingDto->travel->insurances = array_values(array_filter(
|
||||
$bookingDto->travel->insurances,
|
||||
static fn (Insurance $i) => false === $i->familyInsurance
|
||||
));
|
||||
|
||||
$this->allowAllInsurancesAsEligible();
|
||||
|
||||
$this->assertFalse($this->checker->isFamilyInsuranceUpgradeAvailable($bookingDto));
|
||||
}
|
||||
|
||||
public function testUpgradeIsNotAvailableWhenNoFamilyPriceTierMatches(): void
|
||||
{
|
||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||
|
||||
// Family composition qualifies, but no family insurance matches the total price
|
||||
$this->insuranceService->method('getEligibleInsurances')->willReturn([]);
|
||||
|
||||
$this->assertFalse($this->checker->isFamilyInsuranceUpgradeAvailable($bookingDto));
|
||||
}
|
||||
|
||||
public function testUpgradeIsNotAvailableWhenTheBookingIsNotAFamilyBooking(): void
|
||||
{
|
||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||
// Dependent is an adult - no children left, so the constellation no longer qualifies
|
||||
$bookingDto->getParticipant(1)->dateOfBirth = new \DateTimeImmutable('1992-01-01');
|
||||
|
||||
$this->allowAllInsurancesAsEligible();
|
||||
|
||||
$this->assertFalse($this->checker->isFamilyInsuranceUpgradeAvailable($bookingDto));
|
||||
}
|
||||
|
||||
public function testHasEligibleFamilyInsuranceIgnoresTheSelectionTimeRecording(): void
|
||||
{
|
||||
// The raw availability predicate is what the field handler records against, so it
|
||||
// must not itself depend on the flag it is used to compute
|
||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||
$bookingDto->applicantInsuranceChosenWhileFamilyIneligible = false;
|
||||
|
||||
$this->allowAllInsurancesAsEligible();
|
||||
|
||||
$this->assertTrue($this->checker->hasEligibleFamilyInsurance($bookingDto));
|
||||
}
|
||||
|
||||
private function allowAllInsurancesAsEligible(): void
|
||||
{
|
||||
$this->insuranceService->method('getEligibleInsurances')->willReturnCallback(
|
||||
fn (array $insurances) => $insurances
|
||||
);
|
||||
}
|
||||
|
||||
private function createFamilyBookingDtoWithNonFamilyApplicantInsurance(): BookingDto
|
||||
{
|
||||
$nonFamilyInsurance = $this->createInsurance('1');
|
||||
$nonFamilyInsurance->familyInsurance = false;
|
||||
|
||||
$familyInsurance = $this->createInsurance('2', 'Reise-Rücktritt Familie');
|
||||
$familyInsurance->familyInsurance = true;
|
||||
|
||||
$travel = new Travel();
|
||||
$travel->dateFrom = new \DateTimeImmutable('2025-08-01');
|
||||
$travel->dateTo = new \DateTimeImmutable('2025-08-08');
|
||||
$travel->insurances = [$nonFamilyInsurance, $familyInsurance];
|
||||
|
||||
$applicant = new ParticipantDto();
|
||||
$applicant->index = 0;
|
||||
$applicant->dateOfBirth = new \DateTimeImmutable('1990-06-15');
|
||||
$applicant->insurance = $nonFamilyInsurance;
|
||||
|
||||
$child = new ParticipantDto();
|
||||
$child->index = 1;
|
||||
$child->dateOfBirth = new \DateTimeImmutable('2015-01-01');
|
||||
|
||||
$bookingDto = new BookingDto($travel, 1);
|
||||
$bookingDto->participants = [$applicant, $child];
|
||||
$bookingDto->applicantInsuranceChosenWhileFamilyIneligible = true;
|
||||
|
||||
return $bookingDto;
|
||||
}
|
||||
|
||||
private function createInsurance(string|int $id, string $label = 'Test Insurance'): Insurance
|
||||
{
|
||||
$insurance = new Insurance();
|
||||
$insurance->id = (string) $id;
|
||||
$insurance->label = $label;
|
||||
|
||||
return $insurance;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user