currentStep = 3; $dto->accommodationId = 1; $dto->dateFrom = new \DateTimeImmutable('2026-03-01'); $dto->dateTo = new \DateTimeImmutable('2026-03-05'); $dto->selectedBoardServiceId = 10; $dto->selectedAdditionalServiceIds = [20]; $session = new Session(new MockArraySessionStorage()); $request = Request::create('/groups/booking/step-3', 'GET'); $request->setSession($session); $sessionManager = new AccommodationSessionManager(); $sessionManager->save($request, $dto); $form = $this->createFormMock(submitted: false, valid: false); $accommodation = (new Accommodation()) ->setName('Hotel') ->setCalendarCode('HOTEL') ->setMaxAdolescentAge(17); $boardService = (new BoardService())->setLabel('Halbpension')->setPrice(1000); $additionalService = (new AdditionalService())->setLabel('Skipass')->setPrice(2000); $bookingService = $this->createMock(AccommodationBookingService::class); $bookingService ->method('loadAccommodation') ->with(1) ->willReturn($accommodation); $bookingService ->expects(self::once()) ->method('loadAvailableServices') ->with($dto, $accommodation) ->willReturn([ 'boardServices' => [$boardService], 'additionalServices' => [$additionalService], 'groupedAdditionalServices' => [], 'ungroupedAdditionalServices' => [$additionalService], ]); $controller = new TestableAccommodationStep3Controller($bookingService, $sessionManager, $form); $response = $controller->index($request); self::assertSame(200, $response->getStatusCode()); self::assertSame('groups/booking/step_3.html.twig', $controller->renderedView); self::assertSame([$boardService], $controller->renderedParameters['ctx']->boardServices); self::assertSame([$additionalService], $controller->renderedParameters['ctx']->additionalServices); } public function testValidSubmitRedirectsToStep4WithoutPersisting(): void { $dto = new AccommodationBookingDto(); $dto->currentStep = 3; $dto->accommodationId = 1; $dto->isInquiry = false; $session = new Session(new MockArraySessionStorage()); $request = Request::create('/groups/booking/step-3', 'POST'); $request->setSession($session); $sessionManager = new AccommodationSessionManager(); $sessionManager->save($request, $dto); $form = $this->createFormMock(submitted: true, valid: true); $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->expects(self::never())->method('finalizeBooking'); $controller = new TestableAccommodationStep3Controller($bookingService, $sessionManager, $form); $response = $controller->index($request); self::assertInstanceOf(RedirectResponse::class, $response); self::assertSame('/app_groups_booking_step_4', $response->getTargetUrl()); self::assertSame(4, $dto->currentStep); self::assertSame($dto, $sessionManager->getDto($request)); } public function testInvalidSubmitReRendersFormWithErrors(): void { $dto = new AccommodationBookingDto(); $dto->currentStep = 3; $dto->accommodationId = 1; $session = new Session(new MockArraySessionStorage()); $request = Request::create('/groups/booking/step-3', 'POST'); $request->setSession($session); $sessionManager = new AccommodationSessionManager(); $sessionManager->save($request, $dto); $form = $this->createFormMock(submitted: true, valid: false); $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' => [], ]); $controller = new TestableAccommodationStep3Controller($bookingService, $sessionManager, $form); $response = $controller->index($request); self::assertSame(200, $response->getStatusCode()); self::assertSame('groups/booking/step_3.html.twig', $controller->renderedView); self::assertSame(3, $dto->currentStep); self::assertSame($dto, $sessionManager->getDto($request)); } private function createFormMock(bool $submitted, bool $valid): FormInterface { $form = $this->createMock(FormInterface::class); $form->method('handleRequest')->willReturnSelf(); $form->method('isSubmitted')->willReturn($submitted); $form->method('isValid')->willReturn($valid); return $form; } } final class TestableAccommodationStep3Controller extends Step3Controller { /** * @var array */ public array $renderedParameters = []; public string $renderedView = ''; public function __construct( AccommodationBookingService $bookingService, AccommodationSessionManager $sessionManager, private readonly FormInterface $form, ) { parent::__construct($bookingService, $sessionManager); } /** * @return FormInterface */ protected function createForm(string $type, mixed $data = null, array $options = []): FormInterface { return $this->form; } /** * @param array $parameters */ protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse { return new RedirectResponse('/'.$route, $status); } /** * @param array $parameters */ protected function generateUrl(string $route, array $parameters = [], int $referenceType = 1): string { return '/'.$route; } protected function addFlash(string $type, mixed $message): void { // no-op: avoids requiring a full service container in these unit tests } /** * @param array $parameters */ protected function render(string $view, array $parameters = [], ?Response $response = null): Response { $this->renderedView = $view; $this->renderedParameters = $parameters; return new Response('ok'); } }