feat: analyse import + all tests fixed

This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2025-10-15 16:14:15 +02:00
parent fbe9619224
commit 3170a7c60e
74 changed files with 4318 additions and 183 deletions

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Tests\Domain\Manga\Adapter;
use App\Domain\Manga\Domain\Contract\Service\ChapterSynchronizationServiceInterface;
use App\Domain\Manga\Domain\Model\Manga;
class InMemoryChapterSynchronizationService implements ChapterSynchronizationServiceInterface
{
/** @var array<string, array> */
private array $synchronizedChapters = [];
public function synchronizeChapters(Manga $manga): array
{
$this->synchronizedChapters[$manga->getId()->getValue()] = [
'manga_id' => $manga->getId()->getValue(),
'external_id' => $manga->getExternalId()?->getValue(),
'synchronized_at' => new \DateTimeImmutable()
];
// Retourne les IDs des chapitres synchronisés (simulation)
return ['chapter-1', 'chapter-2'];
}
/**
* @return array<string, array>
*/
public function getSynchronizedChapters(): array
{
return $this->synchronizedChapters;
}
public function clear(): void
{
$this->synchronizedChapters = [];
}
}

View File

@@ -128,13 +128,14 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
return null;
}
public function saveChapter(Chapter $chapter): void
public function saveChapter(Chapter $chapter): ChapterId
{
$this->savedChapters[] = $chapter;
if (!isset($this->chapters[$chapter->getMangaId()])) {
$this->chapters[$chapter->getMangaId()] = [];
}
$this->chapters[$chapter->getMangaId()][] = $chapter;
return new ChapterId($chapter->getId());
}
/** @return array<Chapter> */
@@ -160,6 +161,11 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
$manga->getDescription()
];
// Ajouter les slugs alternatifs aux champs de recherche
foreach ($manga->getAlternativeSlugs() as $altSlug) {
$searchableFields[] = $altSlug;
}
foreach ($searchableFields as $field) {
if (str_contains(strtolower($field), strtolower($query))) {
return true;

View File

@@ -13,7 +13,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle;
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\InMemoryMessageBus;
use App\Tests\Shared\Adapter\InMemoryEventDispatcher;
use PHPUnit\Framework\TestCase;
class CreateMangaFromMangadexHandlerTest extends TestCase
@@ -22,7 +22,7 @@ class CreateMangaFromMangadexHandlerTest extends TestCase
private InMemoryMangaRepository $repository;
private InMemoryImageProcessor $imageProcessor;
private CreateMangaFromMangadexHandler $handler;
private InMemoryMessageBus $messageBus;
private InMemoryEventDispatcher $eventDispatcher;
protected function setUp(): void
{
$manga = new Manga(
@@ -41,12 +41,12 @@ class CreateMangaFromMangadexHandlerTest extends TestCase
$this->provider = new InMemoryMangaProvider([$manga]);
$this->repository = new InMemoryMangaRepository();
$this->imageProcessor = new InMemoryImageProcessor();
$this->messageBus = new InMemoryMessageBus();
$this->eventDispatcher = new InMemoryEventDispatcher();
$this->handler = new CreateMangaFromMangadexHandler(
$this->provider,
$this->repository,
$this->imageProcessor,
$this->messageBus
$this->eventDispatcher
);
}
@@ -76,4 +76,4 @@ class CreateMangaFromMangadexHandlerTest extends TestCase
// Act
$this->handler->handle($command);
}
}
}

View File

