From 99b6876ce2e4807e57ee6b6cc6f2ec3d39d4bb9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 24 Jun 2026 16:12:52 +0200 Subject: [PATCH] fix: ensure mandatory services are preselected in edit mode --- .../Booking/Edit/IndexController.php | 5 + .../Booking/Edit/ParticipantController.php | 11 ++ .../Booking/Edit/IndexControllerTest.php | 20 ++- .../Edit/ParticipantControllerTest.php | 123 ++++++++++++++++++ 4 files changed, 158 insertions(+), 1 deletion(-) diff --git a/src/Controller/Booking/Edit/IndexController.php b/src/Controller/Booking/Edit/IndexController.php index f4d5ea6..be0e109 100644 --- a/src/Controller/Booking/Edit/IndexController.php +++ b/src/Controller/Booking/Edit/IndexController.php @@ -12,6 +12,7 @@ use App\Form\Model\BookingDto; use App\Htmx\HxTrait; use App\Model\BookingEditSubmissionResult; use App\Service\BookingChangeTracker; +use App\Service\BookingConfigurator; use App\Service\BookingEditContextFactory; use App\Service\BookingEditDataLoader; use App\Service\BookingEditDraftManager; @@ -42,6 +43,7 @@ class IndexController extends AbstractController private readonly BookingEditDraftManager $draftService, private readonly BookingSessionManager $bookingSessionService, private readonly BookingChangeTracker $fingerprintService, + private readonly BookingConfigurator $bookingConfigurator, private readonly BookingEditContextFactory $editContextFactory, private readonly BookingEditPreFlightChecker $preFlightChecker, private readonly BookingEditSubmitter $formSubmitter, @@ -92,6 +94,9 @@ class IndexController extends AbstractController return $this->redirectToRoute('app_bookings'); } + $this->bookingConfigurator->preselectDefaultServices($bookingDto); + $this->bookingSessionService->saveBookingDto($request, $bookingDto, BookingDto::MODE_EDIT); + // Show flash message if draft was restored if (true === $this->dataLoader->isDraftRestored()) { $this->addFlash('info', 'Dein zuvor gespeicherter Entwurf wurde wiederhergestellt.'); diff --git a/src/Controller/Booking/Edit/ParticipantController.php b/src/Controller/Booking/Edit/ParticipantController.php index 31809ea..27c0c0f 100644 --- a/src/Controller/Booking/Edit/ParticipantController.php +++ b/src/Controller/Booking/Edit/ParticipantController.php @@ -13,6 +13,7 @@ use App\Htmx\HxTrait; use App\Service\BookingEditContextFactory; use App\Service\BookingEditDataLoader; use App\Service\BookingEditDraftManager; +use App\Service\BookingConfigurator; use App\Service\ParticipantDataPrefiller; use App\Service\BookingSessionManager; use App\Service\ParticipantFormSupport; @@ -34,6 +35,7 @@ class ParticipantController extends AbstractController private readonly BookingEditDataLoader $dataLoader, private readonly BookingEditDraftManager $draftService, private readonly BookingEditContextFactory $editContextFactory, + private readonly BookingConfigurator $bookingConfigurator, private readonly BookingSessionManager $bookingSessionService, private readonly ParticipantDataPrefiller $prepopulationService, private readonly ParticipantFormSupport $participantFormSupportService, @@ -148,6 +150,15 @@ class ParticipantController extends AbstractController $form->handleRequest($request); + $this->bookingConfigurator->preselectDefaultServices($bookingDto); + $this->bookingSessionService->saveBookingDto($request, $bookingDto, BookingDto::MODE_EDIT); + + $form = $this->createForm( + BookingParticipantType::class, + $this->participantFormSupportService->createParticipantEditDto($bookingDto, $index), + $this->participantFormSupportService->getParticipantFormOptions($bookingDto, true) + ); + $notifications = $this->participantFormSupportService->collectAndClearNotifications($bookingDto); $context = $this->editContextFactory->createParticipantContext( $bookingDto, diff --git a/tests/Controller/Booking/Edit/IndexControllerTest.php b/tests/Controller/Booking/Edit/IndexControllerTest.php index bd14c04..8cc02ff 100644 --- a/tests/Controller/Booking/Edit/IndexControllerTest.php +++ b/tests/Controller/Booking/Edit/IndexControllerTest.php @@ -16,6 +16,7 @@ use App\Form\Model\ParticipantCardDataDto; use App\Form\Model\ParticipantCardPriceDto; use App\Model\BookingEditSubmissionResult; use App\Service\BookingChangeTracker; +use App\Service\BookingConfigurator; use App\Service\BookingEditContextFactory; use App\Service\BookingEditDataLoader; use App\Service\BookingEditDraftManager; @@ -81,6 +82,16 @@ class IndexControllerTest extends TestCase ->method('isDraftRestored') ->willReturn(false); + $bookingSessionService = $this->createMock(BookingSessionManager::class); + $bookingSessionService->expects($this->once()) + ->method('saveBookingDto') + ->with($request, $bookingDto, BookingDto::MODE_EDIT); + + $bookingConfigurator = $this->createMock(BookingConfigurator::class); + $bookingConfigurator->expects($this->once()) + ->method('preselectDefaultServices') + ->with($bookingDto); + $preflightChecker = $this->createMock(BookingEditPreFlightChecker::class); $preflightChecker->expects($this->once()) ->method('findMissingValueLabelsByParticipantIndex') @@ -96,8 +107,9 @@ class IndexControllerTest extends TestCase $controller = new TestableIndexController( $dataLoader, $this->createMock(BookingEditDraftManager::class), - $this->createMock(BookingSessionManager::class), + $bookingSessionService, $fingerprintService, + $bookingConfigurator, $contextFactory, $preflightChecker, $this->createMock(BookingEditSubmitter::class), @@ -170,6 +182,7 @@ class IndexControllerTest extends TestCase $this->createMock(BookingEditDraftManager::class), $this->createMock(BookingSessionManager::class), $fingerprintService, + $this->createMock(BookingConfigurator::class), $contextFactory, $preflightChecker, $this->createMock(BookingEditSubmitter::class), @@ -253,6 +266,7 @@ class IndexControllerTest extends TestCase $this->createMock(BookingEditDraftManager::class), $this->createMock(BookingSessionManager::class), $fingerprintService, + $this->createMock(BookingConfigurator::class), $contextFactory, $preflightChecker, $submitter, @@ -335,6 +349,7 @@ class IndexControllerTest extends TestCase $this->createMock(BookingEditDraftManager::class), $this->createMock(BookingSessionManager::class), $fingerprintService, + $this->createMock(BookingConfigurator::class), $contextFactory, $preflightChecker, $submitter, @@ -526,6 +541,7 @@ class IndexControllerTest extends TestCase $this->createMock(BookingEditDraftManager::class), $this->createMock(BookingSessionManager::class), $fingerprintService, + $this->createMock(BookingConfigurator::class), $this->createMock(BookingEditContextFactory::class), $preflightChecker, $submitter, @@ -566,6 +582,7 @@ final class TestableIndexController extends IndexController BookingEditDraftManager $draftService, BookingSessionManager $bookingSessionService, BookingChangeTracker $fingerprintService, + BookingConfigurator $bookingService, BookingEditContextFactory $editContextFactory, BookingEditPreFlightChecker $preFlightChecker, BookingEditSubmitter $formSubmitter, @@ -579,6 +596,7 @@ final class TestableIndexController extends IndexController $draftService, $bookingSessionService, $fingerprintService, + $bookingService, $editContextFactory, $preFlightChecker, $formSubmitter, diff --git a/tests/Controller/Booking/Edit/ParticipantControllerTest.php b/tests/Controller/Booking/Edit/ParticipantControllerTest.php index 36f65dd..2e330cd 100644 --- a/tests/Controller/Booking/Edit/ParticipantControllerTest.php +++ b/tests/Controller/Booking/Edit/ParticipantControllerTest.php @@ -15,6 +15,7 @@ use App\Form\Model\BookingEditContext; use App\Form\Model\BookingSummaryDto; use App\Form\Model\ParticipantDto; use App\Form\Model\ParticipantEditDto; +use App\Service\BookingConfigurator; use App\Service\BookingEditContextFactory; use App\Service\BookingEditDataLoader; use App\Service\BookingEditDraftManager; @@ -26,6 +27,7 @@ use Carbon\CarbonImmutable; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -126,6 +128,7 @@ class ParticipantControllerTest extends TestCase $dataLoader, $draftService, $contextFactory, + $this->createMock(BookingConfigurator::class), $bookingSessionService, $prepopulationService, $participantFormSupportService, @@ -145,6 +148,109 @@ class ParticipantControllerTest extends TestCase $this->assertSame(BookingParticipantType::class, $controller->createdFormTypes[1]); } + public function testRefreshPreselectsDefaultServicesAndRerendersParticipantForm(): void + { + $request = Request::create('/bookings/42/edit/participants/0/refresh', 'POST'); + $user = $this->createUser(); + $bookingDto = $this->createBookingDto(); + $bookingData = $this->createBooking(); + $participant = $bookingDto->participants[0]; + + $form = $this->createMock(FormInterface::class); + $form->expects($this->once()) + ->method('handleRequest') + ->with($request) + ->willReturnSelf(); + $form->expects($this->once()) + ->method('createView') + ->willReturn(new FormView()); + + $dataLoader = $this->createMock(BookingEditDataLoader::class); + $dataLoader->expects($this->once()) + ->method('fetchBookingData') + ->with(42, $user) + ->willReturn($bookingData); + + $bookingSessionService = $this->createMock(BookingSessionManager::class); + $bookingSessionService->expects($this->once()) + ->method('getBookingDto') + ->with($request, BookingDto::MODE_EDIT) + ->willReturn($bookingDto); + $bookingSessionService->expects($this->once()) + ->method('saveBookingDto') + ->with($request, $bookingDto, BookingDto::MODE_EDIT); + + $bookingConfigurator = $this->createMock(BookingConfigurator::class); + $bookingConfigurator->expects($this->once()) + ->method('preselectDefaultServices') + ->with($bookingDto); + + $participantFormSupportService = $this->createMock(ParticipantFormSupport::class); + $participantFormSupportService->expects($this->once()) + ->method('ensureParticipantExists') + ->with($bookingDto, 0) + ->willReturn($participant); + $participantFormSupportService->expects($this->exactly(2)) + ->method('createParticipantEditDto') + ->with($bookingDto, 0) + ->willReturn(new ParticipantEditDto($participant, $bookingDto)); + $participantFormSupportService->expects($this->exactly(2)) + ->method('getParticipantFormOptions') + ->with($bookingDto, true) + ->willReturn([ + 'booking_context' => $bookingDto, + 'body_dimension_ranges' => [ + 'height_min' => 100, + 'height_max' => 250, + 'weight_min' => 20, + 'weight_max' => 200, + 'shoe_size_min' => 20, + 'shoe_size_max' => 55, + ], + 'validation_groups' => false, + ]); + $participantFormSupportService->expects($this->once()) + ->method('collectAndClearNotifications') + ->with($bookingDto) + ->willReturn([]); + + $summaryData = $this->createMock(BookingSummaryDto::class); + $context = new BookingEditContext($bookingDto, $bookingData, null, $summaryData); + + $contextFactory = $this->createMock(BookingEditContextFactory::class); + $contextFactory->expects($this->once()) + ->method('prepareBookingDto') + ->with($bookingDto); + $contextFactory->expects($this->once()) + ->method('createParticipantContext') + ->with($bookingDto, $bookingData) + ->willReturn($context); + + $prepopulationService = new ParticipantDataPrefiller( + $this->createMock(ApiClient::class), + $this->createMock(Crypt::class), + $this->createMock(LoggerInterface::class), + ); + + $controller = new TestableParticipantController( + $dataLoader, + $this->createMock(BookingEditDraftManager::class), + $contextFactory, + $bookingConfigurator, + $bookingSessionService, + $prepopulationService, + $participantFormSupportService, + $user, + $form, + ); + + $response = $controller->refreshParticipantForm(42, 0, $request); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame([BookingParticipantType::class, BookingParticipantType::class], $controller->createdFormTypes); + $this->assertSame(['participant_form', 'booking_summary'], $controller->renderedBlocks); + } + private function createUser(): User { return new User('tester@example.com'); @@ -184,6 +290,11 @@ final class TestableParticipantController extends ParticipantController public ?string $renderTemplate = null; + /** + * @var list + */ + public array $renderedBlocks = []; + /** * @var array */ @@ -201,6 +312,7 @@ final class TestableParticipantController extends ParticipantController BookingEditDataLoader $dataLoader, BookingEditDraftManager $draftService, BookingEditContextFactory $editContextFactory, + BookingConfigurator $bookingService, BookingSessionManager $bookingSessionService, ParticipantDataPrefiller $prepopulationService, ParticipantFormSupport $participantFormSupportService, @@ -213,6 +325,7 @@ final class TestableParticipantController extends ParticipantController $dataLoader, $draftService, $editContextFactory, + $bookingService, $bookingSessionService, $prepopulationService, $participantFormSupportService, @@ -256,4 +369,14 @@ final class TestableParticipantController extends ParticipantController return new Response(''); } + + /** + * @param array $parameters + */ + protected function renderBlockView(string $view, string $block, array $parameters = []): string + { + $this->renderedBlocks[] = $block; + + return '
'; + } }