participantEligibilityService = $this->createMock(ParticipantEligibilityService::class); $this->participantEligibilityService ->method('isParticipantEligible') ->willReturnCallback(fn (): bool => $this->participantEligible); return new MandatoryAdditionalServicesSelectedValidator( $this->participantEligibilityService, new ServiceAgeEvaluator() ); } public function testMissingMandatoryAdditionalServiceCreatesViolation(): void { $participantEditDto = $this->createWrapperWithParticipant( $this->createParticipant(), [ $this->createMandatoryAdditionalService(194695, 'Ortstaxe'), ] ); $this->validator->validate($participantEditDto, new MandatoryAdditionalServicesSelected()); $this->buildViolation('Bitte wähle alle Pflichtleistungen aus.') ->atPath('property.path.participant.additionalServices') ->assertRaised(); } public function testSelectedMandatoryAdditionalServicePassesValidation(): void { $mandatoryService = $this->createMandatoryAdditionalService(194695, 'Ortstaxe'); $participant = $this->createParticipant(); $participant->additionalServices = [$mandatoryService]; $participantEditDto = $this->createWrapperWithParticipant($participant, [$mandatoryService]); $this->validator->validate($participantEditDto, new MandatoryAdditionalServicesSelected()); $this->assertNoViolation(); } public function testAgeEligibleMandatoryServiceMissingCreatesViolation(): void { $adultMandatory = $this->createMandatoryAdditionalService( id: 2, label: 'Adult Mandatory', ageConstraintType: 'absolute_age', ageFrom: 18, ageTo: 99 ); $teenMandatory = $this->createMandatoryAdditionalService( id: 1, label: 'Teen Mandatory', ageConstraintType: 'absolute_age', ageFrom: 12, ageTo: 17 ); $participant = $this->createParticipant(); $participant->additionalServices = [$teenMandatory]; $participant->dateOfBirth = new \DateTimeImmutable('2000-01-01'); $participantEditDto = $this->createWrapperWithParticipant($participant, [$teenMandatory, $adultMandatory]); $this->validator->validate($participantEditDto, new MandatoryAdditionalServicesSelected()); $this->buildViolation('Bitte wähle alle Pflichtleistungen aus.') ->atPath('property.path.participant.additionalServices') ->assertRaised(); } public function testCanceledParticipantSkipsValidation(): void { $participant = $this->createParticipant(); $participant->status = 'S'; $participantEditDto = $this->createWrapperWithParticipant( $participant, [$this->createMandatoryAdditionalService(194695, 'Ortstaxe')] ); $this->validator->validate($participantEditDto, new MandatoryAdditionalServicesSelected()); $this->assertNoViolation(); } public function testIneligibleParticipantSkipsValidation(): void { $this->participantEligible = false; $participantEditDto = $this->createWrapperWithParticipant( $this->createParticipant(), [$this->createMandatoryAdditionalService(194695, 'Ortstaxe')] ); $this->validator->validate($participantEditDto, new MandatoryAdditionalServicesSelected()); $this->assertNoViolation(); } private function createWrapperWithParticipant(ParticipantDto $participant, array $additionalServices): ParticipantEditDto { $travel = new Travel(); $travel->dateFrom = new \DateTimeImmutable('2026-04-11'); $travel->additionalServices = array_reduce( $additionalServices, function (array $carry, Service $service): array { $carry[$service->id] = $service; return $carry; }, [] ); $bookingDto = new BookingDto($travel, 1); $bookingDto->participants = [$participant]; return new ParticipantEditDto( participant: $participant, bookingContext: $bookingDto, ); } private function createParticipant(): ParticipantDto { $participant = new ParticipantDto(); $participant->index = 0; $participant->status = 'F'; $participant->dateOfBirth = new \DateTimeImmutable('2005-01-01'); $participant->additionalServices = []; return $participant; } private function createMandatoryAdditionalService( int $id, string $label, ?string $ageConstraintType = null, ?int $ageFrom = null, ?int $ageTo = null, ): Service { $service = new Service(); $service->id = $id; $service->label = $label; $service->subType = Constants::TOKEN_ADDITIONAL; $service->mandatory = true; $service->ageConstraintType = $ageConstraintType; $service->ageFrom = $ageFrom; $service->ageTo = $ageTo; return $service; } }