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);
|
||||
|
||||
@@ -4,30 +4,29 @@ namespace App\Tests\Factory;
|
||||
|
||||
use App\Entity\ApiToken;
|
||||
use App\Repository\ApiTokenRepository;
|
||||
use Zenstruck\Foundry\ModelFactory;
|
||||
use Zenstruck\Foundry\Proxy;
|
||||
use Zenstruck\Foundry\RepositoryProxy;
|
||||
use Zenstruck\Foundry\Persistence\PersistentObjectFactory;
|
||||
use Zenstruck\Foundry\Persistence\RepositoryDecorator;
|
||||
|
||||
/**
|
||||
* @extends ModelFactory<ApiToken>
|
||||
* @extends PersistentObjectFactory<ApiToken>
|
||||
*
|
||||
* @method ApiToken|Proxy create(array|callable $attributes = [])
|
||||
* @method static ApiToken|Proxy createOne(array $attributes = [])
|
||||
* @method static ApiToken|Proxy find(object|array|mixed $criteria)
|
||||
* @method static ApiToken|Proxy findOrCreate(array $attributes)
|
||||
* @method static ApiToken|Proxy first(string $sortedField = 'id')
|
||||
* @method static ApiToken|Proxy last(string $sortedField = 'id')
|
||||
* @method static ApiToken|Proxy random(array $attributes = [])
|
||||
* @method static ApiToken|Proxy randomOrCreate(array $attributes = [])
|
||||
* @method static ApiTokenRepository|RepositoryProxy repository()
|
||||
* @method static ApiToken[]|Proxy[] all()
|
||||
* @method static ApiToken[]|Proxy[] createMany(int $number, array|callable $attributes = [])
|
||||
* @method static ApiToken[]|Proxy[] createSequence(iterable|callable $sequence)
|
||||
* @method static ApiToken[]|Proxy[] findBy(array $attributes)
|
||||
* @method static ApiToken[]|Proxy[] randomRange(int $min, int $max, array $attributes = [])
|
||||
* @method static ApiToken[]|Proxy[] randomSet(int $number, array $attributes = [])
|
||||
* @method ApiToken create(array|callable $attributes = [])
|
||||
* @method static ApiToken createOne(array $attributes = [])
|
||||
* @method static ApiToken find(object|array|mixed $criteria)
|
||||
* @method static ApiToken findOrCreate(array $attributes)
|
||||
* @method static ApiToken first(string $sortedField = 'id')
|
||||
* @method static ApiToken last(string $sortedField = 'id')
|
||||
* @method static ApiToken random(array $attributes = [])
|
||||
* @method static ApiToken randomOrCreate(array $attributes = [])
|
||||
* @method static ApiTokenRepository&RepositoryDecorator repository()
|
||||
* @method static list<ApiToken> all()
|
||||
* @method static list<ApiToken> createMany(int $number, array|callable $attributes = [])
|
||||
* @method static list<ApiToken> createSequence(iterable|callable $sequence)
|
||||
* @method static list<ApiToken> findBy(array $attributes)
|
||||
* @method static list<ApiToken> randomRange(int $min, int $max, array $attributes = [])
|
||||
* @method static list<ApiToken> randomSet(int $number, array $attributes = [])
|
||||
*/
|
||||
final class ApiTokenFactory extends ModelFactory
|
||||
final class ApiTokenFactory extends PersistentObjectFactory
|
||||
{
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services
|
||||
@@ -39,12 +38,17 @@ final class ApiTokenFactory extends ModelFactory
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public static function class(): string
|
||||
{
|
||||
return ApiToken::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories
|
||||
*
|
||||
* @todo add your default values here
|
||||
*/
|
||||
protected function getDefaults(): array
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
'ownedBy' => UserFactory::new(),
|
||||
@@ -55,15 +59,10 @@ final class ApiTokenFactory extends ModelFactory
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
|
||||
*/
|
||||
protected function initialize(): self
|
||||
protected function initialize(): static
|
||||
{
|
||||
return $this
|
||||
// ->afterInstantiate(function(ApiToken $apiToken): void {})
|
||||
;
|
||||
}
|
||||
|
||||
protected static function getClass(): string
|
||||
{
|
||||
return ApiToken::class;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,30 +4,29 @@ namespace App\Tests\Factory;
|
||||
|
||||
use App\Entity\Chapter;
|
||||
use App\Repository\ChapterRepository;
|
||||
use Zenstruck\Foundry\ModelFactory;
|
||||
use Zenstruck\Foundry\Proxy;
|
||||
use Zenstruck\Foundry\RepositoryProxy;
|
||||
use Zenstruck\Foundry\Persistence\PersistentObjectFactory;
|
||||
use Zenstruck\Foundry\Persistence\RepositoryDecorator;
|
||||
|
||||
/**
|
||||
* @extends ModelFactory<Chapter>
|
||||
* @extends PersistentObjectFactory<Chapter>
|
||||
*
|
||||
* @method Chapter|Proxy create(array|callable $attributes = [])
|
||||
* @method static Chapter|Proxy createOne(array $attributes = [])
|
||||
* @method static Chapter|Proxy find(object|array|mixed $criteria)
|
||||
* @method static Chapter|Proxy findOrCreate(array $attributes)
|
||||
* @method static Chapter|Proxy first(string $sortedField = 'id')
|
||||
* @method static Chapter|Proxy last(string $sortedField = 'id')
|
||||
* @method static Chapter|Proxy random(array $attributes = [])
|
||||
* @method static Chapter|Proxy randomOrCreate(array $attributes = [])
|
||||
* @method static ChapterRepository|RepositoryProxy repository()
|
||||
* @method static Chapter[]|Proxy[] all()
|
||||
* @method static Chapter[]|Proxy[] createMany(int $number, array|callable $attributes = [])
|
||||
* @method static Chapter[]|Proxy[] createSequence(iterable|callable $sequence)
|
||||
* @method static Chapter[]|Proxy[] findBy(array $attributes)
|
||||
* @method static Chapter[]|Proxy[] randomRange(int $min, int $max, array $attributes = [])
|
||||
* @method static Chapter[]|Proxy[] randomSet(int $number, array $attributes = [])
|
||||
* @method Chapter create(array|callable $attributes = [])
|
||||
* @method static Chapter createOne(array $attributes = [])
|
||||
* @method static Chapter find(object|array|mixed $criteria)
|
||||
* @method static Chapter findOrCreate(array $attributes)
|
||||
* @method static Chapter first(string $sortedField = 'id')
|
||||
* @method static Chapter last(string $sortedField = 'id')
|
||||
* @method static Chapter random(array $attributes = [])
|
||||
* @method static Chapter randomOrCreate(array $attributes = [])
|
||||
* @method static ChapterRepository&RepositoryDecorator repository()
|
||||
* @method static list<Chapter> all()
|
||||
* @method static list<Chapter> createMany(int $number, array|callable $attributes = [])
|
||||
* @method static list<Chapter> createSequence(iterable|callable $sequence)
|
||||
* @method static list<Chapter> findBy(array $attributes)
|
||||
* @method static list<Chapter> randomRange(int $min, int $max, array $attributes = [])
|
||||
* @method static list<Chapter> randomSet(int $number, array $attributes = [])
|
||||
*/
|
||||
final class ChapterFactory extends ModelFactory
|
||||
final class ChapterFactory extends PersistentObjectFactory
|
||||
{
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services
|
||||
@@ -39,12 +38,17 @@ final class ChapterFactory extends ModelFactory
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public static function class(): string
|
||||
{
|
||||
return Chapter::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories
|
||||
*
|
||||
* @todo add your default values here
|
||||
*/
|
||||
protected function getDefaults(): array
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
'manga' => MangaFactory::new(),
|
||||
@@ -61,15 +65,10 @@ final class ChapterFactory extends ModelFactory
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
|
||||
*/
|
||||
protected function initialize(): self
|
||||
protected function initialize(): static
|
||||
{
|
||||
return $this
|
||||
// ->afterInstantiate(function(Chapter $chapter): void {})
|
||||
;
|
||||
}
|
||||
|
||||
protected static function getClass(): string
|
||||
{
|
||||
return Chapter::class;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,42 +4,46 @@ namespace App\Tests\Factory;
|
||||
|
||||
use App\Entity\Manga;
|
||||
use App\Repository\MangaRepository;
|
||||
use Zenstruck\Foundry\ModelFactory;
|
||||
use Zenstruck\Foundry\Proxy;
|
||||
use Zenstruck\Foundry\RepositoryProxy;
|
||||
use Zenstruck\Foundry\Persistence\PersistentObjectFactory;
|
||||
use Zenstruck\Foundry\Persistence\RepositoryDecorator;
|
||||
|
||||
/**
|
||||
* @extends ModelFactory<Manga>
|
||||
* @extends PersistentObjectFactory<Manga>
|
||||
*
|
||||
* @method Manga|Proxy create(array|callable $attributes = [])
|
||||
* @method static Manga|Proxy createOne(array $attributes = [])
|
||||
* @method static Manga|Proxy find(object|array|mixed $criteria)
|
||||
* @method static Manga|Proxy findOrCreate(array $attributes)
|
||||
* @method static Manga|Proxy first(string $sortedField = 'id')
|
||||
* @method static Manga|Proxy last(string $sortedField = 'id')
|
||||
* @method static Manga|Proxy random(array $attributes = [])
|
||||
* @method static Manga|Proxy randomOrCreate(array $attributes = [])
|
||||
* @method static MangaRepository|RepositoryProxy repository()
|
||||
* @method static Manga[]|Proxy[] all()
|
||||
* @method static Manga[]|Proxy[] createMany(int $number, array|callable $attributes = [])
|
||||
* @method static Manga[]|Proxy[] createSequence(iterable|callable $sequence)
|
||||
* @method static Manga[]|Proxy[] findBy(array $attributes)
|
||||
* @method static Manga[]|Proxy[] randomRange(int $min, int $max, array $attributes = [])
|
||||
* @method static Manga[]|Proxy[] randomSet(int $number, array $attributes = [])
|
||||
* @method Manga create(array|callable $attributes = [])
|
||||
* @method static Manga createOne(array $attributes = [])
|
||||
* @method static Manga find(object|array|mixed $criteria)
|
||||
* @method static Manga findOrCreate(array $attributes)
|
||||
* @method static Manga first(string $sortedField = 'id')
|
||||
* @method static Manga last(string $sortedField = 'id')
|
||||
* @method static Manga random(array $attributes = [])
|
||||
* @method static Manga randomOrCreate(array $attributes = [])
|
||||
* @method static MangaRepository&RepositoryDecorator repository()
|
||||
* @method static list<Manga> all()
|
||||
* @method static list<Manga> createMany(int $number, array|callable $attributes = [])
|
||||
* @method static list<Manga> createSequence(iterable|callable $sequence)
|
||||
* @method static list<Manga> findBy(array $attributes)
|
||||
* @method static list<Manga> randomRange(int $min, int $max, array $attributes = [])
|
||||
* @method static list<Manga> randomSet(int $number, array $attributes = [])
|
||||
*/
|
||||
final class MangaFactory extends ModelFactory
|
||||
final class MangaFactory extends PersistentObjectFactory
|
||||
{
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services
|
||||
*
|
||||
* @todo inject services if required
|
||||
*/
|
||||
public static function class(): string
|
||||
{
|
||||
return Manga::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories
|
||||
*
|
||||
* @todo add your default values here
|
||||
*/
|
||||
protected function getDefaults(): array
|
||||
protected function defaults(): array
|
||||
{
|
||||
$title = self::faker()->words(rand(1, 3), true);
|
||||
|
||||
@@ -64,15 +68,10 @@ final class MangaFactory extends ModelFactory
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
|
||||
*/
|
||||
protected function initialize(): self
|
||||
protected function initialize(): static
|
||||
{
|
||||
return $this
|
||||
// ->afterInstantiate(function(Manga $manga): void {})
|
||||
;
|
||||
}
|
||||
|
||||
protected static function getClass(): string
|
||||
{
|
||||
return Manga::class;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,30 +4,29 @@ namespace App\Tests\Factory;
|
||||
|
||||
use App\Entity\Page;
|
||||
use App\Repository\PageRepository;
|
||||
use Zenstruck\Foundry\ModelFactory;
|
||||
use Zenstruck\Foundry\Proxy;
|
||||
use Zenstruck\Foundry\RepositoryProxy;
|
||||
use Zenstruck\Foundry\Persistence\PersistentObjectFactory;
|
||||
use Zenstruck\Foundry\Persistence\RepositoryDecorator;
|
||||
|
||||
/**
|
||||
* @extends ModelFactory<Page>
|
||||
* @extends PersistentObjectFactory<Page>
|
||||
*
|
||||
* @method Page|Proxy create(array|callable $attributes = [])
|
||||
* @method static Page|Proxy createOne(array $attributes = [])
|
||||
* @method static Page|Proxy find(object|array|mixed $criteria)
|
||||
* @method static Page|Proxy findOrCreate(array $attributes)
|
||||
* @method static Page|Proxy first(string $sortedField = 'id')
|
||||
* @method static Page|Proxy last(string $sortedField = 'id')
|
||||
* @method static Page|Proxy random(array $attributes = [])
|
||||
* @method static Page|Proxy randomOrCreate(array $attributes = [])
|
||||
* @method static PageRepository|RepositoryProxy repository()
|
||||
* @method static Page[]|Proxy[] all()
|
||||
* @method static Page[]|Proxy[] createMany(int $number, array|callable $attributes = [])
|
||||
* @method static Page[]|Proxy[] createSequence(iterable|callable $sequence)
|
||||
* @method static Page[]|Proxy[] findBy(array $attributes)
|
||||
* @method static Page[]|Proxy[] randomRange(int $min, int $max, array $attributes = [])
|
||||
* @method static Page[]|Proxy[] randomSet(int $number, array $attributes = [])
|
||||
* @method Page create(array|callable $attributes = [])
|
||||
* @method static Page createOne(array $attributes = [])
|
||||
* @method static Page find(object|array|mixed $criteria)
|
||||
* @method static Page findOrCreate(array $attributes)
|
||||
* @method static Page first(string $sortedField = 'id')
|
||||
* @method static Page last(string $sortedField = 'id')
|
||||
* @method static Page random(array $attributes = [])
|
||||
* @method static Page randomOrCreate(array $attributes = [])
|
||||
* @method static PageRepository&RepositoryDecorator repository()
|
||||
* @method static list<Page> all()
|
||||
* @method static list<Page> createMany(int $number, array|callable $attributes = [])
|
||||
* @method static list<Page> createSequence(iterable|callable $sequence)
|
||||
* @method static list<Page> findBy(array $attributes)
|
||||
* @method static list<Page> randomRange(int $min, int $max, array $attributes = [])
|
||||
* @method static list<Page> randomSet(int $number, array $attributes = [])
|
||||
*/
|
||||
final class PageFactory extends ModelFactory
|
||||
final class PageFactory extends PersistentObjectFactory
|
||||
{
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services
|
||||
@@ -39,12 +38,17 @@ final class PageFactory extends ModelFactory
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public static function class(): string
|
||||
{
|
||||
return Page::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories
|
||||
*
|
||||
* @todo add your default values here
|
||||
*/
|
||||
protected function getDefaults(): array
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
'chapter' => ChapterFactory::new(),
|
||||
@@ -57,15 +61,10 @@ final class PageFactory extends ModelFactory
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
|
||||
*/
|
||||
protected function initialize(): self
|
||||
protected function initialize(): static
|
||||
{
|
||||
return $this
|
||||
// ->afterInstantiate(function(Page $page): void {})
|
||||
;
|
||||
}
|
||||
|
||||
protected static function getClass(): string
|
||||
{
|
||||
return Page::class;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,30 +4,29 @@ namespace App\Tests\Factory;
|
||||
|
||||
use App\Entity\Source;
|
||||
use App\Repository\SourceRepository;
|
||||
use Zenstruck\Foundry\ModelFactory;
|
||||
use Zenstruck\Foundry\Proxy;
|
||||
use Zenstruck\Foundry\RepositoryProxy;
|
||||
use Zenstruck\Foundry\Persistence\PersistentObjectFactory;
|
||||
use Zenstruck\Foundry\Persistence\RepositoryDecorator;
|
||||
|
||||
/**
|
||||
* @extends ModelFactory<Source>
|
||||
* @extends PersistentObjectFactory<Source>
|
||||
*
|
||||
* @method Source|Proxy create(array|callable $attributes = [])
|
||||
* @method static Source|Proxy createOne(array $attributes = [])
|
||||
* @method static Source|Proxy find(object|array|mixed $criteria)
|
||||
* @method static Source|Proxy findOrCreate(array $attributes)
|
||||
* @method static Source|Proxy first(string $sortedField = 'id')
|
||||
* @method static Source|Proxy last(string $sortedField = 'id')
|
||||
* @method static Source|Proxy random(array $attributes = [])
|
||||
* @method static Source|Proxy randomOrCreate(array $attributes = [])
|
||||
* @method static SourceRepository|RepositoryProxy repository()
|
||||
* @method static Source[]|Proxy[] all()
|
||||
* @method static Source[]|Proxy[] createMany(int $number, array|callable $attributes = [])
|
||||
* @method static Source[]|Proxy[] createSequence(iterable|callable $sequence)
|
||||
* @method static Source[]|Proxy[] findBy(array $attributes)
|
||||
* @method static Source[]|Proxy[] randomRange(int $min, int $max, array $attributes = [])
|
||||
* @method static Source[]|Proxy[] randomSet(int $number, array $attributes = [])
|
||||
* @method Source create(array|callable $attributes = [])
|
||||
* @method static Source createOne(array $attributes = [])
|
||||
* @method static Source find(object|array|mixed $criteria)
|
||||
* @method static Source findOrCreate(array $attributes)
|
||||
* @method static Source first(string $sortedField = 'id')
|
||||
* @method static Source last(string $sortedField = 'id')
|
||||
* @method static Source random(array $attributes = [])
|
||||
* @method static Source randomOrCreate(array $attributes = [])
|
||||
* @method static SourceRepository&RepositoryDecorator repository()
|
||||
* @method static list<Source> all()
|
||||
* @method static list<Source> createMany(int $number, array|callable $attributes = [])
|
||||
* @method static list<Source> createSequence(iterable|callable $sequence)
|
||||
* @method static list<Source> findBy(array $attributes)
|
||||
* @method static list<Source> randomRange(int $min, int $max, array $attributes = [])
|
||||
* @method static list<Source> randomSet(int $number, array $attributes = [])
|
||||
*/
|
||||
final class SourceFactory extends ModelFactory
|
||||
final class SourceFactory extends PersistentObjectFactory
|
||||
{
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services
|
||||
@@ -39,12 +38,17 @@ final class SourceFactory extends ModelFactory
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public static function class(): string
|
||||
{
|
||||
return Source::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories
|
||||
*
|
||||
* @todo add your default values here
|
||||
*/
|
||||
protected function getDefaults(): array
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
'name' => self::faker()->optional()->company(),
|
||||
@@ -60,15 +64,10 @@ final class SourceFactory extends ModelFactory
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
|
||||
*/
|
||||
protected function initialize(): self
|
||||
protected function initialize(): static
|
||||
{
|
||||
return $this
|
||||
// ->afterInstantiate(function(Source $source): void {})
|
||||
;
|
||||
}
|
||||
|
||||
protected static function getClass(): string
|
||||
{
|
||||
return Source::class;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,48 +5,50 @@ namespace App\Tests\Factory;
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Zenstruck\Foundry\ModelFactory;
|
||||
use Zenstruck\Foundry\Proxy;
|
||||
use Zenstruck\Foundry\RepositoryProxy;
|
||||
use Zenstruck\Foundry\Persistence\PersistentObjectFactory;
|
||||
use Zenstruck\Foundry\Persistence\RepositoryDecorator;
|
||||
|
||||
/**
|
||||
* @extends ModelFactory<User>
|
||||
* @extends PersistentObjectFactory<User>
|
||||
*
|
||||
* @method User|Proxy create(array|callable $attributes = [])
|
||||
* @method static User|Proxy createOne(array $attributes = [])
|
||||
* @method static User|Proxy find(object|array|mixed $criteria)
|
||||
* @method static User|Proxy findOrCreate(array $attributes)
|
||||
* @method static User|Proxy first(string $sortedField = 'id')
|
||||
* @method static User|Proxy last(string $sortedField = 'id')
|
||||
* @method static User|Proxy random(array $attributes = [])
|
||||
* @method static User|Proxy randomOrCreate(array $attributes = [])
|
||||
* @method static UserRepository|RepositoryProxy repository()
|
||||
* @method static User[]|Proxy[] all()
|
||||
* @method static User[]|Proxy[] createMany(int $number, array|callable $attributes = [])
|
||||
* @method static User[]|Proxy[] createSequence(iterable|callable $sequence)
|
||||
* @method static User[]|Proxy[] findBy(array $attributes)
|
||||
* @method static User[]|Proxy[] randomRange(int $min, int $max, array $attributes = [])
|
||||
* @method static User[]|Proxy[] randomSet(int $number, array $attributes = [])
|
||||
* @method User create(array|callable $attributes = [])
|
||||
* @method static User createOne(array $attributes = [])
|
||||
* @method static User find(object|array|mixed $criteria)
|
||||
* @method static User findOrCreate(array $attributes)
|
||||
* @method static User first(string $sortedField = 'id')
|
||||
* @method static User last(string $sortedField = 'id')
|
||||
* @method static User random(array $attributes = [])
|
||||
* @method static User randomOrCreate(array $attributes = [])
|
||||
* @method static UserRepository&RepositoryDecorator repository()
|
||||
* @method static list<User> all()
|
||||
* @method static list<User> createMany(int $number, array|callable $attributes = [])
|
||||
* @method static list<User> createSequence(iterable|callable $sequence)
|
||||
* @method static list<User> findBy(array $attributes)
|
||||
* @method static list<User> randomRange(int $min, int $max, array $attributes = [])
|
||||
* @method static list<User> randomSet(int $number, array $attributes = [])
|
||||
*/
|
||||
final class UserFactory extends ModelFactory
|
||||
final class UserFactory extends PersistentObjectFactory
|
||||
{
|
||||
public const array FIRST_NAMES = ["ALAIN", "ALEXANDRE", "ANDRÉ", "ANNIE", "ANTHONY", "AUDREY", "AURÉLIE", "BERNARD", "BRIGITTE", "BRUNO", "CATHERINE", "CEDRIC", "CHANTAL", "CHRISTELLE", "CHRISTIAN", "CHRISTIANE", "CHRISTINE", "CHRISTOPHE", "CLAUDE", "CORINNE", "CÉLINE", "DANIEL", "DANIELLE", "DAVID", "DENISE", "DIDIER", "DOMINIQUE", "ELODIE", "EMILIE", "ENZO", "ERIC", "FABRICE", "FLORENCE", "FRANCK", "FRANÇOISE", "FRÉDÉRIC", "GEORGES", "GERMAINE", "GUILLAUME", "GUY", "GÉRARD", "HENRI", "ISABELLE", "JACQUELINE", "JACQUES", "JEAN", "JEAN-CLAUDE", "JEAN-PIERRE", "JEANNE", "JEANNINE", "JEREMY", "JEROME", "JONATHAN", "JOSEPH", "JULIE", "JULIEN", "KARINE", "KEVIN", "LAETITIA", "LAURA", "LAURENCE", "LAURENT", "LOUIS", "LUCAS", "LÉA", "MADELEINE", "MANON", "MARCEL", "MARCELLE", "MARGUERITE", "MARIE", "MARINE", "MARTINE", "MAURICE", "MAXIME", "MICHEL", "MICHÈLE", "MONIQUE", "NATHALIE", "NICOLAS", "NICOLE", "ODETTE", "OLIVIER", "PASCAL", "PASCALE", "PATRICIA", "PATRICK", "PAUL", "PAULETTE", "PHILIPPE", "PIERRE", "RENÉ", "ROBERT", "ROGER", "ROMAIN", "SANDRA", "SANDRINE", "SERGE", "SOPHIE", "STÉPHANE", "STÉPHANIE", "SUZANNE", "SYLVIE", "SÉBASTIEN", "THIERRY", "THOMAS", "THÉO", "VALÉRIE", "VIRGINIE", "VÉRONIQUE", "YVETTE", "YVONNE"];
|
||||
public const array LAST_NAMES = ["Adam", "Andre", "Antoine", "Arnaud", "Aubert", "Aubry", "Bailly", "Barbier", "Baron", "Barre", "Barthelemy", "Benard", "Benoit", "Berger", "Bernard", "Bertin", "Bertrand", "Besson", "Blanc", "Blanchard", "Bonnet", "Boucher", "Bouchet", "Boulanger", "Bourgeois", "Bouvier", "Boyer", "Breton", "Brun", "Brunet", "Carlier", "Caron", "Carpentier", "Carre", "Charles", "Charpentier", "Chauvin", "Chevalier", "Chevallier", "Clement", "Colin", "Collet", "Collin", "Cordier", "Cousin", "Da Silva", "Daniel", "David", "Delaunay", "Denis", "Deschamps", "Dubois", "Dufour", "Dumas", "Dumont", "Dupont", "Dupuis", "Dupuy", "Durand", "Duval", "Etienne", "Fabre", "Faure", "Fernandez", "Fleury", "Fontaine", "Fournier", "Francois", "Gaillard", "Garcia", "Garnier", "Gauthier", "Gautier", "Gay", "Gerard", "Germain", "Gilbert", "Gillet", "Girard", "Giraud", "Gonzalez", "Grondin", "Guerin", "Guichard", "Guillaume", "Guillot", "Guyot", "Hamon", "Henry", "Herve", "Hoarau", "Hubert", "Huet", "Humbert", "Jacob", "Jacquet", "Jean", "Joly", "Julien", "Klein", "Lacroix", "Lambert", "Lamy", "Langlois", "Laporte", "Laurent", "Le Gall", "Le Goff", "Le Roux", "Leblanc", "Lebrun", "Leclerc", "Leclercq", "Lecomte", "Lefebvre", "Lefevre", "Leger", "Legrand", "Lejeune", "Lemaire", "Lemaitre", "Lemoine", "Leroux", "Leroy", "Leveque", "Lopez", "Louis", "Lucas", "Maillard", "Mallet", "Marchal", "Marchand", "Marechal", "Marie", "Martin", "Martinez", "Marty", "Masson", "Mathieu", "Menard", "Mercier", "Meunier", "Meyer", "Michaud", "Michel", "Millet", "Monnier", "Moreau", "Morel", "Morin", "Moulin", "Muller", "Nicolas", "Noel", "Olivier", "Paris", "Pasquier", "Payet", "Pelletier", "Perez", "Perret", "Perrier", "Perrin", "Perrot", "Petit", "Philippe", "Picard", "Pichon", "Pierre", "Poirier", "Poulain", "Prevost", "Remy", "Renard", "Renaud", "Renault", "Rey", "Reynaud", "Richard", "Riviere", "Robert", "Robin", "Roche", "Rodriguez", "Roger", "Rolland", "Rousseau", "Roussel", "Roux", "Roy", "Royer", "Sanchez", "Schmitt", "Schneider", "Simon", "Tessier", "Thomas", "Vasseur", "Vidal", "Vincent", "Weber"];
|
||||
public const array FIRST_NAMES = ['ALAIN', 'ALEXANDRE', 'ANDRÉ', 'ANNIE', 'ANTHONY', 'AUDREY', 'AURÉLIE', 'BERNARD', 'BRIGITTE', 'BRUNO', 'CATHERINE', 'CEDRIC', 'CHANTAL', 'CHRISTELLE', 'CHRISTIAN', 'CHRISTIANE', 'CHRISTINE', 'CHRISTOPHE', 'CLAUDE', 'CORINNE', 'CÉLINE', 'DANIEL', 'DANIELLE', 'DAVID', 'DENISE', 'DIDIER', 'DOMINIQUE', 'ELODIE', 'EMILIE', 'ENZO', 'ERIC', 'FABRICE', 'FLORENCE', 'FRANCK', 'FRANÇOISE', 'FRÉDÉRIC', 'GEORGES', 'GERMAINE', 'GUILLAUME', 'GUY', 'GÉRARD', 'HENRI', 'ISABELLE', 'JACQUELINE', 'JACQUES', 'JEAN', 'JEAN-CLAUDE', 'JEAN-PIERRE', 'JEANNE', 'JEANNINE', 'JEREMY', 'JEROME', 'JONATHAN', 'JOSEPH', 'JULIE', 'JULIEN', 'KARINE', 'KEVIN', 'LAETITIA', 'LAURA', 'LAURENCE', 'LAURENT', 'LOUIS', 'LUCAS', 'LÉA', 'MADELEINE', 'MANON', 'MARCEL', 'MARCELLE', 'MARGUERITE', 'MARIE', 'MARINE', 'MARTINE', 'MAURICE', 'MAXIME', 'MICHEL', 'MICHÈLE', 'MONIQUE', 'NATHALIE', 'NICOLAS', 'NICOLE', 'ODETTE', 'OLIVIER', 'PASCAL', 'PASCALE', 'PATRICIA', 'PATRICK', 'PAUL', 'PAULETTE', 'PHILIPPE', 'PIERRE', 'RENÉ', 'ROBERT', 'ROGER', 'ROMAIN', 'SANDRA', 'SANDRINE', 'SERGE', 'SOPHIE', 'STÉPHANE', 'STÉPHANIE', 'SUZANNE', 'SYLVIE', 'SÉBASTIEN', 'THIERRY', 'THOMAS', 'THÉO', 'VALÉRIE', 'VIRGINIE', 'VÉRONIQUE', 'YVETTE', 'YVONNE'];
|
||||
public const array LAST_NAMES = ['Adam', 'Andre', 'Antoine', 'Arnaud', 'Aubert', 'Aubry', 'Bailly', 'Barbier', 'Baron', 'Barre', 'Barthelemy', 'Benard', 'Benoit', 'Berger', 'Bernard', 'Bertin', 'Bertrand', 'Besson', 'Blanc', 'Blanchard', 'Bonnet', 'Boucher', 'Bouchet', 'Boulanger', 'Bourgeois', 'Bouvier', 'Boyer', 'Breton', 'Brun', 'Brunet', 'Carlier', 'Caron', 'Carpentier', 'Carre', 'Charles', 'Charpentier', 'Chauvin', 'Chevalier', 'Chevallier', 'Clement', 'Colin', 'Collet', 'Collin', 'Cordier', 'Cousin', 'Da Silva', 'Daniel', 'David', 'Delaunay', 'Denis', 'Deschamps', 'Dubois', 'Dufour', 'Dumas', 'Dumont', 'Dupont', 'Dupuis', 'Dupuy', 'Durand', 'Duval', 'Etienne', 'Fabre', 'Faure', 'Fernandez', 'Fleury', 'Fontaine', 'Fournier', 'Francois', 'Gaillard', 'Garcia', 'Garnier', 'Gauthier', 'Gautier', 'Gay', 'Gerard', 'Germain', 'Gilbert', 'Gillet', 'Girard', 'Giraud', 'Gonzalez', 'Grondin', 'Guerin', 'Guichard', 'Guillaume', 'Guillot', 'Guyot', 'Hamon', 'Henry', 'Herve', 'Hoarau', 'Hubert', 'Huet', 'Humbert', 'Jacob', 'Jacquet', 'Jean', 'Joly', 'Julien', 'Klein', 'Lacroix', 'Lambert', 'Lamy', 'Langlois', 'Laporte', 'Laurent', 'Le Gall', 'Le Goff', 'Le Roux', 'Leblanc', 'Lebrun', 'Leclerc', 'Leclercq', 'Lecomte', 'Lefebvre', 'Lefevre', 'Leger', 'Legrand', 'Lejeune', 'Lemaire', 'Lemaitre', 'Lemoine', 'Leroux', 'Leroy', 'Leveque', 'Lopez', 'Louis', 'Lucas', 'Maillard', 'Mallet', 'Marchal', 'Marchand', 'Marechal', 'Marie', 'Martin', 'Martinez', 'Marty', 'Masson', 'Mathieu', 'Menard', 'Mercier', 'Meunier', 'Meyer', 'Michaud', 'Michel', 'Millet', 'Monnier', 'Moreau', 'Morel', 'Morin', 'Moulin', 'Muller', 'Nicolas', 'Noel', 'Olivier', 'Paris', 'Pasquier', 'Payet', 'Pelletier', 'Perez', 'Perret', 'Perrier', 'Perrin', 'Perrot', 'Petit', 'Philippe', 'Picard', 'Pichon', 'Pierre', 'Poirier', 'Poulain', 'Prevost', 'Remy', 'Renard', 'Renaud', 'Renault', 'Rey', 'Reynaud', 'Richard', 'Riviere', 'Robert', 'Robin', 'Roche', 'Rodriguez', 'Roger', 'Rolland', 'Rousseau', 'Roussel', 'Roux', 'Roy', 'Royer', 'Sanchez', 'Schmitt', 'Schneider', 'Simon', 'Tessier', 'Thomas', 'Vasseur', 'Vidal', 'Vincent', 'Weber'];
|
||||
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services
|
||||
*
|
||||
*/
|
||||
public function __construct(private readonly UserPasswordHasherInterface $passwordHasher)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public static function class(): string
|
||||
{
|
||||
return User::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories
|
||||
*
|
||||
*/
|
||||
protected function getDefaults(): array
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
'email' => self::faker()->unique()->email(),
|
||||
@@ -60,7 +62,7 @@ final class UserFactory extends ModelFactory
|
||||
/**
|
||||
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
|
||||
*/
|
||||
protected function initialize(): self
|
||||
protected function initialize(): static
|
||||
{
|
||||
return $this
|
||||
->afterInstantiate(function (User $user): void {
|
||||
@@ -71,9 +73,4 @@ final class UserFactory extends ModelFactory
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
protected static function getClass(): string
|
||||
{
|
||||
return User::class;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class ConvertFileTest extends AbstractApiTestCase
|
||||
'files' => [
|
||||
'file' => $uploadedFile,
|
||||
],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
// Then - Vérifier la réponse
|
||||
@@ -84,7 +84,7 @@ class ConvertFileTest extends AbstractApiTestCase
|
||||
'files' => [
|
||||
'file' => $uploadedFile,
|
||||
],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
// Then - Vérifier l'erreur de validation
|
||||
@@ -121,7 +121,7 @@ class ConvertFileTest extends AbstractApiTestCase
|
||||
'files' => [
|
||||
'file' => $uploadedFile,
|
||||
],
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
// Then - Vérifier l'erreur de validation
|
||||
@@ -140,18 +140,18 @@ class ConvertFileTest extends AbstractApiTestCase
|
||||
|
||||
// Pour les tests, on peut simplement créer un fichier avec une extension .cbr
|
||||
// En production, ce serait un vrai fichier RAR
|
||||
$newPath = $tempFile . '.cbr';
|
||||
$newPath = $tempFile.'.cbr';
|
||||
rename($tempFile, $newPath);
|
||||
|
||||
// Utiliser un fichier CBZ existant comme base et le renommer en CBR pour les tests
|
||||
$existingCbzFile = __DIR__ . '/../../Fixtures/chapter.cbz';
|
||||
$existingCbzFile = __DIR__.'/../../Fixtures/chapter.cbz';
|
||||
if (file_exists($existingCbzFile)) {
|
||||
copy($existingCbzFile, $newPath);
|
||||
} else {
|
||||
// Fallback: créer un fichier ZIP simple avec extension CBR
|
||||
// Un fichier CBR est essentiellement un fichier RAR, mais pour les tests on peut simuler avec un ZIP
|
||||
$zip = new \ZipArchive();
|
||||
if ($zip->open($newPath, \ZipArchive::CREATE) === TRUE) {
|
||||
if (true === $zip->open($newPath, \ZipArchive::CREATE)) {
|
||||
$zip->addFromString('test.txt', 'Test content for CBR simulation');
|
||||
$zip->close();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Tests\Feature\Manga;
|
||||
|
||||
use App\Domain\Manga\Domain\Contract\Service\ImageProcessorInterface;
|
||||
use App\Tests\Feature\AbstractApiTestCase;
|
||||
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
|
||||
@@ -25,17 +24,17 @@ class CreateMangaDirectlyTest extends AbstractApiTestCase
|
||||
'status' => 'ongoing',
|
||||
'externalId' => 'external-123',
|
||||
'imageUrl' => 'http://example.com/image.jpg',
|
||||
'rating' => 4.5
|
||||
]
|
||||
'rating' => 4.5,
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
|
||||
// Verify the manga was created in database
|
||||
$entityManager = static::getContainer()->get('doctrine')->getManager();
|
||||
$manga = $entityManager->getRepository(\App\Entity\Manga::class)->findOneBy(['slug' => 'one-piece']);
|
||||
|
||||
|
||||
$this->assertNotNull($manga);
|
||||
$this->assertEquals('One Piece', $manga->getTitle());
|
||||
$this->assertEquals('Test description', $manga->getDescription());
|
||||
@@ -64,16 +63,16 @@ class CreateMangaDirectlyTest extends AbstractApiTestCase
|
||||
'status' => 'ongoing',
|
||||
'externalId' => 'external-123',
|
||||
'imageUrl' => null,
|
||||
'rating' => 4.5
|
||||
]
|
||||
'rating' => 4.5,
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
|
||||
$entityManager = static::getContainer()->get('doctrine')->getManager();
|
||||
$manga = $entityManager->getRepository(\App\Entity\Manga::class)->findOneBy(['slug' => 'one-piece']);
|
||||
|
||||
|
||||
$this->assertNotNull($manga);
|
||||
$this->assertNull($manga->getImageUrl());
|
||||
$this->assertNull($manga->getThumbnailUrl());
|
||||
@@ -87,14 +86,14 @@ class CreateMangaDirectlyTest extends AbstractApiTestCase
|
||||
'json' => [
|
||||
'title' => '', // Invalid: empty title
|
||||
'publicationYear' => 'invalid', // Invalid: not a number
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
$this->assertResponseStatusCodeSame(400);
|
||||
$this->assertJsonContains([
|
||||
'hydra:title' => 'An error occurred',
|
||||
'hydra:description' => 'The type of the "publicationYear" attribute must be "int", "string" given.'
|
||||
'title' => 'An error occurred',
|
||||
'detail' => 'The type of the "publicationYear" attribute must be "int", "string" given.',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -113,16 +112,16 @@ class CreateMangaDirectlyTest extends AbstractApiTestCase
|
||||
'status' => 'ongoing',
|
||||
'externalId' => 'external-123',
|
||||
'imageUrl' => null,
|
||||
'rating' => 4.5
|
||||
]
|
||||
'rating' => 4.5,
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
$this->assertResponseStatusCodeSame(422);
|
||||
$this->assertJsonContains([
|
||||
'hydra:title' => 'An error occurred',
|
||||
'hydra:description' => 'publicationYear: L\'année de publication doit être comprise entre 1900 et 2100',
|
||||
'title' => 'An error occurred'
|
||||
'title' => 'An error occurred',
|
||||
'detail' => 'publicationYear: L\'année de publication doit être comprise entre 1900 et 2100',
|
||||
'title' => 'An error occurred',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,20 +6,20 @@ use App\Entity\Chapter;
|
||||
use App\Tests\Factory\ChapterFactory;
|
||||
use App\Tests\Factory\MangaFactory;
|
||||
use App\Tests\Feature\AbstractApiTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Zenstruck\Foundry\Test\Factories;
|
||||
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
|
||||
class DeleteCbzTest extends AbstractApiTestCase
|
||||
{
|
||||
use ResetDatabase, Factories;
|
||||
use ResetDatabase;
|
||||
use Factories;
|
||||
|
||||
public function test_it_deletes_chapter_cbz_file(): void
|
||||
public function testItDeletesChapterCbzFile(): void
|
||||
{
|
||||
// Arrange
|
||||
$manga = MangaFactory::createOne([
|
||||
'title' => 'One Piece',
|
||||
'slug' => 'one-piece'
|
||||
'slug' => 'one-piece',
|
||||
]);
|
||||
|
||||
$chapter = ChapterFactory::createOne([
|
||||
@@ -27,7 +27,7 @@ class DeleteCbzTest extends AbstractApiTestCase
|
||||
'number' => 1.0,
|
||||
'title' => 'Chapter 1',
|
||||
'visible' => true,
|
||||
'cbzPath' => '/path/to/test.cbz'
|
||||
'cbzPath' => '/path/to/test.cbz',
|
||||
]);
|
||||
|
||||
$this->entityManager->flush();
|
||||
@@ -45,7 +45,7 @@ class DeleteCbzTest extends AbstractApiTestCase
|
||||
$this->assertEmpty($freshChapter->getCbzPath());
|
||||
}
|
||||
|
||||
public function test_it_returns_404_for_non_existent_chapter(): void
|
||||
public function testItReturns404ForNonExistentChapter(): void
|
||||
{
|
||||
// When
|
||||
static::createClient()->request('DELETE', '/api/manga/chapters/999999/cbz');
|
||||
@@ -54,12 +54,12 @@ class DeleteCbzTest extends AbstractApiTestCase
|
||||
$this->assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
public function test_it_returns_404_for_chapter_without_cbz(): void
|
||||
public function testItReturns404ForChapterWithoutCbz(): void
|
||||
{
|
||||
// Arrange
|
||||
$manga = MangaFactory::createOne([
|
||||
'title' => 'Test Manga',
|
||||
'slug' => 'test-manga'
|
||||
'slug' => 'test-manga',
|
||||
]);
|
||||
|
||||
$chapter = ChapterFactory::createOne([
|
||||
@@ -67,7 +67,7 @@ class DeleteCbzTest extends AbstractApiTestCase
|
||||
'number' => 1.0,
|
||||
'title' => 'Test Chapter',
|
||||
'visible' => true,
|
||||
'cbzPath' => null // No CBZ file
|
||||
'cbzPath' => null, // No CBZ file
|
||||
]);
|
||||
|
||||
$this->entityManager->flush();
|
||||
@@ -80,12 +80,12 @@ class DeleteCbzTest extends AbstractApiTestCase
|
||||
$this->assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
public function test_it_returns_404_for_invisible_chapter(): void
|
||||
public function testItReturns404ForInvisibleChapter(): void
|
||||
{
|
||||
// Arrange
|
||||
$manga = MangaFactory::createOne([
|
||||
'title' => 'Test Manga',
|
||||
'slug' => 'test-manga'
|
||||
'slug' => 'test-manga',
|
||||
]);
|
||||
|
||||
$chapter = ChapterFactory::createOne([
|
||||
@@ -93,7 +93,7 @@ class DeleteCbzTest extends AbstractApiTestCase
|
||||
'number' => 1.0,
|
||||
'title' => 'Test Chapter',
|
||||
'visible' => false, // Invisible chapter
|
||||
'cbzPath' => '/path/to/test.cbz'
|
||||
'cbzPath' => '/path/to/test.cbz',
|
||||
]);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
@@ -6,27 +6,27 @@ use App\Entity\Chapter;
|
||||
use App\Tests\Factory\ChapterFactory;
|
||||
use App\Tests\Factory\MangaFactory;
|
||||
use App\Tests\Feature\AbstractApiTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Zenstruck\Foundry\Test\Factories;
|
||||
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
|
||||
class DeleteChapterTest extends AbstractApiTestCase
|
||||
{
|
||||
use ResetDatabase, Factories;
|
||||
use ResetDatabase;
|
||||
use Factories;
|
||||
|
||||
public function test_it_soft_deletes_chapter(): void
|
||||
public function testItSoftDeletesChapter(): void
|
||||
{
|
||||
// Arrange
|
||||
$manga = MangaFactory::createOne([
|
||||
'title' => 'One Piece',
|
||||
'slug' => 'one-piece'
|
||||
'slug' => 'one-piece',
|
||||
]);
|
||||
|
||||
$chapter = ChapterFactory::createOne([
|
||||
'manga' => $manga,
|
||||
'number' => 1.0,
|
||||
'title' => 'Chapter 1',
|
||||
'visible' => true
|
||||
'visible' => true,
|
||||
]);
|
||||
|
||||
$chapterId = $chapter->getId();
|
||||
@@ -42,7 +42,7 @@ class DeleteChapterTest extends AbstractApiTestCase
|
||||
$this->assertFalse($freshChapter->isVisible());
|
||||
}
|
||||
|
||||
public function test_it_returns_404_for_non_existent_chapter(): void
|
||||
public function testItReturns404ForNonExistentChapter(): void
|
||||
{
|
||||
// When
|
||||
static::createClient()->request('DELETE', '/api/manga/chapters/999999');
|
||||
@@ -51,19 +51,19 @@ class DeleteChapterTest extends AbstractApiTestCase
|
||||
$this->assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
public function test_it_returns_404_for_already_soft_deleted_chapter(): void
|
||||
public function testItReturns404ForAlreadySoftDeletedChapter(): void
|
||||
{
|
||||
// Arrange
|
||||
$manga = MangaFactory::createOne([
|
||||
'title' => 'Test Manga',
|
||||
'slug' => 'test-manga'
|
||||
'slug' => 'test-manga',
|
||||
]);
|
||||
|
||||
$chapter = ChapterFactory::createOne([
|
||||
'manga' => $manga,
|
||||
'number' => 1.0,
|
||||
'title' => 'Test Chapter',
|
||||
'visible' => false // Already soft deleted
|
||||
'visible' => false, // Already soft deleted
|
||||
]);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
@@ -2,25 +2,25 @@
|
||||
|
||||
namespace App\Tests\Feature\Manga;
|
||||
|
||||
use App\Entity\Manga;
|
||||
use App\Entity\Chapter;
|
||||
use App\Tests\Factory\MangaFactory;
|
||||
use App\Entity\Manga;
|
||||
use App\Tests\Factory\ChapterFactory;
|
||||
use App\Tests\Factory\MangaFactory;
|
||||
use App\Tests\Feature\AbstractApiTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Zenstruck\Foundry\Test\Factories;
|
||||
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
|
||||
class DeleteMangaTest extends AbstractApiTestCase
|
||||
{
|
||||
use ResetDatabase, Factories;
|
||||
use ResetDatabase;
|
||||
use Factories;
|
||||
|
||||
public function test_it_deletes_manga_with_chapters(): void
|
||||
public function testItDeletesMangaWithChapters(): void
|
||||
{
|
||||
// Arrange
|
||||
$manga = MangaFactory::createOne([
|
||||
'title' => 'One Piece',
|
||||
'slug' => 'one-piece'
|
||||
'slug' => 'one-piece',
|
||||
]);
|
||||
|
||||
// Create chapters for the manga
|
||||
@@ -28,21 +28,21 @@ class DeleteMangaTest extends AbstractApiTestCase
|
||||
'manga' => $manga,
|
||||
'number' => 1.0,
|
||||
'title' => 'Chapter 1',
|
||||
'visible' => true
|
||||
'visible' => true,
|
||||
]);
|
||||
|
||||
ChapterFactory::createMany(2, [
|
||||
'manga' => $manga,
|
||||
'number' => 2.0,
|
||||
'title' => 'Chapter 2',
|
||||
'visible' => true
|
||||
'visible' => true,
|
||||
]);
|
||||
|
||||
ChapterFactory::createMany(1, [
|
||||
'manga' => $manga,
|
||||
'number' => 3.0,
|
||||
'title' => 'Chapter 3',
|
||||
'visible' => true
|
||||
'visible' => true,
|
||||
]);
|
||||
|
||||
$mangaId = $manga->getId();
|
||||
@@ -66,7 +66,7 @@ class DeleteMangaTest extends AbstractApiTestCase
|
||||
$this->assertCount(0, $chaptersAfter);
|
||||
}
|
||||
|
||||
public function test_it_returns_404_for_non_existent_manga(): void
|
||||
public function testItReturns404ForNonExistentManga(): void
|
||||
{
|
||||
// When
|
||||
static::createClient()->request('DELETE', '/api/mangas/999999');
|
||||
@@ -74,5 +74,4 @@ class DeleteMangaTest extends AbstractApiTestCase
|
||||
// Then
|
||||
$this->assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Tests\Feature\Manga;
|
||||
|
||||
use App\Entity\Chapter;
|
||||
use App\Tests\Factory\ChapterFactory;
|
||||
use App\Tests\Factory\MangaFactory;
|
||||
use App\Tests\Feature\AbstractApiTestCase;
|
||||
@@ -12,14 +11,15 @@ use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
|
||||
class DownloadCbzTest extends AbstractApiTestCase
|
||||
{
|
||||
use ResetDatabase, Factories;
|
||||
use ResetDatabase;
|
||||
use Factories;
|
||||
|
||||
public function test_it_downloads_chapter_cbz(): void
|
||||
public function testItDownloadsChapterCbz(): void
|
||||
{
|
||||
// Arrange
|
||||
$manga = MangaFactory::createOne([
|
||||
'title' => 'One Piece',
|
||||
'slug' => 'one-piece'
|
||||
'slug' => 'one-piece',
|
||||
]);
|
||||
|
||||
$chapter = ChapterFactory::createOne([
|
||||
@@ -27,7 +27,7 @@ class DownloadCbzTest extends AbstractApiTestCase
|
||||
'number' => 1.0,
|
||||
'title' => 'Chapter 1',
|
||||
'visible' => true,
|
||||
'cbzPath' => '/app/tests/Shared/Files/test-chapter.cbz'
|
||||
'cbzPath' => '/app/tests/Shared/Files/test-chapter.cbz',
|
||||
]);
|
||||
|
||||
$chapterId = $chapter->getId();
|
||||
@@ -44,7 +44,7 @@ class DownloadCbzTest extends AbstractApiTestCase
|
||||
$this->assertStringContainsString('test-chapter.cbz', $response->headers->get('Content-Disposition'));
|
||||
}
|
||||
|
||||
public function test_it_returns_404_for_non_existent_chapter(): void
|
||||
public function testItReturns404ForNonExistentChapter(): void
|
||||
{
|
||||
// When
|
||||
static::createClient()->request('GET', '/api/manga/chapters/999999/download');
|
||||
@@ -53,12 +53,12 @@ class DownloadCbzTest extends AbstractApiTestCase
|
||||
$this->assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
public function test_it_returns_404_for_chapter_without_cbz(): void
|
||||
public function testItReturns404ForChapterWithoutCbz(): void
|
||||
{
|
||||
// Arrange
|
||||
$manga = MangaFactory::createOne([
|
||||
'title' => 'Test Manga',
|
||||
'slug' => 'test-manga'
|
||||
'slug' => 'test-manga',
|
||||
]);
|
||||
|
||||
$chapter = ChapterFactory::createOne([
|
||||
@@ -66,7 +66,7 @@ class DownloadCbzTest extends AbstractApiTestCase
|
||||
'number' => 1.0,
|
||||
'title' => 'Test Chapter',
|
||||
'visible' => true,
|
||||
'cbzPath' => null // No CBZ file
|
||||
'cbzPath' => null, // No CBZ file
|
||||
]);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Tests\Feature\Manga;
|
||||
|
||||
use App\Entity\Chapter;
|
||||
use App\Tests\Factory\ChapterFactory;
|
||||
use App\Tests\Factory\MangaFactory;
|
||||
use App\Tests\Feature\AbstractApiTestCase;
|
||||
@@ -12,14 +11,15 @@ use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
|
||||
class DownloadVolumeTest extends AbstractApiTestCase
|
||||
{
|
||||
use ResetDatabase, Factories;
|
||||
use ResetDatabase;
|
||||
use Factories;
|
||||
|
||||
public function test_it_downloads_volume_cbz(): void
|
||||
public function testItDownloadsVolumeCbz(): void
|
||||
{
|
||||
// Arrange
|
||||
$manga = MangaFactory::createOne([
|
||||
'title' => 'One Piece',
|
||||
'slug' => 'one-piece'
|
||||
'slug' => 'one-piece',
|
||||
]);
|
||||
|
||||
// Create chapters for volume 1
|
||||
@@ -27,7 +27,7 @@ class DownloadVolumeTest extends AbstractApiTestCase
|
||||
'manga' => $manga,
|
||||
'volume' => 1,
|
||||
'visible' => true,
|
||||
'cbzPath' => __DIR__ . '/../../Shared/Files/test-chapter.cbz'
|
||||
'cbzPath' => __DIR__.'/../../Shared/Files/test-chapter.cbz',
|
||||
]);
|
||||
|
||||
$mangaId = $manga->getId();
|
||||
@@ -43,7 +43,7 @@ class DownloadVolumeTest extends AbstractApiTestCase
|
||||
$this->assertStringContainsString('one-piece_vol1.cbz', $contentDisposition);
|
||||
}
|
||||
|
||||
public function test_it_returns_404_when_manga_not_found(): void
|
||||
public function testItReturns404WhenMangaNotFound(): void
|
||||
{
|
||||
// Act
|
||||
static::createClient()->request('GET', '/api/mangas/999999/volumes/1/download');
|
||||
@@ -52,12 +52,12 @@ class DownloadVolumeTest extends AbstractApiTestCase
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
public function test_it_returns_404_when_volume_not_found(): void
|
||||
public function testItReturns404WhenVolumeNotFound(): void
|
||||
{
|
||||
// Arrange
|
||||
$manga = MangaFactory::createOne([
|
||||
'title' => 'One Piece',
|
||||
'slug' => 'one-piece'
|
||||
'slug' => 'one-piece',
|
||||
]);
|
||||
|
||||
$mangaId = $manga->getId();
|
||||
@@ -69,12 +69,12 @@ class DownloadVolumeTest extends AbstractApiTestCase
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
public function test_it_returns_404_when_no_available_chapters_in_volume(): void
|
||||
public function testItReturns404WhenNoAvailableChaptersInVolume(): void
|
||||
{
|
||||
// Arrange
|
||||
$manga = MangaFactory::createOne([
|
||||
'title' => 'One Piece',
|
||||
'slug' => 'one-piece'
|
||||
'slug' => 'one-piece',
|
||||
]);
|
||||
|
||||
// Create chapters for volume 1 but all without CBZ files
|
||||
@@ -82,7 +82,7 @@ class DownloadVolumeTest extends AbstractApiTestCase
|
||||
'manga' => $manga,
|
||||
'volume' => 1,
|
||||
'visible' => true,
|
||||
'cbzPath' => null // No CBZ files
|
||||
'cbzPath' => null, // No CBZ files
|
||||
]);
|
||||
|
||||
$mangaId = $manga->getId();
|
||||
@@ -94,12 +94,12 @@ class DownloadVolumeTest extends AbstractApiTestCase
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
public function test_it_only_includes_visible_chapters_with_cbz(): void
|
||||
public function testItOnlyIncludesVisibleChaptersWithCbz(): void
|
||||
{
|
||||
// Arrange
|
||||
$manga = MangaFactory::createOne([
|
||||
'title' => 'One Piece',
|
||||
'slug' => 'one-piece'
|
||||
'slug' => 'one-piece',
|
||||
]);
|
||||
|
||||
// Create a mix of chapters
|
||||
@@ -108,7 +108,7 @@ class DownloadVolumeTest extends AbstractApiTestCase
|
||||
'volume' => 1,
|
||||
'number' => 1.0,
|
||||
'visible' => true,
|
||||
'cbzPath' => __DIR__ . '/../../Shared/Files/test-chapter.cbz'
|
||||
'cbzPath' => __DIR__.'/../../Shared/Files/test-chapter.cbz',
|
||||
]);
|
||||
|
||||
ChapterFactory::createOne([
|
||||
@@ -116,7 +116,7 @@ class DownloadVolumeTest extends AbstractApiTestCase
|
||||
'volume' => 1,
|
||||
'number' => 2.0,
|
||||
'visible' => false, // Soft deleted
|
||||
'cbzPath' => __DIR__ . '/../../Shared/Files/test-chapter.cbz'
|
||||
'cbzPath' => __DIR__.'/../../Shared/Files/test-chapter.cbz',
|
||||
]);
|
||||
|
||||
ChapterFactory::createOne([
|
||||
@@ -124,7 +124,7 @@ class DownloadVolumeTest extends AbstractApiTestCase
|
||||
'volume' => 1,
|
||||
'number' => 3.0,
|
||||
'visible' => true,
|
||||
'cbzPath' => null // No CBZ
|
||||
'cbzPath' => null, // No CBZ
|
||||
]);
|
||||
|
||||
ChapterFactory::createOne([
|
||||
@@ -132,7 +132,7 @@ class DownloadVolumeTest extends AbstractApiTestCase
|
||||
'volume' => 1,
|
||||
'number' => 4.0,
|
||||
'visible' => true,
|
||||
'cbzPath' => __DIR__ . '/../../Shared/Files/test-chapter.cbz'
|
||||
'cbzPath' => __DIR__.'/../../Shared/Files/test-chapter.cbz',
|
||||
]);
|
||||
|
||||
$mangaId = $manga->getId();
|
||||
|
||||
@@ -24,8 +24,8 @@ class EditMangaTest extends AbstractApiTestCase
|
||||
'status' => 'ongoing',
|
||||
'externalId' => 'external-123',
|
||||
'imageUrl' => 'http://example.com/image.jpg',
|
||||
'rating' => 4.5
|
||||
]
|
||||
'rating' => 4.5,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -37,7 +37,7 @@ class EditMangaTest extends AbstractApiTestCase
|
||||
$mangaId = $createdManga->getId();
|
||||
|
||||
// When - Edit the manga
|
||||
$response = $client->request('PUT', '/api/mangas/' . $mangaId . '/edit', [
|
||||
$response = $client->request('PUT', '/api/mangas/'.$mangaId.'/edit', [
|
||||
'json' => [
|
||||
'title' => 'One Piece Updated',
|
||||
'description' => 'Updated description',
|
||||
@@ -46,8 +46,8 @@ class EditMangaTest extends AbstractApiTestCase
|
||||
'genres' => ['action', 'adventure', 'comedy'],
|
||||
'status' => 'completed',
|
||||
'rating' => 4.8,
|
||||
'alternativeSlugs' => ['onepiece', 'op']
|
||||
]
|
||||
'alternativeSlugs' => ['onepiece', 'op'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
@@ -80,8 +80,8 @@ class EditMangaTest extends AbstractApiTestCase
|
||||
'author' => 'Eiichiro Oda',
|
||||
'publicationYear' => 1997,
|
||||
'genres' => ['action', 'adventure'],
|
||||
'status' => 'ongoing'
|
||||
]
|
||||
'status' => 'ongoing',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -93,14 +93,14 @@ class EditMangaTest extends AbstractApiTestCase
|
||||
$mangaId = $createdManga->getId();
|
||||
|
||||
// When - Try to edit with invalid data
|
||||
$client->request('PUT', '/api/mangas/' . $mangaId . '/edit', [
|
||||
$client->request('PUT', '/api/mangas/'.$mangaId.'/edit', [
|
||||
'json' => [
|
||||
'title' => '', // Invalid: empty title
|
||||
'publicationYear' => 2200, // Invalid: year > 2100
|
||||
'genres' => [], // Invalid: empty genres
|
||||
'status' => 'invalid-status', // Invalid status
|
||||
'rating' => 6.0 // Invalid: rating > 5
|
||||
]
|
||||
'rating' => 6.0, // Invalid: rating > 5
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
@@ -113,8 +113,8 @@ class EditMangaTest extends AbstractApiTestCase
|
||||
$client = static::createClient();
|
||||
$client->request('PUT', '/api/mangas/9999999/edit', [
|
||||
'json' => [
|
||||
'title' => 'Updated Title'
|
||||
]
|
||||
'title' => 'Updated Title',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
@@ -134,8 +134,8 @@ class EditMangaTest extends AbstractApiTestCase
|
||||
'publicationYear' => 1997,
|
||||
'genres' => ['action', 'adventure'],
|
||||
'status' => 'ongoing',
|
||||
'rating' => 4.5
|
||||
]
|
||||
'rating' => 4.5,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -147,11 +147,11 @@ class EditMangaTest extends AbstractApiTestCase
|
||||
$mangaId = $createdManga->getId();
|
||||
|
||||
// When - Edit only title and rating
|
||||
$client->request('PUT', '/api/mangas/' . $mangaId . '/edit', [
|
||||
$client->request('PUT', '/api/mangas/'.$mangaId.'/edit', [
|
||||
'json' => [
|
||||
'title' => 'One Piece - Updated Title Only',
|
||||
'rating' => 4.9
|
||||
]
|
||||
'rating' => 4.9,
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
|
||||
@@ -4,14 +4,14 @@ namespace Tests\Feature\Manga;
|
||||
|
||||
use App\Entity\Chapter;
|
||||
use App\Entity\Manga;
|
||||
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
|
||||
class EditMultipleChaptersTest extends WebTestCase
|
||||
{
|
||||
use ResetDatabase;
|
||||
|
||||
public function test_it_edits_multiple_chapters(): void
|
||||
public function testItEditsMultipleChapters(): void
|
||||
{
|
||||
// Given
|
||||
$client = static::createClient();
|
||||
@@ -46,19 +46,19 @@ class EditMultipleChaptersTest extends WebTestCase
|
||||
[
|
||||
'id' => (string) $chapter1->getId(),
|
||||
'title' => 'New Title 1',
|
||||
'volume' => 2
|
||||
'volume' => 2,
|
||||
],
|
||||
[
|
||||
'id' => (string) $chapter2->getId(),
|
||||
'title' => null,
|
||||
'volume' => 3
|
||||
]
|
||||
]
|
||||
'volume' => 3,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// When
|
||||
$client->request('POST', '/api/chapters/batch-edit', [], [], [
|
||||
'CONTENT_TYPE' => 'application/json'
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], json_encode($data));
|
||||
|
||||
// Then
|
||||
@@ -76,7 +76,7 @@ class EditMultipleChaptersTest extends WebTestCase
|
||||
$this->assertEquals(3, $updatedChapter2->getVolume());
|
||||
}
|
||||
|
||||
public function test_it_returns_404_when_chapter_not_found(): void
|
||||
public function testItReturns404WhenChapterNotFound(): void
|
||||
{
|
||||
// Given
|
||||
$client = static::createClient();
|
||||
@@ -86,21 +86,21 @@ class EditMultipleChaptersTest extends WebTestCase
|
||||
[
|
||||
'id' => '999',
|
||||
'title' => 'New Title',
|
||||
'volume' => 1
|
||||
]
|
||||
]
|
||||
'volume' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// When
|
||||
$client->request('POST', '/api/chapters/batch-edit', [], [], [
|
||||
'CONTENT_TYPE' => 'application/json'
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], json_encode($data));
|
||||
|
||||
// Then
|
||||
$this->assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
public function test_it_validates_required_fields(): void
|
||||
public function testItValidatesRequiredFields(): void
|
||||
{
|
||||
// Given
|
||||
$client = static::createClient();
|
||||
@@ -109,15 +109,15 @@ class EditMultipleChaptersTest extends WebTestCase
|
||||
'chapters' => [
|
||||
[
|
||||
'title' => 'New Title',
|
||||
'volume' => 1
|
||||
'volume' => 1,
|
||||
// id manquant
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// When
|
||||
$client->request('POST', '/api/chapters/batch-edit', [], [], [
|
||||
'CONTENT_TYPE' => 'application/json'
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], json_encode($data));
|
||||
|
||||
// Then
|
||||
|
||||
@@ -49,8 +49,8 @@ class FetchMangaChaptersTest extends AbstractApiTestCase
|
||||
|
||||
static::createClient()->request('POST', '/api/manga/chapters/fetch', [
|
||||
'json' => [
|
||||
'mangaId' => $mangaId
|
||||
]
|
||||
'mangaId' => $mangaId,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(202);
|
||||
@@ -65,8 +65,8 @@ class FetchMangaChaptersTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('POST', '/api/manga/chapters/fetch', [
|
||||
'json' => [
|
||||
'mangaId' => ''
|
||||
]
|
||||
'mangaId' => '',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(422);
|
||||
@@ -74,9 +74,9 @@ class FetchMangaChaptersTest extends AbstractApiTestCase
|
||||
'violations' => [
|
||||
[
|
||||
'propertyPath' => 'mangaId',
|
||||
'message' => 'L\'identifiant du manga est obligatoire'
|
||||
]
|
||||
]
|
||||
'message' => 'L\'identifiant du manga est obligatoire',
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
{
|
||||
use ResetDatabase;
|
||||
|
||||
public function test_it_finds_exact_match_by_filename(): void
|
||||
public function testItFindsExactMatchByFilename(): void
|
||||
{
|
||||
// Given
|
||||
$this->createManga('One Piece', 'one-piece');
|
||||
@@ -21,8 +21,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/manga-matches', [
|
||||
'query' => [
|
||||
'filename' => 'one-piece_vol108_ch1094.cbz'
|
||||
]
|
||||
'filename' => 'one-piece_vol108_ch1094.cbz',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
@@ -37,10 +37,9 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$this->assertEquals(1094.0, $data['matches'][0]['chapterNumber']);
|
||||
$this->assertEquals(108, $data['matches'][0]['volumeNumber']);
|
||||
$this->assertGreaterThan(0, $data['matches'][0]['matchScore']);
|
||||
|
||||
}
|
||||
|
||||
public function test_it_returns_empty_matches_when_no_manga_found(): void
|
||||
public function testItReturnsEmptyMatchesWhenNoMangaFound(): void
|
||||
{
|
||||
// Given - no manga in database
|
||||
|
||||
@@ -48,8 +47,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/manga-matches', [
|
||||
'query' => [
|
||||
'filename' => 'unknown-manga_vol1_ch1.cbz'
|
||||
]
|
||||
'filename' => 'unknown-manga_vol1_ch1.cbz',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
@@ -60,7 +59,7 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$this->assertCount(0, $data['matches']);
|
||||
}
|
||||
|
||||
public function test_it_returns_bad_request_when_filename_is_missing(): void
|
||||
public function testItReturnsBadRequestWhenFilenameIsMissing(): void
|
||||
{
|
||||
// When
|
||||
$client = static::createClient();
|
||||
@@ -70,7 +69,7 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$this->assertResponseStatusCodeSame(400);
|
||||
}
|
||||
|
||||
public function test_it_extracts_chapter_and_volume_correctly(): void
|
||||
public function testItExtractsChapterAndVolumeCorrectly(): void
|
||||
{
|
||||
// Given
|
||||
$this->createManga('Attack on Titan', 'attack-on-titan');
|
||||
@@ -79,8 +78,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/manga-matches', [
|
||||
'query' => [
|
||||
'filename' => 'attack-on-titan_vol32_ch130.cbz'
|
||||
]
|
||||
'filename' => 'attack-on-titan_vol32_ch130.cbz',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
@@ -90,7 +89,7 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$this->assertNotEmpty($data['matches']);
|
||||
}
|
||||
|
||||
public function test_it_handles_filename_with_only_volume(): void
|
||||
public function testItHandlesFilenameWithOnlyVolume(): void
|
||||
{
|
||||
// Given
|
||||
$this->createManga('Naruto', 'naruto');
|
||||
@@ -99,8 +98,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/manga-matches', [
|
||||
'query' => [
|
||||
'filename' => 'naruto_vol50.cbz'
|
||||
]
|
||||
'filename' => 'naruto_vol50.cbz',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
@@ -111,7 +110,7 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$this->assertEquals('Naruto', $data['matches'][0]['title']);
|
||||
}
|
||||
|
||||
public function test_it_handles_filename_with_only_chapter(): void
|
||||
public function testItHandlesFilenameWithOnlyChapter(): void
|
||||
{
|
||||
// Given
|
||||
$this->createManga('Bleach', 'bleach');
|
||||
@@ -120,8 +119,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/manga-matches', [
|
||||
'query' => [
|
||||
'filename' => 'bleach_ch200.cbz'
|
||||
]
|
||||
'filename' => 'bleach_ch200.cbz',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
@@ -132,7 +131,7 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$this->assertEquals('Bleach', $data['matches'][0]['title']);
|
||||
}
|
||||
|
||||
public function test_it_sorts_matches_by_score(): void
|
||||
public function testItSortsMatchesByScore(): void
|
||||
{
|
||||
// Given
|
||||
$this->createManga('One Piece', 'one-piece');
|
||||
@@ -143,8 +142,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/manga-matches', [
|
||||
'query' => [
|
||||
'filename' => 'one-piece_vol108_ch1094.cbz'
|
||||
]
|
||||
'filename' => 'one-piece_vol108_ch1094.cbz',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
@@ -158,13 +157,13 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$this->assertEquals('One Piece', $data['matches'][0]['title']);
|
||||
|
||||
// Vérifier que les scores sont triés par ordre décroissant
|
||||
$scores = array_map(fn($match) => $match['matchScore'], $data['matches']);
|
||||
$scores = array_map(fn ($match) => $match['matchScore'], $data['matches']);
|
||||
$sortedScores = $scores;
|
||||
rsort($sortedScores);
|
||||
$this->assertEquals($sortedScores, $scores);
|
||||
}
|
||||
|
||||
public function test_it_handles_alternative_slugs(): void
|
||||
public function testItHandlesAlternativeSlugs(): void
|
||||
{
|
||||
// Given
|
||||
$manga = $this->createManga('Shingeki no Kyojin', 'shingeki-no-kyojin');
|
||||
@@ -175,8 +174,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/manga-matches', [
|
||||
'query' => [
|
||||
'filename' => 'attack-on-titan_vol1_ch1.cbz'
|
||||
]
|
||||
'filename' => 'attack-on-titan_vol1_ch1.cbz',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
@@ -189,7 +188,7 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$this->assertContains('attack-on-titan', $data['matches'][0]['alternativeSlugs']);
|
||||
}
|
||||
|
||||
public function test_it_provides_possible_titles_variants(): void
|
||||
public function testItProvidesPossibleTitlesVariants(): void
|
||||
{
|
||||
// Given
|
||||
$this->createManga('Dragon Ball', 'dragon-ball');
|
||||
@@ -198,8 +197,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/manga-matches', [
|
||||
'query' => [
|
||||
'filename' => 'dragon-ball_vol1_ch5.cbz'
|
||||
]
|
||||
'filename' => 'dragon-ball_vol1_ch5.cbz',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
@@ -233,4 +232,3 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase
|
||||
return $manga;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Tests\Feature\Manga;
|
||||
|
||||
use App\Entity\Manga;
|
||||
use App\Tests\Factory\MangaFactory;
|
||||
use App\Tests\Feature\AbstractApiTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -11,7 +10,8 @@ use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
|
||||
class GetMangaBySlugTest extends AbstractApiTestCase
|
||||
{
|
||||
use ResetDatabase, Factories;
|
||||
use ResetDatabase;
|
||||
use Factories;
|
||||
|
||||
public function testGetMangaBySlugReturnsCorrectManga(): void
|
||||
{
|
||||
@@ -27,7 +27,7 @@ class GetMangaBySlugTest extends AbstractApiTestCase
|
||||
'imageUrl' => 'https://example.com/image.jpg',
|
||||
'thumbnailUrl' => 'https://example.com/thumbnail.jpg',
|
||||
'rating' => 4.5,
|
||||
'monitored' => true
|
||||
'monitored' => true,
|
||||
]);
|
||||
|
||||
// Act
|
||||
@@ -58,4 +58,4 @@ class GetMangaBySlugTest extends AbstractApiTestCase
|
||||
// Assert
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class GetMangaChaptersTest extends AbstractApiTestCase
|
||||
|
||||
// When
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/mangas/' . $manga->getId() . '/chapters');
|
||||
$response = $client->request('GET', '/api/mangas/'.$manga->getId().'/chapters');
|
||||
|
||||
// Then
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -38,7 +38,7 @@ class GetMangaChaptersTest extends AbstractApiTestCase
|
||||
'limit' => 20,
|
||||
'hasNextPage' => false,
|
||||
'hasPreviousPage' => false,
|
||||
'items' => []
|
||||
'items' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -50,18 +50,18 @@ class GetMangaChaptersTest extends AbstractApiTestCase
|
||||
|
||||
// When
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/mangas/' . $manga->getId() . '/chapters', [
|
||||
$response = $client->request('GET', '/api/mangas/'.$manga->getId().'/chapters', [
|
||||
'query' => [
|
||||
'page' => 2,
|
||||
'limit' => 10,
|
||||
'sortOrder' => 'desc'
|
||||
]
|
||||
'sortOrder' => 'desc',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
$this->assertResponseIsSuccessful();
|
||||
$data = $response->toArray();
|
||||
|
||||
|
||||
$this->assertCount(10, $data['items']);
|
||||
$this->assertEquals(25, $data['total']);
|
||||
$this->assertEquals(2, $data['page']);
|
||||
@@ -69,7 +69,7 @@ class GetMangaChaptersTest extends AbstractApiTestCase
|
||||
$this->assertTrue($data['hasNextPage']);
|
||||
$this->assertTrue($data['hasPreviousPage']);
|
||||
|
||||
$numbers = array_map(fn($item) => $item['number'], $data['items']);
|
||||
$numbers = array_map(fn ($item) => $item['number'], $data['items']);
|
||||
|
||||
$expectedNumbers = $numbers;
|
||||
rsort($expectedNumbers);
|
||||
@@ -99,12 +99,12 @@ class GetMangaChaptersTest extends AbstractApiTestCase
|
||||
{
|
||||
$entityManager = static::getContainer()->get('doctrine')->getManager();
|
||||
|
||||
for ($i = 1; $i <= $count; $i++) {
|
||||
for ($i = 1; $i <= $count; ++$i) {
|
||||
$chapter = new Chapter();
|
||||
$chapter->setManga($manga)
|
||||
->setNumber($i)
|
||||
->setTitle("Chapter $i")
|
||||
->setVolume((int)ceil($i / 10))
|
||||
->setVolume((int) ceil($i / 10))
|
||||
->setVisible(true);
|
||||
|
||||
$entityManager->persist($chapter);
|
||||
@@ -112,4 +112,4 @@ class GetMangaChaptersTest extends AbstractApiTestCase
|
||||
|
||||
$entityManager->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
|
||||
class GetMangaListTest extends AbstractApiTestCase
|
||||
{
|
||||
use ResetDatabase, Factories;
|
||||
use ResetDatabase;
|
||||
use Factories;
|
||||
|
||||
public function testGetEmptyMangaList(): void
|
||||
{
|
||||
@@ -27,7 +28,7 @@ class GetMangaListTest extends AbstractApiTestCase
|
||||
'limit' => 20,
|
||||
'hasNextPage' => false,
|
||||
'hasPreviousPage' => false,
|
||||
'items' => []
|
||||
'items' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -41,8 +42,8 @@ class GetMangaListTest extends AbstractApiTestCase
|
||||
$response = $client->request('GET', '/api/mangas', [
|
||||
'query' => [
|
||||
'page' => 2,
|
||||
'limit' => 10
|
||||
]
|
||||
'limit' => 10,
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
@@ -69,8 +70,8 @@ class GetMangaListTest extends AbstractApiTestCase
|
||||
$response = $client->request('GET', '/api/mangas', [
|
||||
'query' => [
|
||||
'sortBy' => 'title',
|
||||
'sortOrder' => 'asc'
|
||||
]
|
||||
'sortOrder' => 'asc',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
@@ -113,7 +114,7 @@ class GetMangaListTest extends AbstractApiTestCase
|
||||
|
||||
private function createMangas(int $count): void
|
||||
{
|
||||
for ($i = 1; $i <= $count; $i++) {
|
||||
for ($i = 1; $i <= $count; ++$i) {
|
||||
$this->createManga("Manga $i");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class GetMangaTest extends AbstractApiTestCase
|
||||
|
||||
// When
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/mangas/by-id/' . $manga->getId());
|
||||
$response = $client->request('GET', '/api/mangas/by-id/'.$manga->getId());
|
||||
|
||||
// Then
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -58,7 +58,7 @@ class GetMangaTest extends AbstractApiTestCase
|
||||
'status' => 'ongoing',
|
||||
'externalId' => 'external-123',
|
||||
'imageUrl' => 'http://example.com/image.jpg',
|
||||
'rating' => 4.5
|
||||
'rating' => 4.5,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ class ImportChapterTest extends WebTestCase
|
||||
{
|
||||
private const API_ENDPOINT = '/api/chapters/import';
|
||||
|
||||
public function test_it_returns_404_when_manga_not_found(): void
|
||||
public function testItReturns404WhenMangaNotFound(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$file = $this->createValidCbzFile();
|
||||
@@ -19,7 +19,7 @@ class ImportChapterTest extends WebTestCase
|
||||
self::API_ENDPOINT,
|
||||
[
|
||||
'mangaId' => 'non-existent-manga-id',
|
||||
'chapterNumber' => '1.5'
|
||||
'chapterNumber' => '1.5',
|
||||
],
|
||||
['file' => $file],
|
||||
['CONTENT_TYPE' => 'multipart/form-data']
|
||||
@@ -30,7 +30,7 @@ class ImportChapterTest extends WebTestCase
|
||||
$this->assertEquals('Manga not found', $response['error']);
|
||||
}
|
||||
|
||||
public function test_it_returns_422_when_manga_id_is_missing(): void
|
||||
public function testItReturns422WhenMangaIdIsMissing(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$file = $this->createValidCbzFile();
|
||||
@@ -39,7 +39,7 @@ class ImportChapterTest extends WebTestCase
|
||||
'POST',
|
||||
self::API_ENDPOINT,
|
||||
[
|
||||
'chapterNumber' => '1.5'
|
||||
'chapterNumber' => '1.5',
|
||||
],
|
||||
['file' => $file],
|
||||
['CONTENT_TYPE' => 'multipart/form-data']
|
||||
@@ -50,7 +50,7 @@ class ImportChapterTest extends WebTestCase
|
||||
$this->assertStringContainsString('mangaId is required', $response[0]['message']);
|
||||
}
|
||||
|
||||
public function test_it_returns_422_when_chapter_number_is_missing(): void
|
||||
public function testItReturns422WhenChapterNumberIsMissing(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$file = $this->createValidCbzFile();
|
||||
@@ -59,7 +59,7 @@ class ImportChapterTest extends WebTestCase
|
||||
'POST',
|
||||
self::API_ENDPOINT,
|
||||
[
|
||||
'mangaId' => 'some-manga-id'
|
||||
'mangaId' => 'some-manga-id',
|
||||
],
|
||||
['file' => $file],
|
||||
['CONTENT_TYPE' => 'multipart/form-data']
|
||||
@@ -70,7 +70,7 @@ class ImportChapterTest extends WebTestCase
|
||||
$this->assertStringContainsString('chapterNumber is required', $response[0]['message']);
|
||||
}
|
||||
|
||||
public function test_it_returns_422_when_file_is_missing(): void
|
||||
public function testItReturns422WhenFileIsMissing(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
@@ -79,7 +79,7 @@ class ImportChapterTest extends WebTestCase
|
||||
self::API_ENDPOINT,
|
||||
[
|
||||
'mangaId' => 'some-manga-id',
|
||||
'chapterNumber' => '1.5'
|
||||
'chapterNumber' => '1.5',
|
||||
],
|
||||
[],
|
||||
['CONTENT_TYPE' => 'multipart/form-data']
|
||||
@@ -90,7 +90,7 @@ class ImportChapterTest extends WebTestCase
|
||||
$this->assertStringContainsString('Please upload a file', $response[0]['message']);
|
||||
}
|
||||
|
||||
public function test_it_returns_422_when_file_is_not_cbz(): void
|
||||
public function testItReturns422WhenFileIsNotCbz(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
@@ -103,7 +103,7 @@ class ImportChapterTest extends WebTestCase
|
||||
self::API_ENDPOINT,
|
||||
[
|
||||
'mangaId' => 'some-manga-id',
|
||||
'chapterNumber' => '1.5'
|
||||
'chapterNumber' => '1.5',
|
||||
],
|
||||
['file' => $file],
|
||||
['CONTENT_TYPE' => 'multipart/form-data']
|
||||
@@ -120,7 +120,7 @@ class ImportChapterTest extends WebTestCase
|
||||
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,7 @@ class ImportVolumeTest extends WebTestCase
|
||||
{
|
||||
private const API_ENDPOINT = '/api/volumes/import';
|
||||
|
||||
public function test_it_returns_404_when_manga_not_found(): void
|
||||
public function testItReturns404WhenMangaNotFound(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$file = $this->createValidCbzFile();
|
||||
@@ -19,7 +19,7 @@ class ImportVolumeTest extends WebTestCase
|
||||
self::API_ENDPOINT,
|
||||
[
|
||||
'mangaId' => 'non-existent-manga-id',
|
||||
'volumeNumber' => '1'
|
||||
'volumeNumber' => '1',
|
||||
],
|
||||
['file' => $file],
|
||||
['CONTENT_TYPE' => 'multipart/form-data']
|
||||
@@ -30,7 +30,7 @@ class ImportVolumeTest extends WebTestCase
|
||||
$this->assertEquals('Manga not found', $response['error']);
|
||||
}
|
||||
|
||||
public function test_it_returns_422_when_manga_id_is_missing(): void
|
||||
public function testItReturns422WhenMangaIdIsMissing(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$file = $this->createValidCbzFile();
|
||||
@@ -39,7 +39,7 @@ class ImportVolumeTest extends WebTestCase
|
||||
'POST',
|
||||
self::API_ENDPOINT,
|
||||
[
|
||||
'volumeNumber' => '1'
|
||||
'volumeNumber' => '1',
|
||||
],
|
||||
['file' => $file],
|
||||
['CONTENT_TYPE' => 'multipart/form-data']
|
||||
@@ -50,7 +50,7 @@ class ImportVolumeTest extends WebTestCase
|
||||
$this->assertStringContainsString('mangaId is required', $response[0]['message']);
|
||||
}
|
||||
|
||||
public function test_it_returns_422_when_volume_number_is_missing(): void
|
||||
public function testItReturns422WhenVolumeNumberIsMissing(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$file = $this->createValidCbzFile();
|
||||
@@ -59,7 +59,7 @@ class ImportVolumeTest extends WebTestCase
|
||||
'POST',
|
||||
self::API_ENDPOINT,
|
||||
[
|
||||
'mangaId' => 'some-manga-id'
|
||||
'mangaId' => 'some-manga-id',
|
||||
],
|
||||
['file' => $file],
|
||||
['CONTENT_TYPE' => 'multipart/form-data']
|
||||
@@ -70,7 +70,7 @@ class ImportVolumeTest extends WebTestCase
|
||||
$this->assertStringContainsString('volumeNumber is required', $response[0]['message']);
|
||||
}
|
||||
|
||||
public function test_it_returns_422_when_file_is_missing(): void
|
||||
public function testItReturns422WhenFileIsMissing(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
@@ -79,7 +79,7 @@ class ImportVolumeTest extends WebTestCase
|
||||
self::API_ENDPOINT,
|
||||
[
|
||||
'mangaId' => 'some-manga-id',
|
||||
'volumeNumber' => '1'
|
||||
'volumeNumber' => '1',
|
||||
],
|
||||
[],
|
||||
['CONTENT_TYPE' => 'multipart/form-data']
|
||||
@@ -90,7 +90,7 @@ class ImportVolumeTest extends WebTestCase
|
||||
$this->assertStringContainsString('Please upload a file', $response[0]['message']);
|
||||
}
|
||||
|
||||
public function test_it_returns_422_when_file_is_not_cbz(): void
|
||||
public function testItReturns422WhenFileIsNotCbz(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
@@ -103,7 +103,7 @@ class ImportVolumeTest extends WebTestCase
|
||||
self::API_ENDPOINT,
|
||||
[
|
||||
'mangaId' => 'some-manga-id',
|
||||
'volumeNumber' => '1'
|
||||
'volumeNumber' => '1',
|
||||
],
|
||||
['file' => $file],
|
||||
['CONTENT_TYPE' => 'multipart/form-data']
|
||||
@@ -120,7 +120,7 @@ class ImportVolumeTest extends WebTestCase
|
||||
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');
|
||||
}
|
||||
|
||||
|
||||
@@ -16,15 +16,15 @@ class SearchMangaTest extends AbstractApiTestCase
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/manga-search', [
|
||||
'query' => [
|
||||
'q' => ''
|
||||
]
|
||||
'q' => '',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
$this->assertResponseStatusCodeSame(400);
|
||||
$this->assertJsonContains([
|
||||
'hydra:title' => 'An error occurred',
|
||||
'hydra:description' => 'Le terme de recherche doit contenir au moins 3 caractères'
|
||||
'title' => 'An error occurred',
|
||||
'detail' => 'Le terme de recherche doit contenir au moins 3 caractères',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -34,15 +34,15 @@ class SearchMangaTest extends AbstractApiTestCase
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/manga-search', [
|
||||
'query' => [
|
||||
'q' => 'on'
|
||||
]
|
||||
'q' => 'on',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
$this->assertResponseStatusCodeSame(400);
|
||||
$this->assertJsonContains([
|
||||
'hydra:title' => 'An error occurred',
|
||||
'hydra:description' => 'Le terme de recherche doit contenir au moins 3 caractères'
|
||||
'title' => 'An error occurred',
|
||||
'detail' => 'Le terme de recherche doit contenir au moins 3 caractères',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ class SearchMangaTest extends AbstractApiTestCase
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/manga-search', [
|
||||
'query' => [
|
||||
'q' => 'one'
|
||||
]
|
||||
'q' => 'one',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
@@ -66,9 +66,8 @@ class SearchMangaTest extends AbstractApiTestCase
|
||||
|
||||
$data = $response->toArray();
|
||||
|
||||
|
||||
$this->assertCount(2, $data['items']['hydra:member']);
|
||||
$titles = array_map(fn($item) => $item['title'], $data['items']['hydra:member']);
|
||||
$this->assertCount(2, $data['items']['member']);
|
||||
$titles = array_map(fn ($item) => $item['title'], $data['items']['member']);
|
||||
$this->assertContains('One Piece', $titles);
|
||||
$this->assertContains('One Punch Man', $titles);
|
||||
}
|
||||
@@ -83,17 +82,17 @@ class SearchMangaTest extends AbstractApiTestCase
|
||||
$client = static::createClient();
|
||||
$response = $client->request('GET', '/api/manga-search', [
|
||||
'query' => [
|
||||
'q' => 'dragon'
|
||||
]
|
||||
'q' => 'dragon',
|
||||
],
|
||||
]);
|
||||
|
||||
// Then
|
||||
$this->assertResponseIsSuccessful();
|
||||
$data = $response->toArray();
|
||||
|
||||
$this->assertCount(1, $data['items']['hydra:member']);
|
||||
$this->assertEquals('Dragon Ball', $data['items']['hydra:member'][0]['title']);
|
||||
$this->assertEquals('dragon-ball', $data['items']['hydra:member'][0]['slug']);
|
||||
$this->assertCount(1, $data['items']['member']);
|
||||
$this->assertEquals('Dragon Ball', $data['items']['member'][0]['title']);
|
||||
$this->assertEquals('dragon-ball', $data['items']['member'][0]['slug']);
|
||||
}
|
||||
|
||||
// public function testSearchMangaWithPagination(): void
|
||||
|
||||
@@ -39,8 +39,8 @@ class ToggleMonitoringTest extends AbstractApiTestCase
|
||||
// Act
|
||||
static::createClient()->request('POST', "/api/manga/{$mangaId}/monitoring/toggle", [
|
||||
'json' => [
|
||||
'enabled' => true
|
||||
]
|
||||
'enabled' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
// Assert
|
||||
@@ -78,8 +78,8 @@ class ToggleMonitoringTest extends AbstractApiTestCase
|
||||
// Act
|
||||
static::createClient()->request('POST', "/api/manga/{$mangaId}/monitoring/toggle", [
|
||||
'json' => [
|
||||
'enabled' => false
|
||||
]
|
||||
'enabled' => false,
|
||||
],
|
||||
]);
|
||||
|
||||
// Assert
|
||||
@@ -96,8 +96,8 @@ class ToggleMonitoringTest extends AbstractApiTestCase
|
||||
// Act & Assert
|
||||
static::createClient()->request('POST', '/api/manga/99999/monitoring/toggle', [
|
||||
'json' => [
|
||||
'enabled' => true
|
||||
]
|
||||
'enabled' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(404);
|
||||
@@ -127,7 +127,7 @@ class ToggleMonitoringTest extends AbstractApiTestCase
|
||||
|
||||
// Act & Assert
|
||||
static::createClient()->request('POST', "/api/manga/{$mangaId}/monitoring/toggle", [
|
||||
'json' => []
|
||||
'json' => [],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(422);
|
||||
@@ -135,9 +135,9 @@ class ToggleMonitoringTest extends AbstractApiTestCase
|
||||
'violations' => [
|
||||
[
|
||||
'propertyPath' => 'enabled',
|
||||
'message' => 'Le champ enabled est obligatoire'
|
||||
]
|
||||
]
|
||||
'message' => 'Le champ enabled est obligatoire',
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -166,8 +166,8 @@ class ToggleMonitoringTest extends AbstractApiTestCase
|
||||
// Act & Assert
|
||||
static::createClient()->request('POST', "/api/manga/{$mangaId}/monitoring/toggle", [
|
||||
'json' => [
|
||||
'enabled' => 'invalid'
|
||||
]
|
||||
'enabled' => 'invalid',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(422);
|
||||
@@ -175,9 +175,9 @@ class ToggleMonitoringTest extends AbstractApiTestCase
|
||||
'violations' => [
|
||||
[
|
||||
'propertyPath' => 'enabled',
|
||||
'message' => 'Cette valeur doit être de type bool.'
|
||||
]
|
||||
]
|
||||
'message' => 'Cette valeur doit être de type bool.',
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,23 +45,23 @@ final class GetChapterContextTest extends AbstractApiTestCase
|
||||
]);
|
||||
|
||||
// Act
|
||||
static::createClient()->request('GET', '/api/reader/chapter/' . $chapter1->getId());
|
||||
static::createClient()->request('GET', '/api/reader/chapter/'.$chapter1->getId());
|
||||
|
||||
// Assert
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
|
||||
|
||||
$this->assertJsonContains([
|
||||
'id' => (string)$chapter1->getId(),
|
||||
'mangaId' => (string)$manga->getId(),
|
||||
'id' => (string) $chapter1->getId(),
|
||||
'mangaId' => (string) $manga->getId(),
|
||||
'title' => 'Chapter 1',
|
||||
'number' => 1,
|
||||
'totalPages' => 0,
|
||||
'navigation' => [
|
||||
'hydra:member' => [
|
||||
null, (string)$chapter2->getId(),
|
||||
'member' => [
|
||||
null, (string) $chapter2->getId(),
|
||||
],
|
||||
]
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -75,8 +75,8 @@ final class GetChapterContextTest extends AbstractApiTestCase
|
||||
$this->assertResponseHeaderSame('content-type', 'application/problem+json; charset=utf-8');
|
||||
|
||||
$this->assertJsonContains([
|
||||
'hydra:title' => 'An error occurred',
|
||||
'hydra:description' => 'Le chapitre 0 n\'existe pas',
|
||||
'title' => 'An error occurred',
|
||||
'detail' => 'Le chapitre 0 n\'existe pas',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use App\Tests\Factory\MangaFactory;
|
||||
use App\Tests\Feature\AbstractApiTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
use ZipArchive;
|
||||
|
||||
final class GetChapterPagesTest extends AbstractApiTestCase
|
||||
{
|
||||
@@ -23,16 +22,16 @@ final class GetChapterPagesTest extends AbstractApiTestCase
|
||||
parent::setUp();
|
||||
|
||||
// Extraire quelques images du CBZ dans un dossier temporaire
|
||||
$this->pagesDirectory = sys_get_temp_dir() . '/mangarr-test-pages-' . uniqid();
|
||||
$this->pagesDirectory = sys_get_temp_dir().'/mangarr-test-pages-'.uniqid();
|
||||
mkdir($this->pagesDirectory);
|
||||
$zip = new ZipArchive();
|
||||
$zip->open(__DIR__ . '/../../Fixtures/chapter.cbz');
|
||||
$zip = new \ZipArchive();
|
||||
$zip->open(__DIR__.'/../../Fixtures/chapter.cbz');
|
||||
$zip->extractTo($this->pagesDirectory, ['007.jpg', '008.jpg']);
|
||||
$zip->close();
|
||||
|
||||
$manga = MangaFactory::createOne([
|
||||
'title' => 'Test Manga',
|
||||
'slug' => 'test-manga'
|
||||
'slug' => 'test-manga',
|
||||
]);
|
||||
|
||||
$chapter = ChapterFactory::createOne([
|
||||
@@ -41,7 +40,7 @@ final class GetChapterPagesTest extends AbstractApiTestCase
|
||||
'number' => 1.0,
|
||||
'volume' => 1,
|
||||
'visible' => true,
|
||||
'pagesDirectory' => $this->pagesDirectory
|
||||
'pagesDirectory' => $this->pagesDirectory,
|
||||
]);
|
||||
|
||||
$this->chapterId = $chapter->getId();
|
||||
@@ -51,7 +50,7 @@ final class GetChapterPagesTest extends AbstractApiTestCase
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
foreach (glob($this->pagesDirectory . '/*') as $file) {
|
||||
foreach (glob($this->pagesDirectory.'/*') as $file) {
|
||||
unlink($file);
|
||||
}
|
||||
rmdir($this->pagesDirectory);
|
||||
@@ -63,7 +62,7 @@ final class GetChapterPagesTest extends AbstractApiTestCase
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
|
||||
$this->assertJsonContains([
|
||||
'detail' => 'Le chapitre 999 n\'existe pas'
|
||||
'detail' => 'Le chapitre 999 n\'existe pas',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -99,8 +98,8 @@ final class GetChapterPagesTest extends AbstractApiTestCase
|
||||
$response = static::createClient()->request('GET', "/api/reader/chapter/{$this->chapterId}/pages", [
|
||||
'query' => [
|
||||
'page' => 1,
|
||||
'itemsPerPage' => 5
|
||||
]
|
||||
'itemsPerPage' => 5,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -133,7 +132,7 @@ final class GetChapterPagesTest extends AbstractApiTestCase
|
||||
// Créer un chapitre sans fichier CBZ
|
||||
$manga = MangaFactory::createOne([
|
||||
'title' => 'Empty Manga',
|
||||
'slug' => 'empty-manga'
|
||||
'slug' => 'empty-manga',
|
||||
]);
|
||||
|
||||
$emptyChapter = ChapterFactory::createOne([
|
||||
@@ -142,7 +141,7 @@ final class GetChapterPagesTest extends AbstractApiTestCase
|
||||
'number' => 1.0,
|
||||
'volume' => 1,
|
||||
'visible' => true,
|
||||
'cbzPath' => null
|
||||
'cbzPath' => null,
|
||||
]);
|
||||
|
||||
$response = static::createClient()->request('GET', "/api/reader/chapter/{$emptyChapter->getId()}/pages");
|
||||
@@ -155,8 +154,8 @@ final class GetChapterPagesTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('GET', "/api/reader/chapter/{$this->chapterId}/pages", [
|
||||
'query' => [
|
||||
'page' => -1
|
||||
]
|
||||
'page' => -1,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -167,11 +166,11 @@ final class GetChapterPagesTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('GET', "/api/reader/chapter/{$this->chapterId}/pages", [
|
||||
'query' => [
|
||||
'itemsPerPage' => 0
|
||||
]
|
||||
'itemsPerPage' => 0,
|
||||
],
|
||||
]);
|
||||
|
||||
//TODO: Corriger la fonctionnalité de pagination pour que l'endpoint retourne une erreur 400 quand itemsPerPage est 0 (division par zéro)
|
||||
// TODO: Corriger la fonctionnalité de pagination pour que l'endpoint retourne une erreur 400 quand itemsPerPage est 0 (division par zéro)
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
// L'endpoint retourne une erreur 500 quand itemsPerPage est 0 (division par zéro)
|
||||
}
|
||||
@@ -180,7 +179,7 @@ final class GetChapterPagesTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('GET', '/api/reader/chapter/invalid-id/pages');
|
||||
|
||||
//TODO: Corriger le cas où l'ID est invalide
|
||||
// TODO: Corriger le cas où l'ID est invalide
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
// L'endpoint retourne une erreur 500 quand l'ID est invalide
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@ use Ramsey\Uuid\Uuid;
|
||||
class ScrapingJobFactory
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ScrapingJobRepositoryInterface $repository
|
||||
) {}
|
||||
private readonly ScrapingJobRepositoryInterface $repository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function createJob(array $attributes = []): ScrapingJob
|
||||
{
|
||||
@@ -48,4 +49,4 @@ class ScrapingJobFactory
|
||||
$reflection->setAccessible(true);
|
||||
$reflection->setValue($job, $status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ final class GetMangaPreferredSourcesTest extends AbstractApiTestCase
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
$this->assertJsonContains([
|
||||
'detail' => 'Manga not found with ID: 999999'
|
||||
'detail' => 'Manga not found with ID: 999999',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,10 @@ namespace App\Tests\Feature\Scraping;
|
||||
|
||||
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
|
||||
use App\Domain\Scraping\Domain\Model\ScrapingJob;
|
||||
use App\Domain\Scraping\Domain\Model\ScrapingStatus;
|
||||
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\Shared\Domain\Contract\JobRepositoryInterface;
|
||||
use App\Domain\Shared\Domain\Model\JobStatus;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
class ScrapingStatusTest extends ApiTestCase
|
||||
{
|
||||
@@ -45,12 +42,12 @@ class ScrapingStatusTest extends ApiTestCase
|
||||
|
||||
$responseData = $response->toArray();
|
||||
|
||||
$this->assertArrayHasKey('hydra:member', $responseData);
|
||||
$this->assertIsArray($responseData['hydra:member']);
|
||||
$this->assertCount(1, $responseData['hydra:member']);
|
||||
$this->assertArrayHasKey('member', $responseData);
|
||||
$this->assertIsArray($responseData['member']);
|
||||
$this->assertCount(1, $responseData['member']);
|
||||
|
||||
$jobData = $responseData['hydra:member'][0];
|
||||
$this->assertEquals('/api/jobs/' . $jobId, $jobData['@id']);
|
||||
$jobData = $responseData['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']);
|
||||
@@ -58,7 +55,7 @@ class ScrapingStatusTest extends ApiTestCase
|
||||
$this->assertEquals([
|
||||
'mangaId' => 'manga-123',
|
||||
'chapterNumber' => 1,
|
||||
'sourceId' => 'source-789'
|
||||
'sourceId' => 'source-789',
|
||||
], $jobData['context']);
|
||||
}
|
||||
|
||||
@@ -66,7 +63,7 @@ class ScrapingStatusTest extends ApiTestCase
|
||||
{
|
||||
// When
|
||||
$response = static::createClient()->request('GET', '/api/jobs/non-existent-id?status=in_progress', [
|
||||
'headers' => ['Accept' => 'application/json']
|
||||
'headers' => ['Accept' => 'application/json'],
|
||||
]);
|
||||
|
||||
// Then
|
||||
|
||||
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Feature\Scraping;
|
||||
|
||||
use App\Domain\Scraping\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Entity\ContentSource;
|
||||
use App\Entity\Manga;
|
||||
use App\Domain\Scraping\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Tests\Feature\AbstractApiTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
@@ -72,13 +72,13 @@ final class SetMangaPreferredSourcesTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('POST', '/api/mangas/999999/preferred-sources', [
|
||||
'json' => [
|
||||
'sourceIds' => [(string) $this->source1Id]
|
||||
]
|
||||
'sourceIds' => [(string) $this->source1Id],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
$this->assertJsonContains([
|
||||
'detail' => 'Manga not found with ID: 999999'
|
||||
'detail' => 'Manga not found with ID: 999999',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -86,13 +86,13 @@ final class SetMangaPreferredSourcesTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('POST', "/api/mangas/{$this->mangaId}/preferred-sources", [
|
||||
'json' => [
|
||||
'sourceIds' => ['999999']
|
||||
]
|
||||
'sourceIds' => ['999999'],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
$this->assertJsonContains([
|
||||
'detail' => 'One or more sources do not exist or are not active'
|
||||
'detail' => 'One or more sources do not exist or are not active',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -100,8 +100,8 @@ final class SetMangaPreferredSourcesTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('POST', "/api/mangas/{$this->mangaId}/preferred-sources", [
|
||||
'json' => [
|
||||
'sourceIds' => [(string) $this->source1Id, (string) $this->source2Id]
|
||||
]
|
||||
'sourceIds' => [(string) $this->source1Id, (string) $this->source2Id],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -127,8 +127,8 @@ final class SetMangaPreferredSourcesTest extends AbstractApiTestCase
|
||||
// Modifier les sources préférées
|
||||
$response = static::createClient()->request('POST', "/api/mangas/{$this->mangaId}/preferred-sources", [
|
||||
'json' => [
|
||||
'sourceIds' => [(string) $this->source2Id]
|
||||
]
|
||||
'sourceIds' => [(string) $this->source2Id],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -143,8 +143,8 @@ final class SetMangaPreferredSourcesTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('POST', "/api/mangas/{$this->mangaId}/preferred-sources", [
|
||||
'json' => [
|
||||
'sourceIds' => []
|
||||
]
|
||||
'sourceIds' => [],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -159,11 +159,11 @@ final class SetMangaPreferredSourcesTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('POST', "/api/mangas/{$this->mangaId}/preferred-sources", [
|
||||
'json' => [
|
||||
'sourceIds' => ['invalid-id', '123']
|
||||
]
|
||||
'sourceIds' => ['invalid-id', '123'],
|
||||
],
|
||||
]);
|
||||
|
||||
//TODO: Corriger le cas où l'ID est invalide
|
||||
// TODO: Corriger le cas où l'ID est invalide
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
@@ -171,11 +171,11 @@ final class SetMangaPreferredSourcesTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('POST', "/api/mangas/{$this->mangaId}/preferred-sources", [
|
||||
'json' => [
|
||||
'invalidField' => 'value'
|
||||
]
|
||||
'invalidField' => 'value',
|
||||
],
|
||||
]);
|
||||
|
||||
//TODO: Corriger le cas où le format de la requête est invalide
|
||||
// TODO: Corriger le cas où le format de la requête est invalide
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ class TestScraperConfigurationTest extends AbstractApiTestCase
|
||||
$this->assertGreaterThan(0, count($responseData['errors']));
|
||||
}
|
||||
|
||||
public function testTestScraperConfigurationWithInvalidSelector(): void
|
||||
public function testTestScraperConfigurationWithInvalidSelector(): void
|
||||
{
|
||||
// Given - Configuration avec un sélecteur CSS qui ne trouvera rien
|
||||
$payload = [
|
||||
@@ -165,7 +165,7 @@ class TestScraperConfigurationTest extends AbstractApiTestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function testTestScraperConfigurationWithUnsupportedScrapingType(): void
|
||||
public function testTestScraperConfigurationWithUnsupportedScrapingType(): void
|
||||
{
|
||||
// Given - Type de scraping non supporté
|
||||
$payload = [
|
||||
|
||||
@@ -26,11 +26,11 @@ final class CreateContentSourceTest extends AbstractApiTestCase
|
||||
'scrapingType' => 'html',
|
||||
'imageSelector' => '.chapter-image img',
|
||||
'nextPageSelector' => '.next-page',
|
||||
'chapterSelector' => '.chapter-list a'
|
||||
'chapterSelector' => '.chapter-list a',
|
||||
];
|
||||
|
||||
$response = static::createClient()->request('POST', '/api/content-sources', [
|
||||
'json' => $sourceData
|
||||
'json' => $sourceData,
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -44,12 +44,14 @@ final class CreateContentSourceTest extends AbstractApiTestCase
|
||||
|
||||
// Vérifier que la source a été sauvegardée en base
|
||||
$source = $this->entityManager->find(ContentSource::class, $sourceId);
|
||||
if ($source === null) {
|
||||
if (null === $source) {
|
||||
// L'ID peut ne pas correspondre, vérifions juste que l'opération s'est bien passée
|
||||
$this->assertIsNumeric($responseContent);
|
||||
|
||||
return;
|
||||
}
|
||||
$this->assertEquals($sourceData['baseUrl'], $source->getBaseUrl());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -74,8 +76,8 @@ final class CreateContentSourceTest extends AbstractApiTestCase
|
||||
'json' => [
|
||||
'baseUrl' => '',
|
||||
'chapterUrlFormat' => '',
|
||||
'scrapingType' => ''
|
||||
]
|
||||
'scrapingType' => '',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
@@ -87,8 +89,8 @@ final class CreateContentSourceTest extends AbstractApiTestCase
|
||||
'json' => [
|
||||
'baseUrl' => 'invalid-url',
|
||||
'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}',
|
||||
'scrapingType' => 'html'
|
||||
]
|
||||
'scrapingType' => 'html',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
@@ -100,8 +102,8 @@ final class CreateContentSourceTest extends AbstractApiTestCase
|
||||
'json' => [
|
||||
'baseUrl' => 'https://mangadex.org',
|
||||
'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}',
|
||||
'scrapingType' => 'invalid-type'
|
||||
]
|
||||
'scrapingType' => 'invalid-type',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
@@ -115,11 +117,11 @@ final class CreateContentSourceTest extends AbstractApiTestCase
|
||||
'scrapingType' => 'html',
|
||||
'imageSelector' => '.chapter-image img',
|
||||
'nextPageSelector' => '.next-page',
|
||||
'chapterSelector' => '.chapter-list a'
|
||||
'chapterSelector' => '.chapter-list a',
|
||||
];
|
||||
|
||||
$response = static::createClient()->request('POST', '/api/content-sources', [
|
||||
'json' => $sourceData
|
||||
'json' => $sourceData,
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -129,6 +131,7 @@ final class CreateContentSourceTest extends AbstractApiTestCase
|
||||
$responseContent = $response->getContent();
|
||||
if (is_numeric($responseContent)) {
|
||||
$this->assertIsNumeric($responseContent);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -146,11 +149,11 @@ final class CreateContentSourceTest extends AbstractApiTestCase
|
||||
'scrapingType' => 'javascript',
|
||||
'imageSelector' => '.page-image img',
|
||||
'nextPageSelector' => '.next-button',
|
||||
'chapterSelector' => '.chapter-link'
|
||||
'chapterSelector' => '.chapter-link',
|
||||
];
|
||||
|
||||
$response = static::createClient()->request('POST', '/api/content-sources', [
|
||||
'json' => $sourceData
|
||||
'json' => $sourceData,
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -160,6 +163,7 @@ final class CreateContentSourceTest extends AbstractApiTestCase
|
||||
$responseContent = $response->getContent();
|
||||
if (is_numeric($responseContent)) {
|
||||
$this->assertIsNumeric($responseContent);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -171,8 +175,8 @@ final class CreateContentSourceTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('POST', '/api/content-sources', [
|
||||
'json' => [
|
||||
'invalidField' => 'value'
|
||||
]
|
||||
'invalidField' => 'value',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
|
||||
@@ -24,8 +24,6 @@ final class ExportContentSourceTest extends AbstractApiTestCase
|
||||
$this->assertResponseIsSuccessful();
|
||||
$data = $response->toArray();
|
||||
|
||||
|
||||
|
||||
$this->assertIsArray($data);
|
||||
// L'endpoint retourne un format Hydra mais les données semblent vides
|
||||
// Pour l'instant, vérifions juste que la réponse est un tableau
|
||||
@@ -63,7 +61,7 @@ final class ExportContentSourceTest extends AbstractApiTestCase
|
||||
// L'endpoint retourne un format Hydra mais les données semblent vides
|
||||
// Pour l'instant, vérifions juste que la réponse est un tableau
|
||||
$this->assertArrayHasKey('@type', $data);
|
||||
$this->assertEquals('hydra:Collection', $data['@type']);
|
||||
$this->assertEquals('Collection', $data['@type']);
|
||||
}
|
||||
|
||||
public function testItExportsSourcesWithNullOptionalFields(): void
|
||||
@@ -89,13 +87,13 @@ final class ExportContentSourceTest extends AbstractApiTestCase
|
||||
// L'endpoint retourne un format Hydra mais les données semblent vides
|
||||
// Pour l'instant, vérifions juste que la réponse est un tableau
|
||||
$this->assertArrayHasKey('@type', $data);
|
||||
$this->assertEquals('hydra:Collection', $data['@type']);
|
||||
$this->assertEquals('Collection', $data['@type']);
|
||||
}
|
||||
|
||||
public function testItExportsLargeNumberOfSources(): void
|
||||
{
|
||||
// Création de plusieurs sources
|
||||
for ($i = 1; $i <= 25; $i++) {
|
||||
for ($i = 1; $i <= 25; ++$i) {
|
||||
$source = new ContentSource();
|
||||
$source->setBaseUrl("https://source{$i}.com")
|
||||
->setChapterUrlFormat("https://source{$i}.com/chapter/{id}")
|
||||
@@ -117,7 +115,7 @@ final class ExportContentSourceTest extends AbstractApiTestCase
|
||||
// L'endpoint retourne un format Hydra mais les données semblent vides
|
||||
// Pour l'instant, vérifions juste que la réponse est un tableau
|
||||
$this->assertArrayHasKey('@type', $data);
|
||||
$this->assertEquals('hydra:Collection', $data['@type']);
|
||||
$this->assertEquals('Collection', $data['@type']);
|
||||
}
|
||||
|
||||
public function testItExportsSourcesInCorrectFormat(): void
|
||||
@@ -152,6 +150,6 @@ final class ExportContentSourceTest extends AbstractApiTestCase
|
||||
// L'endpoint retourne un format Hydra mais les données semblent vides
|
||||
// Pour l'instant, vérifions juste que la réponse est un tableau
|
||||
$this->assertArrayHasKey('@type', $data);
|
||||
$this->assertEquals('hydra:Collection', $data['@type']);
|
||||
$this->assertEquals('Collection', $data['@type']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ final class GetContentSourceTest extends AbstractApiTestCase
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
|
||||
$this->assertJsonContains([
|
||||
'detail' => 'ContentSource with id 999999 not found'
|
||||
'detail' => 'ContentSource with id 999999 not found',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ final class ImportContentSourceTest extends AbstractApiTestCase
|
||||
'scrapingType' => 'html',
|
||||
'imageSelector' => '.chapter-image img',
|
||||
'nextPageSelector' => '.next-page',
|
||||
'chapterSelector' => '.chapter-list a'
|
||||
'chapterSelector' => '.chapter-list a',
|
||||
],
|
||||
[
|
||||
'baseUrl' => 'https://mangakakalot.com',
|
||||
@@ -36,13 +36,13 @@ final class ImportContentSourceTest extends AbstractApiTestCase
|
||||
'scrapingType' => 'javascript',
|
||||
'imageSelector' => '.page-image img',
|
||||
'nextPageSelector' => '.next-button',
|
||||
'chapterSelector' => '.chapter-link'
|
||||
]
|
||||
]
|
||||
'chapterSelector' => '.chapter-link',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$response = static::createClient()->request('POST', '/api/content-sources/import', [
|
||||
'json' => $importData
|
||||
'json' => $importData,
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -52,7 +52,7 @@ final class ImportContentSourceTest extends AbstractApiTestCase
|
||||
$sources = $this->entityManager->getRepository(ContentSource::class)->findAll();
|
||||
$this->assertCount(2, $sources);
|
||||
|
||||
$baseUrls = array_map(fn($source) => $source->getBaseUrl(), $sources);
|
||||
$baseUrls = array_map(fn ($source) => $source->getBaseUrl(), $sources);
|
||||
$this->assertContains('https://mangadex.org', $baseUrls);
|
||||
$this->assertContains('https://mangakakalot.com', $baseUrls);
|
||||
}
|
||||
@@ -65,10 +65,10 @@ final class ImportContentSourceTest extends AbstractApiTestCase
|
||||
[
|
||||
'baseUrl' => '',
|
||||
'chapterUrlFormat' => '',
|
||||
'scrapingType' => ''
|
||||
]
|
||||
]
|
||||
]
|
||||
'scrapingType' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST);
|
||||
@@ -85,10 +85,10 @@ final class ImportContentSourceTest extends AbstractApiTestCase
|
||||
'scrapingType' => 'html',
|
||||
'imageSelector' => '.image',
|
||||
'nextPageSelector' => '.next',
|
||||
'chapterSelector' => '.chapter'
|
||||
]
|
||||
]
|
||||
]
|
||||
'chapterSelector' => '.chapter',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_CREATED);
|
||||
@@ -105,10 +105,10 @@ final class ImportContentSourceTest extends AbstractApiTestCase
|
||||
'scrapingType' => 'invalid-type',
|
||||
'imageSelector' => '.image',
|
||||
'nextPageSelector' => '.next',
|
||||
'chapterSelector' => '.chapter'
|
||||
]
|
||||
]
|
||||
]
|
||||
'chapterSelector' => '.chapter',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_CREATED);
|
||||
@@ -118,8 +118,8 @@ final class ImportContentSourceTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('POST', '/api/content-sources/import', [
|
||||
'json' => [
|
||||
'contentSources' => 'not-an-array'
|
||||
]
|
||||
'contentSources' => 'not-an-array',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST);
|
||||
@@ -129,8 +129,8 @@ final class ImportContentSourceTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('POST', '/api/content-sources/import', [
|
||||
'json' => [
|
||||
'contentSources' => []
|
||||
]
|
||||
'contentSources' => [],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
@@ -140,8 +140,8 @@ final class ImportContentSourceTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('POST', '/api/content-sources/import', [
|
||||
'json' => [
|
||||
'invalidField' => []
|
||||
]
|
||||
'invalidField' => [],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
@@ -157,13 +157,13 @@ final class ImportContentSourceTest extends AbstractApiTestCase
|
||||
'scrapingType' => 'html',
|
||||
'imageSelector' => '.simple-image',
|
||||
'nextPageSelector' => '.simple-next',
|
||||
'chapterSelector' => '.simple-chapter'
|
||||
]
|
||||
]
|
||||
'chapterSelector' => '.simple-chapter',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$response = static::createClient()->request('POST', '/api/content-sources/import', [
|
||||
'json' => $importData
|
||||
'json' => $importData,
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -171,7 +171,7 @@ final class ImportContentSourceTest extends AbstractApiTestCase
|
||||
|
||||
// Vérifier que la source a été créée
|
||||
$source = $this->entityManager->getRepository(ContentSource::class)->findOneBy([
|
||||
'baseUrl' => 'https://simple-source.com'
|
||||
'baseUrl' => 'https://simple-source.com',
|
||||
]);
|
||||
$this->assertNotNull($source);
|
||||
$this->assertEquals('html', $source->getScrapingType());
|
||||
@@ -183,21 +183,21 @@ final class ImportContentSourceTest extends AbstractApiTestCase
|
||||
public function testItHandlesLargeImport(): void
|
||||
{
|
||||
$contentSources = [];
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
for ($i = 1; $i <= 10; ++$i) {
|
||||
$contentSources[] = [
|
||||
'baseUrl' => "https://source{$i}.com",
|
||||
'chapterUrlFormat' => "https://source{$i}.com/chapter/{id}",
|
||||
'scrapingType' => 'html',
|
||||
'imageSelector' => ".source{$i}-image img",
|
||||
'nextPageSelector' => ".source{$i}-next",
|
||||
'chapterSelector' => ".source{$i}-chapter a"
|
||||
'chapterSelector' => ".source{$i}-chapter a",
|
||||
];
|
||||
}
|
||||
|
||||
$response = static::createClient()->request('POST', '/api/content-sources/import', [
|
||||
'json' => [
|
||||
'contentSources' => $contentSources
|
||||
]
|
||||
'contentSources' => $contentSources,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
@@ -24,10 +24,10 @@ final class ListContentSourceTest extends AbstractApiTestCase
|
||||
$this->assertResponseIsSuccessful();
|
||||
$data = $response->toArray();
|
||||
|
||||
$this->assertArrayHasKey('hydra:member', $data);
|
||||
$this->assertCount(0, $data['hydra:member']);
|
||||
$this->assertArrayHasKey('hydra:totalItems', $data);
|
||||
$this->assertEquals(0, $data['hydra:totalItems']);
|
||||
$this->assertArrayHasKey('member', $data);
|
||||
$this->assertCount(0, $data['member']);
|
||||
$this->assertArrayHasKey('totalItems', $data);
|
||||
$this->assertEquals(0, $data['totalItems']);
|
||||
}
|
||||
|
||||
public function testItReturnsAllSources(): void
|
||||
@@ -58,13 +58,13 @@ final class ListContentSourceTest extends AbstractApiTestCase
|
||||
$this->assertResponseIsSuccessful();
|
||||
$data = $response->toArray();
|
||||
|
||||
$this->assertArrayHasKey('hydra:member', $data);
|
||||
$this->assertCount(2, $data['hydra:member']);
|
||||
$this->assertArrayHasKey('hydra:totalItems', $data);
|
||||
$this->assertEquals(2, $data['hydra:totalItems']);
|
||||
$this->assertArrayHasKey('member', $data);
|
||||
$this->assertCount(2, $data['member']);
|
||||
$this->assertArrayHasKey('totalItems', $data);
|
||||
$this->assertEquals(2, $data['totalItems']);
|
||||
|
||||
// Vérifier la structure d'une source
|
||||
$firstSource = $data['hydra:member'][0];
|
||||
$firstSource = $data['member'][0];
|
||||
$this->assertArrayHasKey('id', $firstSource);
|
||||
$this->assertArrayHasKey('baseUrl', $firstSource);
|
||||
$this->assertArrayHasKey('chapterUrlFormat', $firstSource);
|
||||
@@ -75,7 +75,7 @@ final class ListContentSourceTest extends AbstractApiTestCase
|
||||
$this->assertArrayHasKey('cleanBaseUrl', $firstSource);
|
||||
|
||||
// Vérifier que les URLs sont bien présentes
|
||||
$baseUrls = array_column($data['hydra:member'], 'baseUrl');
|
||||
$baseUrls = array_column($data['member'], 'baseUrl');
|
||||
$this->assertContains('https://mangadex.org', $baseUrls);
|
||||
$this->assertContains('https://mangakakalot.com', $baseUrls);
|
||||
}
|
||||
@@ -83,7 +83,7 @@ final class ListContentSourceTest extends AbstractApiTestCase
|
||||
public function testItReturnsSourcesWithPagination(): void
|
||||
{
|
||||
// Création de plusieurs sources
|
||||
for ($i = 1; $i <= 25; $i++) {
|
||||
for ($i = 1; $i <= 25; ++$i) {
|
||||
$source = new ContentSource();
|
||||
$source->setBaseUrl("https://source{$i}.com")
|
||||
->setChapterUrlFormat("https://source{$i}.com/chapter/{id}")
|
||||
@@ -99,20 +99,20 @@ final class ListContentSourceTest extends AbstractApiTestCase
|
||||
$response = static::createClient()->request('GET', '/api/content-sources', [
|
||||
'query' => [
|
||||
'page' => 2,
|
||||
'itemsPerPage' => 10
|
||||
]
|
||||
'itemsPerPage' => 10,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$data = $response->toArray();
|
||||
|
||||
$this->assertArrayHasKey('hydra:member', $data);
|
||||
$this->assertArrayHasKey('hydra:totalItems', $data);
|
||||
$this->assertEquals(25, $data['hydra:totalItems']);
|
||||
$this->assertArrayHasKey('member', $data);
|
||||
$this->assertArrayHasKey('totalItems', $data);
|
||||
$this->assertEquals(25, $data['totalItems']);
|
||||
|
||||
// Vérifier la pagination - l'endpoint peut retourner toutes les sources
|
||||
// même avec des paramètres de pagination
|
||||
$this->assertGreaterThanOrEqual(10, count($data['hydra:member']));
|
||||
$this->assertLessThanOrEqual(25, count($data['hydra:member']));
|
||||
$this->assertGreaterThanOrEqual(10, count($data['member']));
|
||||
$this->assertLessThanOrEqual(25, count($data['member']));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,13 +40,13 @@ final class UpdateContentSourceTest extends AbstractApiTestCase
|
||||
'json' => [
|
||||
'baseUrl' => 'https://updated.com',
|
||||
'chapterUrlFormat' => 'https://updated.com/chapter/{id}',
|
||||
'scrapingType' => 'html'
|
||||
]
|
||||
'scrapingType' => 'html',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
|
||||
$this->assertJsonContains([
|
||||
'detail' => 'ContentSource with id 999999 not found'
|
||||
'detail' => 'ContentSource with id 999999 not found',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -58,11 +58,11 @@ final class UpdateContentSourceTest extends AbstractApiTestCase
|
||||
'scrapingType' => 'javascript',
|
||||
'imageSelector' => '.updated-image img',
|
||||
'nextPageSelector' => '.updated-next',
|
||||
'chapterSelector' => '.updated-chapter a'
|
||||
'chapterSelector' => '.updated-chapter a',
|
||||
];
|
||||
|
||||
$response = static::createClient()->request('PUT', "/api/content-sources/{$this->sourceId}", [
|
||||
'json' => $updatedData
|
||||
'json' => $updatedData,
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -72,6 +72,7 @@ final class UpdateContentSourceTest extends AbstractApiTestCase
|
||||
$responseContent = $response->getContent();
|
||||
if (is_numeric($responseContent)) {
|
||||
$this->assertIsNumeric($responseContent);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,8 +97,8 @@ final class UpdateContentSourceTest extends AbstractApiTestCase
|
||||
'json' => [
|
||||
'baseUrl' => '',
|
||||
'chapterUrlFormat' => '',
|
||||
'scrapingType' => ''
|
||||
]
|
||||
'scrapingType' => '',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
@@ -109,8 +110,8 @@ final class UpdateContentSourceTest extends AbstractApiTestCase
|
||||
'json' => [
|
||||
'baseUrl' => 'invalid-url',
|
||||
'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}',
|
||||
'scrapingType' => 'html'
|
||||
]
|
||||
'scrapingType' => 'html',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
@@ -122,8 +123,8 @@ final class UpdateContentSourceTest extends AbstractApiTestCase
|
||||
'json' => [
|
||||
'baseUrl' => 'https://mangadex.org',
|
||||
'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}',
|
||||
'scrapingType' => 'invalid-type'
|
||||
]
|
||||
'scrapingType' => 'invalid-type',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
@@ -137,11 +138,11 @@ final class UpdateContentSourceTest extends AbstractApiTestCase
|
||||
'scrapingType' => 'html',
|
||||
'imageSelector' => '.updated-image img',
|
||||
'nextPageSelector' => '.updated-next-page',
|
||||
'chapterSelector' => '.updated-chapter-list a'
|
||||
'chapterSelector' => '.updated-chapter-list a',
|
||||
];
|
||||
|
||||
$response = static::createClient()->request('PUT', "/api/content-sources/{$this->sourceId}", [
|
||||
'json' => $updatedData
|
||||
'json' => $updatedData,
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
@@ -150,6 +151,7 @@ final class UpdateContentSourceTest extends AbstractApiTestCase
|
||||
$responseContent = $response->getContent();
|
||||
if (is_numeric($responseContent)) {
|
||||
$this->assertIsNumeric($responseContent);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -169,8 +171,8 @@ final class UpdateContentSourceTest extends AbstractApiTestCase
|
||||
'json' => [
|
||||
'baseUrl' => 'https://test.com',
|
||||
'chapterUrlFormat' => 'https://test.com/chapter/{id}',
|
||||
'scrapingType' => 'html'
|
||||
]
|
||||
'scrapingType' => 'html',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
|
||||
@@ -180,8 +182,8 @@ final class UpdateContentSourceTest extends AbstractApiTestCase
|
||||
{
|
||||
$response = static::createClient()->request('PUT', "/api/content-sources/{$this->sourceId}", [
|
||||
'json' => [
|
||||
'invalidField' => 'value'
|
||||
]
|
||||
'invalidField' => 'value',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
|
||||
@@ -29,15 +29,16 @@ class InMemoryEventDispatcher implements EventDispatcherInterface
|
||||
|
||||
/**
|
||||
* @template T of object
|
||||
*
|
||||
* @param class-string<T> $eventClass
|
||||
*
|
||||
* @return array<T>
|
||||
*/
|
||||
public function getDispatchedEventsOfType(string $eventClass): array
|
||||
{
|
||||
return array_filter(
|
||||
$this->dispatchedEvents,
|
||||
fn(object $event) => $event instanceof $eventClass
|
||||
fn (object $event) => $event instanceof $eventClass
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ class InMemoryMessageBus implements MessageBusInterface
|
||||
public function dispatch(object $message, array $stamps = []): Envelope
|
||||
{
|
||||
self::$messages[] = $message;
|
||||
|
||||
return new Envelope($message);
|
||||
}
|
||||
|
||||
@@ -33,6 +34,7 @@ class InMemoryMessageBus implements MessageBusInterface
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user