@@ -4,28 +4,29 @@ namespace App\Tests\Domain\Manga\Application\CommandHandler;
use App\Domain\Manga\Application\Command\FetchMangaChapters;
use App\Domain\Manga\Application\CommandHandler\FetchMangaChaptersHandler;
use App\Domain\Manga\Domain\Exception\MangadexApiException;
use App\Domain\Manga\Domain\Model\Manga;
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\InMemoryMangadexClient;
use App\Tests\Domain\Manga\Adapter\InMemoryChapterSynchronizationService;
use App\Tests\Domain\Manga\Adapter\InMemoryMangaRepository;
use PHPUnit\Framework\TestCase;
class FetchMangaChaptersHandlerTest extends TestCase
{
private InMemoryMangadexClient $mangadexClient;
private InMemoryChapterSynchronizationService $chapterSynchronizationService;
private InMemoryMangaRepository $mangaRepository;
private FetchMangaChaptersHandler $handler;
protected function setUp(): void
{
$this->mangadexClient = new InMemoryMangadexClient();
$this->chapterSynchronizationService = new InMemoryChapterSynchronizationService();
$this->mangaRepository = new InMemoryMangaRepository();
$this->handler = new FetchMangaChaptersHandler(
$this->mangadexClient,
$this->mangaRepository
$this->mangaRepository,
$this->chapterSynchronizationService
);
}
@@ -47,22 +48,12 @@ class FetchMangaChaptersHandlerTest extends TestCase
$this->mangaRepository->save($manga);
$this->mangadexClient->addFeed($externalId, [
[
'id' => 'chapter-1',
'attributes' => [
'chapter' => '1',
'title' => 'Chapter 1',
'volume' => '1',
'translatedLanguage' => 'fr'
]
]
]);
$command = new FetchMangaChapters($mangaId);
$command = new FetchMangaChapters(new MangaId($mangaId));
$this->handler->handle($command);
$this->assertCount(1, $this->mangaRepository->getSavedChapters());
$synchronizedChapters = $this->chapterSynchronizationService->getSynchronizedChapters();
$this->assertCount(1, $synchronizedChapters);
$this->assertArrayHasKey($mangaId, $synchronizedChapters);
}
public function testHandleWithNonExistingManga(): void
@@ -72,7 +63,7 @@ class FetchMangaChaptersHandlerTest extends TestCase
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Manga not found');
$command = new FetchMangaChapters($mangaId);
$command = new FetchMangaChapters(new MangaId($mangaId));
$this->handler->handle($command);
}
@@ -93,10 +84,10 @@ class FetchMangaChaptersHandlerTest extends TestCase
$this->mangaRepository->save($manga);
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Manga has no external ID');
$this->expectException(MangadexApiException::class);
$this->expectExceptionMessage('Manga has no external_id');
$command = new FetchMangaChapters($mangaId);
$command = new FetchMangaChapters(new MangaId($mangaId));
$this->handler->handle($command);
}
}

View File

