refactor(scraping): job PENDING dès le POST HTTP, handler sans Doctrine

- ScrapingJob: mangaId/chapterNumber/sourceId optionnels (nullable) pour
  permettre la création en PENDING sans lookup DB dans le StateProcessor
- ScrapeChapter: ajoute jobId (pré-généré par le StateProcessor)
- ScrapeChapterStateProcessor: crée et persiste le job PENDING avant
  dispatch; injecte JobRepositoryInterface uniquement
- ScrapeChapterHandler: supprime EntityManagerInterface, beginTransaction/
  commit/rollback; charge le job existant via jobId, complete() sur succès
  seulement, fail() si toutes les sources échouent
- ScrapeChapterHandlerTest: pré-crée le job, passe jobId dans la commande,
  supprime le mock EntityManagerInterface
- ScrapeChapterTest: accès aux messages via static InMemoryMessageBus,
  vérifie la présence du jobId dans la commande dispatchée
This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2026-03-17 15:33:20 +01:00
parent ec4a8be934
commit fa035bfbfa
10 changed files with 252 additions and 356 deletions

View File

@@ -5,13 +5,17 @@ namespace App\Domain\Scraping\Infrastructure\ApiPlatform\State\Processor;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Domain\Scraping\Application\Command\ScrapeChapter;
use App\Domain\Scraping\Domain\Model\ScrapingJob;
use App\Domain\Scraping\Infrastructure\ApiPlatform\Dto\ScrapeChapterRequest;
use App\Domain\Shared\Domain\Contract\JobRepositoryInterface;
use Ramsey\Uuid\Uuid;
use Symfony\Component\Messenger\MessageBusInterface;
final class ScrapeChapterStateProcessor implements ProcessorInterface
{
public function __construct(
private readonly MessageBusInterface $commandBus
private readonly MessageBusInterface $commandBus,
private readonly JobRepositoryInterface $jobRepository,
) {
}
@@ -20,10 +24,11 @@ final class ScrapeChapterStateProcessor implements ProcessorInterface
*/
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void
{
$this->commandBus->dispatch(
new ScrapeChapter(
$data->chapterId
)
);
$jobId = Uuid::uuid4()->toString();
$job = new ScrapingJob($jobId);
$job->context['chapterId'] = $data->chapterId;
$this->jobRepository->save($job);
$this->commandBus->dispatch(new ScrapeChapter($data->chapterId, $jobId));
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Domain\Scraping\Infrastructure\EventSubscriber;
use App\Domain\Shared\Domain\Event\ChapterScraped;
use App\Domain\Scraping\Domain\Event\ChapterScrapingFailed;
use App\Domain\Scraping\Domain\Event\ChapterScrapingStarted;
use App\Domain\Scraping\Domain\Event\PageScrapingProgressed;
use App\Domain\Scraping\Domain\Contract\Repository\ChapterRepositoryInterface;
use App\Domain\Shared\Domain\Contract\JobRepositoryInterface;
use App\Domain\Shared\Domain\Contract\NotificationInterface;
@@ -30,6 +31,22 @@ class ScrapingEventSubscriber implements EventSubscriberInterface
return [];
}
#[AsMessageHandler]
public function onPageScrapingProgressed(PageScrapingProgressed $event): void
{
$progress = (int) round($event->getProgress()->getPercentage());
$update = new Update(
'jobs/activity',
json_encode([
'type' => 'job.progress_updated',
'jobId' => $event->getJobId(),
'progress' => $progress,
])
);
$this->hub->publish($update);
}
#[AsMessageHandler]
public function onChapterScrapingStarted(ChapterScrapingStarted $event): void
{