sessionManager = new VoucherPurchaseSessionManager(); $this->purchaseFlowResolver = new PurchaseFlowResolver(); } public function testStartsAFreshPurchaseAndSendsTheVisitorToTheLogin(): void { $request = $this->createRequest(); $template = new VoucherTemplate(3, 'K', 'Allgemein'); $templateProvider = $this->createStub(VoucherTemplateProvider::class); $templateProvider->method('getTemplates')->willReturn(new VoucherTemplates(10, 5000, [$template])); $templateProvider->method('getPaymentMethod')->willReturn(new VoucherPaymentMethod(2, 'Überweisung', 'UEBERW')); $response = $this->createController($templateProvider)->index($request, 'https://www.ep-reisen.de/gutscheine'); self::assertInstanceOf(RedirectResponse::class, $response); self::assertSame('/app_login', $response->getTargetUrl()); $dto = $this->sessionManager->find($request); self::assertSame(10, $dto?->minAmount); self::assertSame(5000, $dto->maxAmount); self::assertSame($template, $dto->template); self::assertSame('https://www.ep-reisen.de/gutscheine', $this->sessionManager->getReturnUrl($request)); self::assertSame(PurchaseFlow::Voucher, $this->purchaseFlowResolver->resolvePending($request)); } /** * Idempotent so that a speculative fetch in the visitor's own browser cannot wipe their draft. */ public function testResumesAnUnfinishedPurchaseAndOnlyUpdatesTheReturnUrl(): void { $request = $this->createRequest(); $draft = new VoucherPurchaseDto(); $draft->currentStep = 2; $draft->amount = 111; $this->sessionManager->save($request, $draft); $templateProvider = $this->createMock(VoucherTemplateProvider::class); $templateProvider->expects(self::never())->method('getTemplates'); $response = $this->createController($templateProvider)->index($request, 'https://www.snowzone.net/'); self::assertSame('/app_login', $response->getTargetUrl()); self::assertSame($draft, $this->sessionManager->find($request)); self::assertSame(2, $draft->currentStep); self::assertSame('https://www.snowzone.net/', $this->sessionManager->getReturnUrl($request)); } public function testAnUnavailableShopShowsTheErrorPage(): void { $request = $this->createRequest(); $templateProvider = $this->createStub(VoucherTemplateProvider::class); $templateProvider->method('getTemplates')->willThrowException(new VoucherShopUnavailableException()); $controller = $this->createController($templateProvider); $response = $controller->index($request); self::assertSame('/app_voucher_error', $response->getTargetUrl()); self::assertNull($this->sessionManager->find($request)); self::assertSame('error', $controller->flashes[0][0] ?? null); } private function createRequest(): Request { $request = Request::create('/gutschein'); $request->setSession(new Session(new MockArraySessionStorage())); return $request; } private function createController(VoucherTemplateProvider $templateProvider): TestableIndexController { return new TestableIndexController($this->sessionManager, $templateProvider, $this->purchaseFlowResolver); } } final class TestableIndexController extends IndexController { /** * @var list */ public array $flashes = []; protected function addFlash(string $type, mixed $message): void { $this->flashes[] = [$type, $message]; } /** * @param array $parameters */ protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse { return new RedirectResponse('/'.$route, $status); } }