diff --git a/app/config/services.yml b/app/config/services.yml index 56736008e..c427f3610 100644 --- a/app/config/services.yml +++ b/app/config/services.yml @@ -707,7 +707,6 @@ services: autowire: true public: true - app.github.http_client: class: GuzzleHttp\Client arguments: @@ -717,3 +716,11 @@ services: AppBundle\Github\GithubClient: arguments: $githubClient: '@app.github.http_client' + + app.meetup.http_client: + class: GuzzleHttp\Client + + AppBundle\Indexation\Meetups\MeetupClient: + arguments: + $httpClient: '@app.meetup.http_client' + $antennesCollection: '@AppBundle\Antennes\AntennesCollection' diff --git a/composer.json b/composer.json index 892a43269..a8b30a001 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "captioning/captioning": "^2.6", "ccmbenchmark/ting_bundle": "3.4.1", "cocur/slugify": "^2.3", + "cuyz/valinor": "^0.17.1", "doctrine/dbal": "^2.5", "ekino/newrelic-bundle": "^1.4", "erusev/parsedown": "^1.6", diff --git a/composer.lock b/composer.lock index 00fa2c9c2..3ca321ac9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6987c8511259e54a95e24b8d9818e0a1", + "content-hash": "e837825c26c89e1148ab591c5c3db9da", "packages": [ { "name": "algolia/algoliasearch-client-php", @@ -492,6 +492,80 @@ }, "time": "2017-03-23T21:52:55+00:00" }, + { + "name": "cuyz/valinor", + "version": "0.17.1", + "source": { + "type": "git", + "url": "https://github.com/CuyZ/Valinor.git", + "reference": "8dbd01df82bf3eaf57246d1493d54b958b00f900" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CuyZ/Valinor/zipball/8dbd01df82bf3eaf57246d1493d54b958b00f900", + "reference": "8dbd01df82bf3eaf57246d1493d54b958b00f900", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0", + "doctrine/annotations": "^1.11", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0", + "psr/simple-cache": "^1.0 || ^2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.4", + "infection/infection": "^0.26", + "marcocesarato/php-conventional-changelog": "^1.12", + "mikey179/vfsstream": "^1.6.10", + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.12.23", + "vimeo/psalm": "^4.18.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "CuyZ\\Valinor\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Romain Canon", + "email": "romain.hydrocanon@gmail.com", + "homepage": "https://github.com/romm" + } + ], + "description": "Library that helps to map any input into a strongly-typed value object structure.", + "homepage": "https://github.com/CuyZ/Valinor", + "keywords": [ + "array", + "conversion", + "hydrator", + "json", + "mapper", + "mapping", + "object", + "tree", + "yaml" + ], + "support": { + "issues": "https://github.com/CuyZ/Valinor/issues", + "source": "https://github.com/CuyZ/Valinor/tree/0.17.1" + }, + "funding": [ + { + "url": "https://github.com/romm", + "type": "github" + } + ], + "time": "2023-01-18T09:52:40+00:00" + }, { "name": "doctrine/annotations", "version": "1.14.4", diff --git a/db/migrations/20250209185223_meetup_emojis.php b/db/migrations/20250209185223_meetup_emojis.php new file mode 100644 index 000000000..fed21b7b0 --- /dev/null +++ b/db/migrations/20250209185223_meetup_emojis.php @@ -0,0 +1,14 @@ +execute("ALTER TABLE afup_meetup CHANGE description description text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;"); + $this->execute("ALTER TABLE afup_meetup CHANGE title title varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;"); + } +} diff --git a/sources/AppBundle/Command/ScrappingMeetupEventsCommand.php b/sources/AppBundle/Command/ScrappingMeetupEventsCommand.php index 90640a0a6..8b44b7258 100644 --- a/sources/AppBundle/Command/ScrappingMeetupEventsCommand.php +++ b/sources/AppBundle/Command/ScrappingMeetupEventsCommand.php @@ -5,7 +5,7 @@ namespace AppBundle\Command; use AppBundle\Event\Model\Repository\MeetupRepository; -use AppBundle\Indexation\Meetups\MeetupScraper; +use AppBundle\Indexation\Meetups\MeetupClient; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; use Symfony\Component\Console\Command\LockableTrait; use Symfony\Component\Console\Input\InputInterface; @@ -16,9 +16,6 @@ class ScrappingMeetupEventsCommand extends ContainerAwareCommand { use LockableTrait; - /** - * @see Command - */ protected function configure(): void { $this @@ -28,12 +25,6 @@ protected function configure(): void ; } - /** - * - * @see Command - * - * @throws \Exception - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -47,54 +38,32 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { $ting = $this->getContainer()->get('ting'); - $meetupScraper = new MeetupScraper(); - $meetups = $meetupScraper->getEvents(); + + /** @var MeetupClient $meetupClient */ + $meetupClient = $this->getContainer()->get(MeetupClient::class); + $meetups = $meetupClient->getEvents(); $meetupRepository = $ting->get(MeetupRepository::class); - $emlementsLength = $this->countAllNestedElements($meetups); - $io->progressStart($emlementsLength); - foreach ($meetups as $antenneMeetups) { - foreach ($antenneMeetups as $meetup) { - $io->progressAdvance(); + $io->progressStart(count($meetups)); + foreach ($meetups as $meetup) { + $io->progressAdvance(); - $id =$meetup->getId(); - $existingMeetup = $meetupRepository->get($id); - if (!$existingMeetup) { - $meetupRepository->save($meetup); - } else { - $io->note(sprintf('Meetup id %d déjà en base.', $id)); - } + $id = $meetup->getId(); + $existingMeetup = $meetupRepository->get($id); + if (!$existingMeetup) { + $meetupRepository->save($meetup); + } else { + $io->note(sprintf('Meetup id %d déjà en base.', $id)); } } + $io->progressFinish(); $io->success('Terminé avec succès'); + return 1; } catch (\Exception $e) { throw new \Exception('Problème lors du scraping ou de la sauvegarde des évènements Meetup', $e->getCode(), $e); } } - - /** - * Permet de faire un count sur un tableau multi-dimensionnel - * - * @param $array - * - * @return int - */ - private function countAllNestedElements($array) - { - $count = 0; - - foreach ($array as $element) { - if (is_array($element)) { - // Si l'élément est un tableau, on appelle récursivement la fonction - $count += $this->countAllNestedElements($element); - } else { - $count++; - } - } - - return $count; - } } diff --git a/sources/AppBundle/Indexation/Meetups/GraphQL/Edge.php b/sources/AppBundle/Indexation/Meetups/GraphQL/Edge.php new file mode 100644 index 000000000..bee40eadc --- /dev/null +++ b/sources/AppBundle/Indexation/Meetups/GraphQL/Edge.php @@ -0,0 +1,18 @@ +node = $node; + } +} diff --git a/sources/AppBundle/Indexation/Meetups/GraphQL/Events.php b/sources/AppBundle/Indexation/Meetups/GraphQL/Events.php new file mode 100644 index 000000000..096e75754 --- /dev/null +++ b/sources/AppBundle/Indexation/Meetups/GraphQL/Events.php @@ -0,0 +1,22 @@ + */ + public array $edges; + + /** + * @param list $edges + */ + public function __construct(array $edges) + { + $this->edges = $edges; + } +} diff --git a/sources/AppBundle/Indexation/Meetups/GraphQL/Group.php b/sources/AppBundle/Indexation/Meetups/GraphQL/Group.php new file mode 100644 index 000000000..d2ed9a953 --- /dev/null +++ b/sources/AppBundle/Indexation/Meetups/GraphQL/Group.php @@ -0,0 +1,20 @@ +upcomingEvents = $upcomingEvents; + $this->pastEvents = $pastEvents; + } +} diff --git a/sources/AppBundle/Indexation/Meetups/GraphQL/Node.php b/sources/AppBundle/Indexation/Meetups/GraphQL/Node.php new file mode 100644 index 000000000..c9abae146 --- /dev/null +++ b/sources/AppBundle/Indexation/Meetups/GraphQL/Node.php @@ -0,0 +1,28 @@ +id = $id; + $this->title = $title; + $this->description = $description; + $this->dateTime = $dateTime; + $this->venue = $venue; + } +} diff --git a/sources/AppBundle/Indexation/Meetups/GraphQL/QueryGroupsResponse.php b/sources/AppBundle/Indexation/Meetups/GraphQL/QueryGroupsResponse.php new file mode 100644 index 000000000..b15145a8f --- /dev/null +++ b/sources/AppBundle/Indexation/Meetups/GraphQL/QueryGroupsResponse.php @@ -0,0 +1,22 @@ + */ + public array $data; + + /** + * @param array $data + */ + public function __construct(array $data) + { + $this->data = $data; + } +} diff --git a/sources/AppBundle/Indexation/Meetups/GraphQL/Venue.php b/sources/AppBundle/Indexation/Meetups/GraphQL/Venue.php new file mode 100644 index 000000000..04e3d7ef1 --- /dev/null +++ b/sources/AppBundle/Indexation/Meetups/GraphQL/Venue.php @@ -0,0 +1,18 @@ +name = $name; + } +} diff --git a/sources/AppBundle/Indexation/Meetups/MeetupClient.php b/sources/AppBundle/Indexation/Meetups/MeetupClient.php new file mode 100644 index 000000000..ea30a28d7 --- /dev/null +++ b/sources/AppBundle/Indexation/Meetups/MeetupClient.php @@ -0,0 +1,121 @@ +httpClient = $httpClient; + $this->antennesCollection = $antennesCollection; + } + + /** + * @return Meetup[] + */ + public function getEvents(): array + { + $response = $this->httpClient->request('POST', 'https://api.meetup.com/gql', [ + 'body' => json_encode([ + 'query' => $this->getEventsQuery(), + 'variables' => [ + 'quantityUpcoming' => self::QUANTITY_UPCOMING_EVENTS, + 'quantityPast' => self::QUANTITY_PAST_EVENTS, + ], + ]), + 'headers' => [ + 'Content-Type' => 'application/json', + ], + ]); + + /** @var QueryGroupsResponse $groupResponse */ + $groupResponse = (new MapperBuilder()) + ->allowSuperfluousKeys() + ->supportDateFormats('Y-m-d\TH:iP') + ->mapper() + ->map(QueryGroupsResponse::class, Source::json($response->getBody()->getContents())); + + $meetups = []; + + foreach ($groupResponse->data as $nameAntenne => $group) { + $edges = array_merge($group->upcomingEvents->edges, $group->pastEvents->edges); + + foreach ($edges as $edge) { + $meetup = new Meetup(); + $meetup->setId((int) $edge->node->id); + $meetup->setTitle($edge->node->title); + $meetup->setDescription($edge->node->description); + $meetup->setDate($edge->node->dateTime); + $meetup->setAntenneName($nameAntenne); + + if ($edge->node->venue !== null) { + $meetup->setLocation($edge->node->venue->name); + } + + $meetups[] = $meetup; + } + } + + return $meetups; + } + + private function getEventsQuery(): string + { + $queries = []; + + foreach ($this->antennesCollection->getAll() as $antenne) { + if ($antenne->meetup === null) { + continue; + } + + $queries[] = sprintf( + "%s: group(id: %s) { ...GroupFragment }\n", + $antenne->code, + $antenne->meetup->id, + ); + } + + $query = 'query($quantityUpcoming: Int, $quantityPast: Int) { + %s +} + +fragment EventFragment on Event { + id + title + description + dateTime + venue { name } +} + +fragment GroupFragment on Group { + upcomingEvents(input: {last: $quantityUpcoming}) { + edges { + node { ... EventFragment } + } + } + + pastEvents(input: {first: $quantityPast}, sortOrder: DESC) { + edges { + node { ... EventFragment } + } + } +}'; + + return sprintf($query, implode("\n", $queries)); + } +} diff --git a/sources/AppBundle/Indexation/Meetups/MeetupDateTimeConverter.php b/sources/AppBundle/Indexation/Meetups/MeetupDateTimeConverter.php deleted file mode 100644 index 482e91133..000000000 --- a/sources/AppBundle/Indexation/Meetups/MeetupDateTimeConverter.php +++ /dev/null @@ -1,147 +0,0 @@ -getDateTimeStringWithoutAccents($dateTimeString); - - $parsedDateTime = $this->parseDateString($unAccentDateString); - $this->validateTime($parsedDateTime['time']); - - $monthNames = [ - 'janv' => 1, 'fevr' => 2, 'mars' => 3, 'avr' => 4, 'mai' => 5, 'juin' => 6, - 'juil' => 7, 'aout' => 8, 'sept' => 9, 'oct' => 10, 'nov' => 11, 'dec' => 12, - ]; - - $monthAbbreviation = $parsedDateTime['monthAbbreviation']; - if (!array_key_exists($monthAbbreviation, $monthNames)) { - throw new Exception('Abbréviation de mois invalide : ' . $monthAbbreviation); - } - - [$year, $day, $hour, $minute, $timezoneAbbreviation] = [ - $parsedDateTime['year'], - $parsedDateTime['day'], - $parsedDateTime['time'][0], - $parsedDateTime['time'][1], - $parsedDateTime['timezoneAbbreviation'], - ]; - - $dateTimeString = sprintf( - '%04d-%02d-%02d %02d:%02d', - $year, - $monthNames[$monthAbbreviation], - $day, - $hour, - $minute - ); - - $timezone = $this->getTimezoneFromAbbreviation($timezoneAbbreviation); - - try { - return new DateTime($dateTimeString, $timezone); - } catch (Exception $e) { - throw new Exception('Format de date invalide', $e->getCode(), $e); - } - } - - /** - * - * @throws Exception - */ - private function parseDateString(string $dateTimeString): array - { - $pattern = '/(\S+)\s+(\d+)\s+(\S+)\s+(\d+),\s+(\d+:\d+)\s+([A-Za-z0-9\s\+\-]+)/u'; - preg_match($pattern, $dateTimeString, $matches); - - if (count($matches) !== 7) { - throw new Exception('Format de date invalide : ' . $dateTimeString); - } - - return [ - 'day' => intval($matches[2]), - 'monthAbbreviation' => mb_strtolower(str_replace('.', '', $matches[3]), 'UTF-8'), - 'year' => intval($matches[4]), - 'time' => explode(':', $matches[5]), - 'timezoneAbbreviation' => $matches[6], - ]; - } - - /** - * @param int $time - * - * - * @throws Exception - */ - private function validateTime($time): void - { - [$hour, $minute] = $time; - - if ($hour >= 24 || $minute >= 60) { - throw new Exception('Heure invalide : ' . implode(':', $time)); - } - } - - /** - * - * - * @throws Exception - */ - public function getTimezoneFromAbbreviation(string $timezoneAbbreviation): \DateTimeZone - { - $timezoneNameMap = $this->getTimezoneNameMap(); - - if (isset($timezoneNameMap[$timezoneAbbreviation])) { - return new DateTimeZone($timezoneNameMap[$timezoneAbbreviation]); - } - - throw new Exception('Fuseau horaire inconnu : ' . $timezoneAbbreviation); - } - - /** - * Mapping des abréviations de fuseau horaire aux noms complets des fuseaux horaires. - * - * Cela permet de gérer les conversions entre les heures d'été (CEST) et l'heure standard (CET) - * pour les événements en France. - * - * @return string[] - */ - private function getTimezoneNameMap(): array - { - return [ - 'UTC+0' => 'UTC', - 'UTC' => 'UTC', - 'GMT' => 'GMT', - 'UTC+1' => 'Europe/Paris', // CET (Central European Time) - 'CET' => 'Europe/Paris', // CET (Central European Time) - 'UTC+2' => 'Europe/Athens', // CEST (Central European Summer Time) - 'CEST' => 'Europe/Athens', // CEST (Central European Summer Time) - ]; - } - - /** - * @param string $dateTimeString - * @return string - */ - private function getDateTimeStringWithoutAccents($dateTimeString): ?string - { - $normalized = Normalizer::normalize($dateTimeString, Normalizer::FORM_KD); - - return preg_replace('/\p{Mn}/u', '', $normalized); - } -} diff --git a/sources/AppBundle/Indexation/Meetups/MeetupScraper.php b/sources/AppBundle/Indexation/Meetups/MeetupScraper.php deleted file mode 100644 index 118802959..000000000 --- a/sources/AppBundle/Indexation/Meetups/MeetupScraper.php +++ /dev/null @@ -1,126 +0,0 @@ - - * - * @throws Exception - * @throws InvalidArgumentException - */ - public function getEvents() - { - try { - $antennes = $this->getAntennesFromOfficesCollection(); - - $eventsArray = []; - foreach ($antennes as $antenneKey => $antenne) { - if ($antenne->meetup === null) { - continue; - } - - $meetupAntenneName = $antenneKey; - - $xpath = $this->getDomContent($antenne->meetup->urlName); - - $events = $xpath->query("//*[contains(@id, 'event-card')]"); - foreach ($events as $event) { - try { - if (!$event instanceof \DOMElement) { - throw new \Exception('Élement DOM de type invalide'); - } - - $eventUrl = $event->getAttribute('href'); - - if (preg_match('/\/events\/(\d+)\//', $eventUrl, $matches)) { - $id = (int) $matches[1]; - } else { - throw new Exception(sprintf('Pas d\'id pour cet évent de l\'antenne %s', $antenne->code)); - } - - $dateString = $xpath->query(".//time", $event)->item(0)->nodeValue; - $dateTime = (new MeetupDateTimeConverter())->convertStringToDateTime($dateString); - - $title = $xpath->query(".//span[contains(@class, 'cardTitle')]", $event)->item(0)->nodeValue; - - $descriptionElements = $xpath->query("//div[contains(@class, 'utils_cardDescription__alO8K')]"); - - $description = ''; - foreach ($descriptionElements as $descriptionElement) { - $description .= ' ' . $descriptionElement->nodeValue; - } - - $eventsArray[$meetupAntenneName][] = (new Meetup()) - ->setId($id) - ->setDate($dateTime) - ->setTitle($title) - ->setDescription($description) - ->setAntenneName($meetupAntenneName); - } catch (Exception $e) { - throw new Exception('Problème à la construction d\'un évenement', $e->getCode(), $e); - } - } - } - } catch (Exception $e) { - throw new Exception('Problème à la construction de la liste des évenements', $e->getCode(), $e); - } - - if ([] === $eventsArray) { - throw new Exception('Le DOM sur Meetup a très certainement changé (aucun event détecté)'); - } - - return $eventsArray; - } - - /** - * Récupère et charge les données de la page pour une meetup url donnée - * - * @throws Exception - */ - public function getDomContent(string $antenneUrl): DOMXPath - { - $url = self::MEETUP_URL . $antenneUrl; - $content = file_get_contents($url); - - if (strpos($content, 'Groupe introuvable')) { - throw new Exception(sprintf('Antenne url icorrecte %s', $url), 500); - } - - // Charger le contenu dans un objet DOMDocument - $dom = new DOMDocument(); - libxml_use_internal_errors(true); // Désactiver les erreurs de libxml pour éviter les messages d'erreur lors de l'analyse HTML - $dom->loadHTML($content); - libxml_clear_errors(); // Effacer les erreurs de libxml après l'analyse HTML - - return new DOMXPath($dom); - } - - /** - * @return array - * @throws Exception - */ - public function getAntennesFromOfficesCollection(): array - { - $antennes = (new AntennesCollection())->getAll(); - if ([] === $antennes) { - throw new Exception("The antennes array is invalid or is empty"); - } - - return $antennes; - } -} diff --git a/tests/units/AppBundle/Indexation/Meetups/MeetupClient.php b/tests/units/AppBundle/Indexation/Meetups/MeetupClient.php new file mode 100644 index 000000000..a0997961c --- /dev/null +++ b/tests/units/AppBundle/Indexation/Meetups/MeetupClient.php @@ -0,0 +1,152 @@ +makeGuzzleMockClient( + new Response(500) + ); + + $meetupClient = new TestedClass($httpClient, new AntennesCollection()); + + $this + ->exception(fn () => $meetupClient->getEvents()) + ->isInstanceOf(Exception::class) + ->hasMessage("Server error: `POST https://api.meetup.com/gql` resulted in a `500 Internal Server Error` response"); + } + + public function testInvalidJsonInResponse(): void + { + $httpClient = $this->makeGuzzleMockClient( + new Response(200, [], 'invalid json') + ); + + $meetupClient = new TestedClass($httpClient, new AntennesCollection()); + + $this + ->exception(fn () => $meetupClient->getEvents()) + ->isInstanceOf(Exception::class) + ->hasMessage('The given value is not a valid JSON entry.'); + } + + public function testReturnsValidResponse(): void + { + $httpClient = $this->makeGuzzleMockClient( + new Response( + 200, + [], + json_encode([ + 'data' => [ + 'lyon' => [ + 'upcomingEvents' => [ + 'edges' => [ + [ + 'node' => [ + 'id' => "12", + 'title' => 'Upcoming 1', + 'description' => 'Desc 1', + 'dateTime' => '2025-02-11T18:30+01:00', + 'venue' => [ + 'name' => 'Lieu 1', + ], + ], + ], + [ + 'node' => [ + 'id' => "34", + 'title' => 'Upcoming 2', + 'description' => 'Desc 2', + 'dateTime' => '2025-03-20T18:30+01:00', + 'venue' => null, + ], + ], + ], + ], + 'pastEvents' => [ + 'edges' => [ + [ + 'node' => [ + 'id' => "56", + 'title' => 'Past 1', + 'description' => 'Desc 3', + 'dateTime' => '2019-04-08T18:30+01:00', + 'venue' => null, + ], + ], + [ + 'node' => [ + 'id' => "78", + 'title' => 'Past 2', + 'description' => 'Desc 4', + 'dateTime' => '2020-10-17T18:30+01:00', + 'venue' => [ + 'name' => 'Lieu 2', + ], + ], + ], + ], + ], + ], + ], + ]), + ), + ); + + $meetupClient = new TestedClass($httpClient, new AntennesCollection()); + + $antennes = $meetupClient->getEvents(); + + $this->integer(count($antennes))->isEqualTo(4); + + $this->integer($antennes[0]->getId())->isEqualTo(12); + $this->string($antennes[0]->getTitle())->isEqualTo('Upcoming 1'); + $this->string($antennes[0]->getDescription())->isEqualTo('Desc 1'); + $this->dateTime($antennes[0]->getDate())->isEqualTo(new \DateTime('2025-02-11T18:30+01:00')); + $this->string($antennes[0]->getAntenneName())->isEqualTo('lyon'); + $this->string($antennes[0]->getLocation())->isEqualTo('Lieu 1'); + + $this->integer($antennes[1]->getId())->isEqualTo(34); + $this->string($antennes[1]->getTitle())->isEqualTo('Upcoming 2'); + $this->string($antennes[1]->getDescription())->isEqualTo('Desc 2'); + $this->dateTime($antennes[1]->getDate())->isEqualTo(new \DateTime('2025-03-20T18:30+01:00')); + $this->string($antennes[1]->getAntenneName())->isEqualTo('lyon'); + $this->variable($antennes[1]->getLocation())->isNull(); + + $this->integer($antennes[2]->getId())->isEqualTo(56); + $this->string($antennes[2]->getTitle())->isEqualTo('Past 1'); + $this->string($antennes[2]->getDescription())->isEqualTo('Desc 3'); + $this->dateTime($antennes[2]->getDate())->isEqualTo(new \DateTime('2019-04-08T18:30+01:00')); + $this->string($antennes[2]->getAntenneName())->isEqualTo('lyon'); + $this->variable($antennes[2]->getLocation())->isNull(); + + $this->integer($antennes[3]->getId())->isEqualTo(78); + $this->string($antennes[3]->getTitle())->isEqualTo('Past 2'); + $this->string($antennes[3]->getDescription())->isEqualTo('Desc 4'); + $this->dateTime($antennes[3]->getDate())->isEqualTo(new \DateTime('2020-10-17T18:30+01:00')); + $this->string($antennes[3]->getAntenneName())->isEqualTo('lyon'); + $this->string($antennes[3]->getLocation())->isEqualTo('Lieu 2'); + } + + private function makeGuzzleMockClient(Response $response): Client + { + return new Client([ + 'handler' => HandlerStack::create( + new MockHandler([$response]) + ) + ]); + } +} diff --git a/tests/units/AppBundle/Indexation/Meetups/MeetupDateTimeConverter.php b/tests/units/AppBundle/Indexation/Meetups/MeetupDateTimeConverter.php deleted file mode 100644 index 33e084be9..000000000 --- a/tests/units/AppBundle/Indexation/Meetups/MeetupDateTimeConverter.php +++ /dev/null @@ -1,99 +0,0 @@ -getTimezoneFromAbbreviation($timezoneName)->getName(); - $timezone = new DateTimeZone($timezoneName); - - $this - ->given($meetupDateTimeConverter) - ->then - ->dateTime($meetupDateTimeConverter->convertStringToDateTime($dateString)) - ->hasDate($year, $month, $day) - ->hasTime($hour, $minute, $seconds) - ->hasTimeZone($timezone); - } - - /** - * Data provider for valid date strings and their expected DateTime objects. - * - * @return array - */ - public function validDateStringProvider() - { - return [ - ['jeu. 03 janv. 2022, 02:30 UTC+1', 2022, 01, 03, 02, 30, 0, 'UTC+1'], - ['lun. 01 févr. 2022, 18:30 UTC+2', 2022, 02, 01, 18, 30, 0, 'UTC+2'], - ['lun. 01 fevr. 2022, 18:30 UTC+2', 2022, 02, 01, 18, 30, 0, 'UTC+2'], - ['ven. 04 mars 2022, 18:30 UTC+1', 2022, 03, 04, 18, 30, 0, 'UTC+1'], - ['dim. 02 avr. 2020, 18:30 UTC+1', 2020, 04, 02, 18, 30, 0, 'UTC+1'], - ['mar. 02 mai 2022, 18:30 UTC', 2022, 05, 02, 18, 30, 0, 'UTC'], - ['ven. 03 juin 2021, 18:30 UTC+1', 2021, 06, 03, 18, 30, 0, 'UTC+1'], - ['dim. 03 juil. 2022, 18:30 UTC+2', 2022, 07, 03, 18, 30, 0, 'UTC+2'], - ['mer. 02 août 2022, 11:30 UTC+1', 2022, 8, 02, 11, 30, 0, 'UTC+1'], - ['mer. 02 aout. 2022, 11:30 UTC+1', 2022, 8, 02, 11, 30, 0, 'UTC+1'], - ['sam. 03 sept. 2022, 18:30 UTC', 2022, 9, 03, 18, 30, 0, 'UTC'], - ['lun. 03 oct. 2025, 18:30 UTC+1', 2025, 10, 03, 18, 30, 0, 'UTC+1'], - ['jeu. 03 nov. 2022, 16:30 UTC+2', 2022, 11, 03, 16, 30, 0, 'UTC+2'], - ['sam. 03 déc. 2019, 18:30 UTC+1', 2019, 12, 03, 18, 30, 0, 'UTC+1'], - ]; - } - - /** - * @dataProvider invalidDateStringProvider - * - * @param string $invalidDateString - */ - public function testConvertInvalidStringToDateTime($invalidDateString): void - { - $this - ->given($meetupDateTimeConverter = new TestedClass()) - ->exception(function () use ($meetupDateTimeConverter, $invalidDateString): void { - $meetupDateTimeConverter->convertStringToDateTime($invalidDateString); - }) - ->isInstanceOf(\Exception::class); // Remplacez par le type de l'exception personnalisée si nécessaire - } - - /** - * Data provider for invalid date strings. - * - * @return array - */ - public function invalidDateStringProvider() - { - return [ - ['invalid_date_format'], - ['sam. 03 déc. 2019, 18:30 UTC+100'], - ['sam. 31 fev. 2019, 18:30 UTC+1'], - ['mar. 31 dec. 2021, 24:00 CET'], - ['jeu. 15 juil. 2020, 10:30 InvalidTimeZone'], - ['dim. 20 sep. 2025, 12:45'], - ['ven. 18 dece. 2023, 15:45 UTC',], - ]; - } -} diff --git a/tests/units/AppBundle/Indexation/Meetups/MeetupScrapper.php b/tests/units/AppBundle/Indexation/Meetups/MeetupScrapper.php deleted file mode 100644 index 2a88b0135..000000000 --- a/tests/units/AppBundle/Indexation/Meetups/MeetupScrapper.php +++ /dev/null @@ -1,103 +0,0 @@ -exception(function () use ($meetupScraper, $antenneUrl): void { - $meetupScraper->getDomContent($antenneUrl); - }) - ->isInstanceOf(\Exception::class) - ->hasMessage(sprintf('Antenne url icorrecte %s', $url)); - } - - /** - * @param string $antenneUrl - * - * @dataProvider validMeetupUrlProvider - */ - public function testGetDomContentWithValidMeetupUrl($antenneUrl): void - { - $meetupScraper = new TestedClass(); - $xpath = $meetupScraper->getDomContent($antenneUrl); - - $this - ->object($xpath) - ->isInstanceOf(\DOMXPath::class); - } - - /** - * @throws \Exception - */ - public function testGetMeetupEvents(): void - { - $meetupScraper = new TestedClass(); - $events = $meetupScraper->getEvents(); - - $eventLength = count($events); - - $this - ->integer($eventLength) - ->isGreaterThan(0); - - foreach ($events as $antenneEvents) { - foreach ($antenneEvents as $event) { - $title = $event->getTitle(); - $date = $event->getDate(); - - $this->string($title)->isNotEmpty(); - $this->dateTime($date)->isInstanceOf(\DateTime::class); - } - } - } - - /** - * @throws \Exception - */ - public function testGetAntennesFromOfficesCollection(): void - { - $meetupScraper = new TestedClass(); - $antennes = $meetupScraper->getAntennesFromOfficesCollection(); - - $this - ->array($antennes) - ->isNotEmpty(); - } - - // Provide valid Meetup URLs for testing - protected function validMeetupUrlProvider() - { - return [ - ['Bordeaux-PHP-Meetup'], - ['afup-lyon-php'], - ]; - } - - // Provide invalid Meetup URLs for testing - protected function invalidMeetupUrlProvider() - { - return [ - ['invalid-url-1'], - ['invalid-url-2'], - ['afuep-lyon-phpe'], - ]; - } -}