diff --git a/config/services_test.yaml b/config/services_test.yaml index 6c7f80480..382164973 100644 --- a/config/services_test.yaml +++ b/config/services_test.yaml @@ -26,6 +26,9 @@ services: Afup\Tests\Support\Wordpress\FakeWordpressClient: ~ AppBundle\Event\Wordpress\WordpressClient: '@Afup\Tests\Support\Wordpress\FakeWordpressClient' + Afup\Tests\Support\Github\FakeGithubClient: ~ + AppBundle\Github\GithubClient: '@Afup\Tests\Support\Github\FakeGithubClient' + AppBundle\Offices\OfficeFinder: class: AppBundle\Offices\NullOfficeFinder autowire: true diff --git a/phpstan-baseline.php b/phpstan-baseline.php index f85ead6d8..6e65e6e1f 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -3271,12 +3271,6 @@ 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Event/StatsAction.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot call method getPrettyName\\(\\) on AppBundle\\\\Event\\\\Model\\\\TicketType\\|null\\.$#', - 'identifier' => 'method.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Event/StatsAction.php', -]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$eventId of method AppBundle\\\\Event\\\\Model\\\\Repository\\\\EventStatsRepository\\:\\:getRegistrationTracking\\(\\) expects int, int\\|null given\\.$#', 'identifier' => 'argument.type', @@ -9103,12 +9097,6 @@ 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Slack/MessageFactory.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot call method getPrettyName\\(\\) on AppBundle\\\\Event\\\\Model\\\\TicketType\\|null\\.$#', - 'identifier' => 'method.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Slack/MessageFactory.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getTitle\\(\\) on AppBundle\\\\Event\\\\Model\\\\Talk\\|null\\.$#', 'identifier' => 'method.nonObject', diff --git a/sources/AppBundle/Command/TicketStatsNotificationCommand.php b/sources/AppBundle/Command/TicketStatsNotificationCommand.php index e23b4b018..ace881024 100644 --- a/sources/AppBundle/Command/TicketStatsNotificationCommand.php +++ b/sources/AppBundle/Command/TicketStatsNotificationCommand.php @@ -4,10 +4,10 @@ namespace AppBundle\Command; +use AppBundle\Event\Entity\Repository\TicketTypeRepository; use AppBundle\Event\Model\Event; use AppBundle\Event\Model\Repository\EventRepository; use AppBundle\Event\Model\Repository\EventStatsRepository; -use AppBundle\Event\Model\Repository\TicketTypeRepository; use AppBundle\Notifier\SlackNotifier; use AppBundle\Slack\MessageFactory; use Symfony\Component\Console\Command\Command; diff --git a/sources/AppBundle/Controller/Admin/Event/AddBilleteriePriveeAction.php b/sources/AppBundle/Controller/Admin/Event/AddBilleteriePriveeAction.php index 99061b727..172037e28 100644 --- a/sources/AppBundle/Controller/Admin/Event/AddBilleteriePriveeAction.php +++ b/sources/AppBundle/Controller/Admin/Event/AddBilleteriePriveeAction.php @@ -7,8 +7,8 @@ use AppBundle\Event\AdminEventSelection; use AppBundle\Event\Entity\BilleteriePrivee; use AppBundle\Event\Entity\Repository\BilleteriePriveeRepository; +use AppBundle\Event\Entity\Repository\TicketTypeRepository; use AppBundle\Event\Form\BilleteriePriveeType; -use AppBundle\Event\Model\Repository\TicketTypeRepository; use DateTimeImmutable; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; @@ -33,7 +33,7 @@ public function __invoke(Request $request, AdminEventSelection $eventSelection): $billeteriePrivee->createdOn = new DateTimeImmutable(); $form = $this->createForm(BilleteriePriveeType::class, $billeteriePrivee, [ - 'ticketTypes' => $this->ticketTypeRepository->getAll(), + 'ticketTypes' => $this->ticketTypeRepository->findAllOrderedById(), ]); $form->handleRequest($request); diff --git a/sources/AppBundle/Controller/Admin/Event/BilleteriePriveeAction.php b/sources/AppBundle/Controller/Admin/Event/BilleteriePriveeAction.php index 8dea5aff8..265b69e9a 100644 --- a/sources/AppBundle/Controller/Admin/Event/BilleteriePriveeAction.php +++ b/sources/AppBundle/Controller/Admin/Event/BilleteriePriveeAction.php @@ -6,7 +6,7 @@ use AppBundle\Event\AdminEventSelection; use AppBundle\Event\Entity\Repository\BilleteriePriveeRepository; -use AppBundle\Event\Model\Repository\TicketTypeRepository; +use AppBundle\Event\Entity\Repository\TicketTypeRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; @@ -23,10 +23,10 @@ public function __invoke(AdminEventSelection $eventSelection): Response $billeteries = []; foreach ($this->billeteriePriveeRepository->findByEvent((int) $event->getId()) as $billeterie) { - $ticketType = $this->ticketTypeRepository->get($billeterie->ticketTypeId); + $ticketType = $this->ticketTypeRepository->find($billeterie->ticketTypeId); $billeteries[] = [ 'billeterie_privee' => $billeterie, - 'ticket_type' => $ticketType !== null ? $ticketType->getPrettyName() : '', + 'ticket_type' => $ticketType !== null ? $ticketType->prettyName : '', 'places_prises' => $this->billeteriePriveeRepository->countPlacesPrisesParToken($billeterie->token), ]; } diff --git a/sources/AppBundle/Controller/Admin/Event/EditBilleteriePriveeAction.php b/sources/AppBundle/Controller/Admin/Event/EditBilleteriePriveeAction.php index 83d4f8750..9192e199c 100644 --- a/sources/AppBundle/Controller/Admin/Event/EditBilleteriePriveeAction.php +++ b/sources/AppBundle/Controller/Admin/Event/EditBilleteriePriveeAction.php @@ -7,8 +7,8 @@ use AppBundle\Controller\Event\EventActionHelper; use AppBundle\Event\Entity\BilleteriePrivee; use AppBundle\Event\Entity\Repository\BilleteriePriveeRepository; +use AppBundle\Event\Entity\Repository\TicketTypeRepository; use AppBundle\Event\Form\BilleteriePriveeType; -use AppBundle\Event\Model\Repository\TicketTypeRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormError; use Symfony\Component\HttpFoundation\Request; @@ -34,7 +34,7 @@ public function __invoke(Request $request, int $event, int $id): Response $maxPlacesActuelles = $billeteriePrivee->maxPlaces; $form = $this->createForm(BilleteriePriveeType::class, $billeteriePrivee, [ - 'ticketTypes' => $this->ticketTypeRepository->getAll(), + 'ticketTypes' => $this->ticketTypeRepository->findAllOrderedById(), 'is_edit' => true, ]); diff --git a/sources/AppBundle/Controller/Admin/Event/StatsAction.php b/sources/AppBundle/Controller/Admin/Event/StatsAction.php index 5ae932c0d..f9a9ee9d1 100644 --- a/sources/AppBundle/Controller/Admin/Event/StatsAction.php +++ b/sources/AppBundle/Controller/Admin/Event/StatsAction.php @@ -5,10 +5,10 @@ namespace AppBundle\Controller\Admin\Event; use AppBundle\Controller\Event\EventActionHelper; +use AppBundle\Event\Entity\Repository\TicketTypeRepository; use AppBundle\Event\Form\EventCompareSelectType; use AppBundle\Event\Model\Repository\EventRepository; use AppBundle\Event\Model\Repository\EventStatsRepository; -use AppBundle\Event\Model\Repository\TicketTypeRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -78,8 +78,8 @@ public function __invoke(Request $request): Response $totalInscrits = array_sum($rawStatsByType); array_walk($rawStatsByType, function (&$item, $key) use (&$ticketTypes, $totalInscrits): void { if (isset($ticketTypes[$key]) === false) { - $type = $this->ticketTypeRepository->get($key); - $ticketTypes[$key] = $type->getPrettyName(); + $type = $this->ticketTypeRepository->find($key); + $ticketTypes[$key] = $type !== null ? $type->prettyName : ''; } $item = ['name' => $ticketTypes[$key], 'y' => $item / $totalInscrits]; }); diff --git a/sources/AppBundle/Event/Entity/Repository/TicketTypeRepository.php b/sources/AppBundle/Event/Entity/Repository/TicketTypeRepository.php new file mode 100644 index 000000000..2eef02230 --- /dev/null +++ b/sources/AppBundle/Event/Entity/Repository/TicketTypeRepository.php @@ -0,0 +1,31 @@ + + */ +final class TicketTypeRepository extends EntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, TicketType::class); + } + + /** + * @return list + */ + public function findAllOrderedById(): array + { + /** @var list $ticketTypes */ + $ticketTypes = $this->findBy([], ['id' => 'ASC']); + + return $ticketTypes; + } +} diff --git a/sources/AppBundle/Event/Entity/TicketType.php b/sources/AppBundle/Event/Entity/TicketType.php new file mode 100644 index 000000000..edb2f616a --- /dev/null +++ b/sources/AppBundle/Event/Entity/TicketType.php @@ -0,0 +1,95 @@ +technicalName, + $this->prettyName, + $this->isRestrictedToMembers ? ' - (réservé aux membres)' : '', + $this->getPrettyDays(), + ); + } + + public function isEarly(): bool + { + return in_array($this->technicalName, self::EARLY_BIRD_TECHNICAL_NAME); + } + + /** + * @return list + */ + public function getDays(): array + { + return explode(',', $this->day); + } + + public function getPrettyDays(): string + { + $days = []; + foreach ($this->getDays() as $day) { + switch ($day) { + case Ticket::DAY_ONE: + $days[] = 'JOUR 1'; + break; + case Ticket::DAY_TWO: + $days[] = 'JOUR 2'; + break; + default: + break; + } + } + + return implode(', ', $days); + } +} diff --git a/sources/AppBundle/Event/Form/BilleteriePriveeType.php b/sources/AppBundle/Event/Form/BilleteriePriveeType.php index 8ff95b0bb..8dd6f7026 100644 --- a/sources/AppBundle/Event/Form/BilleteriePriveeType.php +++ b/sources/AppBundle/Event/Form/BilleteriePriveeType.php @@ -5,7 +5,7 @@ namespace AppBundle\Event\Form; use AppBundle\Event\Entity\BilleteriePrivee; -use AppBundle\Event\Model\TicketType; +use AppBundle\Event\Entity\TicketType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; @@ -108,7 +108,7 @@ private function ticketTypesToChoices(iterable $ticketTypes): array foreach ($ticketTypes as $ticketType) { if ($ticketType instanceof TicketType) { - $choices[$ticketType->getLabel()] = $ticketType->getId(); + $choices[$ticketType->getLabel()] = $ticketType->id; } } diff --git a/sources/AppBundle/Event/Form/TicketType.php b/sources/AppBundle/Event/Form/TicketType.php index 905f5962a..db15a94b9 100644 --- a/sources/AppBundle/Event/Form/TicketType.php +++ b/sources/AppBundle/Event/Form/TicketType.php @@ -7,10 +7,11 @@ use AppBundle\Antennes\AntenneRepository; use AppBundle\Association\Genre; use AppBundle\Event\Entity\BilleteriePrivee; +use AppBundle\Event\Entity\Repository\TicketTypeRepository; use AppBundle\Event\Model\Repository\EventRepository; use AppBundle\Event\Model\Repository\TicketEventTypeRepository; use AppBundle\Event\Model\Repository\TicketSpecialPriceRepository; -use AppBundle\Event\Model\Repository\TicketTypeRepository; +use AppBundle\Event\Model\Repository\TicketTypeRepository as TingTicketTypeRepository; use AppBundle\Event\Model\Ticket; use AppBundle\Event\Model\TicketEventType; use AppBundle\Event\Model\TicketSpecialPrice; @@ -39,6 +40,7 @@ public function __construct( private readonly TicketTypeAvailability $ticketTypeAvailability, private readonly TicketSpecialPriceRepository $ticketSpecialPriceRepository, private readonly TicketTypeRepository $ticketTypeRepository, + private readonly TingTicketTypeRepository $tingTicketTypeRepository, private readonly AntenneRepository $antenneRepository, ) {} @@ -102,8 +104,8 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $choiceLabel = 'ticketType.prettyName'; if ($billeteriePrivee instanceof BilleteriePrivee) { $filteredEventTickets = $this->createBilleteriePriveeTicketEventType($billeteriePrivee, $filteredEventTickets); - $typeDePlace = $this->ticketTypeRepository->get($billeteriePrivee->ticketTypeId); - $choiceLabel = static fn(): string => $typeDePlace !== null ? $typeDePlace->getPrettyName() : 'Billet'; + $typeDePlace = $this->ticketTypeRepository->find($billeteriePrivee->ticketTypeId); + $choiceLabel = static fn(): string => $typeDePlace !== null ? $typeDePlace->prettyName : 'Billet'; } $formEvent->getForm()->add('ticketEventType', ChoiceType::class, [ @@ -181,7 +183,7 @@ public function configureOptions(OptionsResolver $resolver): void */ private function createSpecialPriceTicketEventType(TicketSpecialPrice $ticketSpecialPrice, array $filteredEventTickets): array { - $ticketType = $this->ticketTypeRepository->get(Ticket::TYPE_SPECIAL_PRICE); + $ticketType = $this->tingTicketTypeRepository->get(Ticket::TYPE_SPECIAL_PRICE); if (!$ticketType instanceof \AppBundle\Event\Model\TicketType) { return $filteredEventTickets; } @@ -209,7 +211,7 @@ private function createSpecialPriceTicketEventType(TicketSpecialPrice $ticketSpe */ private function createBilleteriePriveeTicketEventType(BilleteriePrivee $billeteriePrivee, array $filteredEventTickets): array { - $ticketType = $this->ticketTypeRepository->get(Ticket::TYPE_SPECIAL_PRICE); + $ticketType = $this->tingTicketTypeRepository->get(Ticket::TYPE_SPECIAL_PRICE); if (!$ticketType instanceof \AppBundle\Event\Model\TicketType) { return $filteredEventTickets; } diff --git a/sources/AppBundle/Event/Model/TicketType.php b/sources/AppBundle/Event/Model/TicketType.php index 970fa9642..a4b1182dc 100644 --- a/sources/AppBundle/Event/Model/TicketType.php +++ b/sources/AppBundle/Event/Model/TicketType.php @@ -4,6 +4,7 @@ namespace AppBundle\Event\Model; +use AppBundle\Event\Entity\TicketType as DoctrineTicketType; use CCMBenchmark\Ting\Entity\NotifyProperty; use CCMBenchmark\Ting\Entity\NotifyPropertyInterface; @@ -11,8 +12,9 @@ class TicketType implements NotifyPropertyInterface { use NotifyProperty; - public const string SPECIAL_PRICE_TECHNICAL_NAME = 'SPECIAL_PRICE'; - public const array EARLY_BIRD_TECHNICAL_NAME = ['EARLY_BIRD', 'EARLY_BIRD_AFUP', 'AFUP_DAY_EARLY']; + // Constantes portées par l'entité Doctrine AppBundle\Event\Entity\TicketType, source de vérité commune + public const string SPECIAL_PRICE_TECHNICAL_NAME = DoctrineTicketType::SPECIAL_PRICE_TECHNICAL_NAME; + public const array EARLY_BIRD_TECHNICAL_NAME = DoctrineTicketType::EARLY_BIRD_TECHNICAL_NAME; /** * @var int diff --git a/sources/AppBundle/Event/Ticket/TicketOffers.php b/sources/AppBundle/Event/Ticket/TicketOffers.php index 18aadd85c..3b92c3eaf 100644 --- a/sources/AppBundle/Event/Ticket/TicketOffers.php +++ b/sources/AppBundle/Event/Ticket/TicketOffers.php @@ -4,11 +4,10 @@ namespace AppBundle\Event\Ticket; +use AppBundle\Event\Entity\Repository\TicketTypeRepository; use AppBundle\Event\Model\Event; use AppBundle\Event\Model\Repository\TicketEventTypeRepository; -use AppBundle\Event\Model\Repository\TicketTypeRepository; use AppBundle\Event\Model\TicketOffer; -use AppBundle\Event\Model\TicketType; readonly class TicketOffers { @@ -25,14 +24,11 @@ public function getAllOffersForEvent(Event $event): array { $offers = []; - /** @var TicketType[] $ticketTypes */ - $ticketTypes = $this->ticketTypeRepository->getAll(); - foreach ($ticketTypes as $ticketType) { - $ticketTypeId = $ticketType->getId(); - $offers[$ticketTypeId] = new TicketOffer( - $ticketTypeId, - $ticketType->getPrettyName(), - $ticketType->getDefaultPrice(), + foreach ($this->ticketTypeRepository->findAllOrderedById() as $ticketType) { + $offers[$ticketType->id] = new TicketOffer( + $ticketType->id, + $ticketType->prettyName, + $ticketType->defaultPrice, $event->getSeats(), ); } diff --git a/sources/AppBundle/Slack/MessageFactory.php b/sources/AppBundle/Slack/MessageFactory.php index 19cc4c821..666e2beba 100644 --- a/sources/AppBundle/Slack/MessageFactory.php +++ b/sources/AppBundle/Slack/MessageFactory.php @@ -5,12 +5,12 @@ namespace AppBundle\Slack; use AppBundle\Association\Model\Repository\UserRepository; +use AppBundle\Event\Entity\Repository\TicketTypeRepository; use AppBundle\Event\Model\Event; use AppBundle\Event\Model\EventStats\SalesPilotage; use AppBundle\Event\Model\Repository\EventStatsRepository; use AppBundle\Event\Model\Repository\TalkRepository; use AppBundle\Event\Model\Repository\TalkToSpeakersRepository; -use AppBundle\Event\Model\Repository\TicketTypeRepository; use AppBundle\Event\Model\Talk; use AppBundle\Event\Model\Vote; use AppBundle\AssembleeGenerale\Entity\Repository\PresenceRepository; @@ -199,7 +199,11 @@ public function createMessageForTicketStats(Event $event, EventStatsRepository $ if (0 === $value) { continue; } - $attachment->addField(new Field()->setShort(true)->setTitle($ticketRepository->get($typeId)->getPrettyName())->setValue($value)); + $ticketType = $ticketRepository->find($typeId); + if ($ticketType === null) { + continue; + } + $attachment->addField(new Field()->setShort(true)->setTitle($ticketType->prettyName)->setValue($value)); } $message->addAttachment($attachment); diff --git a/tests/behat/bootstrap/AuthContext.php b/tests/behat/bootstrap/AuthContext.php index 20a88725b..7e08c220e 100644 --- a/tests/behat/bootstrap/AuthContext.php +++ b/tests/behat/bootstrap/AuthContext.php @@ -4,16 +4,49 @@ namespace Afup\Tests\Behat\Bootstrap; +use Behat\Mink\Exception\ExpectationException; use Behat\Step\Given; use Behat\Step\When; +use Facebook\WebDriver\Exception\StaleElementReferenceException; trait AuthContext { + private const RETRY_DELAY_MS = 500; + private const MAX_RETRIES = 3; + #[Given('I am logged in as admin and on the Administration')] public function iAmLoggedInAsAdminAndOnTheAdministration(): void { $this->iAmLoggedInAsAdmin(); - $this->minkContext->clickLink('Administration'); + try { + $this->minkContext->clickLink('Administration'); + } catch (StaleElementReferenceException $exception) { + // Le DOM est remplacé pendant la redirection post-login : on retente. + $this->retry(fn() => $this->minkContext->clickLink('Administration'), $exception); + } + } + + private function retry(callable $callback, ?\Throwable $previous = null): void + { + $exception = $previous; + + for ($attempt = 0; $attempt < self::MAX_RETRIES; $attempt++) { + usleep(self::RETRY_DELAY_MS * 1000); + + try { + $callback(); + + return; + } catch (\Throwable $thrown) { + $exception = $thrown; + } + } + + throw new ExpectationException( + sprintf('Step failed after %d attempts: %s', self::MAX_RETRIES, $exception?->getMessage()), + $this->minkContext->getSession()->getDriver(), + $exception, + ); } #[Given('I am logged in as admin')] diff --git a/tests/behat/bootstrap/FeatureContext.php b/tests/behat/bootstrap/FeatureContext.php index 49a01be05..626513c77 100644 --- a/tests/behat/bootstrap/FeatureContext.php +++ b/tests/behat/bootstrap/FeatureContext.php @@ -9,6 +9,8 @@ use Behat\Behat\Hook\Scope\BeforeScenarioScope; use Behat\Gherkin\Node\PyStringNode; use Behat\Hook\BeforeScenario; +use Behat\Mink\Driver\PantherDriver; +use Behat\Mink\Element\NodeElement; use Behat\Mink\Exception\ExpectationException; use Behat\MinkExtension\Context\MinkContext; use Behat\Step\Then; @@ -98,7 +100,7 @@ public function assertCurrentUrlContains(string $regex): void #[When('I follow the button of tooltip :arg1')] public function clickLinkOfTooltip(string $tooltip): void { - $link = $this->minkContext->getSession()->getPage()->find('css', sprintf('a[data-tooltip="%s"]', $tooltip)); + $link = $this->findTooltipLinkWhenRendered($tooltip, 'a'); if (null === $link) { throw new ExpectationException( @@ -113,7 +115,7 @@ public function clickLinkOfTooltip(string $tooltip): void #[When('I press the button of tooltip :arg1')] public function pressButtonOfTooltip(string $tooltip): void { - $button = $this->minkContext->getSession()->getPage()->find('css', sprintf('button[data-tooltip="%s"]', $tooltip)); + $button = $this->findTooltipLinkWhenRendered($tooltip, 'button'); if (null === $button) { throw new ExpectationException( @@ -125,6 +127,23 @@ public function pressButtonOfTooltip(string $tooltip): void $button->press(); } + private function findTooltipLinkWhenRendered(string $tooltip, string $tag): ?NodeElement + { + $session = $this->minkContext->getSession(); + $find = fn(): ?NodeElement => $session->getPage()->find('css', sprintf('%s[data-tooltip="%s"]', $tag, $tooltip)); + $element = $find(); + + if (null === $element && $session->getDriver() instanceof PantherDriver) { + $this->waitForCondition( + fn(): bool => $find() !== null, + sprintf('Tooltip "%s" not rendered', $tooltip), + ); + $element = $find(); + } + + return $element; + } + #[Then('/^the rows of table "(?P[^"]+)" should be in the following order:$/')] public function assertTableRowsInOrder(string $selector, PyStringNode $expectedRows): void { diff --git a/tests/integration/AppBundle/Event/Entity/Repository/TicketTypeRepositoryTest.php b/tests/integration/AppBundle/Event/Entity/Repository/TicketTypeRepositoryTest.php new file mode 100644 index 000000000..0984ba27c --- /dev/null +++ b/tests/integration/AppBundle/Event/Entity/Repository/TicketTypeRepositoryTest.php @@ -0,0 +1,100 @@ +get(TicketTypeRepository::class); + $connection = self::getContainer()->get(Connection::class); + + $idA = $this->insertTicketType($connection, ['technical_name' => 'TEST_A', 'pretty_name' => 'Tarif A']); + $idB = $this->insertTicketType($connection, ['technical_name' => 'TEST_B', 'pretty_name' => 'Tarif B']); + + $ticketTypes = $repository->findAllOrderedById(); + + self::assertCount(2, $ticketTypes); + self::assertSame($idA, $ticketTypes[0]->id); + self::assertSame($idB, $ticketTypes[1]->id); + } + + public function testFindReturnsHydratedEntity(): void + { + $repository = self::getContainer()->get(TicketTypeRepository::class); + $connection = self::getContainer()->get(Connection::class); + + $id = $this->insertTicketType($connection, [ + 'technical_name' => 'TEST_FULL', + 'pretty_name' => 'Tarif complet', + 'public' => 1, + 'members_only' => 0, + 'default_price' => 120.5, + 'active' => 1, + 'day' => 'one,two', + 'cfp_submitter_only' => 1, + ]); + + $ticketType = $repository->find($id); + + self::assertInstanceOf(TicketType::class, $ticketType); + self::assertSame($id, $ticketType->id); + self::assertSame('TEST_FULL', $ticketType->technicalName); + self::assertSame('Tarif complet', $ticketType->prettyName); + self::assertTrue($ticketType->isPublic); + self::assertFalse($ticketType->isRestrictedToMembers); + self::assertTrue($ticketType->isRestrictedToCfpSubmitter); + self::assertSame(120.5, $ticketType->defaultPrice); + self::assertTrue($ticketType->isActive); + self::assertSame(['one', 'two'], $ticketType->getDays()); + self::assertSame('JOUR 1, JOUR 2', $ticketType->getPrettyDays()); + self::assertSame('TEST_FULL - Tarif complet - JOUR 1, JOUR 2', $ticketType->getLabel()); + } + + public function testIsEarlyDetectsEarlyBirdTechnicalNames(): void + { + $repository = self::getContainer()->get(TicketTypeRepository::class); + $connection = self::getContainer()->get(Connection::class); + + $earlyId = $this->insertTicketType($connection, ['technical_name' => 'EARLY_BIRD']); + $earlyAfupId = $this->insertTicketType($connection, ['technical_name' => 'AFUP_DAY_EARLY']); + $classicId = $this->insertTicketType($connection, ['technical_name' => 'COMITE']); + + self::assertTrue($repository->find($earlyId)->isEarly()); + self::assertTrue($repository->find($earlyAfupId)->isEarly()); + self::assertFalse($repository->find($classicId)->isEarly()); + } + + public function testFindReturnsNullForUnknownId(): void + { + $repository = self::getContainer()->get(TicketTypeRepository::class); + + self::assertNull($repository->find(999999)); + } + + /** + * @param array $data + */ + private function insertTicketType(Connection $connection, array $data = []): int + { + $connection->insert('afup_forum_tarif', $data + [ + 'technical_name' => 'TEST', + 'pretty_name' => 'Tarif', + 'public' => 1, + 'members_only' => 0, + 'default_price' => 10.0, + 'active' => 1, + 'day' => 'one', + 'cfp_submitter_only' => 0, + ]); + + return (int) $connection->lastInsertId(); + } +} diff --git a/tests/support/Github/FakeGithubClient.php b/tests/support/Github/FakeGithubClient.php new file mode 100644 index 000000000..b9be25a13 --- /dev/null +++ b/tests/support/Github/FakeGithubClient.php @@ -0,0 +1,50 @@ +> $apiDataByLogin Données API simulées indexées par login GitHub + */ + public function __construct(private readonly array $apiDataByLogin = self::DEFAULT_API_DATA) + { + parent::__construct(new MockHttpClient()); + } + + private const array DEFAULT_API_DATA = [ + 'amenophis' => [ + 'login' => 'amenophis', + 'id' => 189419, + 'avatar_url' => 'https://avatars.githubusercontent.com/u/189419?v=4', + 'company' => 'AFUP', + 'name' => 'Jérémy Leherpeur', + 'html_url' => 'https://github.com/amenophis', + ], + 'vinceAmstoutz' => [ + 'login' => 'vinceAmstoutz', + 'id' => 6927270, + 'avatar_url' => 'https://avatars.githubusercontent.com/u/6927270?v=4', + 'company' => 'AFUP', + 'name' => 'Vincent Amstoutz (afup)', + 'html_url' => 'https://github.com/vinceAmstoutz', + ], + ]; + + #[\Override] + public function getUserInfos($username): GithubUser + { + if (!isset($this->apiDataByLogin[$username])) { + throw new UnableToFindGithubUserException($username); + } + + return GithubUser::fromApi($this->apiDataByLogin[$username]); + } +}