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

@@ -12,6 +12,7 @@ use App\Entity\Groups\AccommodationPrice;
use App\Enum\Groups\AccommodationBookingOrigin;
use App\Enum\Groups\AccommodationBookingStatus;
use App\Enum\Groups\AdditionalServiceType;
use App\Exception\ImplausibleBookingPriceException;
use App\Form\Model\AccommodationBookingDto;
use App\Model\AccommodationBookingQueryParams;
use App\Repository\Groups\AccommodationPriceRepository;
@@ -65,6 +66,18 @@ class AccommodationBookingServiceTest extends TestCase
$service->initFromParams(new AccommodationBookingQueryParams('HOTEL', '2026-02-31', '2026-03-05'));
}
public function testApplyDatesRejectsAnImplausiblyLongStay(): void
{
// The customer picks from a calendar, so this is a hand-built URL rather than a typo —
// the cap is the same one the backoffice form enforces on the entity.
$service = $this->createServiceWithAccommodation();
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage(sprintf('Der Aufenthalt darf höchstens %d Nächte umfassen.', AccommodationBooking::MAX_NIGHTS));
$service->applyDates(new AccommodationBookingDto(), '2026-02-28', '2226-03-01');
}
public function testIssueAccessLinkForDirectBookingSetsTimestampForDirectBooking(): void
{
$entityManager = $this->createMock(EntityManagerInterface::class);
@@ -962,6 +975,33 @@ class AccommodationBookingServiceTest extends TestCase
self::assertNull($booking->getTotalPrice(), 'the frozen price belonged to the old house');
}
public function testChangeAccommodationLeavesNothingHalfAppliedWhenTheNewHousePricesImplausibly(): void
{
// The swap and the dropped services are already on the managed entity by the time the
// price is computed, so a refusal has to put the row back rather than leave a booking
// sitting at a new house with no price.
$breakdown = ['total' => AccommodationBooking::MAX_TOTAL_PRICE + 1, 'currency' => 'EUR'];
$breakdownCalculator = $this->createStub(AccommodationBookingBreakdownCalculator::class);
$breakdownCalculator->method('computeCurrent')->willReturn($breakdown);
$breakdownCalculator->method('withDiscounts')
->willReturn($breakdown + ['discounts' => [], 'discountedTotal' => AccommodationBooking::MAX_TOTAL_PRICE + 1]);
$entityManager = $this->createMock(EntityManagerInterface::class);
$entityManager->expects(self::once())->method('refresh');
$entityManager->expects(self::never())->method('flush');
$service = $this->createServiceWithAccommodation(
entityManager: $entityManager,
breakdownCalculator: $breakdownCalculator,
);
$booking = $this->draftWithServices();
$this->expectException(ImplausibleBookingPriceException::class);
$service->changeAccommodation($booking, (new Accommodation())->setName('Berghaus')->setCalendarCode('BERG'));
}
public function testChangeAccommodationNoOpsForANonDraft(): void
{
$entityManager = $this->createMock(EntityManagerInterface::class);