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 250–400 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; } }