fix: refresh live room availability during create flow

This commit is contained in:
Björn Fromme committed 2026-07-14 11:25:23 +02:00
1 parent a73e8a593f
commit cdce7d57e0
26 files changed
+1976 -54

No files matched your search

@@ -0,0 +1,199 @@
<?php
declare(strict_types=1);
namespace App\Tests\Controller\Booking\Create;
use App\BusProNet\Model\Room;
use App\BusProNet\Model\Travel;
use App\Controller\Booking\Create\Step2ParticipantController;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use App\Form\Model\RoomSelectionDto;
use App\Htmx\HxRedirectResponse;
use App\Service\BookingAvailabilityRefreshResult;
use App\Service\BookingConfigurator;
use App\Service\BookingCreateContextFactory;
use App\Service\BookingSessionManager;
use App\Service\ParticipantDataPrefiller;
use App\Service\ParticipantFormSupport;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
class Step2ParticipantControllerTest extends TestCase
{
public function testEditParticipantPersistsBookingStatusOnPlainGetRequest(): void
{
$request = Request::create('/bookings/create/participants/0', 'GET');
$bookingDto = $this->createBookingDto();
$bookingSessionService = $this->createMock(BookingSessionManager::class);
$bookingSessionService->expects($this->once())
->method('getBookingDto')
->with($request, BookingDto::MODE_CREATE)
->willReturn($bookingDto);
$bookingSessionService->expects($this->once())
->method('refreshCreateAvailability')
->with($request, $bookingDto, false)
->willReturn(new BookingAvailabilityRefreshResult(
availabilityRefreshed: true,
roomSelectionQuantityReset: false,
roomSelectionSnapshotChanged: false,
hasAvailableRooms: true,
));
$bookingSessionService->expects($this->once())
->method('saveBookingDto')
->with($request, $bookingDto, BookingDto::MODE_CREATE);
$bookingService = $this->createMock(BookingConfigurator::class);
$bookingService->expects($this->once())
->method('updateBookingStatusFromRoomSelection')
->with($bookingDto);
$participantFormSupportService = $this->createMock(ParticipantFormSupport::class);
$participantFormSupportService->method('ensureParticipantExists')->willReturn(new ParticipantDto());
$participantFormSupportService->method('createParticipantEditDto')->willReturn($this->createMock(\App\Form\Model\ParticipantEditDto::class));
$participantFormSupportService->method('getParticipantFormOptions')->willReturn([]);
$participantFormSupportService->method('collectAndClearNotifications')->willReturn([]);
$prepopulationService = $this->createMock(ParticipantDataPrefiller::class);
$prepopulationService->method('isDummyDataFillRequested')->willReturn(false);
$form = $this->createMock(FormInterface::class);
$form->expects($this->once())->method('handleRequest')->with($request)->willReturnSelf();
$form->expects($this->once())->method('isSubmitted')->willReturn(false);
$bookingDto->participants[0] = new ParticipantDto();
$createContextFactory = $this->createMock(BookingCreateContextFactory::class);
$createContextFactory->method('create')->willReturn(new \App\Form\Model\BookingCreateContext(
bookingDto: $bookingDto,
summaryData: $this->createMock(\App\Form\Model\BookingSummaryDto::class),
groupedRooms: new \App\Form\Model\RoomGroupsDto([], []),
));
$controller = new TestableStep2ParticipantController(
$bookingService,
$bookingSessionService,
$createContextFactory,
$prepopulationService,
$participantFormSupportService,
);
$controller->form = $form;
$response = $controller->editParticipant(0, $request);
$this->assertSame('rendered', $response->getContent());
}
public function testRefreshParticipantFormReturnsHxRedirectForHtmxRequestWhenSelectionStale(): void
{
$request = Request::create('/bookings/create/participants/0/refresh', 'POST');
$request->headers->set('HX-Request', 'true');
$response = $this->invokeRefreshWithStaleSelection($request);
$this->assertInstanceOf(HxRedirectResponse::class, $response);
$this->assertSame('app_booking_create_step_1', $response->headers->get('HX-Redirect'));
}
public function testRefreshParticipantFormReturnsPlainRedirectForNonHtmxRequestWhenSelectionStale(): void
{
$request = Request::create('/bookings/create/participants/0/refresh', 'POST');
$response = $this->invokeRefreshWithStaleSelection($request);
$this->assertInstanceOf(RedirectResponse::class, $response);
$this->assertNotInstanceOf(HxRedirectResponse::class, $response);
$this->assertSame('app_booking_create_step_1', $response->getTargetUrl());
}
private function invokeRefreshWithStaleSelection(Request $request): \Symfony\Component\HttpFoundation\Response
{
$bookingDto = $this->createBookingDto();
$bookingSessionService = $this->createMock(BookingSessionManager::class);
$bookingSessionService->expects($this->once())
->method('getBookingDto')
->with($request, BookingDto::MODE_CREATE)
->willReturn($bookingDto);
$bookingSessionService->expects($this->once())
->method('refreshCreateAvailability')
->with($request, $bookingDto, false)
->willReturn(new BookingAvailabilityRefreshResult(
availabilityRefreshed: true,
roomSelectionQuantityReset: true,
roomSelectionSnapshotChanged: true,
hasAvailableRooms: true,
));
$bookingService = $this->createMock(BookingConfigurator::class);
$bookingService->expects($this->never())->method('updateBookingStatusFromRoomSelection');
$controller = new TestableStep2ParticipantController(
$bookingService,
$bookingSessionService,
$this->createMock(BookingCreateContextFactory::class),
$this->createMock(ParticipantDataPrefiller::class),
$this->createMock(ParticipantFormSupport::class),
);
return $controller->refreshParticipantForm(0, $request);
}
private function createBookingDto(): BookingDto
{
$travel = new Travel();
$travel->id = 1234;
$travel->hotelId = 77;
$room = new Room();
$room->id = 1;
$room->label = 'Doppelzimmer';
$room->available = 2;
$room->status = 'Frei';
$travel->rooms = [$room];
$bookingDto = new BookingDto($travel, 77);
$bookingDto->currentStep = 2;
$selection = new RoomSelectionDto();
$selection->id = 1;
$selection->quantity = 1;
$bookingDto->roomSelections = [$selection];
return $bookingDto;
}
}
final class TestableStep2ParticipantController extends Step2ParticipantController
{
/**
* @var array<int, array{string, mixed}>
*/
public array $flashes = [];
public ?FormInterface $form = null;
protected function addFlash(string $type, mixed $message): void
{
$this->flashes[] = [$type, $message];
}
protected function generateUrl(string $route, array $parameters = [], int $referenceType = 0): string
{
return $route;
}
protected function createForm(string $type, mixed $data = null, array $options = []): FormInterface
{
return $this->form;
}
protected function render(string $view, array $parameters = [], ?\Symfony\Component\HttpFoundation\Response $response = null): \Symfony\Component\HttpFoundation\Response
{
return new \Symfony\Component\HttpFoundation\Response('rendered');
}
}