priceCalculator = $this->createStub(BookingPriceCalculator::class); $this->validator = $this->createStub(ValidatorInterface::class); } private function service(): ParticipantCardAssembler { return $this->service ??= new ParticipantCardAssembler( $this->priceCalculator, $this->validator ); } public function testGetCardDataWithFullParticipantData(): void { $this->priceCalculator = $this->createMock(BookingPriceCalculator::class); // Create test room $room = new Room(); $room->id = 1; $room->label = 'Doppelzimmer'; $room->price = 100.0; $travel = new Travel(); $travel->rooms = [$room]; // Create participant with full data $participant = new ParticipantDto(); $participant->firstName = 'Max'; $participant->lastName = 'Mustermann'; $participant->assignedRoomId = 1; $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; // Mock price calculation $this->priceCalculator ->expects($this->once()) ->method('calculateAllParticipantIndividualPrices') ->with($bookingDto) ->willReturn([450.50]); $result = $this->service()->getCardData($bookingDto, 0); $this->assertSame('Max Mustermann', $result->name); $this->assertSame('Doppelzimmer', $result->roomName); $this->assertSame(450.50, $result->price->amount); $this->assertFalse($result->price->showDash); } public function testGetCardDataWithPartialName(): void { $travel = new Travel(); $travel->rooms = []; // Participant with only first name $participant = new ParticipantDto(); $participant->firstName = 'Max'; $participant->lastName = null; $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; $this->priceCalculator ->method('calculateAllParticipantIndividualPrices') ->willReturn([0.0]); $result = $this->service()->getCardData($bookingDto, 0); $this->assertSame('Max', $result->name); } public function testGetCardDataWithNoName(): void { $travel = new Travel(); $travel->rooms = []; // Participant without name $participant = new ParticipantDto(); $participant->firstName = null; $participant->lastName = null; $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; $this->priceCalculator ->method('calculateAllParticipantIndividualPrices') ->willReturn([0.0]); $result = $this->service()->getCardData($bookingDto, 0); $this->assertSame('Anmelder:in', $result->name); } public function testGetCardDataWithEmptyName(): void { $travel = new Travel(); $travel->rooms = []; // Participant with empty strings for names $participant = new ParticipantDto(); $participant->firstName = ' '; $participant->lastName = ' '; $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; $this->priceCalculator ->method('calculateAllParticipantIndividualPrices') ->willReturn([0.0]); $result = $this->service()->getCardData($bookingDto, 0); $this->assertSame('Anmelder:in', $result->name); } public function testGetCardDataWithNoRoomAssignment(): void { $travel = new Travel(); $travel->rooms = []; $participant = new ParticipantDto(); $participant->firstName = 'Max'; $participant->lastName = 'Mustermann'; $participant->assignedRoomId = null; $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; $this->priceCalculator ->method('calculateAllParticipantIndividualPrices') ->willReturn([0.0]); $result = $this->service()->getCardData($bookingDto, 0); $this->assertSame('Kein Zimmer zugewiesen', $result->roomName); } public function testGetCardDataWithUnknownRoom(): void { $travel = new Travel(); $travel->rooms = []; $participant = new ParticipantDto(); $participant->firstName = 'Max'; $participant->lastName = 'Mustermann'; $participant->assignedRoomId = 999; // Non-existent room $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; $this->priceCalculator ->method('calculateAllParticipantIndividualPrices') ->willReturn([0.0]); $result = $this->service()->getCardData($bookingDto, 0); $this->assertSame('Unbekanntes Zimmer', $result->roomName); } public function testGetCardDataWithZeroPriceAndNoRoom(): void { $travel = new Travel(); $travel->rooms = []; $participant = new ParticipantDto(); $participant->firstName = 'Max'; $participant->lastName = 'Mustermann'; $participant->assignedRoomId = null; // No room assigned $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; $this->priceCalculator ->method('calculateAllParticipantIndividualPrices') ->willReturn([0.0]); $result = $this->service()->getCardData($bookingDto, 0); // When no room assigned and price is zero, display dash (incomplete configuration) $this->assertNull($result->price->amount); $this->assertTrue($result->price->showDash); } public function testGetCardDataWithZeroPriceButRoomAssigned(): void { // Create test room $room = new Room(); $room->id = 1; $room->label = 'Doppelzimmer'; $room->price = 0.0; $travel = new Travel(); $travel->rooms = [$room]; $participant = new ParticipantDto(); $participant->firstName = 'Max'; $participant->lastName = 'Mustermann'; $participant->assignedRoomId = 1; // Room is assigned $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; $this->priceCalculator ->method('calculateAllParticipantIndividualPrices') ->willReturn([0.0]); $result = $this->service()->getCardData($bookingDto, 0); // When room is assigned but price is zero, display formatted zero price $this->assertSame(0.0, $result->price->amount); $this->assertFalse($result->price->showDash); } public function testGetCardDataWithInvalidIndex(): void { $travel = new Travel(); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = []; $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Participant at index 0 does not exist'); $this->service()->getCardData($bookingDto, 0); } public function testGetAllCardsDataWithMultipleParticipants(): void { $this->priceCalculator = $this->createMock(BookingPriceCalculator::class); // Create test rooms $room1 = new Room(); $room1->id = 1; $room1->label = 'Einzelzimmer'; $room2 = new Room(); $room2->id = 2; $room2->label = 'Doppelzimmer'; $travel = new Travel(); $travel->rooms = [$room1, $room2]; // Create participants $participant1 = new ParticipantDto(); $participant1->firstName = 'Max'; $participant1->lastName = 'Mustermann'; $participant1->assignedRoomId = 1; $participant2 = new ParticipantDto(); $participant2->firstName = 'Anna'; $participant2->lastName = 'Schmidt'; $participant2->assignedRoomId = 2; $participant3 = new ParticipantDto(); $participant3->firstName = null; $participant3->lastName = null; $participant3->assignedRoomId = 2; $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant1, $participant2, $participant3]; // Mock price calculation $this->priceCalculator ->expects($this->once()) ->method('calculateAllParticipantIndividualPrices') ->with($bookingDto) ->willReturn([450.0, 500.0, 480.0]); $result = $this->service()->getAllCardsData($bookingDto); $this->assertCount(3, $result); // First participant $this->assertSame('Max Mustermann', $result[0]->name); $this->assertSame('Einzelzimmer', $result[0]->roomName); $this->assertSame(450.0, $result[0]->price->amount); $this->assertFalse($result[0]->price->showDash); // Second participant $this->assertSame('Anna Schmidt', $result[1]->name); $this->assertSame('Doppelzimmer', $result[1]->roomName); $this->assertSame(500.0, $result[1]->price->amount); $this->assertFalse($result[1]->price->showDash); // Third participant (no name) $this->assertSame('Teilnehmer:in', $result[2]->name); $this->assertSame('Doppelzimmer', $result[2]->roomName); $this->assertSame(480.0, $result[2]->price->amount); $this->assertFalse($result[2]->price->showDash); } public function testGetAllCardsDataUsesCanceledSurchargesBeforePrecomputedActivePrices(): void { $this->priceCalculator = $this->createMock(BookingPriceCalculator::class); $room = new Room(); $room->id = 1; $room->label = 'Doppelzimmer'; $travel = new Travel(); $travel->rooms = [$room]; $activeParticipant = new ParticipantDto(); $activeParticipant->firstName = 'Max'; $activeParticipant->lastName = 'Mustermann'; $activeParticipant->assignedRoomId = 1; $canceledParticipant = new ParticipantDto(); $canceledParticipant->firstName = 'Anna'; $canceledParticipant->lastName = 'Schmidt'; $canceledParticipant->assignedRoomId = 1; $surcharge = new Surcharge(); $surcharge->mapping = [1]; $surcharge->individualPrice = [1 => 75.0]; $booking = new Booking(); $booking->participantsStatus = [0 => 'A', 1 => 'S']; $booking->surcharges = [$surcharge]; $bookingDto = new BookingDto($travel, 1); $bookingDto->booking = $booking; $bookingDto->participants = [$activeParticipant, $canceledParticipant]; $this->priceCalculator ->expects($this->once()) ->method('calculateAllParticipantIndividualPrices') ->with($bookingDto) ->willReturn([0 => 450.0, 1 => 999.0]); $result = $this->service()->getAllCardsData($bookingDto); $this->assertSame(450.0, $result[0]->price->amount); $this->assertSame(75.0, $result[1]->price->amount); $this->assertFalse($result[1]->price->showDash); } public function testGetAllCardsDataWithEmptyParticipants(): void { $travel = new Travel(); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = []; $result = $this->service()->getAllCardsData($bookingDto); $this->assertEmpty($result); } public function testPriceFormattingWithLargeAmount(): void { $travel = new Travel(); $travel->rooms = []; $participant = new ParticipantDto(); $participant->firstName = 'Max'; $participant->lastName = 'Mustermann'; $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; $this->priceCalculator ->method('calculateAllParticipantIndividualPrices') ->willReturn([1234.56]); $result = $this->service()->getCardData($bookingDto, 0); $this->assertSame(1234.56, $result->price->amount); $this->assertFalse($result->price->showDash); } public function testFallbackNameIndexingIsOneBasedNotZeroBased(): void { $travel = new Travel(); $travel->rooms = []; $participant1 = new ParticipantDto(); $participant2 = new ParticipantDto(); $participant3 = new ParticipantDto(); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant1, $participant2, $participant3]; $this->priceCalculator ->method('calculateAllParticipantIndividualPrices') ->willReturn([0.0, 0.0, 0.0]); $result1 = $this->service()->getCardData($bookingDto, 0); $result2 = $this->service()->getCardData($bookingDto, 1); $result3 = $this->service()->getCardData($bookingDto, 2); $this->assertSame('Anmelder:in', $result1->name); $this->assertSame('Teilnehmer:in', $result2->name); $this->assertSame('Teilnehmer:in', $result3->name); } public function testGetCardDataWithValidationReturnsValidCard(): void { $this->validator = $this->createMock(ValidatorInterface::class); $room = new Room(); $room->id = 1; $room->label = 'Doppelzimmer'; $travel = new Travel(); $travel->rooms = [$room]; $participant = new ParticipantDto(); $participant->firstName = 'Max'; $participant->lastName = 'Mustermann'; $participant->assignedRoomId = 1; $participant->email = 'max@example.com'; $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; $this->priceCalculator ->method('calculateAllParticipantIndividualPrices') ->willReturn([450.50]); // Mock validator to return no violations (valid) $violations = $this->createMock(ConstraintViolationListInterface::class); $violations->expects($this->once()) ->method('count') ->willReturn(0); $this->validator ->expects($this->once()) ->method('validate') ->willReturn($violations); $result = $this->service()->getCardDataWithValidation($bookingDto, 0); $this->assertSame('Max Mustermann', $result->name); $this->assertSame('Doppelzimmer', $result->roomName); $this->assertSame(450.50, $result->price->amount); $this->assertFalse($result->price->showDash); $this->assertTrue($result->isValid); $this->assertEmpty($result->errorMessages); } public function testGetCardDataWithValidationReturnsInvalidCard(): void { $this->validator = $this->createMock(ValidatorInterface::class); $travel = new Travel(); $travel->rooms = []; $participant = new ParticipantDto(); $participant->firstName = 'Max'; $participant->lastName = 'Mustermann'; $participant->email = 'duplicate@example.com'; $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; $this->priceCalculator ->method('calculateAllParticipantIndividualPrices') ->willReturn([0.0]); // Mock validator to return violations (invalid) $violation = $this->createMock(ConstraintViolationInterface::class); $violation->expects($this->once()) ->method('getMessage') ->willReturn('Diese E-Mail Adresse wird bereits von einem anderen Teilnehmer verwendet'); // Use ConstraintViolationList directly instead of mocking $violations = new ConstraintViolationList([$violation]); $this->validator ->expects($this->once()) ->method('validate') ->willReturn($violations); $result = $this->service()->getCardDataWithValidation($bookingDto, 0); $this->assertSame('Max Mustermann', $result->name); $this->assertFalse($result->isValid); $this->assertCount(1, $result->errorMessages); $this->assertSame('Diese E-Mail Adresse wird bereits von einem anderen Teilnehmer verwendet', $result->errorMessages[0]); } public function testGetAllCardsDataWithValidationReturnsAllCards(): void { $this->priceCalculator = $this->createMock(BookingPriceCalculator::class); $this->validator = $this->createMock(ValidatorInterface::class); $room = new Room(); $room->id = 1; $room->label = 'Doppelzimmer'; $travel = new Travel(); $travel->rooms = [$room]; $participant1 = new ParticipantDto(); $participant1->firstName = 'Max'; $participant1->lastName = 'Mustermann'; $participant1->assignedRoomId = 1; $participant1->email = 'max@example.com'; $participant2 = new ParticipantDto(); $participant2->firstName = 'Anna'; $participant2->lastName = 'Schmidt'; $participant2->assignedRoomId = 1; $participant2->email = 'anna@example.com'; $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant1, $participant2]; $this->priceCalculator ->expects($this->once()) ->method('calculateAllParticipantIndividualPrices') ->willReturn([450.0, 500.0]); // Mock validator to return no violations for both participants $violations = $this->createMock(ConstraintViolationListInterface::class); $violations->expects($this->exactly(2)) ->method('count') ->willReturn(0); $this->validator ->expects($this->exactly(2)) ->method('validate') ->willReturn($violations); $result = $this->service()->getAllCardsDataWithValidation($bookingDto); $this->assertCount(2, $result); $this->assertTrue($result[0]->isValid); $this->assertTrue($result[1]->isValid); $this->assertSame('Max Mustermann', $result[0]->name); $this->assertSame('Anna Schmidt', $result[1]->name); } public function testGetCardDataWithValidationCanForceStrictRequiredInEditMode(): void { $this->validator = $this->createMock(ValidatorInterface::class); $travel = new Travel(); $travel->rooms = []; $participant = new ParticipantDto(); $participant->index = 0; $participant->firstName = null; $participant->lastName = 'Mustermann'; $participant->email = 'max@example.com'; $bookingDto = new BookingDto($travel, 1); $bookingDto->agencyCode = AgencyLoader::INTERNAL_AGENCY_CODE; $bookingDto->booking = new Booking(); $bookingDto->participants = [$participant]; $this->priceCalculator ->method('calculateAllParticipantIndividualPrices') ->willReturn([0.0]); $violation = $this->createMock(ConstraintViolationInterface::class); $violation->expects($this->once()) ->method('getMessage') ->willReturn('Bitte angeben'); $violations = new ConstraintViolationList([$violation]); $this->validator ->expects($this->once()) ->method('validate') ->with( $this->isInstanceOf(ParticipantEditDto::class), null, ['booking_edit', 'strict_required'] ) ->willReturn($violations); $result = $this->service()->getCardDataWithValidation($bookingDto, 0, true); $this->assertFalse($result->isValid); $this->assertSame(['Bitte angeben'], $result->errorMessages); } }