388 lines
14 KiB
PHP
388 lines
14 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Controller\Booking\Create;
|
|
|
|
use App\BusProNet\Constants;
|
|
use App\BusProNet\Model\CrmSelection;
|
|
use App\BusProNet\Model\CrmSelectionGroup;
|
|
use App\BusProNet\Model\Room;
|
|
use App\BusProNet\Model\Travel;
|
|
use App\Controller\Booking\Create\Step2Controller;
|
|
use App\Form\Model\BookingDto;
|
|
use App\Form\Model\ParticipantDto;
|
|
use App\Form\Model\RoomSelectionDto;
|
|
use App\Model\RoomSelectionAdjustment;
|
|
use App\Service\BookingConfigurator;
|
|
use App\Service\BookingCreateContextFactory;
|
|
use App\Service\BookingSessionManager;
|
|
use App\Service\ParticipantDataPrefiller;
|
|
use App\Service\RoomAssigner;
|
|
use App\Service\TravelDataProvider;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Psr\Log\LoggerInterface;
|
|
use Symfony\Component\Form\FormInterface;
|
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\Security\Core\User\UserInterface;
|
|
|
|
class Step2ControllerTest extends TestCase
|
|
{
|
|
public function testSendsCustomerBackToStep1WhenASelectedRoomIsNoLongerBookable(): void
|
|
{
|
|
$request = Request::create('/bookings/create/participants', 'GET');
|
|
$travel = $this->createTravel([$this->createRoom(1409, 'Einzelzimmer', minPax: 1)]);
|
|
$bookingDto = $this->createBookingDto($travel);
|
|
|
|
$bookingService = $this->createMock(BookingConfigurator::class);
|
|
$bookingService->expects($this->once())
|
|
->method('reconcileRoomSelections')
|
|
->with($bookingDto)
|
|
->willReturn([new RoomSelectionAdjustment(1408, 'Doppelzimmer', 2, 0)]);
|
|
$bookingService->expects($this->never())->method('preselectDefaultServices');
|
|
|
|
$bookingSessionService = $this->createMock(BookingSessionManager::class);
|
|
$bookingSessionService->method('getOrCreateBookingCreateDto')->willReturn($bookingDto);
|
|
$bookingSessionService->expects($this->once())
|
|
->method('saveBookingDto')
|
|
->with($request, $bookingDto, BookingDto::MODE_CREATE);
|
|
|
|
$logger = $this->createMock(LoggerInterface::class);
|
|
$logger->expects($this->once())
|
|
->method('error')
|
|
->with(
|
|
'Room selection exceeds what the current travel data still offers.',
|
|
[
|
|
'travelId' => 4711,
|
|
'travelCode' => 'TRV-4711',
|
|
'hotelId' => 1,
|
|
'rooms' => [[
|
|
'roomId' => 1408,
|
|
'label' => 'Doppelzimmer',
|
|
'requestedQuantity' => 2,
|
|
'bookableQuantity' => 0,
|
|
]],
|
|
]
|
|
);
|
|
|
|
$controller = $this->createController($bookingService, $bookingSessionService, $logger);
|
|
|
|
$response = $controller->index($request);
|
|
|
|
$this->assertInstanceOf(RedirectResponse::class, $response);
|
|
$this->assertSame('/app_booking_create_step_1', $response->getTargetUrl());
|
|
$this->assertSame(
|
|
[['error', 'Dein gewähltes Zimmer ist leider nicht mehr verfügbar. Bitte wähle dein Zimmer erneut.']],
|
|
$controller->flashes
|
|
);
|
|
}
|
|
|
|
public function testSendsCustomerBackToStep1WithTheCappedQuantityPrefilled(): void
|
|
{
|
|
$request = Request::create('/bookings/create/participants', 'GET');
|
|
$travel = $this->createTravel([$this->createRoom(1408, 'Doppelzimmer', minPax: 2)]);
|
|
$bookingDto = $this->createBookingDto($travel);
|
|
$bookingDto->roomSelections = [
|
|
1408 => $this->createRoomSelection(1408, 'Doppelzimmer', quantity: 1, capacity: 2),
|
|
];
|
|
|
|
$bookingService = $this->createMock(BookingConfigurator::class);
|
|
$bookingService->expects($this->once())
|
|
->method('reconcileRoomSelections')
|
|
->with($bookingDto)
|
|
->willReturn([new RoomSelectionAdjustment(1408, 'Doppelzimmer', 3, 1)]);
|
|
|
|
$bookingSessionService = $this->createMock(BookingSessionManager::class);
|
|
$bookingSessionService->method('getOrCreateBookingCreateDto')->willReturn($bookingDto);
|
|
$bookingSessionService->expects($this->once())
|
|
->method('saveBookingDto')
|
|
->with($request, $bookingDto, BookingDto::MODE_CREATE);
|
|
|
|
$controller = $this->createController(
|
|
$bookingService,
|
|
$bookingSessionService,
|
|
$this->createStub(LoggerInterface::class)
|
|
);
|
|
|
|
$response = $controller->index($request);
|
|
|
|
$this->assertInstanceOf(RedirectResponse::class, $response);
|
|
$this->assertSame('/app_booking_create_step_1', $response->getTargetUrl());
|
|
$this->assertSame(
|
|
[['error', 'Für dein gewähltes Zimmer sind nicht mehr genügend Plätze verfügbar. Wir haben deine Auswahl angepasst, bitte überprüfe sie.']],
|
|
$controller->flashes
|
|
);
|
|
$this->assertSame(1, $bookingDto->roomSelections[1408]->quantity, 'Step 1 reopens on the capped selection');
|
|
}
|
|
|
|
public function testSendsCustomerToTheErrorPageWhenTheTravelSoldOutCompletely(): void
|
|
{
|
|
$request = Request::create('/bookings/create/participants', 'GET');
|
|
$bookingDto = $this->createBookingDto($this->createTravel([]));
|
|
|
|
$bookingService = $this->createMock(BookingConfigurator::class);
|
|
$bookingService->expects($this->once())
|
|
->method('reconcileRoomSelections')
|
|
->with($bookingDto)
|
|
->willReturn([new RoomSelectionAdjustment(1408, 'Doppelzimmer', 2, 0)]);
|
|
|
|
$bookingSessionService = $this->createMock(BookingSessionManager::class);
|
|
$bookingSessionService->method('getOrCreateBookingCreateDto')->willReturn($bookingDto);
|
|
$bookingSessionService->expects($this->once())
|
|
->method('saveBookingDto')
|
|
->with($request, $bookingDto, BookingDto::MODE_CREATE);
|
|
|
|
$logger = $this->createMock(LoggerInterface::class);
|
|
$logger->expects($this->once())
|
|
->method('error')
|
|
->with('Travel has no bookable rooms left while a booking is in progress.', $this->anything());
|
|
|
|
$controller = $this->createController($bookingService, $bookingSessionService, $logger);
|
|
|
|
$response = $controller->index($request);
|
|
|
|
$this->assertInstanceOf(RedirectResponse::class, $response);
|
|
$this->assertSame('/app_booking_create_error', $response->getTargetUrl());
|
|
$this->assertSame(
|
|
[['error', 'Für diese Reise sind leider keine Zimmer mehr verfügbar.']],
|
|
$controller->flashes
|
|
);
|
|
}
|
|
|
|
public function testContinuesWhenOnlyUnselectedRoomsWereDropped(): void
|
|
{
|
|
$request = Request::create('/bookings/create/participants', 'GET');
|
|
$travel = $this->createTravel([$this->createRoom(1409, 'Einzelzimmer', minPax: 3)]);
|
|
$bookingDto = $this->createBookingDto($travel);
|
|
$bookingDto->roomSelections = [
|
|
1409 => $this->createRoomSelection(1409, 'Einzelzimmer', quantity: 2, capacity: 3),
|
|
];
|
|
|
|
$bookingService = $this->createMock(BookingConfigurator::class);
|
|
$bookingService->expects($this->once())
|
|
->method('reconcileRoomSelections')
|
|
->with($bookingDto)
|
|
->willReturn([]);
|
|
|
|
$bookingSessionService = $this->createMock(BookingSessionManager::class);
|
|
$bookingSessionService->method('getOrCreateBookingCreateDto')->willReturn($bookingDto);
|
|
$bookingSessionService->expects($this->exactly(2))
|
|
->method('saveBookingDto')
|
|
->with($request, $bookingDto, BookingDto::MODE_CREATE);
|
|
|
|
$logger = $this->createMock(LoggerInterface::class);
|
|
$logger->expects($this->never())->method('error');
|
|
|
|
$controller = $this->createController($bookingService, $bookingSessionService, $logger);
|
|
|
|
$response = $controller->index($request);
|
|
|
|
$this->assertSame([], $controller->flashes);
|
|
$this->assertInstanceOf(Response::class, $response);
|
|
$this->assertCount(6, $bookingDto->participants, 'Two rooms for three people each yield six participants');
|
|
}
|
|
|
|
public function testEndsASplitTheTravelDateDoesNotOffer(): void
|
|
{
|
|
$bookingDto = $this->createSplitBookingDto($this->createTravel([$this->createRoom(1409, 'Einzelzimmer', minPax: 1)]));
|
|
$applicant = $bookingDto->applicant;
|
|
|
|
$controller = $this->createControllerForFullRun($bookingDto);
|
|
$controller->index(Request::create('/bookings/create/participants', 'GET'));
|
|
|
|
$this->assertTrue($bookingDto->applicantTravels);
|
|
$this->assertNull($bookingDto->applicant);
|
|
$this->assertSame($applicant, $bookingDto->participants[0]);
|
|
}
|
|
|
|
public function testKeepsASplitTheTravelDateOffers(): void
|
|
{
|
|
$travel = $this->createTravel([$this->createRoom(1409, 'Einzelzimmer', minPax: 1)]);
|
|
$selection = new CrmSelection();
|
|
$selection->id = 1488;
|
|
$group = new CrmSelectionGroup();
|
|
$group->id = 10;
|
|
$group->selections = [$selection];
|
|
$travel->selectionGroups = [10 => $group];
|
|
|
|
$bookingDto = $this->createSplitBookingDto($travel);
|
|
$applicant = $bookingDto->applicant;
|
|
|
|
$controller = $this->createControllerForFullRun($bookingDto);
|
|
$controller->index(Request::create('/bookings/create/participants', 'GET'));
|
|
|
|
$this->assertFalse($bookingDto->applicantTravels);
|
|
$this->assertSame($applicant, $bookingDto->applicant);
|
|
$this->assertNotSame($applicant, $bookingDto->participants[0]);
|
|
}
|
|
|
|
private function createSplitBookingDto(Travel $travel): BookingDto
|
|
{
|
|
$bookingDto = $this->createBookingDto($travel);
|
|
$bookingDto->roomSelections = [
|
|
1409 => $this->createRoomSelection(1409, 'Einzelzimmer', quantity: 1, capacity: 1),
|
|
];
|
|
$bookingDto->applicantTravels = false;
|
|
$bookingDto->applicant = new ParticipantDto();
|
|
|
|
return $bookingDto;
|
|
}
|
|
|
|
private function createControllerForFullRun(BookingDto $bookingDto): TestableStep2Controller
|
|
{
|
|
$bookingService = $this->createStub(BookingConfigurator::class);
|
|
$bookingService->method('reconcileRoomSelections')->willReturn([]);
|
|
|
|
$bookingSessionService = $this->createStub(BookingSessionManager::class);
|
|
$bookingSessionService->method('getOrCreateBookingCreateDto')->willReturn($bookingDto);
|
|
|
|
return $this->createController($bookingService, $bookingSessionService, $this->createStub(LoggerInterface::class));
|
|
}
|
|
|
|
private function createController(
|
|
BookingConfigurator $bookingService,
|
|
BookingSessionManager $bookingSessionService,
|
|
LoggerInterface $logger,
|
|
): TestableStep2Controller {
|
|
$form = $this->createStub(FormInterface::class);
|
|
$form->method('isSubmitted')->willReturn(false);
|
|
|
|
return new TestableStep2Controller(
|
|
$bookingService,
|
|
$bookingSessionService,
|
|
$this->createStub(BookingCreateContextFactory::class),
|
|
$this->createStub(TravelDataProvider::class),
|
|
$this->createStub(RoomAssigner::class),
|
|
$this->createStub(ParticipantDataPrefiller::class),
|
|
$logger,
|
|
$form,
|
|
);
|
|
}
|
|
|
|
private function createBookingDto(Travel $travel): BookingDto
|
|
{
|
|
$bookingDto = new BookingDto($travel, 1);
|
|
$bookingDto->currentStep = 2;
|
|
|
|
return $bookingDto;
|
|
}
|
|
|
|
/**
|
|
* @param array<int, Room> $rooms
|
|
*/
|
|
private function createTravel(array $rooms): Travel
|
|
{
|
|
$travel = new Travel();
|
|
$travel->id = 4711;
|
|
$travel->code = 'TRV-4711';
|
|
$travel->rooms = $rooms;
|
|
|
|
return $travel;
|
|
}
|
|
|
|
private function createRoom(int $id, string $label, int $minPax): Room
|
|
{
|
|
$room = new Room();
|
|
$room->id = $id;
|
|
$room->label = $label;
|
|
$room->available = 4;
|
|
$room->minPax = $minPax;
|
|
$room->status = Constants::STATUS_AVAILABLE;
|
|
|
|
return $room;
|
|
}
|
|
|
|
private function createRoomSelection(int $id, string $label, int $quantity, int $capacity): RoomSelectionDto
|
|
{
|
|
$selection = new RoomSelectionDto();
|
|
$selection->id = $id;
|
|
$selection->label = $label;
|
|
$selection->quantity = $quantity;
|
|
$selection->capacity = $capacity;
|
|
|
|
return $selection;
|
|
}
|
|
}
|
|
|
|
final class TestableStep2Controller extends Step2Controller
|
|
{
|
|
/**
|
|
* @var array<int, array{string, mixed}>
|
|
*/
|
|
public array $flashes = [];
|
|
|
|
/**
|
|
* @var array<string, mixed>
|
|
*/
|
|
public array $renderParameters = [];
|
|
|
|
/**
|
|
* @var FormInterface<mixed>
|
|
*/
|
|
private readonly FormInterface $form;
|
|
|
|
/**
|
|
* @param FormInterface<mixed> $form
|
|
*/
|
|
public function __construct(
|
|
BookingConfigurator $bookingService,
|
|
BookingSessionManager $bookingSessionService,
|
|
BookingCreateContextFactory $createContextFactory,
|
|
TravelDataProvider $travelDataService,
|
|
RoomAssigner $roomAssignmentService,
|
|
ParticipantDataPrefiller $prepopulationService,
|
|
LoggerInterface $logger,
|
|
FormInterface $form,
|
|
) {
|
|
$this->form = $form;
|
|
|
|
parent::__construct(
|
|
$bookingService,
|
|
$bookingSessionService,
|
|
$createContextFactory,
|
|
$travelDataService,
|
|
$roomAssignmentService,
|
|
$prepopulationService,
|
|
$logger,
|
|
);
|
|
}
|
|
|
|
protected function getUser(): ?UserInterface
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @return FormInterface<mixed>
|
|
*/
|
|
protected function createForm(string $type, mixed $data = null, array $options = []): FormInterface
|
|
{
|
|
return $this->form;
|
|
}
|
|
|
|
protected function addFlash(string $type, mixed $message): void
|
|
{
|
|
$this->flashes[] = [$type, $message];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $parameters
|
|
*/
|
|
protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse
|
|
{
|
|
return new RedirectResponse('/'.$route, $status);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $parameters
|
|
*/
|
|
protected function render(string $view, array $parameters = [], ?Response $response = null): Response
|
|
{
|
|
$this->renderParameters = $parameters;
|
|
|
|
return new Response('');
|
|
}
|
|
}
|