@@ -0,0 +1,414 @@
<?php
declare(strict_types=1);
namespace App\Tests\Domain\Manga\Application\QueryHandler;
use App\Domain\Manga\Application\Query\FindMangaMatchByFilename;
use App\Domain\Manga\Application\QueryHandler\FindMangaMatchByFilenameHandler;
use App\Domain\Manga\Domain\Model\Manga;
use App\Domain\Manga\Domain\Model\ValueObject\ImageUrls;
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\Infrastructure\Service\FilenameAnalyzer;
use App\Tests\Domain\Manga\Adapter\InMemoryMangaRepository;
use PHPUnit\Framework\TestCase;
class FindMangaMatchByFilenameHandlerTest extends TestCase
{
private InMemoryMangaRepository $repository;
private FilenameAnalyzer $filenameAnalyzer;
private FindMangaMatchByFilenameHandler $handler;
protected function setUp(): void
{
$this->repository = new InMemoryMangaRepository();
$this->filenameAnalyzer = new FilenameAnalyzer();
$this->handler = new FindMangaMatchByFilenameHandler(
$this->filenameAnalyzer,
$this->repository
);
}
public function test_it_finds_exact_match_by_title(): void
{
// Given
$manga = $this->createManga(
id: '123',
title: 'One Piece',
slug: 'one-piece'
);
$this->repository->save($manga);
// When
$query = new FindMangaMatchByFilename('one-piece_vol108_ch1094.cbz');
$response = $this->handler->handle($query);
// Then
$this->assertTrue($response->hasMatches());
$this->assertCount(1, $response->matches);
$match = $response->matches[0];
$this->assertEquals('123', $match->id);
$this->assertEquals('One Piece', $match->title);
$this->assertEquals('one-piece', $match->slug);
$this->assertEquals(1094.0, $match->chapterNumber);
$this->assertEquals(108, $match->volumeNumber);
// Vérifier aussi dans la réponse globale
$this->assertEquals(1094.0, $response->chapterNumber);
$this->assertEquals(108, $response->volumeNumber);
$this->assertNotEmpty($response->possibleTitles);
}
public function test_it_returns_empty_matches_when_no_manga_found(): void
{
// Given - no manga in repository
// When
$query = new FindMangaMatchByFilename('unknown-manga_vol1_ch1.cbz');
$response = $this->handler->handle($query);
// Then
$this->assertFalse($response->hasMatches());
$this->assertCount(0, $response->matches);
$this->assertNull($response->getBestMatch());
}
public function test_it_finds_multiple_matches_and_sorts_by_score(): void
{
// Given
$manga1 = $this->createManga(
id: '1',
title: 'One Piece',
slug: 'one-piece'
);
$manga2 = $this->createManga(
id: '2',
title: 'One Piece Z',
slug: 'one-piece-z'
);
$manga3 = $this->createManga(
id: '3',
title: 'The One Piece',
slug: 'the-one-piece'
);
$this->repository->save($manga1);
$this->repository->save($manga2);
$this->repository->save($manga3);
// When
$query = new FindMangaMatchByFilename('one-piece_vol108_ch1094.cbz');
$response = $this->handler->handle($query);
// Then
$this->assertTrue($response->hasMatches());
$this->assertGreaterThanOrEqual(1, count($response->matches));
// Le meilleur match devrait être "One Piece" (correspondance exacte)
$bestMatch = $response->getBestMatch();
$this->assertNotNull($bestMatch);
$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);
$sortedScores = $scores;
rsort($sortedScores);
$this->assertEquals($sortedScores, $scores);
}
public function test_it_extracts_chapter_and_volume_numbers(): void
{
// Given
$manga = $this->createManga(
id: '123',
title: 'Attack on Titan',
slug: 'attack-on-titan'
);
$this->repository->save($manga);
// When
$query = new FindMangaMatchByFilename('attack-on-titan_vol32_ch130.cbz');
$response = $this->handler->handle($query);
// Then
$this->assertEquals(130.0, $response->chapterNumber);
$this->assertEquals(32, $response->volumeNumber);
// Vérifier que chaque match contient aussi ces informations
foreach ($response->matches as $match) {
$this->assertEquals(130.0, $match->chapterNumber);
$this->assertEquals(32, $match->volumeNumber);
}
}
public function test_it_handles_decimal_chapter_numbers(): void
{
// Given
$manga = $this->createManga(
id: '123',
title: 'Naruto',
slug: 'naruto'
);
$this->repository->save($manga);
// When
$query = new FindMangaMatchByFilename('naruto_vol50_ch456.5.cbz');
$response = $this->handler->handle($query);
// Then
$this->assertEquals(456.5, $response->chapterNumber);
$this->assertEquals(50, $response->volumeNumber);
// Vérifier que chaque match contient aussi ces informations
foreach ($response->matches as $match) {
$this->assertEquals(456.5, $match->chapterNumber);
$this->assertEquals(50, $match->volumeNumber);
}
}
public function test_it_finds_matches_with_alternative_slugs(): void
{
// Given
$manga = $this->createManga(
id: '123',
title: 'Shingeki no Kyojin',
slug: 'shingeki-no-kyojin',
alternativeSlugs: ['attack-on-titan', 'aot']
);
$this->repository->save($manga);
// When
$query = new FindMangaMatchByFilename('attack-on-titan_vol1_ch1.cbz');
$response = $this->handler->handle($query);
// Then
$this->assertTrue($response->hasMatches());
$bestMatch = $response->getBestMatch();
$this->assertNotNull($bestMatch);
$this->assertEquals('123', $bestMatch->id);
$this->assertEquals('Shingeki no Kyojin', $bestMatch->title);
}
public function test_it_handles_filename_without_chapter_or_volume(): void
{
// Given
$manga = $this->createManga(
id: '123',
title: 'One Piece',
slug: 'one-piece'
);
$this->repository->save($manga);
// When
$query = new FindMangaMatchByFilename('one-piece.cbz');
$response = $this->handler->handle($query);
// Then
$this->assertTrue($response->hasMatches());
$this->assertNull($response->chapterNumber);
$this->assertNull($response->volumeNumber);
$bestMatch = $response->getBestMatch();
$this->assertEquals('123', $bestMatch->id);
$this->assertNull($bestMatch->chapterNumber);
$this->assertNull($bestMatch->volumeNumber);
}
public function test_it_finds_single_match_with_unique_id(): void
{
// Given
$manga = $this->createManga(
id: '123',
title: 'One Piece',
slug: 'one-piece'
);
$this->repository->save($manga);
// When
$query = new FindMangaMatchByFilename('one-piece_vol1_ch1.cbz');
$response = $this->handler->handle($query);
// Then - Vérifier qu'on a bien un seul résultat avec l'ID correct
$this->assertCount(1, $response->matches);
$this->assertEquals('123', $response->matches[0]->id);
}
public function test_it_provides_possible_titles_in_response(): void
{
// Given
$manga = $this->createManga(
id: '123',
title: 'Dragon Ball',
slug: 'dragon-ball'
);
$this->repository->save($manga);
// When
$query = new FindMangaMatchByFilename('dragon-ball_vol1_ch5.cbz');
$response = $this->handler->handle($query);
// Then
$this->assertNotEmpty($response->possibleTitles);
$this->assertEquals(['dragon-ball'], $response->possibleTitles);
}
public function test_it_handles_filename_with_only_volume(): void
{
// Given
$manga = $this->createManga(
id: '123',
title: 'One Piece',
slug: 'one-piece'
);
$this->repository->save($manga);
// When - Fichier avec seulement un volume, sans chapitre
$query = new FindMangaMatchByFilename('one-piece_vol108.cbz');
$response = $this->handler->handle($query);
// Then
$this->assertTrue($response->hasMatches());
$this->assertEquals(108, $response->volumeNumber);
$this->assertNull($response->chapterNumber);
$bestMatch = $response->getBestMatch();
$this->assertNotNull($bestMatch);
$this->assertEquals('123', $bestMatch->id);
$this->assertEquals(108, $bestMatch->volumeNumber);
$this->assertNull($bestMatch->chapterNumber);
}
public function test_it_handles_filename_with_only_chapter(): void
{
// Given
$manga = $this->createManga(
id: '123',
title: 'Naruto',
slug: 'naruto'
);
$this->repository->save($manga);
// When - Fichier avec seulement un chapitre, sans volume
$query = new FindMangaMatchByFilename('naruto_ch456.cbz');
$response = $this->handler->handle($query);
// Then
$this->assertTrue($response->hasMatches());
$this->assertEquals(456.0, $response->chapterNumber);
$this->assertNull($response->volumeNumber);
$bestMatch = $response->getBestMatch();
$this->assertNotNull($bestMatch);
$this->assertEquals('123', $bestMatch->id);
$this->assertEquals(456.0, $bestMatch->chapterNumber);
$this->assertNull($bestMatch->volumeNumber);
}
public function test_it_handles_various_volume_only_formats(): void
{
// Given
$manga = $this->createManga(
id: '123',
title: 'Attack on Titan',
slug: 'attack-on-titan'
);
$this->repository->save($manga);
$testCases = [
['filename' => 'attack-on-titan vol 32.cbz', 'expectedVolume' => 32],
['filename' => 'attack-on-titan-tome-15.cbz', 'expectedVolume' => 15],
['filename' => 'attack-on-titan_t10.cbz', 'expectedVolume' => 10],
['filename' => 'attack-on-titan Volume 5.cbr', 'expectedVolume' => 5],
];
foreach ($testCases as $case) {
// When
$query = new FindMangaMatchByFilename($case['filename']);
$response = $this->handler->handle($query);
// Then
$this->assertTrue($response->hasMatches(), "Should find match for {$case['filename']}");
$this->assertEquals($case['expectedVolume'], $response->volumeNumber,
"Failed volume extraction for: {$case['filename']}");
$this->assertNull($response->chapterNumber,
"Should not have chapter for: {$case['filename']}");
$bestMatch = $response->getBestMatch();
$this->assertEquals($case['expectedVolume'], $bestMatch->volumeNumber,
"Match should have correct volume for: {$case['filename']}");
$this->assertNull($bestMatch->chapterNumber,
"Match should not have chapter for: {$case['filename']}");
}
}
public function test_it_handles_various_chapter_only_formats(): void
{
// Given
$manga = $this->createManga(
id: '123',
title: 'My Hero Academia',
slug: 'my-hero-academia'
);
$this->repository->save($manga);
$testCases = [
['filename' => 'my-hero-academia ch 150.cbz', 'expectedChapter' => 150.0],
['filename' => 'my-hero-academia-chap-200.cbz', 'expectedChapter' => 200.0],
['filename' => 'my-hero-academia_chapter_75.cbz', 'expectedChapter' => 75.0],
['filename' => 'my-hero-academia chapitre 100.cbr', 'expectedChapter' => 100.0],
['filename' => 'my-hero-academia_ch99.5.cbz', 'expectedChapter' => 99.5],
];
foreach ($testCases as $case) {
// When
$query = new FindMangaMatchByFilename($case['filename']);
$response = $this->handler->handle($query);
// Then
$this->assertTrue($response->hasMatches(), "Should find match for {$case['filename']}");
$this->assertEquals($case['expectedChapter'], $response->chapterNumber,
"Failed chapter extraction for: {$case['filename']}");
$this->assertNull($response->volumeNumber,
"Should not have volume for: {$case['filename']}");
$bestMatch = $response->getBestMatch();
$this->assertEquals($case['expectedChapter'], $bestMatch->chapterNumber,
"Match should have correct chapter for: {$case['filename']}");
$this->assertNull($bestMatch->volumeNumber,
"Match should not have volume for: {$case['filename']}");
}
}
private function createManga(
string $id,
string $title,
string $slug,
array $alternativeSlugs = [],
?string $thumbnailUrl = null
): Manga {
return new Manga(
id: new MangaId($id),
title: new MangaTitle($title),
slug: new MangaSlug($slug),
description: 'Test description',
author: 'Test Author',
publicationYear: 2000,
genres: ['action'],
status: 'ongoing',
imageUrls: new ImageUrls(
'http://example.com/full.jpg',
$thumbnailUrl ?? 'http://example.com/thumbnail.jpg'
),
alternativeSlugs: $alternativeSlugs
);
}
protected function tearDown(): void
{
$this->repository->clear();
}
}

