Files
myep/tests/Service/InsuranceManagerTest.php
2026-08-05 15:11:37 +02:00

627 lines
23 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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\InsuranceManager;
use Carbon\Carbon;
use PHPUnit\Framework\TestCase;
class InsuranceManagerTest extends TestCase
{
private InsuranceManager $service;
protected function setUp(): void
{
// InsuranceManager is stateless and has no dependencies
$this->service = new InsuranceManager();
// Set a fixed test date for consistent test results
Carbon::setTestNow('2024-06-01 12:00:00');
}
protected function tearDown(): void
{
// Reset Carbon's test now after each test
Carbon::setTestNow();
}
public function testGetSelectableInsurancesFiltersComplementary(): void
{
$regularInsurance = $this->createInsurance(['id' => '1', 'complementary' => false]);
$complementaryInsurance = $this->createInsurance(['id' => '2', 'complementary' => true]);
$travel = new Travel();
$travel->id = 123;
$travel->insurances = [$regularInsurance, $complementaryInsurance];
$result = $this->service->getSelectableInsurances($travel);
$this->assertCount(1, $result);
$this->assertSame($regularInsurance, $result[0]);
}
public function testGetSelectableInsurancesCachesResults(): void
{
$insurance = $this->createInsurance(['id' => '1', 'complementary' => false]);
$travel = new Travel();
$travel->id = 456;
$travel->insurances = [$insurance];
// Call twice - second call should use cache
$result1 = $this->service->getSelectableInsurances($travel);
$result2 = $this->service->getSelectableInsurances($travel);
$this->assertSame($result1, $result2);
}
public function testGetEligibleInsurancesWithMatchingCriteria(): void
{
$participant = $this->createParticipant('1990-06-15');
$booking = $this->createBooking('2024-08-01', '2024-08-08');
$insurance = $this->createInsurance([
'ageFrom' => 20,
'ageTo' => 50,
'travelDateFrom' => new \DateTimeImmutable('2024-01-01'),
'travelDateTo' => new \DateTimeImmutable('2024-12-31'),
'bookingDateFrom' => new \DateTimeImmutable('2024-01-01'),
'bookingDateTo' => new \DateTimeImmutable('2024-12-31'),
'travelPriceFrom' => 0.0,
'travelPriceTo' => 1000.0,
'travelDurationFrom' => 1,
'travelDurationTo' => 14,
]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
$this->assertCount(1, $result);
$this->assertContains($insurance, $result);
}
public function testGetEligibleInsurancesExcludesInsuranceWithAgeTooYoung(): void
{
$participant = $this->createParticipant('2010-06-15'); // 14 years old in 2024
$booking = $this->createBooking('2024-08-01', '2024-08-08');
$insurance = $this->createInsurance(['ageFrom' => 18, 'ageTo' => 65]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
$this->assertEmpty($result);
}
public function testGetEligibleInsurancesExcludesInsuranceWithAgeTooOld(): void
{
$participant = $this->createParticipant('1950-06-15'); // 74 years old in 2024
$booking = $this->createBooking('2024-08-01', '2024-08-08');
$insurance = $this->createInsurance(['ageFrom' => 18, 'ageTo' => 65]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
$this->assertEmpty($result);
}
public function testGetEligibleInsurancesExcludesInsuranceWithTravelDateTooEarly(): void
{
$participant = $this->createParticipant('1990-06-15');
$booking = $this->createBooking('2024-08-01', '2024-08-08');
$insurance = $this->createInsurance([
'travelDateFrom' => new \DateTimeImmutable('2024-09-01'),
'travelDateTo' => new \DateTimeImmutable('2024-12-31'),
]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
$this->assertEmpty($result);
}
public function testGetEligibleInsurancesExcludesInsuranceWithTravelDateTooLate(): void
{
$participant = $this->createParticipant('1990-06-15');
$booking = $this->createBooking('2024-08-01', '2024-08-08');
$insurance = $this->createInsurance([
'travelDateFrom' => new \DateTimeImmutable('2024-01-01'),
'travelDateTo' => new \DateTimeImmutable('2024-07-31'),
]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
$this->assertEmpty($result);
}
public function testGetEligibleInsurancesExcludesInsuranceWithBookingDateTooEarly(): void
{
$participant = $this->createParticipant('1990-06-15');
$booking = $this->createBooking('2024-08-01', '2024-08-08');
$insurance = $this->createInsurance([
'bookingDateFrom' => new \DateTimeImmutable('2025-01-01'),
'bookingDateTo' => new \DateTimeImmutable('2025-12-31'),
]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
$this->assertEmpty($result);
}
public function testGetEligibleInsurancesExcludesInsuranceWithBookingDateTooLate(): void
{
$participant = $this->createParticipant('1990-06-15');
$booking = $this->createBooking('2024-08-01', '2024-08-08');
$insurance = $this->createInsurance([
'bookingDateFrom' => new \DateTimeImmutable('2023-01-01'),
'bookingDateTo' => new \DateTimeImmutable('2023-12-31'),
]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
$this->assertEmpty($result);
}
public function testGetEligibleInsurancesExcludesInsuranceWithTravelPriceTooLow(): void
{
$participant = $this->createParticipant('1990-06-15');
$booking = $this->createBooking('2024-08-01', '2024-08-08');
// Price calculator returns 500.0, insurance requires at least 1000.0
$insurance = $this->createInsurance(['travelPriceFrom' => 1000.0, 'travelPriceTo' => 2000.0]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
$this->assertEmpty($result);
}
public function testGetEligibleInsurancesExcludesInsuranceWithTravelPriceTooHigh(): void
{
$participant = $this->createParticipant('1990-06-15');
$booking = $this->createBooking('2024-08-01', '2024-08-08');
// Price calculator returns 500.0, insurance allows maximum 100.0
$insurance = $this->createInsurance(['travelPriceFrom' => 0.0, 'travelPriceTo' => 100.0]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
$this->assertEmpty($result);
}
public function testGetEligibleInsurancesExcludesInsuranceWithTravelDurationTooShort(): void
{
$participant = $this->createParticipant('1990-06-15');
$booking = $this->createBooking('2024-08-01', '2024-08-03'); // 2 days
$insurance = $this->createInsurance(['travelDurationFrom' => 7, 'travelDurationTo' => 30]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
$this->assertEmpty($result);
}
public function testGetEligibleInsurancesExcludesInsuranceWithTravelDurationTooLong(): void
{
$participant = $this->createParticipant('1990-06-15');
$booking = $this->createBooking('2024-08-01', '2024-08-31'); // 30 days
$insurance = $this->createInsurance(['travelDurationFrom' => 1, 'travelDurationTo' => 14]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
$this->assertEmpty($result);
}
public function testGetEligibleInsurancesHandlesParticipantWithoutBirthDate(): void
{
$participant = new ParticipantDto();
$participant->index = 0;
$participant->dateOfBirth = null;
$booking = $this->createBooking('2024-08-01', '2024-08-08');
$insurance = $this->createInsurance(['ageFrom' => 18, 'ageTo' => 65]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
// Participants without birth date should have insurances available
// (field visibility is controlled by field state conditions, not service logic)
$this->assertNotEmpty($result);
$this->assertCount(1, $result);
$this->assertSame($insurance, $result[0]);
}
public function testGetEligibleInsurancesWithNullConstraints(): void
{
$participant = $this->createParticipant('1990-06-15');
$booking = $this->createBooking('2024-08-01', '2024-08-08');
// Insurance with no constraints should match any criteria
$insurance = $this->createInsurance([]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
$this->assertCount(1, $result);
$this->assertContains($insurance, $result);
}
public function testGetEligibleInsurancesWithMultipleInsurances(): void
{
$participant = $this->createParticipant('1990-06-15');
$booking = $this->createBooking('2024-08-01', '2024-08-08');
$eligibleInsurance1 = $this->createInsurance(['ageFrom' => 20, 'ageTo' => 50]);
$eligibleInsurance2 = $this->createInsurance(['ageFrom' => 30, 'ageTo' => 40]);
$ineligibleInsurance = $this->createInsurance(['ageFrom' => 60, 'ageTo' => 80]);
$insurances = [$eligibleInsurance1, $eligibleInsurance2, $ineligibleInsurance];
$result = $this->service->getEligibleInsurances($insurances, $participant, $booking, 500.0);
$this->assertCount(2, $result);
$this->assertContains($eligibleInsurance1, $result);
$this->assertContains($eligibleInsurance2, $result);
$this->assertNotContains($ineligibleInsurance, $result);
}
public function testGetEligibleInsurancesWithAgeExactlyAtBoundary(): void
{
$participant = $this->createParticipant('1990-08-01'); // Exactly 34 on travel start
$booking = $this->createBooking('2024-08-01', '2024-08-08');
$insurance = $this->createInsurance(['ageFrom' => 34, 'ageTo' => 34]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
$this->assertCount(1, $result);
$this->assertContains($insurance, $result);
}
public function testGetEligibleInsurancesWithTravelDurationExactlyAtBoundary(): void
{
$participant = $this->createParticipant('1990-06-15');
$booking = $this->createBooking('2024-08-01', '2024-08-08'); // Exactly 7 days
$insurance = $this->createInsurance(['travelDurationFrom' => 7, 'travelDurationTo' => 7]);
$result = $this->service->getEligibleInsurances([$insurance], $participant, $booking, 500.0);
$this->assertCount(1, $result);
$this->assertContains($insurance, $result);
}
public function testFilterByTypeForPackages(): void
{
$packageInsurance1 = $this->createInsurance([
'id' => '1',
'package' => true,
'label' => 'Premium Package',
'familyInsurance' => false,
]);
$packageInsurance2 = $this->createInsurance([
'id' => '2',
'package' => true,
'label' => 'Premium Package',
'familyInsurance' => false,
]);
$differentPackage = $this->createInsurance([
'id' => '3',
'package' => true,
'label' => 'Basic Package',
'familyInsurance' => false,
]);
$insurances = [$packageInsurance1, $packageInsurance2, $differentPackage];
$result = $this->service->filterByType($insurances, $packageInsurance1);
$this->assertCount(2, $result);
$this->assertContains($packageInsurance1, $result);
$this->assertContains($packageInsurance2, $result);
}
public function testFilterByTypeForIndividualInsurances(): void
{
$insurance1 = $this->createInsurance([
'id' => '1',
'package' => false,
'subType' => 'RRV',
'familyInsurance' => false,
]);
$insurance2 = $this->createInsurance([
'id' => '2',
'package' => false,
'subType' => 'RRV',
'familyInsurance' => false,
]);
$differentType = $this->createInsurance([
'id' => '3',
'package' => false,
'subType' => 'PAK',
'familyInsurance' => false,
]);
$insurances = [$insurance1, $insurance2, $differentType];
$result = $this->service->filterByType($insurances, $insurance1);
$this->assertCount(2, $result);
$this->assertContains($insurance1, $result);
$this->assertContains($insurance2, $result);
}
public function testReassignInsuranceForPriceChange(): void
{
$participant = $this->createParticipant('1990-06-15');
$booking = $this->createBooking('2024-08-01', '2024-08-08');
$currentInsurance = $this->createInsurance([
'id' => '1',
'package' => false,
'subType' => 'RRV',
'familyInsurance' => false,
'travelPriceFrom' => 0.0,
'travelPriceTo' => 300.0,
]);
$newTierInsurance = $this->createInsurance([
'id' => '2',
'package' => false,
'subType' => 'RRV',
'familyInsurance' => false,
'travelPriceFrom' => 300.0,
'travelPriceTo' => 600.0,
]);
$availableInsurances = [$currentInsurance, $newTierInsurance];
$result = $this->service->reassignInsuranceForPriceChange(
$availableInsurances,
$currentInsurance,
$participant,
$booking,
500.0
);
$this->assertSame($newTierInsurance, $result);
}
public function testBatchAssignInsuranceToParticipants(): void
{
$booking = $this->createBooking('2024-08-01', '2024-08-08');
$participant1 = $this->createParticipant('1990-06-15');
$participant1->index = 0;
$participant2 = $this->createParticipant('1995-03-20');
$participant2->index = 1;
$booking->participants = [$participant1, $participant2];
$selectedInsurance = $this->createInsurance([
'id' => '1',
'package' => false,
'subType' => 'RRV',
'familyInsurance' => false,
]);
$eligibleInsurance = $this->createInsurance([
'id' => '2',
'package' => false,
'subType' => 'RRV',
'familyInsurance' => false,
]);
$availableInsurances = [$selectedInsurance, $eligibleInsurance];
$participantPrices = [0 => 500.0, 1 => 500.0];
$result = $this->service->batchAssignInsuranceToParticipants(
$availableInsurances,
$selectedInsurance,
$booking,
$participantPrices
);
$this->assertCount(2, $result);
$this->assertNotNull($result[0]);
$this->assertNotNull($result[1]);
}
public function testBatchAssignFamilyInsuranceToApplicantOnly(): void
{
$booking = $this->createFamilyBooking('2024-08-01', '2024-08-08');
// Tier matched by total price (300 + 0 = 300, fits 250400 tier)
$lowTierInsurance = $this->createInsurance([
'id' => '1',
'package' => false,
'subType' => 'FAM',
'familyInsurance' => true,
'travelPriceFrom' => 0.0,
'travelPriceTo' => 249.99,
]);
$correctTierInsurance = $this->createInsurance([
'id' => '2',
'package' => false,
'subType' => 'FAM',
'familyInsurance' => true,
'travelPriceFrom' => 250.0,
'travelPriceTo' => 400.0,
]);
$availableInsurances = [$lowTierInsurance, $correctTierInsurance];
// Individual prices: applicant 300, child 0 → total 300
$participantPrices = [0 => 300.0, 1 => 0.0];
$result = $this->service->batchAssignInsuranceToParticipants(
$availableInsurances,
$correctTierInsurance,
$booking,
$participantPrices
);
$this->assertCount(2, $result);
$this->assertSame($correctTierInsurance, $result[0], 'Applicant should get tier matching total price');
$this->assertNull($result[1], 'Non-applicant participants must be cleared for family insurance');
}
public function testFamilyInsuranceIneligibleForNonApplicant(): void
{
$booking = $this->createFamilyBooking('2024-08-01', '2024-08-08');
$nonApplicant = $this->createParticipant('2015-01-01'); // child
$nonApplicant->index = 1;
$familyInsurance = $this->createInsurance(['familyInsurance' => true]);
$result = $this->service->getEligibleInsurances([$familyInsurance], $nonApplicant, $booking, 500.0);
$this->assertEmpty($result, 'Family insurance must not be eligible for non-applicant participants');
}
public function testNonFamilyInsuranceEligibleForFamilyBooking(): void
{
$booking = $this->createFamilyBooking('2024-08-01', '2024-08-08');
$adult = $this->createParticipant('1990-06-15');
$adult->index = 0;
$child = $this->createParticipant('2015-01-01');
$child->index = 1;
$nonFamilyInsurance = $this->createInsurance(['familyInsurance' => false]);
$adultResult = $this->service->getEligibleInsurances([$nonFamilyInsurance], $adult, $booking, 500.0);
$childResult = $this->service->getEligibleInsurances([$nonFamilyInsurance], $child, $booking, 500.0);
$this->assertNotEmpty($adultResult, 'Non-family insurance must be available in family bookings for adults');
$this->assertNotEmpty($childResult, 'Non-family insurance must be available in family bookings for children');
}
public function testFamilyInsuranceIneligibleForNonFamilyBooking(): void
{
// Two adults, no children — not a family booking
$booking = $this->createBooking('2024-08-01', '2024-08-08');
$adult1 = $this->createParticipant('1990-06-15');
$adult1->index = 0;
$adult2 = $this->createParticipant('1985-03-10');
$adult2->index = 1;
$booking->participants = [$adult1, $adult2];
$familyInsurance = $this->createInsurance(['familyInsurance' => true]);
$result = $this->service->getEligibleInsurances([$familyInsurance], $adult1, $booking, 500.0);
$this->assertEmpty($result, 'Family insurance must not be eligible when booking is not a family booking');
}
public function testGetEligibleInsurancesForParticipantUsesTotalPriceForFamilyInsurance(): void
{
$booking = $this->createFamilyBooking('2024-08-01', '2024-08-08');
$applicant = $booking->participants[0];
// Only matches the total booking price (450), not the individual price (300)
$familyInsurance = $this->createInsurance([
'familyInsurance' => true,
'travelPriceFrom' => 400.01,
'travelPriceTo' => 600.0,
]);
$nonFamilyInsurance = $this->createInsurance([
'familyInsurance' => false,
'travelPriceFrom' => 0.0,
'travelPriceTo' => 350.0,
]);
$result = $this->service->getEligibleInsurancesForParticipant(
[$familyInsurance, $nonFamilyInsurance],
$applicant,
$booking,
300.0,
450.0
);
$ids = array_map(fn (Insurance $insurance) => spl_object_id($insurance), $result);
$this->assertContains(spl_object_id($familyInsurance), $ids, 'Family insurance must be evaluated against the total booking price');
$this->assertContains(spl_object_id($nonFamilyInsurance), $ids, 'Non-family insurance must be evaluated against the individual price');
}
public function testGetEligibleInsurancesForParticipantSkipsFamilyPricingWhenNoneAvailable(): void
{
$booking = $this->createFamilyBooking('2024-08-01', '2024-08-08');
$applicant = $booking->participants[0];
$nonFamilyInsurance = $this->createInsurance([
'familyInsurance' => false,
'travelPriceFrom' => 0.0,
'travelPriceTo' => 350.0,
]);
$result = $this->service->getEligibleInsurancesForParticipant(
[$nonFamilyInsurance],
$applicant,
$booking,
300.0,
450.0
);
$this->assertCount(1, $result);
$this->assertSame($nonFamilyInsurance, $result[0]);
}
private function createParticipant(string $dateOfBirth): ParticipantDto
{
$participant = new ParticipantDto();
$participant->index = 0;
$participant->dateOfBirth = new \DateTimeImmutable($dateOfBirth);
return $participant;
}
private function createBooking(string $travelDateFrom, string $travelDateTo): BookingDto
{
$travel = $this->createTravel($travelDateFrom, $travelDateTo);
$booking = new BookingDto($travel, 1);
return $booking;
}
private function createFamilyBooking(string $travelDateFrom, string $travelDateTo): BookingDto
{
$booking = $this->createBooking($travelDateFrom, $travelDateTo);
$adult = $this->createParticipant('1990-06-15'); // 34 years old
$adult->index = 0;
$child = $this->createParticipant('2015-01-01'); // 9 years old
$child->index = 1;
$booking->participants = [$adult, $child];
return $booking;
}
private function createTravel(string $dateFrom, string $dateTo): Travel
{
$travel = new Travel();
$travel->id = random_int(1, 999999);
$travel->dateFrom = new \DateTimeImmutable($dateFrom);
$travel->dateTo = new \DateTimeImmutable($dateTo);
return $travel;
}
private function createInsurance(array $properties = []): Insurance
{
$insurance = new Insurance();
// Set defaults
$insurance->complementary = false;
$insurance->package = false;
$insurance->familyInsurance = false;
foreach ($properties as $property => $value) {
$insurance->$property = $value;
}
return $insurance;
}
}