feat: migrer vers Symfony 8, PHP 8.4 et les dépendances majeures associées
- PHP 8.3 → 8.4 (Dockerfile + composer.json) - Symfony 7.0 → 8.0 (tous les composants symfony/*) - API Platform 3.x → 4.x : migration openapiContext → openapi: new Operation(...) - Doctrine DBAL 3 → 4 : suppression use_savepoints, replace prepare/executeQuery - Doctrine ORM 2.x → 3.x : ClassMetadataInfo → ClassMetadata, setParameters → setParameter - Doctrine Bundle 2.x → 3.x, Fixtures Bundle 3.x → 4.x - zenstruck/foundry 1.x → 2.x : ModelFactory → PersistentObjectFactory, getDefaults → defaults - phpmd/phpmd 2.x → 3.x-dev (seule version supportant Symfony 8) - phparkitect 0.3 → 0.8 : NotDependsOnTheseNamespaces prend un array - symfony/mercure-bundle 0.3 → 0.4, symfony/monolog-bundle 3 → 4 - Suppression de runtime/frankenphp-symfony (intégré nativement dans symfony/runtime 8) - worker.Caddyfile : suppression de APP_RUNTIME (détection automatique Symfony 8) - Routes errors.xml/wdt.xml/profiler.xml → .php (Symfony 8 supprime le XML) - Types::ARRAY → Types::JSON dans Entity/Manga.php (DBAL 4 retire array type) - Suppression de src/Schedule.php (doublon vide avec MonitoringSchedule) - Tests : hydra:Collection → Collection, hydra:member → member (API Platform 4)
This commit is contained in:
parent
5a0888eb28
commit
5ed303612a
@@ -15,7 +15,7 @@ class InMemoryChapterSynchronizationService implements ChapterSynchronizationSer
|
||||
$this->synchronizedChapters[$manga->getId()->getValue()] = [
|
||||
'manga_id' => $manga->getId()->getValue(),
|
||||
'external_id' => $manga->getExternalId()?->getValue(),
|
||||
'synchronized_at' => new \DateTimeImmutable()
|
||||
'synchronized_at' => new \DateTimeImmutable(),
|
||||
];
|
||||
|
||||
// Retourne les IDs des chapitres synchronisés (simulation)
|
||||
|
||||
@@ -11,19 +11,21 @@ class InMemoryImageProcessor implements ImageProcessorInterface
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $downloadedImages = [];
|
||||
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $thumbnails = [];
|
||||
|
||||
public function downloadImage(string $imageUrl): string
|
||||
{
|
||||
$this->downloadedImages[$imageUrl] = self::FAKE_FULL_IMAGE_PATH;
|
||||
|
||||
return self::FAKE_FULL_IMAGE_PATH;
|
||||
}
|
||||
|
||||
public function createThumbnail(string $originalImagePath): string
|
||||
{
|
||||
$this->thumbnails[$originalImagePath] = self::FAKE_THUMBNAIL_PATH;
|
||||
|
||||
return self::FAKE_THUMBNAIL_PATH;
|
||||
}
|
||||
|
||||
@@ -36,4 +38,4 @@ class InMemoryImageProcessor implements ImageProcessorInterface
|
||||
{
|
||||
return $this->thumbnails;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,4 +48,4 @@ class InMemoryMangaProvider implements MangaProviderInterface
|
||||
{
|
||||
return new MangaCollection([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||
$valueA = $this->getPropertyValue($a, $sortBy);
|
||||
$valueB = $this->getPropertyValue($b, $sortBy);
|
||||
|
||||
return $sortOrder === 'asc'
|
||||
return 'asc' === $sortOrder
|
||||
? $valueA <=> $valueB
|
||||
: $valueB <=> $valueA;
|
||||
});
|
||||
@@ -57,6 +57,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||
return $manga;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -105,10 +106,10 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||
|
||||
private function getPropertyValue(Manga $manga, string $property): mixed
|
||||
{
|
||||
return match($property) {
|
||||
return match ($property) {
|
||||
'title' => $manga->getTitle()->getValue(),
|
||||
'publicationYear' => $manga->getPublicationYear(),
|
||||
default => throw new \InvalidArgumentException("Unknown sort property: $property")
|
||||
default => throw new \InvalidArgumentException("Unknown sort property: $property"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -121,7 +122,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||
$chapters = $this->chapters[$mangaId];
|
||||
|
||||
usort($chapters, function (Chapter $a, Chapter $b) use ($sortOrder) {
|
||||
return $sortOrder === 'desc'
|
||||
return 'desc' === $sortOrder
|
||||
? $b->getNumber() <=> $a->getNumber()
|
||||
: $a->getNumber() <=> $b->getNumber();
|
||||
});
|
||||
@@ -138,12 +139,13 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||
$chapters = $this->chapters[$mangaId];
|
||||
|
||||
usort($chapters, function (Chapter $a, Chapter $b) use ($sortOrder) {
|
||||
return $sortOrder === 'desc'
|
||||
return 'desc' === $sortOrder
|
||||
? $b->getNumber() <=> $a->getNumber()
|
||||
: $a->getNumber() <=> $b->getNumber();
|
||||
});
|
||||
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
return array_slice($chapters, $offset, $limit);
|
||||
}
|
||||
|
||||
@@ -171,6 +173,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||
if ($chapter && $chapter->isVisible()) {
|
||||
return $chapter;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -181,6 +184,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||
return $chapter;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -196,10 +200,9 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||
{
|
||||
return array_values(array_filter(
|
||||
$this->chaptersById,
|
||||
fn (Chapter $chapter) =>
|
||||
$chapter->getMangaId()->getValue() === $mangaId &&
|
||||
$chapter->getVolume() === $volume &&
|
||||
$chapter->isVisible()
|
||||
fn (Chapter $chapter) => $chapter->getMangaId()->getValue() === $mangaId
|
||||
&& $chapter->getVolume() === $volume
|
||||
&& $chapter->isVisible()
|
||||
));
|
||||
}
|
||||
|
||||
@@ -207,11 +210,10 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||
{
|
||||
return array_values(array_filter(
|
||||
$this->chaptersById,
|
||||
fn (Chapter $chapter) =>
|
||||
$chapter->getMangaId()->getValue() === $mangaId &&
|
||||
$chapter->getVolume() === $volume &&
|
||||
$chapter->isVisible() &&
|
||||
$chapter->isAvailable()
|
||||
fn (Chapter $chapter) => $chapter->getMangaId()->getValue() === $mangaId
|
||||
&& $chapter->getVolume() === $volume
|
||||
&& $chapter->isVisible()
|
||||
&& $chapter->isAvailable()
|
||||
));
|
||||
}
|
||||
|
||||
@@ -228,13 +230,13 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||
{
|
||||
$this->chapters[$mangaId] = [];
|
||||
|
||||
for ($i = 1; $i <= $count; $i++) {
|
||||
for ($i = 1; $i <= $count; ++$i) {
|
||||
$chapter = new Chapter(
|
||||
id: new ChapterId((string)$i),
|
||||
id: new ChapterId((string) $i),
|
||||
mangaId: new MangaId($mangaId),
|
||||
number: (float)$i,
|
||||
number: (float) $i,
|
||||
title: "Chapter $i",
|
||||
volume: (int)ceil($i / 10),
|
||||
volume: (int) ceil($i / 10),
|
||||
isVisible: true,
|
||||
createdAt: new \DateTimeImmutable()
|
||||
);
|
||||
@@ -250,6 +252,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||
return $manga;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -267,7 +270,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||
$manga->getTitle()->getValue(),
|
||||
$manga->getSlug()->getValue(),
|
||||
$manga->getAuthor(),
|
||||
$manga->getDescription()
|
||||
$manga->getDescription(),
|
||||
];
|
||||
|
||||
foreach ($manga->getAlternativeSlugs() as $altSlug) {
|
||||
@@ -287,6 +290,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||
usort($sortedMangas, fn (Manga $a, Manga $b) => $a->getTitle()->getValue() <=> $b->getTitle()->getValue());
|
||||
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
return array_slice($sortedMangas, $offset, $limit);
|
||||
}
|
||||
|
||||
@@ -320,9 +324,9 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($criteria->lastCheckBefore !== null) {
|
||||
if (null !== $criteria->lastCheckBefore) {
|
||||
$lastCheck = $manga->getLastMonitoringCheck();
|
||||
if ($lastCheck === null || $lastCheck >= $criteria->lastCheckBefore) {
|
||||
if (null === $lastCheck || $lastCheck >= $criteria->lastCheckBefore) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class InMemoryMangadexClient implements MangadexClientInterface
|
||||
public function __construct(
|
||||
array $mangas = [],
|
||||
array $feeds = [],
|
||||
array $aggregates = []
|
||||
array $aggregates = [],
|
||||
) {
|
||||
$this->mangas = $mangas;
|
||||
$this->feeds = $feeds;
|
||||
@@ -34,8 +34,8 @@ class InMemoryMangadexClient implements MangadexClientInterface
|
||||
{
|
||||
$results = [];
|
||||
foreach ($this->mangas as $id => $manga) {
|
||||
if (isset($manga['attributes']['title']['en']) &&
|
||||
str_contains(
|
||||
if (isset($manga['attributes']['title']['en'])
|
||||
&& str_contains(
|
||||
strtolower($manga['attributes']['title']['en']),
|
||||
strtolower($title)
|
||||
)
|
||||
@@ -52,7 +52,7 @@ class InMemoryMangadexClient implements MangadexClientInterface
|
||||
$statistics = [];
|
||||
foreach ($mangaIds as $id) {
|
||||
$statistics[$id] = [
|
||||
'rating' => ['average' => 4.5] // Default rating for tests
|
||||
'rating' => ['average' => 4.5], // Default rating for tests
|
||||
];
|
||||
}
|
||||
|
||||
@@ -64,18 +64,18 @@ class InMemoryMangadexClient implements MangadexClientInterface
|
||||
if (!isset($this->feeds[$mangaId])) {
|
||||
return [
|
||||
'data' => [],
|
||||
'total' => 0
|
||||
'total' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$feed = $this->feeds[$mangaId];
|
||||
if ($order === 'desc') {
|
||||
if ('desc' === $order) {
|
||||
$feed = array_reverse($feed);
|
||||
}
|
||||
|
||||
return [
|
||||
'data' => array_slice($feed, $offset, $limit),
|
||||
'total' => count($feed)
|
||||
'total' => count($feed),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -84,13 +84,13 @@ class InMemoryMangadexClient implements MangadexClientInterface
|
||||
if (!isset($this->aggregates[$mangaId])) {
|
||||
return [
|
||||
'result' => 'ok',
|
||||
'volumes' => []
|
||||
'volumes' => [],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'result' => 'ok',
|
||||
'volumes' => $this->aggregates[$mangaId]
|
||||
'volumes' => $this->aggregates[$mangaId],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class InMemoryMangadexClient implements MangadexClientInterface
|
||||
|
||||
return [
|
||||
'result' => 'ok',
|
||||
'data' => array_merge(['id' => $mangaId], $this->mangas[$mangaId])
|
||||
'data' => array_merge(['id' => $mangaId], $this->mangas[$mangaId]),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -125,4 +125,4 @@ class InMemoryMangadexClient implements MangadexClientInterface
|
||||
{
|
||||
$this->aggregates[$mangaId] = $aggregate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,28 +11,31 @@ class InMemoryPathManager implements MangaPathManagerInterface
|
||||
|
||||
public function getMangaDirectory(string $mangaTitle, string $publicationYear): string
|
||||
{
|
||||
$dir = '/tmp/manga/' . $this->slugify($mangaTitle) . '_' . $publicationYear;
|
||||
$dir = '/tmp/manga/'.$this->slugify($mangaTitle).'_'.$publicationYear;
|
||||
$this->ensureDirectory($dir);
|
||||
|
||||
return $dir;
|
||||
}
|
||||
|
||||
public function getVolumeDirectory(string $mangaTitle, string $publicationYear, int $volumeNumber): string
|
||||
{
|
||||
$dir = $this->getMangaDirectory($mangaTitle, $publicationYear) . '/volume_' . $volumeNumber;
|
||||
$dir = $this->getMangaDirectory($mangaTitle, $publicationYear).'/volume_'.$volumeNumber;
|
||||
$this->ensureDirectory($dir);
|
||||
|
||||
return $dir;
|
||||
}
|
||||
|
||||
public function buildChapterCbzPath(string $mangaTitle, string $publicationYear, int $volumeNumber, string $chapterNumber): string
|
||||
{
|
||||
$dir = $this->getVolumeDirectory($mangaTitle, $publicationYear, $volumeNumber);
|
||||
return $dir . '/' . $this->slugify($mangaTitle) . '_vol' . $volumeNumber . '_ch' . $chapterNumber . '.cbz';
|
||||
|
||||
return $dir.'/'.$this->slugify($mangaTitle).'_vol'.$volumeNumber.'_ch'.$chapterNumber.'.cbz';
|
||||
}
|
||||
|
||||
public function buildVolumeCbzPath(string $mangaTitle, string $publicationYear, int $volumeNumber): string
|
||||
{
|
||||
return $this->getVolumeDirectory($mangaTitle, $publicationYear, $volumeNumber)
|
||||
. '/' . $this->slugify($mangaTitle) . '_vol' . $volumeNumber . '.cbz';
|
||||
.'/'.$this->slugify($mangaTitle).'_vol'.$volumeNumber.'.cbz';
|
||||
}
|
||||
|
||||
public function createCbzArchive(array $files, string $cbzPath): void
|
||||
@@ -56,7 +59,7 @@ class InMemoryPathManager implements MangaPathManagerInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all stored files
|
||||
* Get all stored files.
|
||||
*/
|
||||
public function getFiles(): array
|
||||
{
|
||||
@@ -64,7 +67,7 @@ class InMemoryPathManager implements MangaPathManagerInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all stored files
|
||||
* Clear all stored files.
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
@@ -79,6 +82,7 @@ class InMemoryPathManager implements MangaPathManagerInterface
|
||||
$text = trim($text, '-');
|
||||
$text = preg_replace('~-+~', '-', $text);
|
||||
$text = strtolower($text);
|
||||
|
||||
return $text ?: 'n-a';
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ToggleMangaMonitoringTest extends TestCase
|
||||
{
|
||||
public function testCreateCommandWithValidData(): void
|
||||
public function testCreateCommandWithValidData(): void
|
||||
{
|
||||
// Arrange & Act
|
||||
$mangaId = new MangaId('manga-123');
|
||||
|
||||
@@ -10,9 +10,9 @@ use App\Domain\Manga\Domain\Model\ValueObject\ExternalId;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaId;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle;
|
||||
use App\Tests\Domain\Manga\Adapter\InMemoryImageProcessor;
|
||||
use App\Tests\Domain\Manga\Adapter\InMemoryMangaProvider;
|
||||
use App\Tests\Domain\Manga\Adapter\InMemoryMangaRepository;
|
||||
use App\Tests\Domain\Manga\Adapter\InMemoryImageProcessor;
|
||||
use App\Tests\Shared\Adapter\InMemoryEventDispatcher;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@@ -23,6 +23,7 @@ class CreateMangaFromMangadexHandlerTest extends TestCase
|
||||
private InMemoryImageProcessor $imageProcessor;
|
||||
private CreateMangaFromMangadexHandler $handler;
|
||||
private InMemoryEventDispatcher $eventDispatcher;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$manga = new Manga(
|
||||
|
||||
@@ -4,8 +4,8 @@ namespace App\Tests\Domain\Manga\Application\CommandHandler;
|
||||
|
||||
use App\Domain\Manga\Application\Command\CreateManga;
|
||||
use App\Domain\Manga\Application\CommandHandler\CreateMangaHandler;
|
||||
use App\Tests\Domain\Manga\Adapter\InMemoryMangaRepository;
|
||||
use App\Tests\Domain\Manga\Adapter\InMemoryImageProcessor;
|
||||
use App\Tests\Domain\Manga\Adapter\InMemoryMangaRepository;
|
||||
use App\Tests\Shared\Adapter\InMemoryMessageBus;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@@ -15,6 +15,7 @@ class CreateMangaHandlerTest extends TestCase
|
||||
private InMemoryImageProcessor $imageProcessor;
|
||||
private CreateMangaHandler $handler;
|
||||
private InMemoryMessageBus $messageBus;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = new InMemoryMangaRepository();
|
||||
@@ -80,4 +81,4 @@ class CreateMangaHandlerTest extends TestCase
|
||||
$this->assertEquals('One Piece', $savedManga->getTitle()->getValue());
|
||||
$this->assertNull($savedManga->getImageUrls());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ namespace App\Tests\Domain\Manga\Application\CommandHandler;
|
||||
|
||||
use App\Domain\Manga\Application\Command\ImportChapter;
|
||||
use App\Domain\Manga\Application\CommandHandler\ImportChapterHandler;
|
||||
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
use App\Domain\Manga\Domain\Model\Chapter;
|
||||
use App\Domain\Manga\Domain\Model\Manga;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\ChapterId;
|
||||
@@ -32,7 +32,7 @@ class ImportChapterHandlerTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function test_it_throws_exception_when_chapter_not_found(): void
|
||||
public function testItThrowsExceptionWhenChapterNotFound(): void
|
||||
{
|
||||
// Arrange
|
||||
$mangaId = 'manga-123';
|
||||
@@ -62,7 +62,7 @@ class ImportChapterHandlerTest extends TestCase
|
||||
$this->handler->handle($command);
|
||||
}
|
||||
|
||||
public function test_it_updates_existing_chapter_with_new_path(): void
|
||||
public function testItUpdatesExistingChapterWithNewPath(): void
|
||||
{
|
||||
// Arrange
|
||||
$mangaId = 'manga-123';
|
||||
@@ -113,7 +113,7 @@ class ImportChapterHandlerTest extends TestCase
|
||||
$this->assertNotNull($updatedChapter->getPagesDirectory());
|
||||
}
|
||||
|
||||
public function test_it_throws_exception_when_manga_not_found(): void
|
||||
public function testItThrowsExceptionWhenMangaNotFound(): void
|
||||
{
|
||||
// Arrange
|
||||
$cbzBinary = $this->createValidCbzBinary();
|
||||
@@ -130,7 +130,7 @@ class ImportChapterHandlerTest extends TestCase
|
||||
$this->handler->handle($command);
|
||||
}
|
||||
|
||||
public function test_it_throws_exception_when_file_is_not_valid_cbz(): void
|
||||
public function testItThrowsExceptionWhenFileIsNotValidCbz(): void
|
||||
{
|
||||
// Arrange
|
||||
$mangaId = 'manga-123';
|
||||
@@ -167,7 +167,7 @@ class ImportChapterHandlerTest extends TestCase
|
||||
unlink($tmpFile);
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
if ($zip->open($tmpFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
|
||||
if (true !== $zip->open($tmpFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE)) {
|
||||
throw new \RuntimeException('Cannot create test CBZ file');
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class ImportVolumeHandlerTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function test_it_updates_all_chapters_in_volume(): void
|
||||
public function testItUpdatesAllChaptersInVolume(): void
|
||||
{
|
||||
// Arrange
|
||||
$mangaId = 'manga-123';
|
||||
@@ -47,11 +47,11 @@ class ImportVolumeHandlerTest extends TestCase
|
||||
'ongoing'
|
||||
);
|
||||
// Create chapters in volume 1 and add through the aggregate
|
||||
for ($i = 1; $i <= 3; $i++) {
|
||||
for ($i = 1; $i <= 3; ++$i) {
|
||||
$chapter = new Chapter(
|
||||
new ChapterId("chapter-$i"),
|
||||
new MangaId($mangaId),
|
||||
(float)$i,
|
||||
(float) $i,
|
||||
"Chapter $i",
|
||||
$volumeNumber,
|
||||
true,
|
||||
@@ -81,7 +81,7 @@ class ImportVolumeHandlerTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_it_throws_exception_when_manga_not_found(): void
|
||||
public function testItThrowsExceptionWhenMangaNotFound(): void
|
||||
{
|
||||
// Arrange
|
||||
$cbzBinary = $this->createValidCbzBinary();
|
||||
@@ -98,7 +98,7 @@ class ImportVolumeHandlerTest extends TestCase
|
||||
$this->handler->handle($command);
|
||||
}
|
||||
|
||||
public function test_it_throws_exception_when_file_is_not_valid_cbz(): void
|
||||
public function testItThrowsExceptionWhenFileIsNotValidCbz(): void
|
||||
{
|
||||
// Arrange
|
||||
$mangaId = 'manga-123';
|
||||
@@ -129,7 +129,7 @@ class ImportVolumeHandlerTest extends TestCase
|
||||
$this->handler->handle($command);
|
||||
}
|
||||
|
||||
public function test_it_throws_exception_when_no_chapters_in_volume(): void
|
||||
public function testItThrowsExceptionWhenNoChaptersInVolume(): void
|
||||
{
|
||||
// Arrange
|
||||
$mangaId = 'manga-123';
|
||||
@@ -166,7 +166,7 @@ class ImportVolumeHandlerTest extends TestCase
|
||||
unlink($tmpFile);
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
if ($zip->open($tmpFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
|
||||
if (true !== $zip->open($tmpFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE)) {
|
||||
throw new \RuntimeException('Cannot create test CBZ file');
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ use App\Domain\Manga\Domain\Model\ValueObject\ExternalId;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaId;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MonitoringStatus;
|
||||
use App\Tests\Domain\Manga\Adapter\InMemoryMangaRepository;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class ChapterScrapedListenerTest extends TestCase
|
||||
$event = new ChapterScraped(
|
||||
jobId: 'job-abc',
|
||||
chapterId: $chapterId,
|
||||
pagesDirectory: '/data/pages/' . $chapterId,
|
||||
pagesDirectory: '/data/pages/'.$chapterId,
|
||||
pageCount: 25,
|
||||
);
|
||||
|
||||
@@ -67,7 +67,7 @@ class ChapterScrapedListenerTest extends TestCase
|
||||
|
||||
$updatedChapter = $this->mangaRepository->findChapterById($chapterId);
|
||||
$this->assertNotNull($updatedChapter);
|
||||
$this->assertEquals('/data/pages/' . $chapterId, $updatedChapter->getPagesDirectory());
|
||||
$this->assertEquals('/data/pages/'.$chapterId, $updatedChapter->getPagesDirectory());
|
||||
$this->assertEquals(25, $updatedChapter->getPageCount());
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function test_it_finds_exact_match_by_title(): void
|
||||
public function testItFindsExactMatchByTitle(): void
|
||||
{
|
||||
// Given
|
||||
$manga = $this->createManga(
|
||||
@@ -62,7 +62,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
$this->assertNotEmpty($response->possibleTitles);
|
||||
}
|
||||
|
||||
public function test_it_returns_empty_matches_when_no_manga_found(): void
|
||||
public function testItReturnsEmptyMatchesWhenNoMangaFound(): void
|
||||
{
|
||||
// Given - no manga in repository
|
||||
|
||||
@@ -76,7 +76,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
$this->assertNull($response->getBestMatch());
|
||||
}
|
||||
|
||||
public function test_it_finds_multiple_matches_and_sorts_by_score(): void
|
||||
public function testItFindsMultipleMatchesAndSortsByScore(): void
|
||||
{
|
||||
// Given
|
||||
$manga1 = $this->createManga(
|
||||
@@ -113,13 +113,13 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
$this->assertEquals('One Piece', $bestMatch->title);
|
||||
|
||||
// Les scores doivent être triés par ordre décroissant
|
||||
$scores = array_map(fn($match) => $match->matchScore, $response->matches);
|
||||
$scores = array_map(fn ($match) => $match->matchScore, $response->matches);
|
||||
$sortedScores = $scores;
|
||||
rsort($sortedScores);
|
||||
$this->assertEquals($sortedScores, $scores);
|
||||
}
|
||||
|
||||
public function test_it_extracts_chapter_and_volume_numbers(): void
|
||||
public function testItExtractsChapterAndVolumeNumbers(): void
|
||||
{
|
||||
// Given
|
||||
$manga = $this->createManga(
|
||||
@@ -144,7 +144,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_it_handles_decimal_chapter_numbers(): void
|
||||
public function testItHandlesDecimalChapterNumbers(): void
|
||||
{
|
||||
// Given
|
||||
$manga = $this->createManga(
|
||||
@@ -169,7 +169,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_it_finds_matches_with_alternative_slugs(): void
|
||||
public function testItFindsMatchesWithAlternativeSlugs(): void
|
||||
{
|
||||
// Given
|
||||
$manga = $this->createManga(
|
||||
@@ -192,7 +192,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
$this->assertEquals('Shingeki no Kyojin', $bestMatch->title);
|
||||
}
|
||||
|
||||
public function test_it_handles_filename_without_chapter_or_volume(): void
|
||||
public function testItHandlesFilenameWithoutChapterOrVolume(): void
|
||||
{
|
||||
// Given
|
||||
$manga = $this->createManga(
|
||||
@@ -217,7 +217,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
$this->assertNull($bestMatch->volumeNumber);
|
||||
}
|
||||
|
||||
public function test_it_finds_single_match_with_unique_id(): void
|
||||
public function testItFindsSingleMatchWithUniqueId(): void
|
||||
{
|
||||
// Given
|
||||
$manga = $this->createManga(
|
||||
@@ -236,7 +236,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
$this->assertEquals('123', $response->matches[0]->id);
|
||||
}
|
||||
|
||||
public function test_it_provides_possible_titles_in_response(): void
|
||||
public function testItProvidesPossibleTitlesInResponse(): void
|
||||
{
|
||||
// Given
|
||||
$manga = $this->createManga(
|
||||
@@ -255,7 +255,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
$this->assertEquals(['dragon-ball'], $response->possibleTitles);
|
||||
}
|
||||
|
||||
public function test_it_handles_filename_with_only_volume(): void
|
||||
public function testItHandlesFilenameWithOnlyVolume(): void
|
||||
{
|
||||
// Given
|
||||
$manga = $this->createManga(
|
||||
@@ -281,7 +281,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
$this->assertNull($bestMatch->chapterNumber);
|
||||
}
|
||||
|
||||
public function test_it_handles_filename_with_only_chapter(): void
|
||||
public function testItHandlesFilenameWithOnlyChapter(): void
|
||||
{
|
||||
// Given
|
||||
$manga = $this->createManga(
|
||||
@@ -307,7 +307,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
$this->assertNull($bestMatch->volumeNumber);
|
||||
}
|
||||
|
||||
public function test_it_handles_various_volume_only_formats(): void
|
||||
public function testItHandlesVariousVolumeOnlyFormats(): void
|
||||
{
|
||||
// Given
|
||||
$manga = $this->createManga(
|
||||
@@ -344,7 +344,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_it_handles_various_chapter_only_formats(): void
|
||||
public function testItHandlesVariousChapterOnlyFormats(): void
|
||||
{
|
||||
// Given
|
||||
$manga = $this->createManga(
|
||||
@@ -387,7 +387,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
string $title,
|
||||
string $slug,
|
||||
array $alternativeSlugs = [],
|
||||
?string $thumbnailUrl = null
|
||||
?string $thumbnailUrl = null,
|
||||
): Manga {
|
||||
return new Manga(
|
||||
id: new MangaId($id),
|
||||
@@ -411,4 +411,3 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase
|
||||
$this->repository->clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class GetMangaBySlugHandlerTest extends TestCase
|
||||
externalId: null,
|
||||
imageUrl: 'https://example.com/image.jpg',
|
||||
imageUrls: new ImageUrls('https://example.com/image.jpg', 'https://example.com/thumbnail.jpg'),
|
||||
|
||||
|
||||
rating: 4.5
|
||||
);
|
||||
$this->repository->save($manga);
|
||||
|
||||
@@ -28,7 +28,7 @@ class GetMangaChaptersHandlerTest extends TestCase
|
||||
public function testHandleThrowsExceptionWhenMangaNotFound(): void
|
||||
{
|
||||
$this->expectException(MangaNotFoundException::class);
|
||||
|
||||
|
||||
$query = new GetMangaChapters('non-existent-id');
|
||||
$this->handler->handle($query);
|
||||
}
|
||||
@@ -37,11 +37,11 @@ class GetMangaChaptersHandlerTest extends TestCase
|
||||
{
|
||||
// Arrange
|
||||
$this->givenMangaExists('123');
|
||||
|
||||
|
||||
// Act
|
||||
$query = new GetMangaChapters('123');
|
||||
$response = $this->handler->handle($query);
|
||||
|
||||
|
||||
// Assert
|
||||
$this->assertEmpty($response->chapters);
|
||||
$this->assertEquals(0, $response->total);
|
||||
@@ -55,11 +55,11 @@ class GetMangaChaptersHandlerTest extends TestCase
|
||||
{
|
||||
// Arrange
|
||||
$this->givenMangaExistsWithChapters('123', 25);
|
||||
|
||||
|
||||
// Act
|
||||
$query = new GetMangaChapters('123', page: 2, limit: 10);
|
||||
$response = $this->handler->handle($query);
|
||||
|
||||
|
||||
// Assert
|
||||
$this->assertCount(10, $response->chapters);
|
||||
$this->assertEquals(25, $response->total);
|
||||
@@ -137,7 +137,7 @@ class GetMangaChaptersHandlerTest extends TestCase
|
||||
title: null,
|
||||
volume: null,
|
||||
isVisible: true,
|
||||
pagesDirectory: '/manga/ch' . $num . '/',
|
||||
pagesDirectory: '/manga/ch'.$num.'/',
|
||||
));
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ class GetMangaChaptersHandlerTest extends TestCase
|
||||
title: null,
|
||||
volume: null,
|
||||
isVisible: true,
|
||||
pagesDirectory: '/manga/ch' . $num . '/',
|
||||
pagesDirectory: '/manga/ch'.$num.'/',
|
||||
));
|
||||
}
|
||||
|
||||
@@ -236,4 +236,4 @@ class GetMangaChaptersHandlerTest extends TestCase
|
||||
rating: null
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ use App\Domain\Manga\Application\Query\GetMangaList;
|
||||
use App\Domain\Manga\Application\QueryHandler\GetMangaListHandler;
|
||||
use App\Domain\Manga\Domain\Model\Manga;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaId;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle;
|
||||
use App\Tests\Domain\Manga\Adapter\InMemoryMangaRepository;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@@ -25,9 +25,9 @@ class GetMangaListHandlerTest extends TestCase
|
||||
public function testHandleReturnsEmptyListWhenNoMangas(): void
|
||||
{
|
||||
$query = new GetMangaList();
|
||||
|
||||
|
||||
$response = $this->handler->handle($query);
|
||||
|
||||
|
||||
$this->assertEmpty($response->mangas);
|
||||
$this->assertEquals(0, $response->total);
|
||||
$this->assertEquals(1, $response->page);
|
||||
@@ -40,11 +40,11 @@ class GetMangaListHandlerTest extends TestCase
|
||||
{
|
||||
// Arrange
|
||||
$this->givenMangasExist(25);
|
||||
|
||||
|
||||
// Act
|
||||
$query = new GetMangaList(page: 2, limit: 10);
|
||||
$response = $this->handler->handle($query);
|
||||
|
||||
|
||||
// Assert
|
||||
$this->assertCount(10, $response->mangas);
|
||||
$this->assertEquals(25, $response->total);
|
||||
@@ -74,9 +74,9 @@ class GetMangaListHandlerTest extends TestCase
|
||||
|
||||
private function givenMangasExist(int $count): void
|
||||
{
|
||||
for ($i = 1; $i <= $count; $i++) {
|
||||
for ($i = 1; $i <= $count; ++$i) {
|
||||
$this->repository->save(
|
||||
$this->createManga((string)$i, "Manga $i", 2020)
|
||||
$this->createManga((string) $i, "Manga $i", 2020)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -99,4 +99,4 @@ class GetMangaListHandlerTest extends TestCase
|
||||
{
|
||||
$this->repository->clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace App\Tests\Domain\Manga\Application\QueryHandler;
|
||||
use App\Domain\Manga\Application\Query\SearchManga;
|
||||
use App\Domain\Manga\Application\QueryHandler\SearchMangaHandler;
|
||||
use App\Domain\Manga\Domain\Model\Manga;
|
||||
use App\Domain\Manga\Domain\Model\MangaCollection;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\ExternalId;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaId;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
|
||||
@@ -57,4 +56,4 @@ class SearchMangaHandlerTest extends TestCase
|
||||
$this->assertEquals('One Piece', $response->items[0]->title);
|
||||
$this->assertEquals('one-piece', $response->items[0]->slug);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,14 @@ class MangadexClientTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
private function mockAuthenticationResponse(): MockObject&ResponseInterface
|
||||
private function mockAuthenticationResponse(): MockObject&ResponseInterface
|
||||
{
|
||||
$authResponse = $this->createMock(ResponseInterface::class);
|
||||
$authResponse->method('toArray')->willReturn([
|
||||
'access_token' => 'access_token',
|
||||
'refresh_token' => 'refresh_token'
|
||||
'refresh_token' => 'refresh_token',
|
||||
]);
|
||||
|
||||
return $authResponse;
|
||||
}
|
||||
|
||||
@@ -46,11 +47,11 @@ class MangadexClientTest extends TestCase
|
||||
'POST',
|
||||
'https://auth.mangadex.org/realms/mangadex/protocol/openid-connect/token',
|
||||
$this->callback(function ($options) {
|
||||
return $options['body']['grant_type'] === 'password'
|
||||
&& $options['body']['username'] === 'username'
|
||||
&& $options['body']['password'] === 'password'
|
||||
&& $options['body']['client_id'] === 'client_id'
|
||||
&& $options['body']['client_secret'] === 'client_secret';
|
||||
return 'password' === $options['body']['grant_type']
|
||||
&& 'username' === $options['body']['username']
|
||||
&& 'password' === $options['body']['password']
|
||||
&& 'client_id' === $options['body']['client_id']
|
||||
&& 'client_secret' === $options['body']['client_secret'];
|
||||
})
|
||||
)
|
||||
->willReturn($response);
|
||||
@@ -76,10 +77,10 @@ class MangadexClientTest extends TestCase
|
||||
[
|
||||
'id' => '123',
|
||||
'attributes' => [
|
||||
'title' => ['en' => 'Test Manga']
|
||||
]
|
||||
]
|
||||
]
|
||||
'title' => ['en' => 'Test Manga'],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$authResponse = $this->mockAuthenticationResponse();
|
||||
@@ -93,6 +94,7 @@ class MangadexClientTest extends TestCase
|
||||
if (str_contains($url, 'auth.mangadex.org')) {
|
||||
return $authResponse;
|
||||
}
|
||||
|
||||
return $searchResponse;
|
||||
});
|
||||
|
||||
@@ -105,9 +107,9 @@ class MangadexClientTest extends TestCase
|
||||
$expectedResponse = [
|
||||
'statistics' => [
|
||||
'123' => [
|
||||
'rating' => ['average' => 4.5]
|
||||
]
|
||||
]
|
||||
'rating' => ['average' => 4.5],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$authResponse = $this->mockAuthenticationResponse();
|
||||
@@ -121,10 +123,11 @@ class MangadexClientTest extends TestCase
|
||||
if (str_contains($url, 'auth.mangadex.org')) {
|
||||
return $authResponse;
|
||||
}
|
||||
|
||||
return $ratingsResponse;
|
||||
});
|
||||
|
||||
$result = $this->client->getMangaRatings(['123']);
|
||||
$this->assertEquals($expectedResponse, $result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,30 +43,30 @@ class MangadexProviderTest extends TestCase
|
||||
'year' => 2020,
|
||||
'status' => 'ongoing',
|
||||
'tags' => [
|
||||
['attributes' => ['name' => ['en' => 'Action']]]
|
||||
]
|
||||
['attributes' => ['name' => ['en' => 'Action']]],
|
||||
],
|
||||
],
|
||||
'relationships' => [
|
||||
[
|
||||
'type' => 'author',
|
||||
'attributes' => ['name' => 'Test Author']
|
||||
'attributes' => ['name' => 'Test Author'],
|
||||
],
|
||||
[
|
||||
'type' => 'cover_art',
|
||||
'attributes' => ['fileName' => 'cover.jpg']
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
'attributes' => ['fileName' => 'cover.jpg'],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->client->method('getMangaRatings')
|
||||
->willReturn([
|
||||
'statistics' => [
|
||||
'123' => [
|
||||
'rating' => ['average' => 4.5]
|
||||
]
|
||||
]
|
||||
'rating' => ['average' => 4.5],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$result = $this->provider->search('test');
|
||||
@@ -74,7 +74,7 @@ class MangadexProviderTest extends TestCase
|
||||
|
||||
$this->assertCount(1, $mangas);
|
||||
$manga = $mangas[0];
|
||||
|
||||
|
||||
$this->assertEquals('Test Manga', $manga->getTitle()->getValue());
|
||||
$this->assertEquals('test-manga', $manga->getSlug()->getValue());
|
||||
$this->assertEquals('Test description', $manga->getDescription());
|
||||
@@ -95,14 +95,14 @@ class MangadexProviderTest extends TestCase
|
||||
'id' => '123',
|
||||
'attributes' => [
|
||||
// Missing required 'title' field
|
||||
'description' => ['en' => 'Test description']
|
||||
'description' => ['en' => 'Test description'],
|
||||
],
|
||||
'relationships' => []
|
||||
]
|
||||
]
|
||||
'relationships' => [],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$result = $this->provider->search('test');
|
||||
$this->assertCount(0, $result->getItems());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class FilenameAnalyzerTest extends TestCase
|
||||
$this->analyzer = new FilenameAnalyzer();
|
||||
}
|
||||
|
||||
public function test_it_analyzes_one_piece_filename_correctly(): void
|
||||
public function testItAnalyzesOnePieceFilenameCorrectly(): void
|
||||
{
|
||||
// Given
|
||||
$filename = 'one-piece_vol108_ch1094.cbz';
|
||||
@@ -32,7 +32,7 @@ class FilenameAnalyzerTest extends TestCase
|
||||
$this->assertTrue($result->hasVolumeNumber());
|
||||
}
|
||||
|
||||
public function test_it_handles_different_filename_formats(): void
|
||||
public function testItHandlesDifferentFilenameFormats(): void
|
||||
{
|
||||
$testCases = [
|
||||
// Format underscore
|
||||
@@ -77,7 +77,7 @@ class FilenameAnalyzerTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_it_extracts_and_cleans_title(): void
|
||||
public function testItExtractsAndCleansTitle(): void
|
||||
{
|
||||
// Given
|
||||
$filename = 'one-piece_vol108_ch1094.cbz';
|
||||
@@ -90,7 +90,7 @@ class FilenameAnalyzerTest extends TestCase
|
||||
$this->assertNotEmpty($result->getTitle()->getValue(), 'Title should not be empty');
|
||||
}
|
||||
|
||||
public function test_it_handles_files_without_volume_or_chapter(): void
|
||||
public function testItHandlesFilesWithoutVolumeOrChapter(): void
|
||||
{
|
||||
$testCases = [
|
||||
[
|
||||
@@ -114,7 +114,7 @@ class FilenameAnalyzerTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_it_handles_cbz_and_cbr_extensions(): void
|
||||
public function testItHandlesCbzAndCbrExtensions(): void
|
||||
{
|
||||
// Given
|
||||
$testCases = [
|
||||
@@ -133,7 +133,7 @@ class FilenameAnalyzerTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_it_cleans_common_patterns(): void
|
||||
public function testItCleansCommonPatterns(): void
|
||||
{
|
||||
$testCases = [
|
||||
[
|
||||
@@ -160,7 +160,7 @@ class FilenameAnalyzerTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_it_handles_filename_with_only_volume(): void
|
||||
public function testItHandlesFilenameWithOnlyVolume(): void
|
||||
{
|
||||
$testCases = [
|
||||
[
|
||||
@@ -197,7 +197,7 @@ class FilenameAnalyzerTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_it_handles_filename_with_only_chapter(): void
|
||||
public function testItHandlesFilenameWithOnlyChapter(): void
|
||||
{
|
||||
$testCases = [
|
||||
[
|
||||
@@ -234,7 +234,7 @@ class FilenameAnalyzerTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_it_handles_full_dash_patterns(): void
|
||||
public function testItHandlesFullDashPatterns(): void
|
||||
{
|
||||
$testCases = [
|
||||
[
|
||||
|
||||
@@ -57,7 +57,7 @@ class MangadxChapterSynchronizationServiceTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Chapitres sans volume entre deux volumes différents → assignés au volume précédent
|
||||
* Chapitres sans volume entre deux volumes différents → assignés au volume précédent.
|
||||
*
|
||||
* Ch1→Vol1, Ch2→null, Ch3→null, Ch4→Vol2
|
||||
* Après sync : Ch2 et Ch3 doivent avoir Vol1
|
||||
@@ -83,7 +83,7 @@ class MangadxChapterSynchronizationServiceTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Chapitres en début de série sans volume → assignés au premier volume trouvé
|
||||
* Chapitres en début de série sans volume → assignés au premier volume trouvé.
|
||||
*
|
||||
* Ch1→null, Ch2→null, Ch3→Vol1
|
||||
* Après sync : Ch1 et Ch2 doivent avoir Vol1
|
||||
@@ -107,7 +107,7 @@ class MangadxChapterSynchronizationServiceTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Chapitres avec volumes explicites ne sont pas modifiés
|
||||
* Chapitres avec volumes explicites ne sont pas modifiés.
|
||||
*
|
||||
* Ch1→Vol1, Ch2→Vol1, Ch3→Vol2 → inchangé
|
||||
*/
|
||||
@@ -130,7 +130,7 @@ class MangadxChapterSynchronizationServiceTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* La version française est prioritaire sur l'anglaise
|
||||
* La version française est prioritaire sur l'anglaise.
|
||||
*
|
||||
* Même chapitre disponible EN (volume 1) et FR (volume 2) → FR gagne
|
||||
*/
|
||||
@@ -151,7 +151,7 @@ class MangadxChapterSynchronizationServiceTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Seuls les nouveaux chapitres sont sauvegardés (pas les doublons)
|
||||
* Seuls les nouveaux chapitres sont sauvegardés (pas les doublons).
|
||||
*
|
||||
* Ch1 déjà en DB + Ch2 nouveau → seul Ch2 est retourné
|
||||
*/
|
||||
@@ -188,6 +188,7 @@ class MangadxChapterSynchronizationServiceTest extends TestCase
|
||||
|
||||
/**
|
||||
* @param Chapter[] $chapters
|
||||
*
|
||||
* @return array<float, Chapter>
|
||||
*/
|
||||
private function indexedByNumber(array $chapters): array
|
||||
@@ -196,6 +197,7 @@ class MangadxChapterSynchronizationServiceTest extends TestCase
|
||||
foreach ($chapters as $chapter) {
|
||||
$result[$chapter->getNumber()] = $chapter;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ final class InMemoryChapterRepository implements ChapterRepositoryInterface
|
||||
{
|
||||
$this->chapters[$chapterId->getValue()] = [
|
||||
'pages' => $pages,
|
||||
'context' => $context
|
||||
'context' => $context,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -90,5 +90,4 @@ final class InMemoryChapterRepository implements ChapterRepositoryInterface
|
||||
|
||||
return $nextChapter ? new ChapterId($nextChapter) : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace App\Tests\Domain\Scraping\Adapter;
|
||||
|
||||
use App\Domain\Scraping\Domain\Contract\Repository\ChapterRepositoryInterface;
|
||||
use App\Domain\Scraping\Domain\Model\Chapter;
|
||||
use App\Domain\Scraping\Domain\Exception\ChapterNotFoundException;
|
||||
use App\Domain\Scraping\Domain\Model\Chapter;
|
||||
|
||||
class InMemoryChapterRepository implements ChapterRepositoryInterface
|
||||
{
|
||||
@@ -31,8 +31,6 @@ class InMemoryChapterRepository implements ChapterRepositoryInterface
|
||||
$this->chapters[$chapter->id] = $chapter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
$this->chapters = [];
|
||||
|
||||
@@ -11,7 +11,7 @@ class InMemoryImageStorage implements ImageStorageInterface
|
||||
|
||||
public function storeChapterImages(string $targetId, array $localImagePaths): string
|
||||
{
|
||||
$dir = '/fake/pages/' . $targetId;
|
||||
$dir = '/fake/pages/'.$targetId;
|
||||
$this->stored[$targetId] = $dir;
|
||||
|
||||
return $dir;
|
||||
@@ -19,7 +19,7 @@ class InMemoryImageStorage implements ImageStorageInterface
|
||||
|
||||
public function extractFromCbz(string $targetId, string $cbzBinary): string
|
||||
{
|
||||
$dir = '/fake/pages/' . $targetId;
|
||||
$dir = '/fake/pages/'.$targetId;
|
||||
$this->stored[$targetId] = $dir;
|
||||
|
||||
return $dir;
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace App\Tests\Domain\Scraping\Adapter;
|
||||
use App\Domain\Scraping\Domain\Contract\Repository\SourceRepositoryInterface;
|
||||
use App\Domain\Scraping\Domain\Model\Source;
|
||||
use App\Domain\Scraping\Domain\Model\ValueObject\SourceId;
|
||||
use DateTimeImmutable;
|
||||
|
||||
class InMemorySourceRepository implements SourceRepositoryInterface
|
||||
{
|
||||
@@ -24,11 +23,11 @@ class InMemorySourceRepository implements SourceRepositoryInterface
|
||||
'nextPageSelector' => null,
|
||||
'chapterUrlFormat' => 'https://example.com/manga/{slug}/chapter-{chapterNumber}',
|
||||
'scrapingType' => 'html',
|
||||
'chapterSelector' => '.chapter-item'
|
||||
'chapterSelector' => '.chapter-item',
|
||||
],
|
||||
true,
|
||||
new DateTimeImmutable(),
|
||||
new DateTimeImmutable()
|
||||
new \DateTimeImmutable(),
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,6 +57,7 @@ class InMemorySourceRepository implements SourceRepositoryInterface
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ class InMemorySourceRepository implements SourceRepositoryInterface
|
||||
$sources[] = $this->sources[$sourceId];
|
||||
}
|
||||
}
|
||||
|
||||
return $sources;
|
||||
}
|
||||
|
||||
@@ -82,7 +83,7 @@ class InMemorySourceRepository implements SourceRepositoryInterface
|
||||
{
|
||||
return array_filter(
|
||||
array_values($this->sources),
|
||||
fn(Source $source) => $source->isActive()
|
||||
fn (Source $source) => $source->isActive()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace App\Tests\Domain\Scraping\Application\CommandHandler;
|
||||
|
||||
use App\Domain\Scraping\Application\Command\ScrapeChapter;
|
||||
use App\Domain\Scraping\Application\CommandHandler\ScrapeChapterHandler;
|
||||
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\ScrapingJob;
|
||||
|
||||
@@ -24,12 +24,12 @@ class InMemoryJobRepository implements JobRepositoryInterface
|
||||
|
||||
public function findByStatus(JobStatus $status): array
|
||||
{
|
||||
return array_filter($this->jobs, fn(Job $job) => $job->status === $status);
|
||||
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);
|
||||
return array_filter($this->jobs, fn (Job $job) => $job->type === $type);
|
||||
}
|
||||
|
||||
public function findPendingJobs(): array
|
||||
@@ -52,33 +52,34 @@ class InMemoryJobRepository implements JobRepositoryInterface
|
||||
$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']));
|
||||
$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']);
|
||||
$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']);
|
||||
$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']);
|
||||
$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']);
|
||||
$jobs = array_filter($jobs, fn (Job $job) => $job->createdAt <= $criteria['createdBefore']);
|
||||
}
|
||||
|
||||
if (isset($criteria['sortBy'])) {
|
||||
usort($jobs, function(Job $a, Job $b) use ($criteria) {
|
||||
usort($jobs, function (Job $a, Job $b) use ($criteria) {
|
||||
$sortOrder = $criteria['sortOrder'] ?? 'ASC';
|
||||
$comparison = match($criteria['sortBy']) {
|
||||
$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
|
||||
default => 0,
|
||||
};
|
||||
return $sortOrder === 'ASC' ? $comparison : -$comparison;
|
||||
|
||||
return 'ASC' === $sortOrder ? $comparison : -$comparison;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class DeleteJobHandlerTest extends TestCase
|
||||
$this->handler = new DeleteJobHandler($this->repository);
|
||||
}
|
||||
|
||||
public function test_it_deletes_existing_job(): void
|
||||
public function testItDeletesExistingJob(): void
|
||||
{
|
||||
$job = new ScrapingJob('job-1', 'manga-1', 1.0, 'source-1');
|
||||
$this->repository->save($job);
|
||||
@@ -32,7 +32,7 @@ class DeleteJobHandlerTest extends TestCase
|
||||
$this->assertNull($this->repository->get('job-1'));
|
||||
}
|
||||
|
||||
public function test_it_throws_when_job_not_found(): void
|
||||
public function testItThrowsWhenJobNotFound(): void
|
||||
{
|
||||
$this->expectException(JobNotFoundException::class);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class DeleteJobsByCriteriaHandlerTest extends TestCase
|
||||
$this->handler = new DeleteJobsByCriteriaHandler($this->repository);
|
||||
}
|
||||
|
||||
public function test_it_deletes_jobs_by_status(): void
|
||||
public function testItDeletesJobsByStatus(): void
|
||||
{
|
||||
$job1 = new ScrapingJob('job-1', 'manga-1', 1.0, 'source-1');
|
||||
$job2 = new ScrapingJob('job-2', 'manga-1', 2.0, 'source-1');
|
||||
@@ -43,7 +43,7 @@ class DeleteJobsByCriteriaHandlerTest extends TestCase
|
||||
$this->assertNotNull($this->repository->get('job-1'));
|
||||
}
|
||||
|
||||
public function test_it_deletes_jobs_by_type(): void
|
||||
public function testItDeletesJobsByType(): void
|
||||
{
|
||||
$job1 = new ScrapingJob('job-1', 'manga-1', 1.0, 'source-1');
|
||||
$job2 = new ScrapingJob('job-2', 'manga-1', 2.0, 'source-1');
|
||||
@@ -58,7 +58,7 @@ class DeleteJobsByCriteriaHandlerTest extends TestCase
|
||||
$this->assertNull($this->repository->get('job-2'));
|
||||
}
|
||||
|
||||
public function test_it_does_nothing_when_no_criteria_match(): void
|
||||
public function testItDoesNothingWhenNoCriteriaMatch(): void
|
||||
{
|
||||
$job = new ScrapingJob('job-1', 'manga-1', 1.0, 'source-1');
|
||||
$this->repository->save($job);
|
||||
|
||||
Reference in New Issue
Block a user