Files
myep/tests/Form/Model/BookingDtoTest.php
T
2026-10-16 17:49:04 +02:00

372 lines
13 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Form\Model;
use App\BusProNet\Model\Address;
use App\BusProNet\Model\Booking;
use App\BusProNet\Model\PersonalData;
use App\BusProNet\Model\Travel;
use App\BusProNet\XmlLoader\AgencyLoader;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Validation;
/**
* Tests for BookingDto validation and business logic.
*
* Email uniqueness validation tests have been moved to ParticipantEditDtoTest
* as part of the refactoring to use the wrapper DTO pattern.
*/
class BookingDtoTest extends TestCase
{
public function testBookingDtoCanBeInstantiated(): void
{
$travel = new Travel();
$travel->dateFrom = new \DateTimeImmutable('2025-06-01');
$travel->dateTo = new \DateTimeImmutable('2025-06-08');
$bookingDto = new BookingDto($travel, 1);
$this->assertInstanceOf(BookingDto::class, $bookingDto);
$this->assertSame($travel, $bookingDto->travel);
$this->assertSame(BookingDto::MODE_CREATE, $bookingDto->getMode());
}
public function testUnserializeHandlesIntTravelId(): void
{
$travel = new Travel();
$travel->id = 42;
$travel->dateFrom = new \DateTimeImmutable('2025-06-01');
$travel->dateTo = new \DateTimeImmutable('2025-06-08');
$dto = new BookingDto($travel, 1);
$serialized = serialize($dto);
$restored = unserialize($serialized);
$this->assertInstanceOf(BookingDto::class, $restored);
$this->assertSame(42, $restored->travel->id);
}
public function testUnserializeHandlesLegacyTravelObject(): void
{
$travel = new Travel();
$travel->id = 42;
$travel->dateFrom = new \DateTimeImmutable('2025-06-01');
$travel->dateTo = new \DateTimeImmutable('2025-06-08');
// Simulate old session format where 'travel' is a full Travel object
$data = [
'travel' => $travel,
'hotelId' => 1,
];
$dto = new BookingDto(new Travel(), 1);
$dto->__unserialize($data);
$this->assertSame($travel, $dto->travel);
$this->assertSame(42, $dto->travel->id);
}
public function testUnserializeNormalizesLegacyBodyDimensionsOnParticipants(): void
{
$travel = new Travel();
$travel->id = 42;
$travel->dateFrom = new \DateTimeImmutable('2025-06-01');
$travel->dateTo = new \DateTimeImmutable('2025-06-08');
$participant = new ParticipantDto();
$participant->height = '149-157';
$participant->weight = '75';
$participant->shoeSize = '043';
$dto = new BookingDto($travel, 1);
$dto->participants = [$participant];
$restored = unserialize(serialize($dto));
$this->assertInstanceOf(BookingDto::class, $restored);
$this->assertNull($restored->participants[0]->height);
$this->assertSame('75', $restored->participants[0]->weight);
$this->assertSame('43', $restored->participants[0]->shoeSize);
}
public function testUnserializeNormalizesLegacyParticipantStringsOnLoad(): void
{
$travel = new Travel();
$travel->id = 42;
$travel->dateFrom = new \DateTimeImmutable('2025-06-01');
$travel->dateTo = new \DateTimeImmutable('2025-06-08');
$participant = new ParticipantDto();
$participant->firstName = ' Max ';
$participant->lastName = ' Mustermann ';
$participant->email = ' [email protected] ';
$participant->remarksRoom = ' ';
$participant->licensePlate = ' B-AB 123 ';
$participant->purchaseVoucherCode = ' ABC-123 ';
$participant->promoVoucherCode = ' PROMO ';
$participant->address = new Address();
$participant->address->street = ' Test Street 1 ';
$participant->address->postCode = ' 12345 ';
$participant->address->city = ' Test City ';
$participant->address->country = ' DE ';
$dto = new BookingDto($travel, 1);
$dto->participants = [$participant];
$restored = unserialize(serialize($dto));
$this->assertInstanceOf(BookingDto::class, $restored);
$this->assertSame('Max', $restored->participants[0]->firstName);
$this->assertSame('Mustermann', $restored->participants[0]->lastName);
$this->assertSame('[email protected]', $restored->participants[0]->email);
$this->assertNull($restored->participants[0]->remarksRoom);
$this->assertSame('B-AB 123', $restored->participants[0]->licensePlate);
$this->assertSame('ABC-123', $restored->participants[0]->purchaseVoucherCode);
$this->assertSame('PROMO', $restored->participants[0]->promoVoucherCode);
$this->assertSame('Test Street 1', $restored->participants[0]->address->street);
$this->assertSame('12345', $restored->participants[0]->address->postCode);
$this->assertSame('Test City', $restored->participants[0]->address->city);
$this->assertSame('DE', $restored->participants[0]->address->country);
}
public function testFirstParticipantIsTheApplicantByDefaultInCreateMode(): void
{
$dto = $this->createCreateModeDto();
$this->assertTrue($dto->isFirstParticipantApplicant());
}
public function testFirstParticipantIsNotTheApplicantWhenTheApplicantDoesNotTravel(): void
{
$dto = $this->createCreateModeDto();
$dto->applicantTravels = false;
$this->assertFalse($dto->isFirstParticipantApplicant());
}
public function testInternalAgencyBookingNeverHasTheFirstParticipantAsApplicant(): void
{
$dto = $this->createCreateModeDto();
$dto->agencyCode = AgencyLoader::INTERNAL_AGENCY_CODE;
$this->assertFalse(
$dto->isFirstParticipantApplicant(),
'Their applicant is a staff member who never travels'
);
}
public function testEditModeDerivesTheSplitFromPersonIds(): void
{
$dto = $this->createEditModeDto(applicantPersonId: 4711, firstParticipantPersonId: 4711);
$this->assertTrue($dto->isFirstParticipantApplicant());
$split = $this->createEditModeDto(applicantPersonId: 4711, firstParticipantPersonId: 4712);
$this->assertFalse($split->isFirstParticipantApplicant());
}
public function testEditModeTreatsAnUnknownPersonIdAsTheSamePerson(): void
{
$dto = $this->createEditModeDto(applicantPersonId: 4711, firstParticipantPersonId: null);
$this->assertTrue(
$dto->isFirstParticipantApplicant(),
'Incomplete BPN data must fall back to the established, locked-down behaviour'
);
}
public function testFamilyBookingRequiresTheApplicantToTravel(): void
{
$dto = $this->createCreateModeDto();
$dto->participants = [
$this->createParticipantAged(40),
$this->createParticipantAged(12),
];
$this->assertTrue($dto->isFamilyBooking());
$dto->applicantTravels = false;
$this->assertFalse(
$dto->isFamilyBooking(),
'Family insurance is not offered when the applicant does not travel'
);
}
public function testAdultSiblingWithAChildIsNotAFamilyBookingOnceTheApplicantStaysHome(): void
{
$dto = $this->createCreateModeDto();
$dto->applicantTravels = false;
$dto->participants = [
$this->createParticipantAged(18),
$this->createParticipantAged(12),
];
$this->assertFalse(
$dto->isFamilyBooking(),
'The age distribution alone would still qualify, which is why the flag is checked first'
);
}
public function testOldSessionsWithoutTheApplicantFieldsRestoreToTheDefaults(): void
{
$travel = new Travel();
$travel->id = 42;
$travel->dateFrom = new \DateTimeImmutable('2025-06-01');
$dto = new BookingDto($travel, 1);
$data = $dto->__serialize();
unset($data['applicantTravels'], $data['applicant']);
$restored = new BookingDto($travel, 1);
$restored->__unserialize($data);
$this->assertTrue($restored->applicantTravels);
$this->assertNull($restored->applicant);
}
private function createCreateModeDto(): BookingDto
{
$travel = new Travel();
$travel->dateFrom = new \DateTimeImmutable('2025-06-01');
$travel->dateTo = new \DateTimeImmutable('2025-06-08');
$dto = new BookingDto($travel, 1);
$dto->participants = [$this->createParticipantAged(40)];
return $dto;
}
private function createEditModeDto(?int $applicantPersonId, ?int $firstParticipantPersonId): BookingDto
{
$dto = $this->createCreateModeDto();
$applicant = new PersonalData();
$applicant->personId = $applicantPersonId;
$booking = new Booking();
$booking->applicant = $applicant;
$dto->booking = $booking;
$dto->participants[0]->personId = $firstParticipantPersonId;
return $dto;
}
private function createParticipantAged(int $years): ParticipantDto
{
$participant = new ParticipantDto();
$participant->dateOfBirth = (new \DateTimeImmutable('2025-06-01'))->modify(sprintf('-%d years', $years));
return $participant;
}
public function testApplicantRulesDoNotLeakOntoTravellers(): void
{
$dto = $this->createCreateModeDto();
$dto->applicantTravels = false;
$dto->participants = [$this->createParticipantAged(15)];
$dto->participants[0]->index = 0;
$applicant = new ParticipantDto();
$applicant->firstName = 'Probe';
$applicant->lastName = 'Elternteil';
$applicant->gender = 'W';
$applicant->nationality = 'D';
$applicant->email = '[email protected]';
$applicant->mobile = '0171/222222';
$applicant->dateOfBirth = new \DateTimeImmutable('1981-03-16');
$applicant->address = $this->createAddress();
$dto->applicant = $applicant;
$validator = Validation::createValidatorBuilder()->enableAttributeMapping()->getValidator();
$violations = $validator->validate($dto, null, ['booking_applicant']);
$paths = [];
foreach ($violations as $violation) {
$paths[] = $violation->getPropertyPath();
}
$this->assertSame(
[],
$paths,
'A 15-year-old traveller must not be held to the applicant rules through the group cascade'
);
}
public function testIncompleteApplicantIsReportedUnderTheApplicantPathOnlyOnce(): void
{
$dto = $this->createCreateModeDto();
$dto->applicantTravels = false;
$dto->participants[0]->index = 0;
$applicant = new ParticipantDto();
$applicant->dateOfBirth = new \DateTimeImmutable('1981-03-16');
$dto->applicant = $applicant;
$validator = Validation::createValidatorBuilder()->enableAttributeMapping()->getValidator();
$violations = $validator->validate($dto, null, ['booking_applicant']);
$paths = [];
foreach ($violations as $violation) {
$paths[] = $violation->getPropertyPath();
}
$this->assertContains('applicant.firstName', $paths);
$this->assertContains(
'applicant.address.street',
$paths,
'A fresh ParticipantDto carries an empty Address, so the gaps surface per subfield'
);
$this->assertSame(
array_unique($paths),
$paths,
'Explicit delegation plus a cascade would report each gap twice'
);
}
public function testMinorApplicantIsRejected(): void
{
$dto = $this->createCreateModeDto();
$dto->applicantTravels = false;
$dto->participants[0]->index = 0;
$applicant = new ParticipantDto();
$applicant->firstName = 'Probe';
$applicant->lastName = 'Elternteil';
$applicant->gender = 'W';
$applicant->nationality = 'D';
$applicant->email = '[email protected]';
$applicant->mobile = '0171/222222';
$applicant->dateOfBirth = (new \DateTimeImmutable('today'))->modify('-17 years');
$applicant->address = $this->createAddress();
$dto->applicant = $applicant;
$validator = Validation::createValidatorBuilder()->enableAttributeMapping()->getValidator();
$violations = $validator->validate($dto, null, ['booking_applicant']);
$messages = [];
foreach ($violations as $violation) {
$messages[] = $violation->getMessage();
}
$this->assertContains('Die anmeldende Person muss volljährig sein.', $messages);
}
private function createAddress(): Address
{
$address = new Address();
$address->street = 'Probeweg 7';
$address->postCode = '99999';
$address->city = 'MusterOrt';
$address->country = 'D';
return $address;
}
}