From 0111f1b5f17f199e2977cf53ee3131406cd81c12 Mon Sep 17 00:00:00 2001 From: "ext.jeremy.guillot@maxicoffee.domains" Date: Tue, 1 Apr 2025 16:01:55 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20ajout=20de=20la=20gestion=20des=20chapi?= =?UTF-8?q?tres=20de=20manga,=20incluant=20la=20r=C3=A9cup=C3=A9ration=20e?= =?UTF-8?q?t=20la=20sauvegarde=20des=20chapitres=20en=20fran=C3=A7ais=20et?= =?UTF-8?q?=20en=20anglais,=20ainsi=20que=20l'optimisation=20de=20la=20log?= =?UTF-8?q?ique=20de=20sauvegarde=20pour=20=C3=A9viter=20les=20doublons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FetchMangaChaptersHandler.php | 53 ++++++--- .../Repository/MangaRepositoryInterface.php | 3 +- .../Persistence/LegacyMangaRepository.php | 20 ++++ .../CommandHandler/ScrapeChapterHandler.php | 2 - .../Scraping/Domain/Event/ChapterScraped.php | 12 +- .../Provider/GetJobListStateProvider.php | 15 ++- .../Repository/DoctrineJobRepository.php | 5 - .../Manga/Adapter/InMemoryMangaRepository.php | 21 +++- .../FetchMangaChaptersHandlerTest.php | 3 +- .../ScrapeChapterHandlerTest.php | 57 +++++----- .../Shared/Adapter/InMemoryJobRepository.php | 104 ++++++++++++++++++ tests/Feature/Scraping/ScrapingStatusTest.php | 37 +++++-- 12 files changed, 254 insertions(+), 78 deletions(-) create mode 100644 tests/Domain/Shared/Adapter/InMemoryJobRepository.php diff --git a/src/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandler.php b/src/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandler.php index b7b4d4e..6048ff2 100644 --- a/src/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandler.php @@ -20,7 +20,7 @@ readonly class FetchMangaChaptersHandler public function handle(FetchMangaChapters $command): void { $manga = $this->mangaRepository->findByExternalId(new ExternalId($command->externalId)); - + if ($manga === null) { throw new \RuntimeException('Manga not found'); } @@ -28,6 +28,8 @@ readonly class FetchMangaChaptersHandler $offset = 0; $limit = 500; $hasMore = true; + $chaptersByNumber = []; + $chapterNumbers = []; while ($hasMore) { $feed = $this->mangadexClient->getMangaFeed( @@ -37,22 +39,47 @@ readonly class FetchMangaChaptersHandler ); foreach ($feed['data'] as $chapterData) { - $chapter = new Chapter( - new ChapterId((string) Uuid::uuid4()), - $manga->getId()->getValue(), - (float) $chapterData['attributes']['chapter'], - $chapterData['attributes']['title'], - isset($chapterData['attributes']['volume']) ? (int) $chapterData['attributes']['volume'] : null, - true, - false, - new \DateTimeImmutable() - ); + $chapterNumber = (float) $chapterData['attributes']['chapter']; + $language = $chapterData['attributes']['translatedLanguage']; - $this->mangaRepository->saveChapter($chapter); + // On ne traite que les chapitres en français ou en anglais + if (!in_array($language, ['fr', 'en'])) { + continue; + } + + // Si le chapitre n'existe pas encore ou si c'est une version française + if (!isset($chaptersByNumber[$chapterNumber]) || $language === 'fr') { + $chapter = new Chapter( + new ChapterId((string) Uuid::uuid4()), + $manga->getId()->getValue(), + $chapterNumber, + $chapterData['attributes']['title'], + isset($chapterData['attributes']['volume']) ? (int) $chapterData['attributes']['volume'] : null, + true, + false, + new \DateTimeImmutable() + ); + + $chaptersByNumber[$chapterNumber] = $chapter; + $chapterNumbers[] = $chapterNumber; + } } $offset += $limit; $hasMore = count($feed['data']) === $limit; } + + // Récupère les chapitres existants + $existingChapters = $this->mangaRepository->findExistingChaptersByNumbers( + $manga->getId()->getValue(), + $chapterNumbers + ); + + // Sauvegarde uniquement les nouveaux chapitres + foreach ($chaptersByNumber as $chapterNumber => $chapter) { + if (!isset($existingChapters[$chapterNumber])) { + $this->mangaRepository->saveChapter($chapter); + } + } } -} \ No newline at end of file +} diff --git a/src/Domain/Manga/Domain/Contract/Repository/MangaRepositoryInterface.php b/src/Domain/Manga/Domain/Contract/Repository/MangaRepositoryInterface.php index 0eb0043..fc97771 100644 --- a/src/Domain/Manga/Domain/Contract/Repository/MangaRepositoryInterface.php +++ b/src/Domain/Manga/Domain/Contract/Repository/MangaRepositoryInterface.php @@ -21,4 +21,5 @@ interface MangaRepositoryInterface public function findBySlug(MangaSlug $slug): ?Manga; public function search(string $query, int $page = 1, int $limit = 20): array; public function countSearch(string $query): int; -} \ No newline at end of file + public function findExistingChaptersByNumbers(string $mangaId, array $chapterNumbers): array; +} diff --git a/src/Domain/Manga/Infrastructure/Persistence/LegacyMangaRepository.php b/src/Domain/Manga/Infrastructure/Persistence/LegacyMangaRepository.php index 69a1b33..61e450e 100644 --- a/src/Domain/Manga/Infrastructure/Persistence/LegacyMangaRepository.php +++ b/src/Domain/Manga/Infrastructure/Persistence/LegacyMangaRepository.php @@ -200,6 +200,26 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface ->getSingleScalarResult(); } + public function findExistingChaptersByNumbers(string $mangaId, array $chapterNumbers): array + { + $queryBuilder = $this->entityManager->createQueryBuilder() + ->select('c') + ->from(EntityChapter::class, 'c') + ->where('c.manga = :mangaId') + ->andWhere('c.number IN (:chapterNumbers)') + ->setParameter('mangaId', $mangaId) + ->setParameter('chapterNumbers', $chapterNumbers); + + $chapters = $queryBuilder->getQuery()->getResult(); + + $chaptersByNumber = []; + foreach ($chapters as $chapter) { + $chaptersByNumber[$chapter->getNumber()] = $this->toChapterDomain($chapter); + } + + return $chaptersByNumber; + } + private function toDomain(EntityManga $entity): DomainManga { return new DomainManga( diff --git a/src/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandler.php b/src/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandler.php index be3e8a6..3f695f1 100644 --- a/src/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandler.php +++ b/src/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandler.php @@ -5,14 +5,12 @@ namespace App\Domain\Scraping\Application\CommandHandler; use App\Domain\Scraping\Application\Command\ScrapeChapter; use App\Domain\Scraping\Domain\Contract\Repository\ChapterRepositoryInterface; use App\Domain\Scraping\Domain\Contract\Repository\MangaRepositoryInterface; -use App\Domain\Scraping\Domain\Contract\Repository\ScrapingJobRepositoryInterface; use App\Domain\Scraping\Domain\Contract\Repository\SourceRepositoryInterface; use App\Domain\Scraping\Domain\Contract\Service\CbzGeneratorInterface; use App\Domain\Scraping\Domain\Contract\Service\ImageDownloaderInterface; use App\Domain\Scraping\Domain\Contract\Service\ScraperInterface; use App\Domain\Scraping\Domain\Event\ChapterScraped; use App\Domain\Scraping\Domain\Event\ChapterScrapingFailed; -use App\Domain\Scraping\Domain\Event\ChapterScrapingStarted; use App\Domain\Scraping\Domain\Model\ScrapingJob; use App\Domain\Scraping\Domain\Model\ValueObject\CbzGenerationRequest; use App\Domain\Scraping\Domain\Model\ValueObject\ScrapingRequest; diff --git a/src/Domain/Scraping/Domain/Event/ChapterScraped.php b/src/Domain/Scraping/Domain/Event/ChapterScraped.php index 9e785a0..e11eb3b 100644 --- a/src/Domain/Scraping/Domain/Event/ChapterScraped.php +++ b/src/Domain/Scraping/Domain/Event/ChapterScraped.php @@ -2,13 +2,15 @@ namespace App\Domain\Scraping\Domain\Event; -class ChapterScraped +readonly class ChapterScraped { + public function __construct( + private string $jobId + ) { + } - /** - * @param string $getId - */ - public function __construct(string $getId) + public function getJobId(): string { + return $this->jobId; } } diff --git a/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/GetJobListStateProvider.php b/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/GetJobListStateProvider.php index f88c107..77d471d 100644 --- a/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/GetJobListStateProvider.php +++ b/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/GetJobListStateProvider.php @@ -10,6 +10,7 @@ use App\Domain\Shared\Application\Query\ListJobsQuery; use App\Domain\Shared\Application\QueryHandler\ListJobsQueryHandler; use App\Domain\Shared\Domain\Model\JobStatus; use App\Domain\Shared\Infrastructure\ApiPlatform\Resource\GetJobListResource; +use ApiPlatform\State\Pagination\ArrayPaginator; readonly class GetJobListStateProvider implements ProviderInterface { @@ -17,7 +18,7 @@ readonly class GetJobListStateProvider implements ProviderInterface private ListJobsQueryHandler $handler ) {} - public function provide(Operation $operation, array $uriVariables = [], array $context = []): array + public function provide(Operation $operation, array $uriVariables = [], array $context = []): ArrayPaginator { $filters = $context['filters'] ?? []; @@ -60,15 +61,13 @@ readonly class GetJobListStateProvider implements ProviderInterface $response = $this->handler->handle($query); - return [ - 'items' => array_map( + return new ArrayPaginator( + array_map( fn($job) => GetJobListResource::fromJob($job), $response->items ), - 'total' => $response->total, - 'page' => $response->page, - 'limit' => $response->limit, - 'pages' => $response->pages - ]; + 0, + $response->total + ); } } diff --git a/src/Domain/Shared/Infrastructure/Persistence/Repository/DoctrineJobRepository.php b/src/Domain/Shared/Infrastructure/Persistence/Repository/DoctrineJobRepository.php index 7afdb50..9a467fe 100644 --- a/src/Domain/Shared/Infrastructure/Persistence/Repository/DoctrineJobRepository.php +++ b/src/Domain/Shared/Infrastructure/Persistence/Repository/DoctrineJobRepository.php @@ -20,12 +20,10 @@ readonly class DoctrineJobRepository implements JobRepositoryInterface public function save(Job $job): void { - dump('save', $job); /** @var JobEntity|null $existingJobEntity */ $existingJobEntity = $this->entityManager->find(JobEntity::class, $job->id); if ($existingJobEntity) { - dump('existingJobEntity', $existingJobEntity); $existingJobEntity->setStatus($job->status->value); $existingJobEntity->setStartedAt($job->startedAt); $existingJobEntity->setCompletedAt($job->completedAt); @@ -33,14 +31,11 @@ readonly class DoctrineJobRepository implements JobRepositoryInterface $existingJobEntity->setAttempts($job->attempts); $existingJobEntity->setContext($job->context); $this->entityManager->persist($existingJobEntity); - dump('updated', $existingJobEntity); } else { $entity = $this->mapper->toEntity($job); $this->entityManager->persist($entity); - dump('created', $entity); } $this->entityManager->flush(); - dump('flushed'); } public function get(string $id): Job diff --git a/tests/Domain/Manga/Adapter/InMemoryMangaRepository.php b/tests/Domain/Manga/Adapter/InMemoryMangaRepository.php index df47fc7..996b8da 100644 --- a/tests/Domain/Manga/Adapter/InMemoryMangaRepository.php +++ b/tests/Domain/Manga/Adapter/InMemoryMangaRepository.php @@ -13,7 +13,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface { /** @var array */ private array $mangas = []; - + /** @var array> */ private array $chapters = []; @@ -28,13 +28,13 @@ class InMemoryMangaRepository implements MangaRepositoryInterface $valueA = $this->getPropertyValue($a, $sortBy); $valueB = $this->getPropertyValue($b, $sortBy); - return $sortOrder === 'asc' - ? $valueA <=> $valueB + return $sortOrder === 'asc' + ? $valueA <=> $valueB : $valueB <=> $valueA; }); $offset = ($page - 1) * $limit; - + return array_slice($sortedMangas, $offset, $limit); } @@ -103,7 +103,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface public function addChaptersToManga(string $mangaId, int $count): void { $this->chapters[$mangaId] = []; - + for ($i = 1; $i <= $count; $i++) { $this->chapters[$mangaId][] = new Chapter( id: new ChapterId((string)$i), @@ -179,4 +179,15 @@ class InMemoryMangaRepository implements MangaRepositoryInterface { return count($this->search($query, 1, PHP_INT_MAX)); } + public function findExistingChaptersByNumbers(string $mangaId, array $chapterNumbers): array + { + if (!isset($this->chapters[$mangaId])) { + return []; + } + + return array_filter( + $this->chapters[$mangaId], + fn (Chapter $chapter) => in_array($chapter->getNumber(), $chapterNumbers) + ); + } } \ No newline at end of file diff --git a/tests/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandlerTest.php b/tests/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandlerTest.php index f1c32cd..cb8576b 100644 --- a/tests/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandlerTest.php +++ b/tests/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandlerTest.php @@ -52,7 +52,8 @@ class FetchMangaChaptersHandlerTest extends TestCase 'attributes' => [ 'chapter' => '1', 'title' => 'Chapter 1', - 'volume' => '1' + 'volume' => '1', + 'translatedLanguage' => 'fr' ] ] ]); diff --git a/tests/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandlerTest.php b/tests/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandlerTest.php index 041e993..010d367 100644 --- a/tests/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandlerTest.php +++ b/tests/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandlerTest.php @@ -8,27 +8,30 @@ use App\Domain\Scraping\Domain\Event\ChapterScraped; use App\Domain\Scraping\Domain\Event\ChapterScrapingFailed; use App\Domain\Scraping\Domain\Event\ChapterScrapingStarted; use App\Domain\Scraping\Domain\Model\Chapter; -use App\Domain\Scraping\Domain\Model\ScrapingStatus; +use App\Domain\Shared\Domain\Model\JobStatus; use App\Tests\Domain\Scraping\Adapter\InMemoryChapterRepository; use App\Tests\Domain\Scraping\Adapter\InMemoryCbzGenerator; use App\Tests\Domain\Scraping\Adapter\InMemoryEventBus; use App\Tests\Domain\Scraping\Adapter\InMemoryImageDownloader; use App\Tests\Domain\Scraping\Adapter\InMemoryMangaRepository; use App\Tests\Domain\Scraping\Adapter\InMemoryScraperAdapter; -use App\Tests\Domain\Scraping\Adapter\InMemoryScrapingJobRepository; use App\Tests\Domain\Scraping\Adapter\InMemorySourceRepository; +use App\Tests\Domain\Shared\Adapter\InMemoryJobRepository; +use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; class ScrapeChapterHandlerTest extends TestCase { private InMemoryScraperAdapter $scraper; private InMemoryImageDownloader $imageDownloader; private InMemoryCbzGenerator $cbzGenerator; - private InMemoryScrapingJobRepository $scrapingJobRepository; + private InMemoryJobRepository $jobRepository; private InMemoryChapterRepository $chapterRepository; private InMemoryMangaRepository $mangaRepository; private InMemorySourceRepository $sourceRepository; private InMemoryEventBus $eventBus; + private EntityManagerInterface|MockObject $entityManager; private ScrapeChapterHandler $handler; protected function setUp(): void @@ -36,11 +39,16 @@ class ScrapeChapterHandlerTest extends TestCase $this->scraper = new InMemoryScraperAdapter(); $this->imageDownloader = new InMemoryImageDownloader(); $this->cbzGenerator = new InMemoryCbzGenerator('/test/project/dir'); - $this->scrapingJobRepository = new InMemoryScrapingJobRepository(); + $this->jobRepository = new InMemoryJobRepository(); $this->chapterRepository = new InMemoryChapterRepository(); $this->mangaRepository = new InMemoryMangaRepository(); $this->sourceRepository = new InMemorySourceRepository(); $this->eventBus = new InMemoryEventBus(); + $this->entityManager = $this->createMock(EntityManagerInterface::class); + + $this->entityManager->method('beginTransaction')->willReturn(null); + $this->entityManager->method('commit')->willReturn(null); + $this->entityManager->method('rollback')->willReturn(null); $this->chapterRepository->save(new Chapter( id: '1', @@ -54,11 +62,12 @@ class ScrapeChapterHandlerTest extends TestCase $this->scraper, $this->imageDownloader, $this->cbzGenerator, - $this->scrapingJobRepository, + $this->jobRepository, $this->chapterRepository, $this->mangaRepository, $this->sourceRepository, - $this->eventBus + $this->eventBus, + $this->entityManager ); } @@ -72,15 +81,14 @@ class ScrapeChapterHandlerTest extends TestCase $this->handler->handle($command); - $scrapingJobs = $this->scrapingJobRepository->getJobs(); - $this->assertCount(1, $scrapingJobs); - $job = $scrapingJobs[0]; + $job = $this->jobRepository->findByType('scraping_job'); + $this->assertCount(1, $job); + $job = array_values($job)[0]; $dispatchedMessages = $this->eventBus->getDispatchedMessages(); - $this->assertCount(2, $dispatchedMessages); - $this->assertInstanceOf(ChapterScrapingStarted::class, $dispatchedMessages[0]); - $this->assertInstanceOf(ChapterScraped::class, $dispatchedMessages[1]); - $this->assertEquals($job->getId(), $dispatchedMessages[0]->getJobId()); + $this->assertCount(1, $dispatchedMessages); + $this->assertInstanceOf(ChapterScraped::class, $dispatchedMessages[0]); + $this->assertEquals($job->id, $dispatchedMessages[0]->getJobId()); $chapter = $this->chapterRepository->getByMangaIdAndChapterNumber('test-manga', 2); $this->assertNotNull($chapter->cbzPath); @@ -97,28 +105,25 @@ class ScrapeChapterHandlerTest extends TestCase $exception = new \Exception('Scraping failed'); $this->scraper->simulateError($exception); - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Scraping failed'); - $this->handler->handle($command); $dispatchedMessages = $this->eventBus->getDispatchedMessages(); - $this->assertCount(2, $dispatchedMessages); - $this->assertInstanceOf(ChapterScrapingStarted::class, $dispatchedMessages[0]); - $this->assertInstanceOf(ChapterScrapingFailed::class, $dispatchedMessages[1]); - $this->assertEquals('test-manga', $dispatchedMessages[1]->getMangaId()); - $this->assertEquals('2', $dispatchedMessages[1]->getChapterNumber()); - $this->assertEquals('Scraping failed', $dispatchedMessages[1]->getReason()); + $this->assertCount(1, $dispatchedMessages); + $this->assertInstanceOf(ChapterScrapingFailed::class, $dispatchedMessages[0]); + $this->assertEquals('test-manga', $dispatchedMessages[0]->getMangaId()); + $this->assertEquals('2', $dispatchedMessages[0]->getChapterNumber()); + $this->assertEquals('Scraping failed', $dispatchedMessages[0]->getReason()); - $jobs = $this->scrapingJobRepository->getJobs(); + $jobs = $this->jobRepository->findByType('scraping_job'); + $job = array_values($jobs)[0]; $this->assertCount(1, $jobs); - $this->assertEquals(ScrapingStatus::FAILED, $jobs[0]->status); - $this->assertEquals('Scraping failed', $jobs[0]->failureReason); + $this->assertEquals(JobStatus::FAILED, $job->status); + $this->assertEquals('Scraping failed', $job->failureReason); } protected function tearDown(): void { - $this->scrapingJobRepository->clear(); + $this->jobRepository->clear(); $this->chapterRepository->clear(); $this->mangaRepository->clear(); $this->sourceRepository->clear(); diff --git a/tests/Domain/Shared/Adapter/InMemoryJobRepository.php b/tests/Domain/Shared/Adapter/InMemoryJobRepository.php new file mode 100644 index 0000000..7a402ff --- /dev/null +++ b/tests/Domain/Shared/Adapter/InMemoryJobRepository.php @@ -0,0 +1,104 @@ +jobs[$job->id] = $job; + } + + public function get(string $id): ?Job + { + return $this->jobs[$id] ?? null; + } + + public function findByStatus(JobStatus $status): array + { + return array_filter($this->jobs, fn(Job $job) => $job->status === $status); + } + + public function findByType(string $type): array + { + return array_filter($this->jobs, fn(Job $job) => $job->type === $type); + } + + public function findPendingJobs(): array + { + return $this->findByStatus(JobStatus::PENDING); + } + + public function findInProgressJobs(): array + { + return $this->findByStatus(JobStatus::IN_PROGRESS); + } + + public function findFailedJobs(): array + { + return $this->findByStatus(JobStatus::FAILED); + } + + public function findByCriteria(array $criteria): array + { + $jobs = $this->jobs; + + if (isset($criteria['statuses']) && is_array($criteria['statuses']) && !empty($criteria['statuses'])) { + $jobs = array_filter($jobs, fn(Job $job) => in_array($job->status, $criteria['statuses'])); + } elseif (isset($criteria['status'])) { + $jobs = array_filter($jobs, fn(Job $job) => $job->status === $criteria['status']); + } + + if (isset($criteria['type'])) { + $jobs = array_filter($jobs, fn(Job $job) => $job->type === $criteria['type']); + } + + if (isset($criteria['createdAfter'])) { + $jobs = array_filter($jobs, fn(Job $job) => $job->createdAt >= $criteria['createdAfter']); + } + + if (isset($criteria['createdBefore'])) { + $jobs = array_filter($jobs, fn(Job $job) => $job->createdAt <= $criteria['createdBefore']); + } + + if (isset($criteria['sortBy'])) { + usort($jobs, function(Job $a, Job $b) use ($criteria) { + $sortOrder = $criteria['sortOrder'] ?? 'ASC'; + $comparison = match($criteria['sortBy']) { + 'createdAt' => $a->createdAt <=> $b->createdAt, + 'startedAt' => ($a->startedAt ?? new \DateTimeImmutable()) <=> ($b->startedAt ?? new \DateTimeImmutable()), + 'completedAt' => ($a->completedAt ?? new \DateTimeImmutable()) <=> ($b->completedAt ?? new \DateTimeImmutable()), + default => 0 + }; + return $sortOrder === 'ASC' ? $comparison : -$comparison; + }); + } + + if (isset($criteria['offset'])) { + $jobs = array_slice($jobs, $criteria['offset']); + } + + if (isset($criteria['limit'])) { + $jobs = array_slice($jobs, 0, $criteria['limit']); + } + + return array_values($jobs); + } + + public function countByCriteria(array $criteria): int + { + return count($this->findByCriteria($criteria)); + } + + public function clear(): void + { + $this->jobs = []; + } +} diff --git a/tests/Feature/Scraping/ScrapingStatusTest.php b/tests/Feature/Scraping/ScrapingStatusTest.php index 90be206..a072ad9 100644 --- a/tests/Feature/Scraping/ScrapingStatusTest.php +++ b/tests/Feature/Scraping/ScrapingStatusTest.php @@ -9,12 +9,13 @@ use App\Domain\Scraping\Domain\Model\ValueObject\ImageUrl; use App\Domain\Scraping\Domain\Model\ValueObject\PageNumber; use Ramsey\Uuid\Uuid; use Symfony\Component\Messenger\MessageBusInterface; -use App\Domain\Scraping\Domain\Contract\Repository\ScrapingJobRepositoryInterface; +use App\Domain\Shared\Domain\Contract\JobRepositoryInterface; +use App\Domain\Shared\Domain\Model\JobStatus; class ScrapingStatusTest extends ApiTestCase { private MessageBusInterface $messageBus; - private ScrapingJobRepositoryInterface $repository; + private JobRepositoryInterface $repository; protected function setUp(): void { @@ -23,7 +24,7 @@ class ScrapingStatusTest extends ApiTestCase self::bootKernel(); $this->messageBus = self::getContainer()->get(MessageBusInterface::class); - $this->repository = self::getContainer()->get(ScrapingJobRepositoryInterface::class); + $this->repository = self::getContainer()->get(JobRepositoryInterface::class); } public function testGetScrapingStatus(): void @@ -32,27 +33,39 @@ class ScrapingStatusTest extends ApiTestCase $jobId = Uuid::uuid4()->toString(); $job = new ScrapingJob($jobId, 'manga-123', 1, 'source-789'); - $job->addPage(new PageNumber(1), new ImageUrl('http://example.com/page1.jpg')); - $job->addPage(new PageNumber(2), new ImageUrl('http://example.com/page2.jpg')); + $job->start(); $this->repository->save($job); // When - $response = static::createClient()->request('GET', '/api/scraping/jobs/'.$jobId.'/status'); + $response = static::createClient()->request('GET', '/api/jobs?status=in_progress'); // Then $this->assertResponseIsSuccessful(); - $this->assertJsonContains([ - 'jobId' => $jobId, - 'status' => ScrapingStatus::IN_PROGRESS->value, - 'progress' => 0 - ]); + + $responseData = $response->toArray(); + + $this->assertArrayHasKey('hydra:member', $responseData); + $this->assertIsArray($responseData['hydra:member']); + $this->assertCount(1, $responseData['hydra:member']); + + $jobData = $responseData['hydra:member'][0]; + $this->assertEquals('/api/jobs/' . $jobId, $jobData['@id']); + $this->assertEquals('Job', $jobData['@type']); + $this->assertEquals($jobId, $jobData['id']); + $this->assertEquals('scraping_job', $jobData['type']); + $this->assertEquals(JobStatus::IN_PROGRESS->value, $jobData['status']); + $this->assertEquals([ + 'mangaId' => 'manga-123', + 'chapterNumber' => 1, + 'sourceId' => 'source-789' + ], $jobData['context']); } public function testGetScrapingStatusForNonExistentJob(): void { // When - $response = static::createClient()->request('GET', '/api/scraping/jobs/non-existent-id/status', [ + $response = static::createClient()->request('GET', '/api/jobs/non-existent-id?status=in_progress', [ 'headers' => ['Accept' => 'application/json'] ]);