setEmail('orga-order-'.uniqid().'@test.fr'); $user->setFirstName('Orga'); $user->setLastName('Test'); $user->setPassword('hashed'); $user->setRoles(['ROLE_ORGANIZER']); $user->setIsApproved(true); $user->setIsVerified(true); $user->setCompanyName('Asso Test'); $user->setStripeAccountId('acct_test'); $em->persist($user); return $user; } private function createEventWithBillet(EntityManagerInterface $em, User $user): array { $event = new Event(); $event->setAccount($user); $event->setTitle('Order Event'); $event->setStartAt(new \DateTimeImmutable('2026-08-01 10:00')); $event->setEndAt(new \DateTimeImmutable('2026-08-01 18:00')); $event->setAddress('1 rue'); $event->setZipcode('75001'); $event->setCity('Paris'); $event->setIsOnline(true); $em->persist($event); $category = new Category(); $category->setName('Cat'); $category->setEvent($event); $category->setStartAt(new \DateTimeImmutable('-1 day')); $category->setEndAt(new \DateTimeImmutable('+30 days')); $em->persist($category); $billet = new Billet(); $billet->setName('Entree'); $billet->setCategory($category); $billet->setPriceHT(1500); $billet->setQuantity(10); $em->persist($billet); $em->flush(); return [$event, $billet]; } private function createOrder(EntityManagerInterface $em, Event $event, Billet $billet, ?User $user = null): BilletBuyer { $count = $em->getRepository(BilletBuyer::class)->count([]) + 1; $order = new BilletBuyer(); $order->setEvent($event); $order->setFirstName('Jean'); $order->setLastName('Dupont'); $order->setEmail('jean@test.fr'); $order->setOrderNumber('2026-03-21-'.$count); $order->setTotalHT(1500); if ($user) { $order->setUser($user); } $item = new BilletBuyerItem(); $item->setBillet($billet); $item->setBilletName('Entree'); $item->setQuantity(1); $item->setUnitPriceHT(1500); $order->addItem($item); $em->persist($order); $em->flush(); return $order; } // === CREATE === public function testCreateOrderNotFound(): void { $client = static::createClient(); $client->request('POST', '/evenement/999999/commander', [], [], ['CONTENT_TYPE' => 'application/json'], '[]'); self::assertResponseStatusCodeSame(404); } public function testCreateOrderEmptyCart(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event] = $this->createEventWithBillet($em, $user); $client->request('POST', '/evenement/'.$event->getId().'/commander', [], [], ['CONTENT_TYPE' => 'application/json'], '[]'); self::assertResponseIsSuccessful(); $data = json_decode($client->getResponse()->getContent(), true); self::assertArrayHasKey('redirect', $data); } public function testCreateOrderAsGuest(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $client->request('POST', '/evenement/'.$event->getId().'/commander', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode([ ['billetId' => $billet->getId(), 'qty' => 2], ])); self::assertResponseIsSuccessful(); $data = json_decode($client->getResponse()->getContent(), true); self::assertStringContainsString('/commande/', $data['redirect']); self::assertStringContainsString('/informations', $data['redirect']); } public function testCreateOrderAsLoggedUser(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $buyer = new User(); $buyer->setEmail('buyer-'.uniqid().'@test.fr'); $buyer->setFirstName('Buyer'); $buyer->setLastName('Test'); $buyer->setPassword('hashed'); $em->persist($buyer); $em->flush(); $client->loginUser($buyer); $client->request('POST', '/evenement/'.$event->getId().'/commander', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode([ ['billetId' => $billet->getId(), 'qty' => 1], ])); self::assertResponseIsSuccessful(); $data = json_decode($client->getResponse()->getContent(), true); self::assertStringContainsString('/paiement', $data['redirect']); } public function testCreateOrderInvalidBillet(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event] = $this->createEventWithBillet($em, $user); $client->request('POST', '/evenement/'.$event->getId().'/commander', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode([ ['billetId' => 999999, 'qty' => 1], ])); self::assertResponseIsSuccessful(); $data = json_decode($client->getResponse()->getContent(), true); self::assertArrayHasKey('redirect', $data); } public function testCreateOrderNotBuyableBillet(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event] = $this->createEventWithBillet($em, $user); $notBuyable = new Billet(); $notBuyable->setName('Not Buyable'); $notBuyable->setCategory($em->getRepository(Category::class)->findOneBy(['event' => $event])); $notBuyable->setPriceHT(500); $notBuyable->setNotBuyable(true); $em->persist($notBuyable); $em->flush(); $client->request('POST', '/evenement/'.$event->getId().'/commander', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode([ ['billetId' => $notBuyable->getId(), 'qty' => 1], ])); self::assertResponseIsSuccessful(); } public function testCreateOrderExceedsQuantity(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $client->request('POST', '/evenement/'.$event->getId().'/commander', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode([ ['billetId' => $billet->getId(), 'qty' => 999], ])); self::assertResponseIsSuccessful(); $data = json_decode($client->getResponse()->getContent(), true); self::assertStringContainsString('/informations', $data['redirect']); } // === GUEST === public function testGuestPageNotFound(): void { $client = static::createClient(); $client->request('GET', '/commande/999999/informations'); self::assertResponseStatusCodeSame(404); } public function testGuestPageRenders(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = $this->createOrder($em, $event, $billet); $client->request('GET', '/commande/'.$order->getId().'/informations'); self::assertResponseIsSuccessful(); } public function testGuestSubmitEmptyFields(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = $this->createOrder($em, $event, $billet); $client->request('POST', '/commande/'.$order->getId().'/informations', [ 'first_name' => '', 'last_name' => '', 'email' => '', ]); self::assertResponseRedirects('/commande/'.$order->getId().'/informations'); } public function testGuestSubmitSuccess(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = new BilletBuyer(); $order->setEvent($event); $order->setTotalHT(1500); $order->setOrderNumber('2026-03-21-'.random_int(100, 999)); $item = new BilletBuyerItem(); $item->setBillet($billet); $item->setBilletName('Test'); $item->setQuantity(1); $item->setUnitPriceHT(1500); $order->addItem($item); $em->persist($order); $em->flush(); $client->request('POST', '/commande/'.$order->getId().'/informations', [ 'first_name' => 'Jean', 'last_name' => 'Dupont', 'email' => 'jean@test.fr', ]); self::assertResponseRedirects('/commande/'.$order->getId().'/paiement'); } public function testGuestRedirectsIfUserSet(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $buyer = new User(); $buyer->setEmail('buyer-guest-'.uniqid().'@test.fr'); $buyer->setFirstName('B'); $buyer->setLastName('T'); $buyer->setPassword('hashed'); $em->persist($buyer); $order = $this->createOrder($em, $event, $billet, $buyer); $client->request('GET', '/commande/'.$order->getId().'/informations'); self::assertResponseRedirects('/commande/'.$order->getId().'/paiement'); } // === PAYMENT === public function testPaymentNotFound(): void { $client = static::createClient(); $client->request('GET', '/commande/999999/paiement'); self::assertResponseStatusCodeSame(404); } public function testPaymentRedirectsIfNoName(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = new BilletBuyer(); $order->setEvent($event); $order->setTotalHT(1500); $order->setOrderNumber('2026-03-21-'.random_int(100, 999)); $em->persist($order); $em->flush(); $client->request('GET', '/commande/'.$order->getId().'/paiement'); self::assertResponseRedirects('/commande/'.$order->getId().'/informations'); } public function testPaymentRendersWithStripe(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = $this->createOrder($em, $event, $billet); $client->request('GET', '/commande/'.$order->getId().'/paiement'); self::assertResponseIsSuccessful(); } public function testPaymentNotFoundWithoutStripeAccount(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = new User(); $user->setEmail('no-stripe-'.uniqid().'@test.fr'); $user->setFirstName('No'); $user->setLastName('Stripe'); $user->setPassword('hashed'); $user->setRoles(['ROLE_ORGANIZER']); $user->setIsApproved(true); $user->setIsVerified(true); $em->persist($user); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = $this->createOrder($em, $event, $billet); $client->request('GET', '/commande/'.$order->getId().'/paiement'); self::assertResponseStatusCodeSame(404); } // === SUCCESS === public function testSuccessNotFound(): void { $client = static::createClient(); $client->request('GET', '/commande/999999/confirmation'); self::assertResponseStatusCodeSame(404); } public function testSuccessRenders(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = $this->createOrder($em, $event, $billet); $client->request('GET', '/commande/'.$order->getId().'/confirmation'); self::assertResponseIsSuccessful(); } public function testSuccessFailed(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = $this->createOrder($em, $event, $billet); $client->request('GET', '/commande/'.$order->getId().'/confirmation?redirect_status=failed'); self::assertResponseIsSuccessful(); } public function testSuccessSucceeded(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = $this->createOrder($em, $event, $billet); $billetOrderService = $this->createMock(\App\Service\BilletOrderService::class); $billetOrderService->expects(self::once())->method('generateOrderTickets'); $billetOrderService->expects(self::once())->method('generateAndSendTickets'); static::getContainer()->set(\App\Service\BilletOrderService::class, $billetOrderService); $client->request('GET', '/commande/'.$order->getId().'/confirmation?redirect_status=succeeded'); self::assertResponseIsSuccessful(); } // === PUBLIC ORDER === public function testPublicOrderNotFound(): void { $client = static::createClient(); $client->request('GET', '/ma-commande/2026-03-21-999/invalidtoken'); self::assertResponseStatusCodeSame(404); } public function testPublicOrderRenders(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = $this->createOrder($em, $event, $billet); $client->request('GET', '/ma-commande/'.$order->getOrderNumber().'/'.$order->getAccessToken()); self::assertResponseIsSuccessful(); } // === INVOICE === public function testInvoiceNotFound(): void { $client = static::createClient(); $client->request('GET', '/ma-commande/2026-03-21-999/invalidtoken/facture'); self::assertResponseStatusCodeSame(404); } public function testInvoiceNotPaid(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = $this->createOrder($em, $event, $billet); $client->request('GET', '/ma-commande/'.$order->getOrderNumber().'/'.$order->getAccessToken().'/facture'); self::assertResponseStatusCodeSame(404); } public function testInvoiceSuccess(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = $this->createOrder($em, $event, $billet); $order->setStatus(BilletBuyer::STATUS_PAID); $em->flush(); $invoiceService = $this->createMock(\App\Service\InvoiceService::class); $invoiceService->expects(self::once())->method('generatePdf')->willReturn('%PDF-1.4 fake'); static::getContainer()->set(\App\Service\InvoiceService::class, $invoiceService); $client->request('GET', '/ma-commande/'.$order->getOrderNumber().'/'.$order->getAccessToken().'/facture'); self::assertResponseIsSuccessful(); self::assertResponseHeaderSame('Content-Type', 'application/pdf'); } // === DOWNLOAD TICKET === public function testDownloadTicketNotFoundOrder(): void { $client = static::createClient(); $client->request('GET', '/ma-commande/2026-03-21-999/badtoken/billet/ETICKET-AAAA-BBBB-CCCC'); self::assertResponseStatusCodeSame(404); } public function testDownloadTicketNotFoundTicket(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = $this->createOrder($em, $event, $billet); $client->request('GET', '/ma-commande/'.$order->getOrderNumber().'/'.$order->getAccessToken().'/billet/ETICKET-ZZZZ-ZZZZ-ZZZZ'); self::assertResponseStatusCodeSame(404); } public function testDownloadTicketSuccess(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = $this->createOrder($em, $event, $billet); $ticket = new \App\Entity\BilletOrder(); $ticket->setBilletBuyer($order); $ticket->setBillet($billet); $ticket->setBilletName('Entree'); $ticket->setUnitPriceHT(1500); $em->persist($ticket); $em->flush(); $billetOrderService = $this->createMock(\App\Service\BilletOrderService::class); $billetOrderService->expects(self::once())->method('generatePdf')->willReturn('%PDF-1.4 fake'); static::getContainer()->set(\App\Service\BilletOrderService::class, $billetOrderService); $client->request('GET', '/ma-commande/'.$order->getOrderNumber().'/'.$order->getAccessToken().'/billet/'.$ticket->getReference()); self::assertResponseIsSuccessful(); self::assertResponseHeaderSame('Content-Type', 'application/pdf'); } // === CREATE with qty <= 0 === public function testCreateOrderZeroQty(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $client->request('POST', '/evenement/'.$event->getId().'/commander', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode([ ['billetId' => $billet->getId(), 'qty' => 0], ])); self::assertResponseIsSuccessful(); $data = json_decode($client->getResponse()->getContent(), true); self::assertArrayHasKey('redirect', $data); } public function testCreateOrderEventEnded(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); $event = new Event(); $event->setAccount($user); $event->setTitle('Past Event'); $event->setStartAt(new \DateTimeImmutable('-2 days')); $event->setEndAt(new \DateTimeImmutable('-1 day')); $event->setAddress('1 rue'); $event->setZipcode('75001'); $event->setCity('Paris'); $event->setIsOnline(true); $em->persist($event); $em->flush(); $client->request('POST', '/evenement/'.$event->getId().'/commander', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode([ ['billetId' => 1, 'qty' => 1], ])); self::assertResponseStatusCodeSame(400); $data = json_decode($client->getResponse()->getContent(), true); self::assertStringContainsString('termine', $data['error']); } public function testCreateOrderInvalidCartStructure(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event] = $this->createEventWithBillet($em, $user); $client->request('POST', '/evenement/'.$event->getId().'/commander', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode([ ['invalid' => 'data'], ])); self::assertResponseStatusCodeSame(400); } public function testGuestSubmitInvalidEmail(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); [$event, $billet] = $this->createEventWithBillet($em, $user); $order = $this->createOrder($em, $event, $billet); $client->request('POST', '/commande/'.$order->getId().'/informations', [ 'first_name' => 'Jean', 'last_name' => 'Dupont', 'email' => 'not-an-email', ]); self::assertResponseRedirects('/commande/'.$order->getId().'/informations'); } public function testCreateOrderStockZeroSkipsBillet(): void { $client = static::createClient(); $em = static::getContainer()->get(EntityManagerInterface::class); $user = $this->createOrga($em); $event = new Event(); $event->setAccount($user); $event->setTitle('Stock Zero Event'); $event->setStartAt(new \DateTimeImmutable('2026-08-01 10:00')); $event->setEndAt(new \DateTimeImmutable('2026-08-01 18:00')); $event->setAddress('1 rue'); $event->setZipcode('75001'); $event->setCity('Paris'); $event->setIsOnline(true); $em->persist($event); $category = new Category(); $category->setName('Cat'); $category->setEvent($event); $category->setStartAt(new \DateTimeImmutable('-1 day')); $category->setEndAt(new \DateTimeImmutable('+30 days')); $em->persist($category); $billet = new Billet(); $billet->setName('Sold Out'); $billet->setCategory($category); $billet->setPriceHT(1500); $billet->setQuantity(0); $em->persist($billet); $em->flush(); $client->request('POST', '/evenement/'.$event->getId().'/commander', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode([ ['billetId' => $billet->getId(), 'qty' => 1], ])); self::assertResponseIsSuccessful(); $data = json_decode($client->getResponse()->getContent(), true); self::assertArrayHasKey('redirect', $data); } }