fix: refuse implausible accommodation booking prices

This commit is contained in:
fromme committed 2026-09-26 15:39:04 +02:00
1 parent 773cd4f847
commit 56adf72d12
16 files changed
+628 -15

No files matched your search

@@ -109,6 +109,60 @@ class AccommodationBookingTypeTest extends TestCase
self::assertCount(0, $this->validate($booking, ['Default']));
}
/**
* The overflow this guards against reached production as an out-of-range write on
* total_price: every figure in the breakdown is multiplied by the pax count and the
* nights, so an implausible input for either produces an implausible total.
*/
public function testADepartureBeforeTheArrivalIsRejected(): void
{
$booking = $this->bookingFor('2026-09-23', '2026-09-20', 30);
$violations = $this->validate($booking, ['Default']);
self::assertCount(1, $violations);
self::assertSame('dateTo', $violations[0]->getPropertyPath());
}
public function testAMistypedYearIsRejectedRatherThanPriced(): void
{
// 2226 instead of 2026 — one wrong digit, 200 years of running costs.
$booking = $this->bookingFor('2026-09-23', '2226-09-25', 30);
$violations = $this->validate($booking, ['Default']);
self::assertCount(1, $violations);
self::assertSame('dateTo', $violations[0]->getPropertyPath());
}
public function testAnImplausiblePaxCountIsRejected(): void
{
$booking = $this->bookingFor('2026-09-23', '2026-09-25', AccommodationBooking::MAX_PAX + 1);
$violations = $this->validate($booking, ['Default']);
self::assertCount(1, $violations);
self::assertSame('paxCount', $violations[0]->getPropertyPath());
}
public function testTheLargestBookingOnRecordStillPasses(): void
{
// 150 people for 18 nights: the ceilings must not get in the way of real business.
$booking = $this->bookingFor('2026-09-23', '2026-10-11', 150);
self::assertCount(0, $this->validate($booking, ['Default']));
}
private function bookingFor(string $dateFrom, string $dateTo, int $paxCount): AccommodationBooking
{
$booking = new AccommodationBooking();
$booking->setDateFrom(new \DateTimeImmutable($dateFrom));
$booking->setDateTo(new \DateTimeImmutable($dateTo));
$booking->setPaxCount($paxCount);
return $booking;
}
/**
* @return string[]
*/
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace App\Tests\Form\Model;
use App\Entity\Groups\AccommodationBooking;
use App\Form\Model\AccommodationBookingDto;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Validation;
class AccommodationBookingDtoTest extends TestCase
{
/**
* Step 2 is the screen the customer types the head count on, and the one that prices it.
* Bounding it only in step_3 left the figure to surface two steps later, as a booking that
* could not be stored.
*/
#[DataProvider('implausibleHeadCounts')]
public function testAnImplausibleHeadCountIsRejectedWhereItIsEntered(string $property, int $value): void
{
$dto = new AccommodationBookingDto();
$dto->{$property} = $value;
$properties = array_map(
static fn ($violation) => $violation->getPropertyPath(),
iterator_to_array($this->validate($dto, ['step_2'])),
);
self::assertContains($property, $properties);
}
/**
* @return iterable<string, array{string, int}>
*/
public static function implausibleHeadCounts(): iterable
{
yield 'pax above the ceiling' => ['paxCount', AccommodationBooking::MAX_PAX + 1];
yield 'pax below one' => ['paxCount', 0];
yield 'negative pax' => ['paxCount', -5];
yield 'minors above the ceiling' => ['minorsCount', AccommodationBooking::MAX_PAX + 1];
yield 'negative minors' => ['minorsCount', -1];
yield 'children above the ceiling' => ['childrenCount', AccommodationBooking::MAX_PAX + 1];
}
public function testAFullHouseStillPasses(): void
{
$dto = new AccommodationBookingDto();
$dto->paxCount = AccommodationBooking::MAX_PAX;
$dto->minorsCount = 4;
$dto->childrenCount = 60;
$dto->selectedBoardServiceId = 1;
self::assertCount(0, $this->validate($dto, ['step_2']));
}
/**
* @param string[] $groups
*/
private function validate(AccommodationBookingDto $dto, array $groups): ConstraintViolationListInterface
{
return Validation::createValidatorBuilder()
->enableAttributeMapping()
->getValidator()
->validate($dto, null, $groups);
}
}