eligibilityChecker = $this->createMock(ParticipantEligibilityChecker::class); $this->eligibilityChecker->method('isParticipantEligible')->willReturn(true); $this->assembler = $this->createAssembler($this->eligibilityChecker); } public function testServicePricingWithoutBulkInsurance(): void { $travel = new Travel(); $travel->insurances = []; $insurance1 = $this->createInsurance(1, 'Insurance A', 50.0); $insurance2 = $this->createInsurance(2, 'Insurance B', 75.0); $participant1 = new ParticipantDto(); $participant1->index = 0; $participant1->insurance = $insurance1; $participant1->bulkInsuranceBooking = false; $participant2 = new ParticipantDto(); $participant2->index = 1; $participant2->insurance = $insurance2; $bookingDto = new BookingDto($travel, 2); $bookingDto->participants = [$participant1, $participant2]; $result = $this->assembler->calculateServicePricing($bookingDto); $this->assertNotEmpty($result); $insuranceGroup = $this->findServiceGroup($result, 'Reiseversicherungen'); $this->assertNotNull($insuranceGroup, 'Insurance group should exist'); $this->assertCount(2, $insuranceGroup['services'], 'Should have 2 different insurance line items'); } public function testServicePricingWithBulkInsuranceSamePriceTier(): void { $insurance = $this->createInsurance(1, 'Reise-Rücktritt', 50.0, 'RRV'); $insurance->travelPriceFrom = 0.0; $insurance->travelPriceTo = 10000.0; $travel = new Travel(); $travel->insurances = [$insurance]; $travel->dateFrom = new \DateTimeImmutable('2025-06-01'); $travel->dateTo = new \DateTimeImmutable('2025-06-08'); $participant1 = new ParticipantDto(); $participant1->index = 0; $participant1->insurance = $insurance; $participant1->bulkInsuranceBooking = true; $participant2 = new ParticipantDto(); $participant2->index = 1; $participant2->insurance = null; $participant3 = new ParticipantDto(); $participant3->index = 2; $participant3->insurance = null; $bookingDto = new BookingDto($travel, 3); $bookingDto->participants = [$participant1, $participant2, $participant3]; $result = $this->assembler->calculateServicePricing($bookingDto); // 3× same insurance should aggregate into one line item $insuranceGroup = $this->findServiceGroup($result, 'Reiseversicherungen'); $this->assertNotNull($insuranceGroup, 'Insurance group should exist'); $this->assertCount(1, $insuranceGroup['services'], 'Should have 1 insurance line item'); $this->assertEquals(3, $insuranceGroup['services'][0]['participantCount'], 'Should count all 3 participants'); $this->assertEquals(150.0, $insuranceGroup['services'][0]['totalPrice'], 'Total should be 3 × €50'); } public function testServicePricingWithBulkInsuranceShowsPriceTierAdjustment(): void { $insuranceTier1 = $this->createInsurance(1, 'Reise-Rücktritt', 30.0, 'RRV'); $insuranceTier1->travelPriceFrom = 0.0; $insuranceTier1->travelPriceTo = 500.0; $insuranceTier2 = $this->createInsurance(2, 'Reise-Rücktritt', 50.0, 'RRV'); $insuranceTier2->travelPriceFrom = 501.0; $insuranceTier2->travelPriceTo = 1000.0; $travel = new Travel(); $travel->insurances = [$insuranceTier1, $insuranceTier2]; $travel->dateFrom = new \DateTimeImmutable('2025-06-01'); $travel->dateTo = new \DateTimeImmutable('2025-06-08'); $participant1 = new ParticipantDto(); $participant1->index = 0; $participant1->insurance = $insuranceTier2; $participant1->bulkInsuranceBooking = true; $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant1]; $result = $this->assembler->calculateServicePricing($bookingDto); $insuranceGroup = $this->findServiceGroup($result, 'Reiseversicherungen'); $this->assertNotNull($insuranceGroup, 'Insurance group should exist'); $this->assertEquals(1, $insuranceGroup['services'][0]['participantCount'], 'Should count applicant'); } public function testServicePricingWithBulkInsuranceWhenNoBulkEnabled(): void { $insurance = $this->createInsurance(1, 'Reise-Rücktritt', 50.0, 'RRV'); $travel = new Travel(); $travel->insurances = [$insurance]; $participant1 = new ParticipantDto(); $participant1->index = 0; $participant1->insurance = $insurance; $participant1->bulkInsuranceBooking = false; $participant2 = new ParticipantDto(); $participant2->index = 1; $participant2->insurance = null; $bookingDto = new BookingDto($travel, 2); $bookingDto->participants = [$participant1, $participant2]; $result = $this->assembler->calculateServicePricing($bookingDto); $insuranceGroup = $this->findServiceGroup($result, 'Reiseversicherungen'); $this->assertNotNull($insuranceGroup, 'Insurance group should exist'); $this->assertCount(1, $insuranceGroup['services'], 'Should have 1 insurance line item'); $this->assertEquals(1, $insuranceGroup['services'][0]['participantCount'], 'Should count only applicant'); $this->assertEquals(50.0, $insuranceGroup['services'][0]['totalPrice'], 'Total should be 1 × €50'); } public function testServicePricingWithIneligibleParticipant(): void { $insurance = $this->createInsurance(1, 'Reise-Rücktritt', 50.0, 'RRV'); $travel = new Travel(); $travel->insurances = [$insurance]; $participant1 = new ParticipantDto(); $participant1->index = 0; $participant1->insurance = $insurance; $participant1->bulkInsuranceBooking = false; $participant2 = new ParticipantDto(); $participant2->index = 1; $participant2->insurance = null; $bookingDto = new BookingDto($travel, 2); $bookingDto->participants = [$participant1, $participant2]; $eligibilityChecker = $this->createMock(ParticipantEligibilityChecker::class); $eligibilityChecker->method('isParticipantEligible') ->willReturnCallback(fn ($booking, int $index): bool => 0 === $index); $result = $this->createAssembler($eligibilityChecker)->calculateServicePricing($bookingDto); $insuranceGroup = $this->findServiceGroup($result, 'Reiseversicherungen'); $this->assertNotNull($insuranceGroup, 'Insurance group should exist'); $this->assertCount(1, $insuranceGroup['services'], 'Should have 1 insurance line item'); $this->assertEquals(1, $insuranceGroup['services'][0]['participantCount'], 'Should count only applicant'); } public function testServicePricingWithBulkInsuranceCountsAllParticipants(): void { $insuranceA = $this->createInsurance(1, 'Reise-Rücktritt', 50.0, 'RRV'); $travel = new Travel(); $travel->insurances = [$insuranceA]; $travel->dateFrom = new \DateTimeImmutable('2025-06-01'); $travel->dateTo = new \DateTimeImmutable('2025-06-08'); $participant1 = new ParticipantDto(); $participant1->index = 0; $participant1->insurance = $insuranceA; $participant1->bulkInsuranceBooking = true; $participant2 = new ParticipantDto(); $participant2->index = 1; $participant2->insurance = null; $participant3 = new ParticipantDto(); $participant3->index = 2; $participant3->insurance = null; $bookingDto = new BookingDto($travel, 3); $bookingDto->participants = [$participant1, $participant2, $participant3]; $result = $this->assembler->calculateServicePricing($bookingDto); $insuranceGroup = $this->findServiceGroup($result, 'Reiseversicherungen'); $this->assertNotNull($insuranceGroup, 'Insurance group should exist'); $totalParticipants = array_sum(array_column($insuranceGroup['services'], 'participantCount')); $this->assertEquals(3, $totalParticipants, 'Should count all 3 participants across all tiers'); } // Helpers private function createAssembler(ParticipantEligibilityChecker $eligibilityChecker): BookingPricingAssembler { $roomPricingCalculator = new RoomPricingCalculator(); return new BookingPricingAssembler( $roomPricingCalculator, $eligibilityChecker, new InsuranceManager(), new ParticipantPricingCalculator($roomPricingCalculator), ); } private function createInsurance(int $id, string $label, float $price, string $subType = 'RRV'): Insurance { $insurance = new Insurance(); $insurance->id = (string) $id; $insurance->label = $label; $insurance->price = $price; $insurance->subType = $subType; $insurance->package = false; $insurance->complementary = false; return $insurance; } private function findServiceGroup(array $groups, string $groupName): ?array { foreach ($groups as $group) { if ($group['groupName'] === $groupName) { return $group; } } return null; } }