'on']); $client->disableReboot(); $bookingDto = $this->createBookingDto(); $context = $this->createBookingContext($bookingDto); $bookingSessionManager = $this->createMock(BookingSessionManager::class); $bookingSessionManager ->expects(self::exactly(2)) ->method('getOrCreateBookingCreateDto') ->willReturn($bookingDto); $contextFactory = $this->createMock(BookingCreateContextFactory::class); $contextFactory ->expects(self::exactly(2)) ->method('createWithParticipantPrices') ->with($bookingDto) ->willReturn($context); $userRepository = $this->createMock(UserRepository::class); $userRepository ->expects(self::exactly(2)) ->method('findOneByNormalizedEmail') ->with('customer@example.com') ->willReturn(null); $guestAccountManager = $this->createMock(BookingGuestAccountManager::class); $guestAccountManager ->expects(self::once()) ->method('provisionGuestAccountForBooking') ->with('customer@example.com', 123, 'Alice', 'Example') ->willReturn(true); $bookingConfigurator = $this->createMock(BookingConfigurator::class); $bookingConfigurator ->expects(self::once()) ->method('applyCreateBookingStatusRules'); $apiClient = $this->createMock(ApiClient::class); $apiClient ->expects(self::once()) ->method('createBooking') ->with($bookingDto) ->willReturn(new BookingResponse(BookingResponse::BOOKING_PROCESSED, 123)); $newsletterManager = $this->createMock(NewsletterManager::class); $newsletterManager->expects(self::never())->method('requestConfirmation'); self::getContainer()->set(BookingSessionManager::class, $bookingSessionManager); self::getContainer()->set(BookingCreateContextFactory::class, $contextFactory); self::getContainer()->set(UserRepository::class, $userRepository); self::getContainer()->set(BookingGuestAccountManager::class, $guestAccountManager); self::getContainer()->set(BookingConfigurator::class, $bookingConfigurator); self::getContainer()->set(ApiClient::class, $apiClient); self::getContainer()->set(NewsletterManager::class, $newsletterManager); $crawler = $client->request('GET', '/bookings/create/confirmation'); self::assertResponseIsSuccessful(); self::assertStringContainsString('Benutzeraccount in MyE&P anlegen', $client->getResponse()->getContent()); $form = $crawler->selectButton('Verbindlich buchen')->form([ 'booking_create_step4[confirmationAccepted]' => '1', 'booking_create_step4[termsAccepted]' => '1', 'booking_create_step4[guestAccountOptIn]' => '1', ]); $client->submit($form); self::assertResponseRedirects('/bookings/create/success'); } public function testStep4HidesGuestAccountCheckboxForAuthenticatedUsers(): void { $bookingDto = $this->createBookingDto(); $userRepository = $this->createMock(UserRepository::class); $userRepository->expects(self::never())->method('findOneByNormalizedEmail'); $controller = $this->createAuthenticatedController( user: new User('logged@example.com'), userRepository: $userRepository, ); self::assertNull($controller->exposeGuestAccountTargetEmail($bookingDto)); } public function testStep4HidesGuestAccountCheckboxWhenLocalUserAlreadyExists(): void { $client = self::createClient([], ['HTTPS' => 'on']); $client->disableReboot(); $bookingDto = $this->createBookingDto(); $context = $this->createBookingContext($bookingDto); $bookingSessionManager = $this->createMock(BookingSessionManager::class); $bookingSessionManager ->expects(self::once()) ->method('getOrCreateBookingCreateDto') ->willReturn($bookingDto); $contextFactory = $this->createMock(BookingCreateContextFactory::class); $contextFactory ->expects(self::once()) ->method('createWithParticipantPrices') ->with($bookingDto) ->willReturn($context); $userRepository = $this->createMock(UserRepository::class); $userRepository ->expects(self::once()) ->method('findOneByNormalizedEmail') ->with('customer@example.com') ->willReturn(new User('customer@example.com')); $guestAccountManager = $this->createMock(BookingGuestAccountManager::class); $guestAccountManager->expects(self::never())->method('provisionGuestAccount'); self::getContainer()->set(BookingSessionManager::class, $bookingSessionManager); self::getContainer()->set(BookingCreateContextFactory::class, $contextFactory); self::getContainer()->set(UserRepository::class, $userRepository); self::getContainer()->set(BookingGuestAccountManager::class, $guestAccountManager); $client->request('GET', '/bookings/create/confirmation'); self::assertResponseIsSuccessful(); self::assertStringNotContainsString('Benutzeraccount in MyE&P anlegen', $client->getResponse()->getContent()); } public function testGuestAccountActivationRedirectsToPersonalDataAndForcesPasswordChange(): void { $client = self::createClient([], ['HTTPS' => 'on']); $client->disableReboot(); $guestAccountManager = $this->createMock(BookingGuestAccountManager::class); $guestAccountManager ->expects(self::once()) ->method('validateActivationRequest') ->with(self::isInstanceOf(\Symfony\Component\HttpFoundation\Request::class)) ->willReturn(new User('customer@example.com')); $guestAccountManager ->expects(self::never()) ->method('consumeActivation'); $security = $this->createMock(Security::class); $security ->expects(self::once()) ->method('login') ->with(self::isInstanceOf(User::class), null, 'main') ->willReturn(null); self::getContainer()->set(BookingGuestAccountManager::class, $guestAccountManager); self::getContainer()->set(Security::class, $security); $client->request('GET', '/welcome/activate?email=customer@example.com&nonce=current-nonce&expires=9999999999&_hash=signature'); self::assertResponseRedirects('/personal-data'); self::assertTrue($client->getRequest()->getSession()->get(\App\Controller\Account\PersonalDataController::SESSION_FORCE_PASSWORD_CHANGE_KEY)); } private function createBookingDto(): BookingDto { $travel = new Travel(); $travel->id = 123; $travel->label = 'Winterreise'; $travel->productCode = 'PROD-1'; $travel->dateFrom = new \DateTimeImmutable('2026-01-01'); $travel->dateTo = new \DateTimeImmutable('2026-01-08'); $hotel = new Hotel(); $hotel->name = 'Hotel Test'; $travel->hotel = $hotel; $bookingDto = new BookingDto($travel, 77); $bookingDto->currentStep = 4; $bookingDto->bookingStatus = 'F'; $bookingDto->paymentMethod = Constants::PAYMENT_METHOD_TRANSFER; $participant = new ParticipantDto(); $participant->index = 0; $participant->firstName = 'Alice'; $participant->lastName = 'Example'; $participant->email = 'Customer@Example.COM'; $participant->gender = 'M'; $participant->nationality = 'DE'; $participant->dateOfBirth = new \DateTimeImmutable('1990-01-01'); $bookingDto->participants = [$participant]; return $bookingDto; } private function createBookingContext(BookingDto $bookingDto): BookingCreateContext { $pricing = new BookingSummaryPricingDto([], [], null, [], 0.0); $vouchers = new BookingSummaryVoucherDto(null, 0.0); $summary = new BookingSummaryDto([], 1, $pricing, $vouchers, null); return new BookingCreateContext( bookingDto: $bookingDto, summaryData: $summary, groupedRooms: new RoomGroupsDto([], []), participantPrices: [0.0], ); } private function createAuthenticatedController(User $user, UserRepository $userRepository): TestableStep4Controller { return new TestableStep4Controller( $this->createMock(BookingConfigurator::class), $this->createMock(BookingSessionManager::class), $this->createMock(BookingCreateContextFactory::class), $this->createMock(BookingGuestAccountManager::class), $this->createMock(ApiClient::class), $this->createMock(\Symfony\Contracts\Cache\CacheInterface::class), $this->createMock(NewsletterManager::class), $userRepository, $this->createMock(\Psr\Log\LoggerInterface::class), $user, ); } } final class TestableStep4Controller extends \App\Controller\Booking\Create\Step4Controller { public function __construct( BookingConfigurator $bookingService, BookingSessionManager $bookingSessionService, BookingCreateContextFactory $createContextFactory, BookingGuestAccountManager $guestAccountManager, ApiClient $apiClient, \Symfony\Contracts\Cache\CacheInterface $cache, NewsletterManager $doubleOptInService, UserRepository $userRepository, \Psr\Log\LoggerInterface $logger, private readonly User $user, ) { parent::__construct( $bookingService, $bookingSessionService, $createContextFactory, $guestAccountManager, $apiClient, $cache, $doubleOptInService, $userRepository, $logger, ); } public function exposeGuestAccountTargetEmail(BookingDto $bookingDto): ?string { return $this->resolveGuestAccountTargetEmail($bookingDto); } protected function getUser(): \Symfony\Component\Security\Core\User\UserInterface|null { return $this->user; } }