View File

@@ -0,0 +1,236 @@
<?php
declare(strict_types=1);
namespace Tests\Domain\Manga\Infrastructure\Service;
use App\Domain\Manga\Infrastructure\Service\FilenameAnalyzer;
use PHPUnit\Framework\TestCase;
class FilenameAnalyzerTest extends TestCase
{
private FilenameAnalyzer $analyzer;
protected function setUp(): void
{
$this->analyzer = new FilenameAnalyzer();
}
public function test_it_analyzes_one_piece_filename_correctly(): void
{
// Given
$filename = 'one-piece_vol108_ch1094.cbz';
// When
$result = $this->analyzer->analyze($filename);
// Then
$this->assertEquals('one-piece', $result->getTitle()->getValue());
$this->assertEquals(1094.0, $result->getChapterNumber()->getValue());
$this->assertEquals(108.0, $result->getVolumeNumber()->getValue());
$this->assertTrue($result->hasChapterNumber());
$this->assertTrue($result->hasVolumeNumber());
}
public function test_it_handles_different_filename_formats(): void
{
$testCases = [
// Format underscore
[
'filename' => 'attack-on-titan_vol32_ch130.cbz',
'expectedTitle' => 'attack-on-titan',
'expectedChapter' => 130.0,
'expectedVolume' => 32.0,
],
// Format avec espaces
[
'filename' => 'Dragon Ball vol 1 ch 5.cbz',
'expectedTitle' => 'Dragon Ball',
'expectedChapter' => 5.0,
'expectedVolume' => 1.0,
],
// Format avec tirets
[
'filename' => 'my-hero-academia-vol15-ch150.cbr',
'expectedTitle' => 'my-hero-academia',
'expectedChapter' => 150.0,
'expectedVolume' => 15.0,
],
// Format chapitre décimal
[
'filename' => 'naruto_vol50_ch456.5.cbz',
'expectedTitle' => 'naruto',
'expectedChapter' => 456.5,
'expectedVolume' => 50.0,
],
];
foreach ($testCases as $case) {
$result = $this->analyzer->analyze($case['filename']);
$this->assertEquals($case['expectedTitle'], $result->getTitle()->getValue(),
"Failed for filename: {$case['filename']}");
$this->assertEquals($case['expectedChapter'], $result->getChapterNumber()->getValue(),
"Failed chapter extraction for: {$case['filename']}");
$this->assertEquals($case['expectedVolume'], $result->getVolumeNumber()->getValue(),
"Failed volume extraction for: {$case['filename']}");
}
}
public function test_it_extracts_and_cleans_title(): void
{
// Given
$filename = 'one-piece_vol108_ch1094.cbz';
// When
$result = $this->analyzer->analyze($filename);
// Then - should extract and clean the title
$this->assertEquals('one-piece', $result->getTitle()->getValue());
$this->assertNotEmpty($result->getTitle()->getValue(), 'Title should not be empty');
}
public function test_it_handles_files_without_volume_or_chapter(): void
{
$testCases = [
[
'filename' => 'one-piece.cbz',
'expectedTitle' => 'one-piece',
],
[
'filename' => 'manga_title_only.cbr',
'expectedTitle' => 'manga_title_only',
],
];
foreach ($testCases as $case) {
$result = $this->analyzer->analyze($case['filename']);
$this->assertEquals($case['expectedTitle'], $result->getTitle()->getValue());
$this->assertFalse($result->hasChapterNumber());
$this->assertFalse($result->hasVolumeNumber());
$this->assertNull($result->getChapterNumber());
$this->assertNull($result->getVolumeNumber());
}
}
public function test_it_handles_cbz_and_cbr_extensions(): void
{
// Given
$testCases = [
['filename' => 'one-piece.cbz', 'expectedTitle' => 'one-piece'],
['filename' => 'manga.cbr', 'expectedTitle' => 'manga'],
['filename' => 'test.CBZ', 'expectedTitle' => 'test'],
['filename' => 'test.CBR', 'expectedTitle' => 'test'],
];
foreach ($testCases as $case) {
// When
$result = $this->analyzer->analyze($case['filename']);
// Then - L'extension est enlevée et le titre est extrait
$this->assertEquals($case['expectedTitle'], $result->getTitle()->getValue());
}
}
public function test_it_cleans_common_patterns(): void
{
$testCases = [
[
'filename' => 'one-piece-scan-fr_vol108_ch1094.cbz',
'cleanedTitle' => 'one-piece',
],
[
'filename' => 'manga-raw-jp_vol1_ch1.cbz',
'cleanedTitle' => 'manga',
],
];
foreach ($testCases as $case) {
$result = $this->analyzer->analyze($case['filename']);
$title = $result->getTitle()->getValue();
// Vérifie que le titre est nettoyé
$this->assertEquals($case['cleanedTitle'], $title,
"Title should be cleaned for {$case['filename']}");
// Vérifie que le titre nettoyé ne contient pas les mots indésirables
$this->assertDoesNotMatchRegularExpression('/\b(?:scan|raw|fr|en|jp|hq|lq)\b/i', $title,
"Cleaned title should not contain unwanted patterns for {$case['filename']}");
}
}
public function test_it_handles_filename_with_only_volume(): void
{
$testCases = [
[
'filename' => 'one-piece_vol108.cbz',
'expectedTitle' => 'one-piece',
'expectedVolume' => 108.0,
],
[
'filename' => 'attack-on-titan vol 32.cbz',
'expectedTitle' => 'attack-on-titan',
'expectedVolume' => 32.0,
],
[
'filename' => 'naruto-tome-50.cbz',
'expectedTitle' => 'naruto',
'expectedVolume' => 50.0,
],
[
'filename' => 'bleach_t15.cbz',
'expectedTitle' => 'bleach',
'expectedVolume' => 15.0,
],
];
foreach ($testCases as $case) {
$result = $this->analyzer->analyze($case['filename']);
$this->assertEquals($case['expectedTitle'], $result->getTitle()->getValue(),
"Failed title extraction for: {$case['filename']}");
$this->assertEquals($case['expectedVolume'], $result->getVolumeNumber()->getValue(),
"Failed volume extraction for: {$case['filename']}");
$this->assertFalse($result->hasChapterNumber(),
"Should not have chapter for: {$case['filename']}");
}
}
public function test_it_handles_filename_with_only_chapter(): void
{
$testCases = [
[
'filename' => 'naruto_ch456.cbz',
'expectedTitle' => 'naruto',
'expectedChapter' => 456.0,
],
[
'filename' => 'my-hero-academia ch 150.cbz',
'expectedTitle' => 'my-hero-academia',
'expectedChapter' => 150.0,
],
[
'filename' => 'bleach-chap-200.cbz',
'expectedTitle' => 'bleach',
'expectedChapter' => 200.0,
],
[
'filename' => 'one-piece_chapter_1094.cbz',
'expectedTitle' => 'one-piece',
'expectedChapter' => 1094.0,
],
];
foreach ($testCases as $case) {
$result = $this->analyzer->analyze($case['filename']);
$this->assertEquals($case['expectedTitle'], $result->getTitle()->getValue(),
"Failed title extraction for: {$case['filename']}");
$this->assertEquals($case['expectedChapter'], $result->getChapterNumber()->getValue(),
"Failed chapter extraction for: {$case['filename']}");
$this->assertFalse($result->hasVolumeNumber(),
"Should not have volume for: {$case['filename']}");
}
}
}