Add isHidden to Category, category CRUD tests, coverage improvements
- Add isHidden field to Category entity with migration (DEFAULT false for existing rows) - Add isHidden checkbox to edit category template and "Masquee" badge on category list - Save isHidden in editCategory controller method - Fix Category.isActive() indentation - Create CategoryTest with full coverage (14 tests): defaults, setters, setEvent logic, isActive, isHidden - Add category CRUD tests to AccountControllerTest: add/edit/delete/reorder categories with access control - Add cookie-consent tests for dev env early return and Cloudflare tunnel script - Exclude PayoutPdfService from phpunit coverage and SonarQube analysis Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -838,6 +838,299 @@ class AccountControllerTest extends WebTestCase
|
||||
self::assertResponseStatusCodeSame(403);
|
||||
}
|
||||
|
||||
public function testAddCategory(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
|
||||
$event = $this->createEvent($em, $user);
|
||||
|
||||
$client->loginUser($user);
|
||||
$client->request('POST', '/mon-compte/evenement/'.$event->getId().'/categorie/ajouter', [
|
||||
'name' => 'VIP',
|
||||
'start_at' => '2026-06-01T10:00',
|
||||
'end_at' => '2026-07-31T18:00',
|
||||
]);
|
||||
|
||||
self::assertResponseRedirects('/mon-compte/evenement/'.$event->getId().'/modifier?tab=categories');
|
||||
}
|
||||
|
||||
public function testAddCategoryEmptyName(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
|
||||
$event = $this->createEvent($em, $user);
|
||||
|
||||
$client->loginUser($user);
|
||||
$client->request('POST', '/mon-compte/evenement/'.$event->getId().'/categorie/ajouter', [
|
||||
'name' => '',
|
||||
]);
|
||||
|
||||
self::assertResponseRedirects('/mon-compte/evenement/'.$event->getId().'/modifier?tab=categories');
|
||||
}
|
||||
|
||||
public function testAddCategoryDeniedForOtherUser(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$owner = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
$other = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
|
||||
$event = $this->createEvent($em, $owner);
|
||||
|
||||
$client->loginUser($other);
|
||||
$client->request('POST', '/mon-compte/evenement/'.$event->getId().'/categorie/ajouter', [
|
||||
'name' => 'Hack',
|
||||
]);
|
||||
|
||||
self::assertResponseStatusCodeSame(403);
|
||||
}
|
||||
|
||||
public function testAddCategoryWithInvertedDates(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
|
||||
$event = $this->createEvent($em, $user);
|
||||
|
||||
$client->loginUser($user);
|
||||
$client->request('POST', '/mon-compte/evenement/'.$event->getId().'/categorie/ajouter', [
|
||||
'name' => 'Inverted',
|
||||
'start_at' => '2026-08-01T10:00',
|
||||
'end_at' => '2026-06-01T10:00',
|
||||
]);
|
||||
|
||||
self::assertResponseRedirects('/mon-compte/evenement/'.$event->getId().'/modifier?tab=categories');
|
||||
|
||||
$category = $em->getRepository(\App\Entity\Category::class)->findOneBy(['name' => 'Inverted']);
|
||||
self::assertNotNull($category);
|
||||
self::assertGreaterThanOrEqual($category->getStartAt(), $category->getEndAt());
|
||||
}
|
||||
|
||||
public function testEditCategoryPage(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
|
||||
$event = $this->createEvent($em, $user);
|
||||
$category = $this->createCategory($em, $event);
|
||||
|
||||
$client->loginUser($user);
|
||||
$client->request('GET', '/mon-compte/evenement/'.$event->getId().'/categorie/'.$category->getId().'/modifier');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
public function testEditCategorySubmit(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
|
||||
$event = $this->createEvent($em, $user);
|
||||
$category = $this->createCategory($em, $event);
|
||||
|
||||
$client->loginUser($user);
|
||||
$client->request('POST', '/mon-compte/evenement/'.$event->getId().'/categorie/'.$category->getId().'/modifier', [
|
||||
'name' => 'Updated Name',
|
||||
'start_at' => '2026-06-01T10:00',
|
||||
'end_at' => '2026-07-31T18:00',
|
||||
'is_hidden' => '1',
|
||||
]);
|
||||
|
||||
self::assertResponseRedirects('/mon-compte/evenement/'.$event->getId().'/modifier?tab=categories');
|
||||
|
||||
$em->refresh($category);
|
||||
self::assertSame('Updated Name', $category->getName());
|
||||
self::assertTrue($category->isHidden());
|
||||
}
|
||||
|
||||
public function testEditCategoryWithInvertedDates(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
|
||||
$event = $this->createEvent($em, $user);
|
||||
$category = $this->createCategory($em, $event);
|
||||
|
||||
$client->loginUser($user);
|
||||
$client->request('POST', '/mon-compte/evenement/'.$event->getId().'/categorie/'.$category->getId().'/modifier', [
|
||||
'name' => 'Inverted Edit',
|
||||
'start_at' => '2026-08-01T10:00',
|
||||
'end_at' => '2026-06-01T10:00',
|
||||
]);
|
||||
|
||||
self::assertResponseRedirects('/mon-compte/evenement/'.$event->getId().'/modifier?tab=categories');
|
||||
|
||||
$em->refresh($category);
|
||||
self::assertGreaterThanOrEqual($category->getStartAt(), $category->getEndAt());
|
||||
}
|
||||
|
||||
public function testEditCategoryDeniedForOtherUser(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$owner = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
$other = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
|
||||
$event = $this->createEvent($em, $owner);
|
||||
$category = $this->createCategory($em, $event);
|
||||
|
||||
$client->loginUser($other);
|
||||
$client->request('GET', '/mon-compte/evenement/'.$event->getId().'/categorie/'.$category->getId().'/modifier');
|
||||
|
||||
self::assertResponseStatusCodeSame(403);
|
||||
}
|
||||
|
||||
public function testEditCategoryNotFound(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
|
||||
$event = $this->createEvent($em, $user);
|
||||
|
||||
$client->loginUser($user);
|
||||
$client->request('GET', '/mon-compte/evenement/'.$event->getId().'/categorie/999999/modifier');
|
||||
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
public function testDeleteCategory(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
|
||||
$event = $this->createEvent($em, $user);
|
||||
$category = $this->createCategory($em, $event);
|
||||
$categoryId = $category->getId();
|
||||
|
||||
$client->loginUser($user);
|
||||
$client->request('POST', '/mon-compte/evenement/'.$event->getId().'/categorie/'.$categoryId.'/supprimer');
|
||||
|
||||
self::assertResponseRedirects('/mon-compte/evenement/'.$event->getId().'/modifier?tab=categories');
|
||||
|
||||
self::assertNull($em->getRepository(\App\Entity\Category::class)->find($categoryId));
|
||||
}
|
||||
|
||||
public function testDeleteCategoryDeniedForOtherUser(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$owner = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
$other = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
|
||||
$event = $this->createEvent($em, $owner);
|
||||
$category = $this->createCategory($em, $event);
|
||||
|
||||
$client->loginUser($other);
|
||||
$client->request('POST', '/mon-compte/evenement/'.$event->getId().'/categorie/'.$category->getId().'/supprimer');
|
||||
|
||||
self::assertResponseStatusCodeSame(403);
|
||||
}
|
||||
|
||||
public function testReorderCategories(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
|
||||
$event = $this->createEvent($em, $user);
|
||||
$cat1 = $this->createCategory($em, $event, 'Cat A', 0);
|
||||
$cat2 = $this->createCategory($em, $event, 'Cat B', 1);
|
||||
|
||||
$client->loginUser($user);
|
||||
$client->request(
|
||||
'POST',
|
||||
'/mon-compte/evenement/'.$event->getId().'/categorie/reorder',
|
||||
[],
|
||||
[],
|
||||
['CONTENT_TYPE' => 'application/json'],
|
||||
json_encode([$cat2->getId(), $cat1->getId()])
|
||||
);
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
|
||||
$em->refresh($cat1);
|
||||
$em->refresh($cat2);
|
||||
self::assertSame(1, $cat1->getPosition());
|
||||
self::assertSame(0, $cat2->getPosition());
|
||||
}
|
||||
|
||||
public function testReorderCategoriesDeniedForOtherUser(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$owner = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
$other = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
|
||||
$event = $this->createEvent($em, $owner);
|
||||
|
||||
$client->loginUser($other);
|
||||
$client->request(
|
||||
'POST',
|
||||
'/mon-compte/evenement/'.$event->getId().'/categorie/reorder',
|
||||
[],
|
||||
[],
|
||||
['CONTENT_TYPE' => 'application/json'],
|
||||
'[]'
|
||||
);
|
||||
|
||||
self::assertResponseStatusCodeSame(403);
|
||||
}
|
||||
|
||||
public function testEditEventCategoriesTab(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
||||
|
||||
$event = $this->createEvent($em, $user);
|
||||
$this->createCategory($em, $event);
|
||||
|
||||
$client->loginUser($user);
|
||||
$client->request('GET', '/mon-compte/evenement/'.$event->getId().'/modifier?tab=categories');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
private function createEvent(EntityManagerInterface $em, User $user): \App\Entity\Event
|
||||
{
|
||||
$event = new \App\Entity\Event();
|
||||
$event->setAccount($user);
|
||||
$event->setTitle('Test Event '.uniqid());
|
||||
$event->setStartAt(new \DateTimeImmutable('2026-08-01 10:00'));
|
||||
$event->setEndAt(new \DateTimeImmutable('2026-08-01 18:00'));
|
||||
$event->setAddress('1 rue test');
|
||||
$event->setZipcode('75001');
|
||||
$event->setCity('Paris');
|
||||
$em->persist($event);
|
||||
$em->flush();
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
private function createCategory(EntityManagerInterface $em, \App\Entity\Event $event, string $name = 'Test Cat', int $position = 0): \App\Entity\Category
|
||||
{
|
||||
$category = new \App\Entity\Category();
|
||||
$category->setName($name);
|
||||
$category->setEvent($event);
|
||||
$category->setPosition($position);
|
||||
$category->setStartAt(new \DateTimeImmutable('2026-06-01 10:00'));
|
||||
$category->setEndAt(new \DateTimeImmutable('2026-07-31 18:00'));
|
||||
$em->persist($category);
|
||||
$em->flush();
|
||||
|
||||
return $category;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $roles
|
||||
*/
|
||||
|
||||
168
tests/Entity/CategoryTest.php
Normal file
168
tests/Entity/CategoryTest.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\Category;
|
||||
use App\Entity\Event;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CategoryTest extends TestCase
|
||||
{
|
||||
public function testNewCategoryDefaults(): void
|
||||
{
|
||||
$category = new Category();
|
||||
|
||||
self::assertNull($category->getId());
|
||||
self::assertNull($category->getName());
|
||||
self::assertNull($category->getEvent());
|
||||
self::assertSame(0, $category->getPosition());
|
||||
self::assertFalse($category->isHidden());
|
||||
self::assertInstanceOf(\DateTimeImmutable::class, $category->getCreatedAt());
|
||||
self::assertInstanceOf(\DateTimeImmutable::class, $category->getStartAt());
|
||||
self::assertInstanceOf(\DateTimeImmutable::class, $category->getEndAt());
|
||||
}
|
||||
|
||||
public function testSetAndGetName(): void
|
||||
{
|
||||
$category = new Category();
|
||||
$result = $category->setName('VIP');
|
||||
|
||||
self::assertSame('VIP', $category->getName());
|
||||
self::assertSame($category, $result);
|
||||
}
|
||||
|
||||
public function testSetAndGetPosition(): void
|
||||
{
|
||||
$category = new Category();
|
||||
$result = $category->setPosition(3);
|
||||
|
||||
self::assertSame(3, $category->getPosition());
|
||||
self::assertSame($category, $result);
|
||||
}
|
||||
|
||||
public function testSetAndGetStartAt(): void
|
||||
{
|
||||
$category = new Category();
|
||||
$date = new \DateTimeImmutable('2026-06-01 10:00:00');
|
||||
$result = $category->setStartAt($date);
|
||||
|
||||
self::assertSame($date, $category->getStartAt());
|
||||
self::assertSame($category, $result);
|
||||
}
|
||||
|
||||
public function testSetAndGetEndAt(): void
|
||||
{
|
||||
$category = new Category();
|
||||
$date = new \DateTimeImmutable('2026-06-15 18:00:00');
|
||||
$result = $category->setEndAt($date);
|
||||
|
||||
self::assertSame($date, $category->getEndAt());
|
||||
self::assertSame($category, $result);
|
||||
}
|
||||
|
||||
public function testSetAndGetIsHidden(): void
|
||||
{
|
||||
$category = new Category();
|
||||
$result = $category->setIsHidden(true);
|
||||
|
||||
self::assertTrue($category->isHidden());
|
||||
self::assertSame($category, $result);
|
||||
|
||||
$category->setIsHidden(false);
|
||||
self::assertFalse($category->isHidden());
|
||||
}
|
||||
|
||||
public function testSetEventSetsEndAtToOneDayBeforeEventStart(): void
|
||||
{
|
||||
$event = new Event();
|
||||
$event->setStartAt(new \DateTimeImmutable('+60 days'));
|
||||
|
||||
$category = new Category();
|
||||
$result = $category->setEvent($event);
|
||||
|
||||
self::assertSame($event, $category->getEvent());
|
||||
self::assertSame($category, $result);
|
||||
|
||||
$expectedEnd = $event->getStartAt()->modify('-1 day');
|
||||
self::assertSame(
|
||||
$expectedEnd->format('Y-m-d H:i:s'),
|
||||
$category->getEndAt()->format('Y-m-d H:i:s')
|
||||
);
|
||||
}
|
||||
|
||||
public function testSetEventWithPastStartAtUsesEventStartAt(): void
|
||||
{
|
||||
$event = new Event();
|
||||
$event->setStartAt(new \DateTimeImmutable('-1 day'));
|
||||
|
||||
$category = new Category();
|
||||
$category->setEvent($event);
|
||||
|
||||
// endCandidate (-2 days) < startAt (now), so endAt = event.startAt
|
||||
self::assertSame(
|
||||
$event->getStartAt()->format('Y-m-d H:i:s'),
|
||||
$category->getEndAt()->format('Y-m-d H:i:s')
|
||||
);
|
||||
}
|
||||
|
||||
public function testSetEventNull(): void
|
||||
{
|
||||
$category = new Category();
|
||||
$originalEnd = $category->getEndAt();
|
||||
|
||||
$category->setEvent(null);
|
||||
|
||||
self::assertNull($category->getEvent());
|
||||
self::assertSame($originalEnd, $category->getEndAt());
|
||||
}
|
||||
|
||||
public function testIsActiveWhenNowIsBetweenStartAndEnd(): void
|
||||
{
|
||||
$category = new Category();
|
||||
$category->setStartAt(new \DateTimeImmutable('-1 hour'));
|
||||
$category->setEndAt(new \DateTimeImmutable('+1 hour'));
|
||||
|
||||
self::assertTrue($category->isActive());
|
||||
}
|
||||
|
||||
public function testIsActiveWhenNowIsBeforeStart(): void
|
||||
{
|
||||
$category = new Category();
|
||||
$category->setStartAt(new \DateTimeImmutable('+1 hour'));
|
||||
$category->setEndAt(new \DateTimeImmutable('+2 hours'));
|
||||
|
||||
self::assertFalse($category->isActive());
|
||||
}
|
||||
|
||||
public function testIsActiveWhenNowIsAfterEnd(): void
|
||||
{
|
||||
$category = new Category();
|
||||
$category->setStartAt(new \DateTimeImmutable('-2 hours'));
|
||||
$category->setEndAt(new \DateTimeImmutable('-1 hour'));
|
||||
|
||||
self::assertFalse($category->isActive());
|
||||
}
|
||||
|
||||
public function testGetCreatedAt(): void
|
||||
{
|
||||
$before = new \DateTimeImmutable();
|
||||
$category = new Category();
|
||||
$after = new \DateTimeImmutable();
|
||||
|
||||
self::assertGreaterThanOrEqual($before, $category->getCreatedAt());
|
||||
self::assertLessThanOrEqual($after, $category->getCreatedAt());
|
||||
}
|
||||
|
||||
public function testSetEventWithNullStartAt(): void
|
||||
{
|
||||
$event = new Event();
|
||||
|
||||
$category = new Category();
|
||||
$originalEnd = $category->getEndAt();
|
||||
$category->setEvent($event);
|
||||
|
||||
// Event has no startAt set, so endAt should not change
|
||||
self::assertNull($event->getStartAt());
|
||||
self::assertSame($originalEnd, $category->getEndAt());
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,16 @@ class StripeServiceTest extends TestCase
|
||||
{
|
||||
private function createService(): StripeService
|
||||
{
|
||||
return new StripeService('sk_test', 'whsec_test', 'https://example.com');
|
||||
return new StripeService('sk_test', 'whsec_test', 'whsec_connect_test', 'https://example.com');
|
||||
}
|
||||
|
||||
public function testVerifyWebhookSignatureReturnsNullOnInvalid(): void
|
||||
{
|
||||
self::assertNull($this->createService()->verifyWebhookSignature('{}', 'invalid'));
|
||||
}
|
||||
|
||||
public function testVerifyConnectWebhookSignatureReturnsNullOnInvalid(): void
|
||||
{
|
||||
self::assertNull($this->createService()->verifyConnectWebhookSignature('{}', 'invalid'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,4 +91,28 @@ describe('initCookieConsent', () => {
|
||||
const script = document.querySelector('script[data-analytics]')
|
||||
expect(script).not.toBeNull()
|
||||
})
|
||||
|
||||
it('does not load analytics in dev environment', () => {
|
||||
document.body.dataset.env = 'dev'
|
||||
document.cookie = 'e_ticket_consent=accepted;path=/'
|
||||
initCookieConsent()
|
||||
const script = document.querySelector('script[data-analytics]')
|
||||
expect(script).toBeNull()
|
||||
})
|
||||
|
||||
it('loads cloudflare tunnel script on accept', () => {
|
||||
initCookieConsent()
|
||||
document.getElementById('cookie-accept').click()
|
||||
const script = document.querySelector('script[data-cf-beacon]')
|
||||
expect(script).not.toBeNull()
|
||||
expect(script.src).toContain('/assets/perf.js')
|
||||
})
|
||||
|
||||
it('does not duplicate cloudflare script', () => {
|
||||
document.cookie = 'e_ticket_consent=accepted;path=/'
|
||||
initCookieConsent()
|
||||
initCookieConsent()
|
||||
const scripts = document.querySelectorAll('script[data-cf-beacon]')
|
||||
expect(scripts.length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user