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
@@ -8,6 +8,7 @@ use App\Controller\Admin\AccommodationBooking\ChangeAccommodationController;
|
||||
use App\Entity\Groups\Accommodation;
|
||||
use App\Entity\Groups\AccommodationBooking;
|
||||
use App\Enum\Groups\AccommodationBookingStatus;
|
||||
use App\Exception\ImplausibleBookingPriceException;
|
||||
use App\Service\AccommodationBookingService;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -87,6 +88,36 @@ class ChangeAccommodationControllerTest extends TestCase
|
||||
self::assertSame('success', $controller->flashes[0]['type']);
|
||||
}
|
||||
|
||||
public function testAHouseThatPricesImplausiblyIsReportedInTheModal(): void
|
||||
{
|
||||
// The service leaves the booking at its old house and rethrows; the office has to see why
|
||||
// the change did not happen, on the modal it just submitted, not as an error page.
|
||||
$booking = $this->draft();
|
||||
|
||||
$bookingService = $this->createStub(AccommodationBookingService::class);
|
||||
$bookingService->method('changeAccommodation')
|
||||
->willThrowException(new ImplausibleBookingPriceException(AccommodationBooking::MAX_TOTAL_PRICE + 1, 40, 2));
|
||||
|
||||
$field = $this->createStub(FormInterface::class);
|
||||
$field->method('getData')->willReturn(new Accommodation());
|
||||
|
||||
$form = $this->createMock(FormInterface::class);
|
||||
$form->method('handleRequest')->willReturn($form);
|
||||
$form->method('isSubmitted')->willReturn(true);
|
||||
$form->method('isValid')->willReturn(true);
|
||||
$form->method('get')->with('accommodation')->willReturn($field);
|
||||
$form->expects(self::once())->method('addError');
|
||||
|
||||
$controller = $this->buildController($bookingService, $form);
|
||||
|
||||
$response = $controller->index($booking, $this->request('POST'));
|
||||
|
||||
self::assertSame(Response::HTTP_OK, $response->getStatusCode());
|
||||
self::assertFalse($response->headers->has('HX-Redirect'), 'the modal stays open on the error');
|
||||
self::assertSame('admin/accommodation_booking/modal_change_accommodation.html.twig', $controller->renderedView);
|
||||
self::assertSame([], $controller->flashes, 'nothing was changed, so nothing is reported as success');
|
||||
}
|
||||
|
||||
public function testPickingTheSameHouseIsNotReportedAsAChange(): void
|
||||
{
|
||||
$booking = $this->draft();
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace App\Tests\Controller\Admin\AccommodationBooking;
|
||||
use App\Controller\Admin\AccommodationBooking\CreateController;
|
||||
use App\Entity\Groups\AccommodationBooking;
|
||||
use App\Entity\User;
|
||||
use App\Exception\ImplausibleBookingPriceException;
|
||||
use App\Service\AccommodationBookingService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -32,6 +33,37 @@ class CreateControllerTest extends TestCase
|
||||
self::assertSame($user, $controller->capturedBooking?->getManagedBy());
|
||||
}
|
||||
|
||||
public function testAnImplausiblePriceComesBackAsAFormErrorWithoutSaving(): void
|
||||
{
|
||||
// Same as on the edit form: the fix is in the house's price rows, so the office is told
|
||||
// on the form it is standing in front of, and nothing is written.
|
||||
$bookingService = $this->createStub(AccommodationBookingService::class);
|
||||
$bookingService->method('refreshPriceSnapshot')
|
||||
->willThrowException(new ImplausibleBookingPriceException(AccommodationBooking::MAX_TOTAL_PRICE + 1, 40, 2));
|
||||
|
||||
$form = $this->createMock(FormInterface::class);
|
||||
$form->method('handleRequest')->willReturn($form);
|
||||
$form->method('isSubmitted')->willReturn(true);
|
||||
$form->method('isValid')->willReturn(true);
|
||||
$form->expects(self::once())->method('addError');
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager->expects(self::never())->method('persist');
|
||||
$entityManager->expects(self::never())->method('flush');
|
||||
|
||||
$controller = new TestableCreateController(
|
||||
$entityManager,
|
||||
$this->createStub(LoggerInterface::class),
|
||||
$bookingService,
|
||||
$form,
|
||||
new User('[email protected]'),
|
||||
);
|
||||
|
||||
$response = $controller->index(Request::create('/admin/accommodation-booking/create', 'POST'));
|
||||
|
||||
self::assertSame(Response::HTTP_OK, $response->getStatusCode(), 'the modal is re-rendered, not redirected');
|
||||
}
|
||||
|
||||
private function buildController(?UserInterface $user): TestableCreateController
|
||||
{
|
||||
$form = $this->createStub(FormInterface::class);
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Controller\Admin\AccommodationBooking\EditController;
|
||||
use App\Entity\Groups\AccommodationBooking;
|
||||
use App\Entity\User;
|
||||
use App\Enum\Groups\AccommodationBookingStatus;
|
||||
use App\Exception\ImplausibleBookingPriceException;
|
||||
use App\Repository\Groups\AdditionalServiceRepository;
|
||||
use App\Repository\Groups\BoardServiceRepository;
|
||||
use App\Repository\UserRepository;
|
||||
@@ -46,6 +47,39 @@ class EditControllerTest extends TestCase
|
||||
self::assertSame($status, $booking->getStatus(), 'editing leaves the status where it was');
|
||||
}
|
||||
|
||||
public function testAnImplausiblePriceComesBackAsAFormErrorWithoutSaving(): void
|
||||
{
|
||||
// The office can fix this — in the house's price rows — so it belongs on the form rather
|
||||
// than on an error page, and nothing may be flushed in the meantime.
|
||||
$bookingService = $this->createStub(AccommodationBookingService::class);
|
||||
$bookingService->method('refreshPriceSnapshot')
|
||||
->willThrowException(new ImplausibleBookingPriceException(AccommodationBooking::MAX_TOTAL_PRICE + 1, 40, 2));
|
||||
|
||||
$form = $this->createMock(FormInterface::class);
|
||||
$form->method('handleRequest')->willReturn($form);
|
||||
$form->method('isSubmitted')->willReturn(true);
|
||||
$form->method('isValid')->willReturn(true);
|
||||
$form->method('has')->willReturn(false);
|
||||
$form->expects(self::once())->method('addError');
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager->expects(self::never())->method('flush');
|
||||
|
||||
$controller = new TestableEditController(
|
||||
$entityManager,
|
||||
$this->createStub(LoggerInterface::class),
|
||||
$this->createStub(BoardServiceRepository::class),
|
||||
$this->createStub(AdditionalServiceRepository::class),
|
||||
$this->createStub(UserRepository::class),
|
||||
$bookingService,
|
||||
$form,
|
||||
);
|
||||
|
||||
$response = $controller->index(new AccommodationBooking(), Request::create('/admin/accommodation-booking/1/edit', 'POST'));
|
||||
|
||||
self::assertSame(Response::HTTP_OK, $response->getStatusCode(), 'the form is re-rendered, not redirected');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string, array{AccommodationBookingStatus}>
|
||||
*/
|
||||
|
||||
@@ -8,11 +8,13 @@ use App\Controller\Groups\Booking\Step4Controller;
|
||||
use App\Entity\Groups\Accommodation;
|
||||
use App\Entity\Groups\AccommodationBooking;
|
||||
use App\Enum\Groups\AccommodationBookingOrigin;
|
||||
use App\Exception\ImplausibleBookingPriceException;
|
||||
use App\Form\Model\AccommodationBookingDto;
|
||||
use App\Service\AccommodationBookingService;
|
||||
use App\Service\AccommodationSessionManager;
|
||||
use App\Service\AccommodationTermsUrlProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -302,6 +304,96 @@ class Step4ControllerTest extends TestCase
|
||||
self::assertNull($sessionManager->getDto($request));
|
||||
}
|
||||
|
||||
public function testConfirmPostEndsOnTheErrorPageWhenThePriceIsImplausible(): void
|
||||
{
|
||||
// A mistyped catalog price is the one way an implausible total still gets computed, and
|
||||
// the customer must not meet it as a 500 — SQLSTATE 22003 on /admin is what started this.
|
||||
$dto = new AccommodationBookingDto();
|
||||
$dto->currentStep = 4;
|
||||
$dto->accommodationId = 1;
|
||||
|
||||
$session = new Session(new MockArraySessionStorage());
|
||||
$request = Request::create('/groups/booking/step-4/confirm', 'POST');
|
||||
$request->setSession($session);
|
||||
$request->headers->set('HX-Request', 'true');
|
||||
|
||||
$sessionManager = new AccommodationSessionManager();
|
||||
$sessionManager->save($request, $dto);
|
||||
|
||||
$accommodation = (new Accommodation())
|
||||
->setName('Hotel')
|
||||
->setCalendarCode('HOTEL')
|
||||
->setMaxAdolescentAge(17);
|
||||
|
||||
$bookingService = $this->createMock(AccommodationBookingService::class);
|
||||
$bookingService->method('loadAccommodation')->with(1)->willReturn($accommodation);
|
||||
$bookingService->method('loadAvailableServices')->willReturn([
|
||||
'boardServices' => [],
|
||||
'additionalServices' => [],
|
||||
'groupedAdditionalServices' => [],
|
||||
'ungroupedAdditionalServices' => [],
|
||||
]);
|
||||
$bookingService->method('loadPrices')->willReturn([]);
|
||||
$bookingService->method('finalizeBooking')
|
||||
->willThrowException(new ImplausibleBookingPriceException(AccommodationBooking::MAX_TOTAL_PRICE + 1, 40, 2));
|
||||
|
||||
$confirmationForm = $this->createStub(FormInterface::class);
|
||||
$confirmationForm->method('handleRequest')->willReturnSelf();
|
||||
$confirmationForm->method('isSubmitted')->willReturn(true);
|
||||
$confirmationForm->method('isValid')->willReturn(true);
|
||||
|
||||
$controller = new TestableStep4Controller($bookingService, $sessionManager, $confirmationForm);
|
||||
|
||||
$response = $controller->confirm($request);
|
||||
|
||||
self::assertSame('/app_groups_booking_error', $response->headers->get('HX-Redirect'));
|
||||
// The session survives: the desk still has the inquiry to pick up by phone.
|
||||
self::assertNotNull($sessionManager->getDto($request));
|
||||
}
|
||||
|
||||
public function testIndexPostEndsOnTheErrorPageWhenThePriceIsImplausible(): void
|
||||
{
|
||||
$dto = new AccommodationBookingDto();
|
||||
$dto->currentStep = 4;
|
||||
$dto->accommodationId = 1;
|
||||
|
||||
$session = new Session(new MockArraySessionStorage());
|
||||
$request = Request::create('/groups/booking/step-4', 'POST');
|
||||
$request->setSession($session);
|
||||
|
||||
$sessionManager = new AccommodationSessionManager();
|
||||
$sessionManager->save($request, $dto);
|
||||
|
||||
$accommodation = (new Accommodation())
|
||||
->setName('Hotel')
|
||||
->setCalendarCode('HOTEL')
|
||||
->setMaxAdolescentAge(17);
|
||||
|
||||
$bookingService = $this->createMock(AccommodationBookingService::class);
|
||||
$bookingService->method('loadAccommodation')->with(1)->willReturn($accommodation);
|
||||
$bookingService->method('loadAvailableServices')->willReturn([
|
||||
'boardServices' => [],
|
||||
'additionalServices' => [],
|
||||
'groupedAdditionalServices' => [],
|
||||
'ungroupedAdditionalServices' => [],
|
||||
]);
|
||||
$bookingService->method('loadPrices')->willReturn([]);
|
||||
$bookingService->method('finalizeBooking')
|
||||
->willThrowException(new ImplausibleBookingPriceException(AccommodationBooking::MAX_TOTAL_PRICE + 1, 40, 2));
|
||||
|
||||
$inquiryForm = $this->createStub(FormInterface::class);
|
||||
$inquiryForm->method('handleRequest')->willReturnSelf();
|
||||
$inquiryForm->method('isSubmitted')->willReturn(true);
|
||||
$inquiryForm->method('isValid')->willReturn(true);
|
||||
|
||||
$controller = new TestableStep4Controller($bookingService, $sessionManager, $inquiryForm);
|
||||
|
||||
$response = $controller->index($request);
|
||||
|
||||
self::assertInstanceOf(RedirectResponse::class, $response);
|
||||
self::assertSame('/app_groups_booking_error', $response->getTargetUrl());
|
||||
}
|
||||
|
||||
public function testConfirmPostInvalidReRendersModalWithoutPersisting(): void
|
||||
{
|
||||
$dto = new AccommodationBookingDto();
|
||||
@@ -358,6 +450,7 @@ final class TestableStep4Controller extends Step4Controller
|
||||
$bookingService,
|
||||
$sessionManager,
|
||||
new AccommodationTermsUrlProvider(['AT' => '', 'CH' => '', 'IT' => ''], 'https://example.test/agb/'),
|
||||
new NullLogger(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user