diff --git a/src/Form/Model/ParticipantDto.php b/src/Form/Model/ParticipantDto.php index 0992281..f74cc15 100644 --- a/src/Form/Model/ParticipantDto.php +++ b/src/Form/Model/ParticipantDto.php @@ -8,7 +8,7 @@ use App\BusProNet\Model\Service; use App\Validator\Constraints as AppAssert; use Symfony\Component\Validator\Constraints as Assert; -#[AppAssert\Participant(groups: ['booking_edit'])] +#[AppAssert\Participant(groups: ['booking_edit', 'booking_create_step_2'])] class ParticipantDto { public ?int $index = null; @@ -33,7 +33,6 @@ class ParticipantDto #[Assert\NotNull(message: 'Bitte angeben', groups: ['booking_edit', 'booking_create_step_2'])] public ?\DateTimeImmutable $dateOfBirth = null; - #[Assert\NotBlank(message: 'Bitte angeben', groups: ['booking_edit', 'booking_create_step_2'])] #[Assert\Email(message: 'Bitte eine gültige E-Mail Adresse angeben', mode: 'strict', groups: ['booking_edit', 'booking_create_step_2'])] public ?string $email = null; diff --git a/tests/BusProNet/Model/BookingTest.php b/tests/BusProNet/Model/BookingTest.php index d38bb21..e38babc 100644 --- a/tests/BusProNet/Model/BookingTest.php +++ b/tests/BusProNet/Model/BookingTest.php @@ -69,4 +69,4 @@ class BookingTest extends TestCase $result = $booking->getSkiPassForParticipant(0); $this->assertNull($result); } -} \ No newline at end of file +} diff --git a/tests/Validator/Constraints/ApplicantEmailValidatorTest.php b/tests/Validator/Constraints/ApplicantEmailValidatorTest.php new file mode 100644 index 0000000..d9887c0 --- /dev/null +++ b/tests/Validator/Constraints/ApplicantEmailValidatorTest.php @@ -0,0 +1,115 @@ +index = 0; // Applicant + $participant->email = 'test@example.com'; + + $this->validator->validate($participant, new ApplicantEmail()); + + $this->assertNoViolation(); + } + + public function testApplicantWithoutEmailFailsValidation(): void + { + $participant = new ParticipantDto(); + $participant->index = 0; // Applicant + $participant->email = null; + + $this->validator->validate($participant, new ApplicantEmail()); + + $this->buildViolation('Bitte angeben') + ->atPath('property.path.email') + ->assertRaised(); + } + + public function testApplicantWithEmptyEmailFailsValidation(): void + { + $participant = new ParticipantDto(); + $participant->index = 0; // Applicant + $participant->email = ''; + + $this->validator->validate($participant, new ApplicantEmail()); + + $this->buildViolation('Bitte angeben') + ->atPath('property.path.email') + ->assertRaised(); + } + + public function testApplicantWithInvalidEmailFailsValidation(): void + { + $participant = new ParticipantDto(); + $participant->index = 0; // Applicant + $participant->email = 'invalid-email'; + + $this->validator->validate($participant, new ApplicantEmail()); + + $this->buildViolation('Bitte eine gültige E-Mail Adresse angeben') + ->atPath('property.path.email') + ->assertRaised(); + } + + public function testNonApplicantWithoutEmailPassesValidation(): void + { + $participant = new ParticipantDto(); + $participant->index = 1; // Not applicant + $participant->email = null; + + $this->validator->validate($participant, new ApplicantEmail()); + + $this->assertNoViolation(); + } + + public function testNonApplicantWithEmptyEmailPassesValidation(): void + { + $participant = new ParticipantDto(); + $participant->index = 2; // Not applicant + $participant->email = ''; + + $this->validator->validate($participant, new ApplicantEmail()); + + $this->assertNoViolation(); + } + + public function testNonApplicantWithValidEmailPassesValidation(): void + { + $participant = new ParticipantDto(); + $participant->index = 1; // Not applicant + $participant->email = 'participant@example.com'; + + $this->validator->validate($participant, new ApplicantEmail()); + + $this->assertNoViolation(); + } + + public function testNonApplicantWithInvalidEmailStillValidatesFormat(): void + { + $participant = new ParticipantDto(); + $participant->index = 1; // Not applicant + $participant->email = 'invalid-email'; + + $this->validator->validate($participant, new ApplicantEmail()); + + // Non-applicants don't need email, but if provided it should be valid + // However, our validator only checks format for applicants + // The Email constraint on the property handles format validation for all participants + $this->assertNoViolation(); + } +} diff --git a/tests/Validator/Constraints/ParticipantValidatorTest.php b/tests/Validator/Constraints/ParticipantValidatorTest.php new file mode 100644 index 0000000..6064a3a --- /dev/null +++ b/tests/Validator/Constraints/ParticipantValidatorTest.php @@ -0,0 +1,161 @@ +createValidParticipant(); + $participant->rentals = []; // No rentals, so body measurements not required + $participant->height = null; + $participant->weight = null; + $participant->shoeSize = null; + + $this->validator->validate($participant, new Participant()); + + $this->assertNoViolation(); + } + + public function testParticipantWithRentalsButNoBodyMeasurementsFailsValidation(): void + { + $participant = $this->createValidParticipant(); + $participant->rentals = [$this->createMockService()]; + $participant->height = null; + $participant->weight = null; + $participant->shoeSize = null; + + $this->validator->validate($participant, new Participant()); + + $this->buildViolation('Bitte angeben wegen Leihmaterial') + ->atPath('property.path.height') + ->buildNextViolation('Bitte angeben wegen Leihmaterial') + ->atPath('property.path.shoeSize') + ->buildNextViolation('Bitte angeben wegen Leihmaterial') + ->atPath('property.path.weight') + ->assertRaised(); + } + + public function testParticipantWithRentalsAndPartialBodyMeasurementsFailsValidation(): void + { + $participant = $this->createValidParticipant(); + $participant->rentals = [$this->createMockService()]; + $participant->height = '175'; + $participant->weight = null; // Missing + $participant->shoeSize = '42'; + + $this->validator->validate($participant, new Participant()); + + $this->buildViolation('Bitte angeben wegen Leihmaterial') + ->atPath('property.path.weight') + ->assertRaised(); + } + + public function testParticipantWithRentalsAndCompleteBodyMeasurementsPassesValidation(): void + { + $participant = $this->createValidParticipant(); + $participant->rentals = [$this->createMockService()]; + $participant->height = '175'; + $participant->weight = '70'; + $participant->shoeSize = '42'; + + $this->validator->validate($participant, new Participant()); + + $this->assertNoViolation(); + } + + public function testCanceledParticipantSkipsTransportationValidation(): void + { + $participant = $this->createValidParticipant(); + $participant->status = 'S'; // Canceled + $participant->transportationOutbound = null; + $participant->transportationInbound = null; + + $this->validator->validate($participant, new Participant()); + + $this->assertNoViolation(); + } + + public function testActiveParticipantWithoutTransportationFailsValidation(): void + { + $participant = $this->createValidParticipant(); + $participant->status = 'F'; // Active + $participant->transportationOutbound = null; + $participant->transportationInbound = null; + + $this->validator->validate($participant, new Participant()); + + $this->buildViolation('Bitte angeben') + ->atPath('property.path.transportationOutbound') + ->buildNextViolation('Bitte angeben') + ->atPath('property.path.transportationInbound') + ->assertRaised(); + } + + public function testParticipantWithBusTransportationButNoPickupFailsValidation(): void + { + $participant = $this->createValidParticipant(); + + $busService = new Service(); + $busService->subType = 'BUS'; + $participant->transportationOutbound = $busService; + $participant->pickupOutbound = null; + + $this->validator->validate($participant, new Participant()); + + $this->buildViolation('Bitte auswählen') + ->atPath('property.path.pickupOutbound') + ->assertRaised(); + } + + public function testParticipantWithNonBusTransportationPassesPickupValidation(): void + { + $participant = $this->createValidParticipant(); + + $trainService = new Service(); + $trainService->subType = 'TRAIN'; + $participant->transportationOutbound = $trainService; + $participant->pickupOutbound = null; // Not required for non-bus + + $this->validator->validate($participant, new Participant()); + + $this->assertNoViolation(); + } + + private function createValidParticipant(): ParticipantDto + { + $participant = new ParticipantDto(); + + // Set valid transportation to avoid transportation validation errors + $participant->transportationOutbound = $this->createMockService(); + $participant->transportationInbound = $this->createMockService(); + + // Set status to active + $participant->status = 'F'; + + return $participant; + } + + private function createMockService(): Service + { + $service = new Service(); + $service->id = 123; + $service->label = 'Test Service'; + $service->subType = 'TRAIN'; // Non-bus to avoid pickup validation + + return $service; + } +}