diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results
new file mode 100644
index 0000000..71831d1
--- /dev/null
+++ b/.phpunit.cache/test-results
@@ -0,0 +1 @@
+{"version":1,"defects":{"App\\Tests\\Domain\\Scraping\\Application\\CommandHandler\\ScrapeChapterHandlerTest::testHandleSuccessfully":8,"App\\Tests\\Domain\\Scraping\\Application\\CommandHandler\\ScrapeChapterHandlerTest::testHandleThrowsException":8,"App\\Tests\\Feature\\Scraping\\ScrapeChapterTest::testInitiateChapterScraping":8,"App\\Tests\\Feature\\Scraping\\ScrapeChapterTest::testInitiateChapterScrapingWithInvalidPayload":8,"App\\Tests\\Feature\\Scraping\\ScrapingStatusTest::testGetScrapingStatus":7,"App\\Tests\\Feature\\Scraping\\ScrapingStatusTest::testGetScrapingStatusForNonExistentJob":7},"times":{"App\\Tests\\Domain\\Scraping\\Application\\CommandHandler\\ScrapeChapterHandlerTest::testHandleSuccessfully":0.003,"App\\Tests\\Domain\\Scraping\\Application\\CommandHandler\\ScrapeChapterHandlerTest::testHandleThrowsException":0,"App\\Tests\\Feature\\Scraping\\ScrapeChapterTest::testInitiateChapterScraping":0.038,"App\\Tests\\Feature\\Scraping\\ScrapeChapterTest::testInitiateChapterScrapingWithInvalidPayload":0.008,"App\\Tests\\Feature\\Scraping\\ScrapingStatusTest::testGetScrapingStatus":0.005,"App\\Tests\\Feature\\Scraping\\ScrapingStatusTest::testGetScrapingStatusForNonExistentJob":0.006}}
\ No newline at end of file
diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml
index 74f86b9..0cb406f 100644
--- a/config/packages/api_platform.yaml
+++ b/config/packages/api_platform.yaml
@@ -2,8 +2,8 @@ api_platform:
title: Mangarr API
version: 1.0.0
formats:
- jsonld: ['application/ld+json']
json: ['application/json']
+ jsonld: ['application/ld+json']
html: ['text/html']
jsonhal: ['application/hal+json']
swagger:
@@ -23,3 +23,8 @@ api_platform:
rfc_7807_compliant_errors: true
event_listeners_backward_compatibility_layer: false
keep_legacy_inflector: false
+ mapping:
+ paths:
+ - '%kernel.project_dir%/src/Domain/Scraping/Infrastructure/ApiPlatform/Dto'
+ patch_formats:
+ json: ['application/merge-patch+json']
diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml
index 9fe3f61..8b92ef4 100644
--- a/config/packages/doctrine.yaml
+++ b/config/packages/doctrine.yaml
@@ -21,6 +21,13 @@ doctrine:
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
+ # Ajout du mapping pour le domaine Scraping
+ Scraping:
+ type: attribute
+ is_bundle: false
+ dir: '%kernel.project_dir%/src/Domain/Scraping/Infrastructure/Persistence/Entity'
+ prefix: 'App\Domain\Scraping\Infrastructure\Persistence\Entity'
+ alias: Scraping
when@test:
doctrine:
diff --git a/config/services_test.yaml b/config/services_test.yaml
new file mode 100644
index 0000000..d626944
--- /dev/null
+++ b/config/services_test.yaml
@@ -0,0 +1,15 @@
+services:
+ _defaults:
+ autowire: true
+ autoconfigure: true
+ public: true
+
+ Symfony\Component\Messenger\MessageBusInterface:
+ class: 'App\Tests\Domain\Scraping\Adapter\InMemoryMessageBus'
+ public: true
+
+ App\Domain\Scraping\Domain\Contract\Repository\ScrapingJobRepositoryInterface:
+ class: 'App\Tests\Domain\Scraping\Adapter\InMemoryScrapingJobRepository'
+ public: true
+
+
diff --git a/phpmd.xml b/phpmd.xml
index 22855b2..a0b8d00 100644
--- a/phpmd.xml
+++ b/phpmd.xml
@@ -15,10 +15,10 @@
-
+
-
-
+
+
diff --git a/src/Domain/Scraping/Domain/Contract/Repository/SourceRepositoryInterface.php b/src/Domain/Scraping/Domain/Contract/Repository/SourceRepositoryInterface.php
index b2d3637..ccbafe0 100644
--- a/src/Domain/Scraping/Domain/Contract/Repository/SourceRepositoryInterface.php
+++ b/src/Domain/Scraping/Domain/Contract/Repository/SourceRepositoryInterface.php
@@ -1,10 +1,10 @@
pages;
}
+ public function getTotalPages(): int
+ {
+ return $this->totalPages;
+ }
+
public function getStatus(): ScrapingStatus
{
return $this->status;
diff --git a/src/Domain/Scraping/Infrastructure/ApiPlatform/Dto/ScrapeChapterRequest.php b/src/Domain/Scraping/Infrastructure/ApiPlatform/Dto/ScrapeChapterRequest.php
new file mode 100644
index 0000000..afb9ffd
--- /dev/null
+++ b/src/Domain/Scraping/Infrastructure/ApiPlatform/Dto/ScrapeChapterRequest.php
@@ -0,0 +1,37 @@
+ new Link(
+ fromProperty: 'jobId',
+ toProperty: 'id',
+ fromClass: ScrapingStatusResponse::class,
+ toClass: ScrapingJob::class
+ )
+ ]
+ ),
+ ],
+)]
+readonly class ScrapingStatusResponse
+{
+ public function __construct(
+ #[ApiProperty(identifier: true)]
+ public string $jobId,
+
+ #[ApiProperty]
+ public string $status,
+
+ #[ApiProperty]
+ public ?float $progress = null,
+
+ #[ApiProperty]
+ public ?string $error = null
+ ) {
+ }
+}
diff --git a/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/ScrapeChapterStateProcessor.php b/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/ScrapeChapterStateProcessor.php
new file mode 100644
index 0000000..3c49577
--- /dev/null
+++ b/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/ScrapeChapterStateProcessor.php
@@ -0,0 +1,30 @@
+commandBus->dispatch(
+ new ScrapeChapter(
+ $data->chapterId,
+ $data->sourceId,
+ $data->mangaId
+ )
+ );
+ }
+}
diff --git a/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Provider/ScrapingStatusStateProvider.php b/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Provider/ScrapingStatusStateProvider.php
new file mode 100644
index 0000000..7ad5037
--- /dev/null
+++ b/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Provider/ScrapingStatusStateProvider.php
@@ -0,0 +1,37 @@
+scrapingJobRepository->findById($uriVariables['jobId']);
+
+ if (!$job) {
+ throw new NotFoundHttpException('Job de scraping non trouvé');
+ }
+
+ $progress = 0;
+ if ($job->getTotalPages() > 0) {
+ $progress = (count($job->getPages()) / $job->getTotalPages()) * 100;
+ }
+
+ return new ScrapingStatusResponse(
+ jobId: $job->getId(),
+ status: $job->getStatus()->value,
+ progress: $progress
+ );
+ }
+}
diff --git a/src/Domain/Scraping/Infrastructure/Persistence/DoctrineScrapingJobRepository.php b/src/Domain/Scraping/Infrastructure/Persistence/DoctrineScrapingJobRepository.php
index 0b569f0..f0f3266 100644
--- a/src/Domain/Scraping/Infrastructure/Persistence/DoctrineScrapingJobRepository.php
+++ b/src/Domain/Scraping/Infrastructure/Persistence/DoctrineScrapingJobRepository.php
@@ -2,17 +2,18 @@
namespace App\Domain\Scraping\Infrastructure\Persistence;
+use App\Domain\Scraping\Domain\Contract\Repository\ScrapingJobRepositoryInterface;
use App\Domain\Scraping\Domain\Model\ScrapingJob;
use App\Domain\Scraping\Domain\Model\ScrapingStatus;
-use App\Domain\Scraping\Domain\Repository\ScrapingJobRepositoryInterface;
use App\Domain\Scraping\Infrastructure\Persistence\Entity\ScrapingJobEntity;
use Doctrine\ORM\EntityManagerInterface;
-class DoctrineScrapingJobRepository implements ScrapingJobRepositoryInterface
+readonly class DoctrineScrapingJobRepository implements ScrapingJobRepositoryInterface
{
public function __construct(
- private readonly EntityManagerInterface $entityManager
- ) {}
+ private EntityManagerInterface $entityManager
+ ) {
+ }
public function save(ScrapingJob $job): void
{
@@ -46,7 +47,7 @@ class DoctrineScrapingJobRepository implements ScrapingJobRepositoryInterface
->getQuery()
->getResult();
- return array_map(fn(ScrapingJobEntity $entity) => $entity->toDomain(), $entities);
+ return array_map(fn (ScrapingJobEntity $entity) => $entity->toDomain(), $entities);
}
public function findInProgressJobs(): array
@@ -58,6 +59,6 @@ class DoctrineScrapingJobRepository implements ScrapingJobRepositoryInterface
->getQuery()
->getResult();
- return array_map(fn(ScrapingJobEntity $entity) => $entity->toDomain(), $entities);
+ return array_map(fn (ScrapingJobEntity $entity) => $entity->toDomain(), $entities);
}
-}
\ No newline at end of file
+}
diff --git a/src/Entity/User.php b/src/Entity/User.php.old
similarity index 100%
rename from src/Entity/User.php
rename to src/Entity/User.php.old
diff --git a/tests/Domain/Scraping/Adapter/InMemoryMessageBus.php b/tests/Domain/Scraping/Adapter/InMemoryMessageBus.php
new file mode 100644
index 0000000..bd13dd6
--- /dev/null
+++ b/tests/Domain/Scraping/Adapter/InMemoryMessageBus.php
@@ -0,0 +1,38 @@
+ */
+ public static array $messages = [];
+
+ public function dispatch(object $message, array $stamps = []): Envelope
+ {
+ self::$messages[] = $message;
+ return new Envelope($message);
+ }
+
+ public function getDispatchedMessages(): array
+ {
+ return self::$messages;
+ }
+
+ public function clear(): void
+ {
+ self::$messages = [];
+ }
+
+ public function hasMessageOfType(string $messageClass): bool
+ {
+ foreach (self::$messages as $message) {
+ if ($message instanceof $messageClass) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/tests/Domain/Scraping/Adapter/InMemoryScrapingJobRepository.php b/tests/Domain/Scraping/Adapter/InMemoryScrapingJobRepository.php
index bbb5219..8068b07 100644
--- a/tests/Domain/Scraping/Adapter/InMemoryScrapingJobRepository.php
+++ b/tests/Domain/Scraping/Adapter/InMemoryScrapingJobRepository.php
@@ -8,21 +8,21 @@ use App\Domain\Scraping\Domain\Model\ScrapingJob;
class InMemoryScrapingJobRepository implements ScrapingJobRepositoryInterface
{
/** @var ScrapingJob[] */
- private array $jobs = [];
+ private static array $jobs = [];
public function save(ScrapingJob $job): void
{
- $this->jobs[] = $job;
+ self::$jobs[] = $job;
}
public function getJobs(): array
{
- return $this->jobs;
+ return self::$jobs;
}
public function findById(string $id): ?ScrapingJob
{
- foreach ($this->jobs as $job) {
+ foreach (self::$jobs as $job) {
if ($job->getId() === $id) {
return $job;
}
@@ -33,7 +33,7 @@ class InMemoryScrapingJobRepository implements ScrapingJobRepositoryInterface
public function findByChapterId(string $chapterId): ?ScrapingJob
{
- foreach ($this->jobs as $job) {
+ foreach (self::$jobs as $job) {
if ($job->getChapterId() === $chapterId) {
return $job;
}
@@ -41,4 +41,9 @@ class InMemoryScrapingJobRepository implements ScrapingJobRepositoryInterface
return null;
}
-}
+
+ public function clear(): void
+ {
+ self::$jobs = [];
+ }
+}
\ No newline at end of file
diff --git a/tests/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandlerTest.php b/tests/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandlerTest.php
index 11088ad..3fc5269 100644
--- a/tests/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandlerTest.php
+++ b/tests/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandlerTest.php
@@ -40,17 +40,14 @@ class ScrapeChapterHandlerTest extends TestCase
$this->handler->handle($command);
- // Vérifier que le job a été créé
$scrapingJobs = $this->scraper->getJobs();
$this->assertCount(1, $scrapingJobs);
$job = $scrapingJobs[0];
- // Vérifier que le job a été sauvegardé
$savedJobs = $this->repository->getJobs();
$this->assertCount(1, $savedJobs);
$this->assertSame($job, $savedJobs[0]);
- // Vérifier que l'événement a été dispatché
$dispatchedMessages = $this->eventBus->getDispatchedMessages();
$this->assertCount(1, $dispatchedMessages);
$this->assertInstanceOf(ChapterScrapingStarted::class, $dispatchedMessages[0]);
@@ -74,7 +71,6 @@ class ScrapeChapterHandlerTest extends TestCase
try {
$this->handler->handle($command);
} finally {
- // Vérifier que l'événement d'échec a été dispatché
$dispatchedMessages = $this->eventBus->getDispatchedMessages();
$this->assertCount(1, $dispatchedMessages);
$this->assertInstanceOf(ChapterScrapingFailed::class, $dispatchedMessages[0]);
diff --git a/tests/Feature/Scraping/AbstractApiTestCase.php b/tests/Feature/Scraping/AbstractApiTestCase.php
new file mode 100644
index 0000000..4245ce8
--- /dev/null
+++ b/tests/Feature/Scraping/AbstractApiTestCase.php
@@ -0,0 +1,18 @@
+toString(),
+ $attributes['mangaId'] ?? 'manga-'.Uuid::uuid4()->toString(),
+ $attributes['chapterId'] ?? 'chapter-'.Uuid::uuid4()->toString(),
+ $attributes['sourceId'] ?? 'source-'.Uuid::uuid4()->toString()
+ );
+
+ if (isset($attributes['status'])) {
+ $this->setJobStatus($job, $attributes['status']);
+ }
+
+ if (isset($attributes['pages'])) {
+ foreach ($attributes['pages'] as $index => $page) {
+ $job->addPage(new PageNumber($index + 1), new ImageUrl($page));
+ }
+ }
+
+ $this->repository->save($job);
+
+ return $job;
+ }
+
+ private function setJobStatus(ScrapingJob $job, ScrapingStatus $status): void
+ {
+ // Cette méthode nécessite peut-être d'ajouter des méthodes protégées dans ScrapingJob
+ // pour permettre la modification du statut dans les tests
+ // Ou utiliser de la réflexion si nécessaire
+ $reflection = new \ReflectionProperty($job, 'status');
+ $reflection->setAccessible(true);
+ $reflection->setValue($job, $status);
+ }
+}
\ No newline at end of file
diff --git a/tests/Feature/Scraping/ScrapeChapterTest.php b/tests/Feature/Scraping/ScrapeChapterTest.php
new file mode 100644
index 0000000..1d99acd
--- /dev/null
+++ b/tests/Feature/Scraping/ScrapeChapterTest.php
@@ -0,0 +1,71 @@
+messageBus = self::getContainer()->get(MessageBusInterface::class);
+ }
+
+ public function testInitiateChapterScraping(): void
+ {
+ // Given
+ $payload = [
+ 'chapterId' => 'chapter-123',
+ 'sourceId' => 'source-456',
+ 'mangaId' => 'manga-789',
+ ];
+
+ // When
+ $response = static::createClient()->request('POST', '/api/scraping/chapters', [
+ 'json' => $payload,
+ 'headers' => ['Accept' => 'application/json'],
+ ]);
+
+ // Then
+ $this->assertResponseStatusCodeSame(202);
+
+ $messages = $this->messageBus::$messages;
+ $this->assertCount(1, $messages, 'Un message devrait être dispatché');
+
+ /** @var ScrapeChapter $message */
+ $message = $messages[0];
+ $this->assertInstanceOf(ScrapeChapter::class, $message);
+ }
+
+ public function testInitiateChapterScrapingWithInvalidPayload(): void
+ {
+ // Given
+ $payload = [
+ 'chapterId' => '',
+ 'sourceId' => 'source-456',
+ 'mangaId' => 'manga-789',
+ ];
+
+ // When
+ $response = static::createClient()->request('POST', '/api/scraping/chapters', [
+ 'json' => $payload,
+ 'headers' => ['Accept' => 'application/json'],
+ ]);
+
+ // Then
+ $this->assertResponseStatusCodeSame(422);
+ $this->assertJsonContains([
+ 'violations' => [
+ [
+ 'propertyPath' => 'chapterId',
+ 'message' => 'This value should not be blank.',
+ ],
+ ],
+ ]);
+ }
+}
diff --git a/tests/Feature/Scraping/ScrapingStatusTest.php b/tests/Feature/Scraping/ScrapingStatusTest.php
new file mode 100644
index 0000000..b277347
--- /dev/null
+++ b/tests/Feature/Scraping/ScrapingStatusTest.php
@@ -0,0 +1,69 @@
+messageBus = self::getContainer()->get(MessageBusInterface::class);
+ $this->repository = self::getContainer()->get(ScrapingJobRepositoryInterface::class);
+ }
+
+ public function testGetScrapingStatus(): void
+ {
+ // Given
+ $jobId = Uuid::uuid4()->toString();
+ $job = new ScrapingJob($jobId, 'manga-123', 'chapter-456', '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'));
+
+ $this->repository->save($job);
+
+ // When
+ $response = static::createClient()->request('GET', '/api/scraping/jobs/'.$jobId.'/status');
+
+ // Then
+ $this->assertResponseIsSuccessful();
+ $this->assertJsonContains([
+ 'jobId' => $jobId,
+ 'status' => ScrapingStatus::IN_PROGRESS->value,
+ 'progress' => 0
+ ]);
+ }
+
+ public function testGetScrapingStatusForNonExistentJob(): void
+ {
+ // When
+ $response = static::createClient()->request('GET', '/api/scraping/jobs/non-existent-id/status', [
+ 'headers' => ['Accept' => 'application/json']
+ ]);
+
+ // Then
+ $this->assertResponseStatusCodeSame(404);
+ }
+
+ protected function tearDown(): void
+ {
+ parent::tearDown();
+
+ self::ensureKernelShutdown();
+ }
+}
\ No newline at end of file