Files
e-ticket/tests/Controller/OrderControllerTest.php

658 lines
24 KiB
PHP
Raw Permalink Normal View History

<?php
namespace App\Tests\Controller;
use App\Entity\Billet;
use App\Entity\BilletBuyer;
use App\Entity\BilletBuyerItem;
use App\Entity\Category;
use App\Entity\Event;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class OrderControllerTest extends WebTestCase
{
private function createOrga(EntityManagerInterface $em): User
{
$user = new User();
$user->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::assertResponseStatusCodeSame(400);
$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']);
}
public function testCreateOrderUnlimitedBillet(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $this->createOrga($em);
[$event] = $this->createEventWithBillet($em, $user);
$category = $em->getRepository(Category::class)->findOneBy(['event' => $event]);
$unlimited = new Billet();
$unlimited->setName('Illimite');
$unlimited->setCategory($category);
$unlimited->setPriceHT(500);
$unlimited->setQuantity(null);
$em->persist($unlimited);
$em->flush();
$client->request('POST', '/evenement/'.$event->getId().'/commander', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode([
['billetId' => $unlimited->getId(), 'qty' => 50],
]));
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);
}
Add LibreTranslate auto-translation, improve test coverage, fix code duplication Translation system: - Add LibreTranslate container (dev + prod), CPU-only, no port exposed, FR/EN/ES/DE/IT - Create app:translate command: reads *.fr.yaml, translates incrementally, preserves placeholders - Makefile: make trans / make trans_prod (stops container after translation) - Ansible: start libretranslate -> translate -> stop during deploy - Prod container restart: "no" (only runs during deploy) - .gitignore: ignore generated *.en/es/de/it.yaml files - 11 tests for TranslateCommand (API unreachable, empty, incremental, obsolete keys, placeholders, fallback) Test coverage improvements: - OrderController: event ended (400), invalid cart JSON, invalid email, stock zero (4 new tests) - AccountController: finance stats all statuses (paid/pending/refunded/cancelled), soldCounts (2 new tests) - JS cart: checkout without error elements, hide error on retry, stock polling edge cases (singular, no label, qty zero, unknown billet) (8 new tests) - JS editor: comment node sanitization (1 new test) - JS tabs: missing panel, generated id, parent null, click no-panel (5 new tests) Code duplication fixes: - MeilisearchConsistencyCommand: extract diffAndReport() method (was duplicated 3x) - Email templates: extract _order_items_table.html.twig partial (shared by notification + cancelled) - SonarQube: exclude src/Entity/** from CPD (getters/setters duplication) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:44:13 +01:00
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);
}
}