createBookingDto(); $bookingSessionService = $this->createMock(BookingSessionManager::class); $bookingSessionService->expects($this->once()) ->method('getOrCreateBookingCreateDto') ->with($request) ->willReturn($bookingDto); $bookingSessionService->expects($this->exactly(2)) ->method('refreshCreateAvailability') ->willReturnOnConsecutiveCalls( new BookingAvailabilityRefreshResult( availabilityRefreshed: true, roomSelectionQuantityReset: false, roomSelectionSnapshotChanged: false, hasAvailableRooms: true, ), new BookingAvailabilityRefreshResult( availabilityRefreshed: false, roomSelectionQuantityReset: false, roomSelectionSnapshotChanged: false, hasAvailableRooms: true, ), ); $bookingService = $this->createMock(BookingConfigurator::class); $bookingService->expects($this->never())->method('applyCreateBookingStatusRules'); $apiClient = $this->createMock(ApiClient::class); $apiClient->expects($this->never())->method('createBooking'); $form = $this->createMock(FormInterface::class); $form->expects($this->once())->method('handleRequest')->with($request)->willReturnSelf(); $form->expects($this->once())->method('isSubmitted')->willReturn(true); $form->expects($this->once())->method('isValid')->willReturn(true); $createContextFactory = $this->createMock(BookingCreateContextFactory::class); $createContextFactory->expects($this->once()) ->method('createWithParticipantPrices') ->with($bookingDto) ->willReturn(new BookingCreateContext( bookingDto: $bookingDto, summaryData: $this->createMock(BookingSummaryDto::class), groupedRooms: new RoomGroupsDto([], []), )); $controller = new TestableStep4Controller( $bookingService, $bookingSessionService, $createContextFactory, $apiClient, $this->createMock(CacheInterface::class), $this->createMock(NewsletterManager::class), new NullLogger(), $form, ); $response = $controller->index($request); $this->assertInstanceOf(Response::class, $response); $this->assertNotInstanceOf(RedirectResponse::class, $response); $this->assertSame('rendered:booking/create/step_4.html.twig', $response->getContent()); $this->assertSame(['error', 'Die Verfügbarkeit konnte gerade nicht aktualisiert werden. Bitte versuche es erneut.'], $controller->flashes[0]); } public function testIndexRedirectsWithWarningWhenPriceChangesDuringForcedRefresh(): void { $request = Request::create('/bookings/create/confirmation', 'POST'); $bookingDto = $this->createBookingDto(); $bookingSessionService = $this->createMock(BookingSessionManager::class); $bookingSessionService->expects($this->once()) ->method('getOrCreateBookingCreateDto') ->with($request) ->willReturn($bookingDto); $bookingSessionService->expects($this->exactly(2)) ->method('refreshCreateAvailability') ->willReturnOnConsecutiveCalls( new BookingAvailabilityRefreshResult( availabilityRefreshed: true, roomSelectionQuantityReset: false, roomSelectionSnapshotChanged: false, hasAvailableRooms: true, ), new BookingAvailabilityRefreshResult( availabilityRefreshed: true, roomSelectionQuantityReset: false, roomSelectionSnapshotChanged: true, hasAvailableRooms: true, ), ); $bookingService = $this->createMock(BookingConfigurator::class); $bookingService->expects($this->never())->method('applyCreateBookingStatusRules'); $apiClient = $this->createMock(ApiClient::class); $apiClient->expects($this->never())->method('createBooking'); $form = $this->createMock(FormInterface::class); $form->expects($this->once())->method('handleRequest')->with($request)->willReturnSelf(); $form->expects($this->once())->method('isSubmitted')->willReturn(true); $form->expects($this->once())->method('isValid')->willReturn(true); $createContextFactory = $this->createMock(BookingCreateContextFactory::class); $createContextFactory->expects($this->never())->method('createWithParticipantPrices'); $controller = new TestableStep4Controller( $bookingService, $bookingSessionService, $createContextFactory, $apiClient, $this->createMock(CacheInterface::class), $this->createMock(NewsletterManager::class), new NullLogger(), $form, ); $response = $controller->index($request); $this->assertInstanceOf(RedirectResponse::class, $response); $this->assertSame('app_booking_create_step_4', $response->getTargetUrl()); $this->assertSame(['warning', 'Die Zimmerpreise haben sich aktualisiert. Bitte überprüfe deine Buchung erneut.'], $controller->flashes[0]); } 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 = 4; $selection = new RoomSelectionDto(); $selection->id = 1; $selection->quantity = 1; $bookingDto->roomSelections = [$selection]; return $bookingDto; } } final class TestableStep4Controller extends Step4Controller { /** * @var array */ public array $flashes = []; public function __construct( BookingConfigurator $bookingService, BookingSessionManager $bookingSessionService, BookingCreateContextFactory $createContextFactory, ApiClient $apiClient, CacheInterface $cache, NewsletterManager $doubleOptInService, \Psr\Log\LoggerInterface $logger, private readonly FormInterface $form, ) { parent::__construct( $bookingService, $bookingSessionService, $createContextFactory, $apiClient, $cache, $doubleOptInService, $logger, ); } 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]; } protected function render(string $view, array $parameters = [], ?Response $response = null): Response { return new Response('rendered:'.$view); } protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse { return new RedirectResponse($route, $status); } protected function generateUrl(string $route, array $parameters = [], int $referenceType = 0): string { return $route; } protected function getUser(): ?\Symfony\Component\Security\Core\User\UserInterface { return null; } }