fix: refuse implausible accommodation booking prices
This commit is contained in:
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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user