feat: unique email addresses of participants unless child, cleanup
addresses #869axbn21
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Form\Model;
|
||||
|
||||
use App\BusProNet\Model\Travel;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
class BookingDtoTest extends TestCase
|
||||
{
|
||||
private ValidatorInterface $validator;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->validator = Validation::createValidatorBuilder()
|
||||
->enableAttributeMapping()
|
||||
->getValidator();
|
||||
}
|
||||
|
||||
public function testEmailUniquenessValidationWithUniqueAdultEmailsPasses(): void
|
||||
{
|
||||
$bookingDto = $this->createBookingDtoWithParticipants([
|
||||
$this->createAdultParticipant('[email protected]'),
|
||||
$this->createAdultParticipant('[email protected]'),
|
||||
$this->createAdultParticipant('[email protected]'),
|
||||
]);
|
||||
|
||||
$violations = $this->validator->validate($bookingDto, null, ['booking_create_step_2']);
|
||||
|
||||
$this->assertCount(0, $violations);
|
||||
}
|
||||
|
||||
public function testEmailUniquenessValidationWithTwoAdultsSameEmailFails(): void
|
||||
{
|
||||
$bookingDto = $this->createBookingDtoWithParticipants([
|
||||
$this->createAdultParticipant('[email protected]'),
|
||||
$this->createAdultParticipant('[email protected]'),
|
||||
]);
|
||||
|
||||
$violations = $this->validator->validate($bookingDto, null, ['booking_create_step_2']);
|
||||
|
||||
// Should have 2 violations (one for each adult with duplicate email)
|
||||
$this->assertCount(2, $violations);
|
||||
|
||||
// Check that violations are at the correct paths
|
||||
$violationPaths = [];
|
||||
foreach ($violations as $violation) {
|
||||
$violationPaths[] = $violation->getPropertyPath();
|
||||
}
|
||||
|
||||
$this->assertContains('participants[0].email', $violationPaths);
|
||||
$this->assertContains('participants[1].email', $violationPaths);
|
||||
|
||||
// Check violation message
|
||||
$this->assertSame(
|
||||
'Diese E-Mail Adresse wird bereits von einem anderen erwachsenen Teilnehmer verwendet',
|
||||
$violations->get(0)->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
public function testEmailUniquenessValidationWithThreeAdultsSameEmailFails(): void
|
||||
{
|
||||
$bookingDto = $this->createBookingDtoWithParticipants([
|
||||
$this->createAdultParticipant('[email protected]'),
|
||||
$this->createAdultParticipant('[email protected]'),
|
||||
$this->createAdultParticipant('[email protected]'),
|
||||
]);
|
||||
|
||||
$violations = $this->validator->validate($bookingDto, null, ['booking_create_step_2']);
|
||||
|
||||
// Should have 3 violations (one for each adult with duplicate email)
|
||||
$this->assertCount(3, $violations);
|
||||
}
|
||||
|
||||
public function testEmailUniquenessValidationWithAdultAndChildSameEmailPasses(): void
|
||||
{
|
||||
$bookingDto = $this->createBookingDtoWithParticipants([
|
||||
$this->createAdultParticipant('[email protected]'),
|
||||
$this->createChildParticipant('[email protected]'),
|
||||
]);
|
||||
|
||||
$violations = $this->validator->validate($bookingDto, null, ['booking_create_step_2']);
|
||||
|
||||
$this->assertCount(0, $violations);
|
||||
}
|
||||
|
||||
public function testEmailUniquenessValidationWithTwoChildrenSameEmailPasses(): void
|
||||
{
|
||||
$bookingDto = $this->createBookingDtoWithParticipants([
|
||||
$this->createChildParticipant('[email protected]'),
|
||||
$this->createChildParticipant('[email protected]'),
|
||||
]);
|
||||
|
||||
$violations = $this->validator->validate($bookingDto, null, ['booking_create_step_2']);
|
||||
|
||||
$this->assertCount(0, $violations);
|
||||
}
|
||||
|
||||
public function testEmailUniquenessValidationWithNullBirthDateTreatedAsAdult(): void
|
||||
{
|
||||
$participant1 = new ParticipantDto();
|
||||
$participant1->firstName = 'John';
|
||||
$participant1->lastName = 'Doe';
|
||||
$participant1->dateOfBirth = null; // Unknown age - treated as adult
|
||||
$participant1->email = '[email protected]';
|
||||
|
||||
$participant2 = $this->createAdultParticipant('[email protected]');
|
||||
|
||||
$bookingDto = $this->createBookingDtoWithParticipants([$participant1, $participant2]);
|
||||
|
||||
$violations = $this->validator->validate($bookingDto, null, ['booking_create_step_2']);
|
||||
|
||||
// Should have violations for duplicate emails (both treated as adults)
|
||||
$emailViolations = [];
|
||||
foreach ($violations as $violation) {
|
||||
if (str_contains($violation->getPropertyPath(), '.email')) {
|
||||
$emailViolations[] = $violation;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertCount(2, $emailViolations);
|
||||
}
|
||||
|
||||
public function testEmailUniquenessValidationWithNullEmailsSkipped(): void
|
||||
{
|
||||
// Create participants with explicitly null emails (not using createAdultParticipant helper)
|
||||
$participant1 = $this->createValidParticipant(new \DateTimeImmutable('1990-01-01'), '[email protected]');
|
||||
$participant1->email = null; // Set to null explicitly
|
||||
|
||||
$participant2 = $this->createValidParticipant(new \DateTimeImmutable('1990-01-01'), '[email protected]');
|
||||
$participant2->email = null; // Set to null explicitly
|
||||
|
||||
$bookingDto = $this->createBookingDtoWithParticipants([$participant1, $participant2]);
|
||||
|
||||
$violations = $this->validator->validate($bookingDto, null, ['booking_create_step_2']);
|
||||
|
||||
// Should not have email uniqueness violations (other validations may fail for null email)
|
||||
foreach ($violations as $violation) {
|
||||
$this->assertStringNotContainsString(
|
||||
'wird bereits von einem anderen erwachsenen Teilnehmer verwendet',
|
||||
$violation->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testEmailUniquenessValidationWithEmptyEmailsSkipped(): void
|
||||
{
|
||||
$participant1 = $this->createAdultParticipant('');
|
||||
$participant2 = $this->createAdultParticipant('');
|
||||
|
||||
$bookingDto = $this->createBookingDtoWithParticipants([$participant1, $participant2]);
|
||||
|
||||
$violations = $this->validator->validate($bookingDto, null, ['booking_create_step_2']);
|
||||
|
||||
// Should not have email uniqueness violations (other validations may fail)
|
||||
foreach ($violations as $violation) {
|
||||
$this->assertStringNotContainsString(
|
||||
'wird bereits von einem anderen erwachsenen Teilnehmer verwendet',
|
||||
$violation->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testEmailUniquenessValidationCaseInsensitive(): void
|
||||
{
|
||||
$bookingDto = $this->createBookingDtoWithParticipants([
|
||||
$this->createAdultParticipant('[email protected]'),
|
||||
$this->createAdultParticipant('[email protected]'),
|
||||
]);
|
||||
|
||||
$violations = $this->validator->validate($bookingDto, null, ['booking_create_step_2']);
|
||||
|
||||
// Should have 2 violations (case-insensitive comparison)
|
||||
$emailViolations = [];
|
||||
foreach ($violations as $violation) {
|
||||
if (str_contains($violation->getPropertyPath(), '.email')) {
|
||||
$emailViolations[] = $violation;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertCount(2, $emailViolations);
|
||||
}
|
||||
|
||||
public function testEmailUniquenessValidationWithWhitespaceNormalization(): void
|
||||
{
|
||||
// Create participants with trimmed emails to avoid @Assert\Email strict mode validation failures
|
||||
$participant1 = $this->createValidParticipant(new \DateTimeImmutable('1990-01-01'), '[email protected]');
|
||||
$participant1->email = ' [email protected] '; // Set whitespace email explicitly after creation
|
||||
|
||||
$participant2 = $this->createAdultParticipant('[email protected]');
|
||||
|
||||
$bookingDto = $this->createBookingDtoWithParticipants([$participant1, $participant2]);
|
||||
|
||||
$violations = $this->validator->validate($bookingDto, null, ['booking_create_step_2']);
|
||||
|
||||
// Should have 2 violations for email uniqueness (whitespace trimmed during comparison)
|
||||
// Note: May also have email format violation for the whitespace email
|
||||
$uniquenessViolations = [];
|
||||
foreach ($violations as $violation) {
|
||||
if (str_contains($violation->getMessage(), 'wird bereits von einem anderen erwachsenen Teilnehmer verwendet')) {
|
||||
$uniquenessViolations[] = $violation;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertCount(2, $uniquenessViolations);
|
||||
}
|
||||
|
||||
public function testEmailUniquenessValidationInEditMode(): void
|
||||
{
|
||||
$bookingDto = $this->createBookingDtoWithParticipants([
|
||||
$this->createAdultParticipant('[email protected]'),
|
||||
$this->createAdultParticipant('[email protected]'),
|
||||
]);
|
||||
|
||||
$violations = $this->validator->validate($bookingDto, null, ['booking_edit']);
|
||||
|
||||
// Should have 2 violations in edit mode as well
|
||||
$this->assertCount(2, $violations);
|
||||
}
|
||||
|
||||
public function testEmailUniquenessValidationMixedScenario(): void
|
||||
{
|
||||
$bookingDto = $this->createBookingDtoWithParticipants([
|
||||
$this->createAdultParticipant('[email protected]'), // Unique - OK
|
||||
$this->createAdultParticipant('[email protected]'), // Duplicate - ERROR
|
||||
$this->createChildParticipant('[email protected]'), // Child with duplicate - OK
|
||||
$this->createAdultParticipant('[email protected]'), // Duplicate - ERROR
|
||||
$this->createChildParticipant('[email protected]'), // Unique child - OK
|
||||
$this->createAdultParticipant('[email protected]'), // Unique - OK
|
||||
]);
|
||||
|
||||
$violations = $this->validator->validate($bookingDto, null, ['booking_create_step_2']);
|
||||
|
||||
// Should have 2 violations (participants at index 1 and 3)
|
||||
$emailViolations = [];
|
||||
foreach ($violations as $violation) {
|
||||
if (str_contains($violation->getPropertyPath(), '.email')) {
|
||||
$emailViolations[] = $violation;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertCount(2, $emailViolations);
|
||||
|
||||
// Check that violations are at the correct paths
|
||||
$violationPaths = [];
|
||||
foreach ($emailViolations as $violation) {
|
||||
$violationPaths[] = $violation->getPropertyPath();
|
||||
}
|
||||
|
||||
$this->assertContains('participants[1].email', $violationPaths);
|
||||
$this->assertContains('participants[3].email', $violationPaths);
|
||||
}
|
||||
|
||||
private function createBookingDtoWithParticipants(array $participants): BookingDto
|
||||
{
|
||||
$travel = new Travel();
|
||||
$travel->dateFrom = new \DateTimeImmutable('2025-06-01');
|
||||
$travel->dateTo = new \DateTimeImmutable('2025-06-08');
|
||||
|
||||
$bookingDto = new BookingDto($travel, 1);
|
||||
$bookingDto->participants = $participants;
|
||||
|
||||
return $bookingDto;
|
||||
}
|
||||
|
||||
private function createAdultParticipant(?string $email): ParticipantDto
|
||||
{
|
||||
return $this->createValidParticipant(
|
||||
new \DateTimeImmutable('1990-01-01'), // Adult (over 18)
|
||||
$email
|
||||
);
|
||||
}
|
||||
|
||||
private function createChildParticipant(?string $email): ParticipantDto
|
||||
{
|
||||
return $this->createValidParticipant(
|
||||
new \DateTimeImmutable('2015-01-01'), // Child (under 16)
|
||||
$email
|
||||
);
|
||||
}
|
||||
|
||||
private function createValidParticipant(\DateTimeImmutable $dateOfBirth, ?string $email): ParticipantDto
|
||||
{
|
||||
$participant = new ParticipantDto();
|
||||
$participant->firstName = 'John';
|
||||
$participant->lastName = 'Doe';
|
||||
$participant->dateOfBirth = $dateOfBirth;
|
||||
$participant->email = $email ?? '[email protected]';
|
||||
|
||||
// Set required fields for booking_create_step_2 validation group
|
||||
$participant->assignedRoomId = 1;
|
||||
$participant->skiPass = $this->createMockService();
|
||||
$participant->transportationOutbound = $this->createMockService();
|
||||
$participant->transportationInbound = $this->createMockService();
|
||||
|
||||
return $participant;
|
||||
}
|
||||
|
||||
private function createMockService(): \App\BusProNet\Model\Service
|
||||
{
|
||||
$service = new \App\BusProNet\Model\Service();
|
||||
$service->id = 1;
|
||||
$service->label = 'Test Service';
|
||||
$service->subType = 'TEST';
|
||||
$service->price = 0.0;
|
||||
|
||||
return $service;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user