Add test coverage for remaining controllers, fix label accessibility, refactor duplicated code

New tests (47 added, 622 total):
- MonitorMessengerCommand: no failures, failures with email, null error, multiple (4)
- UnsubscribeController: unsubscribe with invitations refused + admin notified (1)
- AdminController: suspend/reactivate orga, orders page with filters, logs, invite orga submit/empty, delete/resend invitation, export CSV/PDF (13)
- AccountController: export CSV/PDF, getAllowedBilletTypes (free/basic/sur-mesure/null), billet type restriction, finance stats all statuses, soldCounts (9)
- HomeController: city filter, date filter, all filters combined, stock route (4)
- OrderController: event ended, invalid cart JSON, invalid email, stock zero (4)
- MailerService: getAdminEmail, getAdminFrom (2)
- JS: comment node, tabs missing panel/id/parent, cart stock polling edge cases (10)

Accessibility fixes:
- events.html.twig: add for/id on search, city, date labels
- admin/orders.html.twig: add for/id on search, status labels

Code quality:
- cart.js: remove dead ternaire branch (max > 10 always plural)
- tabs.js: use optional chaining for tablist?.setAttribute
- MeilisearchConsistencyCommand: extract diffAndReport() (was duplicated 3x)
- Email templates: extract _order_items_table.html.twig partial
- SonarQube: exclude src/Entity/** from CPD

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-03-23 12:11:07 +01:00
parent 42d06dd49f
commit c2ebd291b8
10 changed files with 518 additions and 14 deletions

View File

@@ -1954,6 +1954,80 @@ class AccountControllerTest extends WebTestCase
return $category;
}
public function testGetAllowedBilletTypesBasic(): void
{
$types = \App\Controller\AccountController::getAllowedBilletTypes('basic');
self::assertSame(['billet', 'reservation_brocante', 'vote'], $types);
}
public function testGetAllowedBilletTypesSurMesure(): void
{
$types = \App\Controller\AccountController::getAllowedBilletTypes('sur-mesure');
self::assertSame(['billet', 'reservation_brocante', 'vote'], $types);
}
public function testGetAllowedBilletTypesFree(): void
{
$types = \App\Controller\AccountController::getAllowedBilletTypes('free');
self::assertSame(['billet'], $types);
}
public function testGetAllowedBilletTypesNull(): void
{
$types = \App\Controller\AccountController::getAllowedBilletTypes(null);
self::assertSame(['billet'], $types);
}
public function testAddBilletTypeRestriction(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $this->createUser(['ROLE_ORGANIZER'], true);
$user->setOffer('free');
$em->flush();
$event = $this->createEvent($em, $user);
$category = $this->createCategory($em, $event);
$client->loginUser($user);
$client->request('POST', '/mon-compte/evenement/'.$event->getId().'/categorie/'.$category->getId().'/billet/ajouter', [
'name' => 'Vote Interdit',
'price_ht' => '5',
'type' => 'vote',
'is_generated_billet' => '1',
]);
self::assertResponseRedirects();
$billet = $em->getRepository(\App\Entity\Billet::class)->findOneBy(['name' => 'Vote Interdit']);
self::assertNotNull($billet);
self::assertSame('billet', $billet->getType());
}
public function testExportCsv(): void
{
$client = static::createClient();
$user = $this->createUser(['ROLE_ORGANIZER'], true);
$client->loginUser($user);
$client->request('GET', '/mon-compte/export/2026/3');
self::assertResponseIsSuccessful();
self::assertStringContainsString('text/csv', $client->getResponse()->headers->get('Content-Type'));
}
public function testExportPdf(): void
{
$client = static::createClient();
$user = $this->createUser(['ROLE_ORGANIZER'], true);
$client->loginUser($user);
$client->request('GET', '/mon-compte/export/2026/3/pdf');
self::assertResponseIsSuccessful();
self::assertStringContainsString('application/pdf', $client->getResponse()->headers->get('Content-Type'));
}
public function testOrganizerFinanceStatsWithAllStatuses(): void
{
$client = static::createClient();

View File

@@ -645,4 +645,199 @@ class AdminControllerTest extends WebTestCase
return $orga;
}
public function testSuspendOrganizer(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$admin = $this->createUser(['ROLE_ROOT']);
$orga = $this->createOrganizer($em);
$orga->setIsApproved(true);
$em->flush();
$client->loginUser($admin);
$client->request('POST', '/admin/organisateur/'.$orga->getId().'/suspendre');
self::assertResponseRedirects('/admin/organisateurs?tab=approved');
$em->refresh($orga);
self::assertTrue($orga->isSuspended());
}
public function testReactivateOrganizer(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$admin = $this->createUser(['ROLE_ROOT']);
$orga = $this->createOrganizer($em);
$orga->setIsApproved(true);
$orga->setIsSuspended(true);
$em->flush();
$client->loginUser($admin);
$client->request('POST', '/admin/organisateur/'.$orga->getId().'/suspendre');
self::assertResponseRedirects('/admin/organisateurs?tab=approved');
$em->refresh($orga);
self::assertNull($orga->isSuspended());
}
public function testOrdersPage(): void
{
$client = static::createClient();
$admin = $this->createUser(['ROLE_ROOT']);
$client->loginUser($admin);
$client->request('GET', '/admin/commandes');
self::assertResponseIsSuccessful();
}
public function testOrdersPageWithFilters(): void
{
$client = static::createClient();
$admin = $this->createUser(['ROLE_ROOT']);
$client->loginUser($admin);
$client->request('GET', '/admin/commandes?status=paid&q=test');
self::assertResponseIsSuccessful();
}
public function testLogsPage(): void
{
$client = static::createClient();
$admin = $this->createUser(['ROLE_ROOT']);
$client->loginUser($admin);
$client->request('GET', '/admin/logs');
self::assertResponseIsSuccessful();
}
public function testInviteOrganizerPage(): void
{
$client = static::createClient();
$admin = $this->createUser(['ROLE_ROOT']);
$mailer = $this->createMock(\App\Service\MailerService::class);
static::getContainer()->set(\App\Service\MailerService::class, $mailer);
$client->loginUser($admin);
$client->request('GET', '/admin/organisateurs/inviter');
self::assertResponseIsSuccessful();
}
public function testInviteOrganizerSubmit(): void
{
$client = static::createClient();
$admin = $this->createUser(['ROLE_ROOT']);
$mailer = $this->createMock(\App\Service\MailerService::class);
$mailer->expects(self::once())->method('sendEmail');
static::getContainer()->set(\App\Service\MailerService::class, $mailer);
$client->loginUser($admin);
$client->request('POST', '/admin/organisateurs/inviter', [
'company_name' => 'New Asso',
'first_name' => 'Jean',
'last_name' => 'Invite',
'email' => 'invite-admin-'.uniqid().'@example.com',
'offer' => 'basic',
'commission_rate' => '2.5',
]);
self::assertResponseRedirects('/admin/organisateurs/inviter');
}
public function testInviteOrganizerEmptyFields(): void
{
$client = static::createClient();
$admin = $this->createUser(['ROLE_ROOT']);
$client->loginUser($admin);
$client->request('POST', '/admin/organisateurs/inviter', [
'company_name' => '',
'first_name' => '',
'last_name' => '',
'email' => '',
]);
self::assertResponseRedirects('/admin/organisateurs/inviter');
}
public function testDeleteInvitation(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$admin = $this->createUser(['ROLE_ROOT']);
$invitation = new \App\Entity\OrganizerInvitation();
$invitation->setCompanyName('Del Asso');
$invitation->setFirstName('Del');
$invitation->setLastName('Test');
$invitation->setEmail('del-'.uniqid().'@example.com');
$em->persist($invitation);
$em->flush();
$client->loginUser($admin);
$client->request('POST', '/admin/organisateurs/invitation/'.$invitation->getId().'/supprimer');
self::assertResponseRedirects('/admin/organisateurs/inviter');
}
public function testResendInvitation(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$admin = $this->createUser(['ROLE_ROOT']);
$mailer = $this->createMock(\App\Service\MailerService::class);
$mailer->expects(self::once())->method('sendEmail');
static::getContainer()->set(\App\Service\MailerService::class, $mailer);
$invitation = new \App\Entity\OrganizerInvitation();
$invitation->setCompanyName('Resend Asso');
$invitation->setFirstName('Resend');
$invitation->setLastName('Test');
$invitation->setEmail('resend-'.uniqid().'@example.com');
$invitation->setStatus(\App\Entity\OrganizerInvitation::STATUS_ACCEPTED);
$em->persist($invitation);
$em->flush();
$client->loginUser($admin);
$client->request('POST', '/admin/organisateurs/invitation/'.$invitation->getId().'/renvoyer');
self::assertResponseRedirects('/admin/organisateurs/inviter');
$freshEm = static::getContainer()->get(EntityManagerInterface::class);
$updated = $freshEm->getRepository(\App\Entity\OrganizerInvitation::class)->find($invitation->getId());
self::assertSame(\App\Entity\OrganizerInvitation::STATUS_SENT, $updated->getStatus());
}
public function testExportCsv(): void
{
$client = static::createClient();
$admin = $this->createUser(['ROLE_ROOT']);
$client->loginUser($admin);
$client->request('GET', '/admin/export/2026/3');
self::assertResponseIsSuccessful();
self::assertStringContainsString('text/csv', $client->getResponse()->headers->get('Content-Type'));
}
public function testExportPdf(): void
{
$client = static::createClient();
$admin = $this->createUser(['ROLE_ROOT']);
$client->loginUser($admin);
$client->request('GET', '/admin/export/2026/3/pdf');
self::assertResponseIsSuccessful();
self::assertStringContainsString('application/pdf', $client->getResponse()->headers->get('Content-Type'));
}
}

View File

@@ -153,6 +153,61 @@ class HomeControllerTest extends WebTestCase
self::assertResponseIsSuccessful();
}
public function testEventsPageWithCityFilter(): void
{
$client = static::createClient();
$client->request('GET', '/evenements?city=Paris');
self::assertResponseIsSuccessful();
}
public function testEventsPageWithDateFilter(): void
{
$client = static::createClient();
$client->request('GET', '/evenements?date=2026-08-01');
self::assertResponseIsSuccessful();
}
public function testEventsPageWithAllFilters(): void
{
$client = static::createClient();
$client->request('GET', '/evenements?q=test&city=Paris&date=2026-08-01');
self::assertResponseIsSuccessful();
}
public function testEventStockRoute(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = new User();
$user->setEmail('test-stock-'.uniqid().'@example.com');
$user->setFirstName('Stock');
$user->setLastName('Test');
$user->setPassword('hashed');
$user->setRoles(['ROLE_ORGANIZER']);
$user->setIsApproved(true);
$user->setIsVerified(true);
$em->persist($user);
$event = new \App\Entity\Event();
$event->setAccount($user);
$event->setTitle('Stock 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);
$em->flush();
$client->request('GET', '/evenement/'.$event->getId().'/stock');
self::assertResponseIsSuccessful();
}
public function testEventDetailNotFoundReturns404(): void
{
$client = static::createClient();

View File

@@ -2,6 +2,9 @@
namespace App\Tests\Controller;
use App\Entity\OrganizerInvitation;
use App\Service\MailerService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class UnsubscribeControllerTest extends WebTestCase
@@ -31,4 +34,42 @@ class UnsubscribeControllerTest extends WebTestCase
self::assertResponseIsSuccessful();
}
public function testPostRefusesInvitationsAndNotifiesAdmin(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$email = 'unsub-invite-'.uniqid().'@example.com';
$invitation = new OrganizerInvitation();
$invitation->setCompanyName('Asso Unsub');
$invitation->setFirstName('Test');
$invitation->setLastName('Unsub');
$invitation->setEmail($email);
$invitation->setStatus(OrganizerInvitation::STATUS_SENT);
$em->persist($invitation);
$em->flush();
$mailer = $this->createMock(MailerService::class);
$mailer->expects(self::once())->method('sendEmail')->with(
$this->anything(),
$this->stringContains('Desinscription'),
$this->stringContains($email),
null,
null,
false,
);
static::getContainer()->set(MailerService::class, $mailer);
$token = base64_encode($email);
$client->request('POST', '/unsubscribe/'.$token);
self::assertResponseIsSuccessful();
$freshEm = static::getContainer()->get(EntityManagerInterface::class);
$updated = $freshEm->getRepository(OrganizerInvitation::class)->find($invitation->getId());
self::assertSame(OrganizerInvitation::STATUS_REFUSED, $updated->getStatus());
self::assertNotNull($updated->getRespondedAt());
}
}