feat: GetPage endpoint
This commit is contained in:
parent
55945adc53
commit
33f5a5568a
24
src/Domain/Reader/Application/Query/GetChapterPage.php
Normal file
24
src/Domain/Reader/Application/Query/GetChapterPage.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Domain\Reader\Application\Query;
|
||||||
|
|
||||||
|
final readonly class GetChapterPage
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $chapterId,
|
||||||
|
private int $pageNumber
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChapterId(): string
|
||||||
|
{
|
||||||
|
return $this->chapterId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPageNumber(): int
|
||||||
|
{
|
||||||
|
return $this->pageNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Domain\Reader\Application\QueryHandler;
|
||||||
|
|
||||||
|
use App\Domain\Reader\Application\Query\GetChapterPage;
|
||||||
|
use App\Domain\Reader\Application\Response\ChapterPageResponse;
|
||||||
|
use App\Domain\Reader\Domain\Contract\Repository\ChapterRepositoryInterface;
|
||||||
|
use App\Domain\Reader\Domain\Exception\ChapterNotFoundException;
|
||||||
|
use App\Domain\Reader\Domain\Exception\PageNotFoundException;
|
||||||
|
use App\Domain\Reader\Domain\ValueObject\ChapterId;
|
||||||
|
use App\Domain\Reader\Domain\ValueObject\PageNumber;
|
||||||
|
|
||||||
|
final readonly class GetChapterPageHandler
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ChapterRepositoryInterface $chapterRepository
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(GetChapterPage $query): ChapterPageResponse
|
||||||
|
{
|
||||||
|
$chapterId = new ChapterId($query->getChapterId());
|
||||||
|
$pageNumber = new PageNumber($query->getPageNumber());
|
||||||
|
|
||||||
|
$totalPages = $this->chapterRepository->getTotalPagesForChapter($chapterId);
|
||||||
|
|
||||||
|
if ($totalPages === 0) {
|
||||||
|
throw ChapterNotFoundException::forChapter($chapterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pageNumber->getValue() > $totalPages) {
|
||||||
|
throw PageNotFoundException::forPage($chapterId, $pageNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
$page = $this->chapterRepository->getPageContent($chapterId, $pageNumber);
|
||||||
|
|
||||||
|
return new ChapterPageResponse(
|
||||||
|
$page->getId(),
|
||||||
|
$page->getPageNumber()->getValue(),
|
||||||
|
$page->getBase64Content(),
|
||||||
|
$page->getMimeType(),
|
||||||
|
$page->getDimensions()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Domain\Reader\Application\Response;
|
||||||
|
|
||||||
|
final readonly class ChapterPageResponse
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $id,
|
||||||
|
private int $pageNumber,
|
||||||
|
private string $base64Content,
|
||||||
|
private string $mimeType,
|
||||||
|
private array $dimensions
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPageNumber(): int
|
||||||
|
{
|
||||||
|
return $this->pageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBase64Content(): string
|
||||||
|
{
|
||||||
|
return $this->base64Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMimeType(): string
|
||||||
|
{
|
||||||
|
return $this->mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDimensions(): array
|
||||||
|
{
|
||||||
|
return $this->dimensions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,9 @@ namespace App\Domain\Reader\Domain\Contract\Repository;
|
|||||||
|
|
||||||
use App\Domain\Reader\Domain\Model\ChapterContext;
|
use App\Domain\Reader\Domain\Model\ChapterContext;
|
||||||
use App\Domain\Reader\Domain\Model\Page;
|
use App\Domain\Reader\Domain\Model\Page;
|
||||||
|
use App\Domain\Reader\Domain\Model\PageContent;
|
||||||
use App\Domain\Reader\Domain\ValueObject\ChapterId;
|
use App\Domain\Reader\Domain\ValueObject\ChapterId;
|
||||||
|
use App\Domain\Reader\Domain\ValueObject\PageNumber;
|
||||||
|
|
||||||
interface ChapterRepositoryInterface
|
interface ChapterRepositoryInterface
|
||||||
{
|
{
|
||||||
@@ -22,4 +24,6 @@ interface ChapterRepositoryInterface
|
|||||||
public function getPreviousChapterId(ChapterId $chapterId): ?ChapterId;
|
public function getPreviousChapterId(ChapterId $chapterId): ?ChapterId;
|
||||||
|
|
||||||
public function getNextChapterId(ChapterId $chapterId): ?ChapterId;
|
public function getNextChapterId(ChapterId $chapterId): ?ChapterId;
|
||||||
|
|
||||||
|
public function getPageContent(ChapterId $chapterId, PageNumber $pageNumber): PageContent;
|
||||||
}
|
}
|
||||||
48
src/Domain/Reader/Domain/Model/PageContent.php
Normal file
48
src/Domain/Reader/Domain/Model/PageContent.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Domain\Reader\Domain\Model;
|
||||||
|
|
||||||
|
use App\Domain\Reader\Domain\ValueObject\PageNumber;
|
||||||
|
|
||||||
|
final readonly class PageContent
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $id,
|
||||||
|
private PageNumber $pageNumber,
|
||||||
|
private string $base64Content,
|
||||||
|
private string $mimeType,
|
||||||
|
private int $width,
|
||||||
|
private int $height
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPageNumber(): PageNumber
|
||||||
|
{
|
||||||
|
return $this->pageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBase64Content(): string
|
||||||
|
{
|
||||||
|
return $this->base64Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMimeType(): string
|
||||||
|
{
|
||||||
|
return $this->mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDimensions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'width' => $this->width,
|
||||||
|
'height' => $this->height,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Domain\Reader\Infrastructure\ApiPlatform\Resource;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use App\Domain\Reader\Infrastructure\ApiPlatform\State\Provider\ChapterPageProvider;
|
||||||
|
|
||||||
|
#[ApiResource(
|
||||||
|
shortName: 'Reader',
|
||||||
|
operations: [
|
||||||
|
new Get(
|
||||||
|
uriTemplate: '/reader/chapter/{chapterId}/page/{pageNumber}',
|
||||||
|
openapiContext: [
|
||||||
|
'summary' => 'Récupère une page spécifique d\'un chapitre',
|
||||||
|
'description' => 'Retourne le contenu d\'une page en base64 avec ses métadonnées',
|
||||||
|
'parameters' => [
|
||||||
|
[
|
||||||
|
'name' => 'chapterId',
|
||||||
|
'in' => 'path',
|
||||||
|
'required' => true,
|
||||||
|
'schema' => ['type' => 'string'],
|
||||||
|
'description' => 'L\'identifiant du chapitre'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'pageNumber',
|
||||||
|
'in' => 'path',
|
||||||
|
'required' => true,
|
||||||
|
'schema' => ['type' => 'integer', 'minimum' => 1],
|
||||||
|
'description' => 'Le numéro de la page à récupérer'
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
provider: ChapterPageProvider::class
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)]
|
||||||
|
class ChapterPageResource
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Domain\Reader\Infrastructure\ApiPlatform\State\Provider;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\Operation;
|
||||||
|
use ApiPlatform\State\ProviderInterface;
|
||||||
|
use App\Domain\Reader\Application\Query\GetChapterPage;
|
||||||
|
use App\Domain\Reader\Application\QueryHandler\GetChapterPageHandler;
|
||||||
|
use App\Domain\Reader\Application\Response\ChapterPageResponse;
|
||||||
|
|
||||||
|
final readonly class ChapterPageProvider implements ProviderInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private GetChapterPageHandler $handler
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ChapterPageResponse
|
||||||
|
{
|
||||||
|
return $this->handler->handle(
|
||||||
|
new GetChapterPage(
|
||||||
|
$uriVariables['chapterId'],
|
||||||
|
(int) $uriVariables['pageNumber']
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@ use App\Domain\Reader\Domain\ValueObject\PageNumber;
|
|||||||
use App\Entity\Chapter as ChapterEntity;
|
use App\Entity\Chapter as ChapterEntity;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use ZipArchive;
|
use ZipArchive;
|
||||||
|
use App\Domain\Reader\Domain\Exception\PageNotFoundException;
|
||||||
|
use App\Domain\Reader\Domain\Model\PageContent;
|
||||||
|
|
||||||
readonly class LegacyChapterRepository implements ChapterRepositoryInterface
|
readonly class LegacyChapterRepository implements ChapterRepositoryInterface
|
||||||
{
|
{
|
||||||
@@ -100,6 +102,10 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface
|
|||||||
'id' => $chapterId->getValue()
|
'id' => $chapterId->getValue()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (!$chapter) {
|
||||||
|
throw ChapterNotFoundException::forChapter($chapterId);
|
||||||
|
}
|
||||||
|
|
||||||
$cbzPath = $chapter->getCbzPath();
|
$cbzPath = $chapter->getCbzPath();
|
||||||
|
|
||||||
if (!$cbzPath) {
|
if (!$cbzPath) {
|
||||||
@@ -156,4 +162,62 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface
|
|||||||
|
|
||||||
return $nextChapter ? new ChapterId((string) $nextChapter->getId()) : null;
|
return $nextChapter ? new ChapterId((string) $nextChapter->getId()) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPageContent(ChapterId $chapterId, PageNumber $pageNumber): PageContent
|
||||||
|
{
|
||||||
|
$chapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([
|
||||||
|
'id' => $chapterId->getValue()
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$chapter) {
|
||||||
|
throw ChapterNotFoundException::forChapter($chapterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cbzPath = $chapter->getCbzPath();
|
||||||
|
if (!$cbzPath || !file_exists($cbzPath)) {
|
||||||
|
throw ChapterNotFoundException::forChapter($chapterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
$zip->open($cbzPath);
|
||||||
|
|
||||||
|
if ($pageNumber->getValue() > $zip->numFiles) {
|
||||||
|
$zip->close();
|
||||||
|
throw PageNotFoundException::forPage($chapterId, $pageNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = $pageNumber->getValue() - 1;
|
||||||
|
$stat = $zip->statIndex($index);
|
||||||
|
|
||||||
|
if ($stat === false) {
|
||||||
|
$zip->close();
|
||||||
|
throw PageNotFoundException::forPage($chapterId, $pageNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
$imageContent = $zip->getFromIndex($index);
|
||||||
|
|
||||||
|
if ($imageContent === false) {
|
||||||
|
$zip->close();
|
||||||
|
throw PageNotFoundException::forPage($chapterId, $pageNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
$imageSize = @getimagesizefromstring($imageContent);
|
||||||
|
|
||||||
|
if ($imageSize === false) {
|
||||||
|
$zip->close();
|
||||||
|
throw PageNotFoundException::forPage($chapterId, $pageNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mimeType = $imageSize['mime'] ?? 'image/jpeg';
|
||||||
|
$zip->close();
|
||||||
|
|
||||||
|
return new PageContent(
|
||||||
|
$stat['name'],
|
||||||
|
$pageNumber,
|
||||||
|
base64_encode($imageContent),
|
||||||
|
$mimeType,
|
||||||
|
$imageSize[0],
|
||||||
|
$imageSize[1]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
121
tests/Domain/Reader/Adapter/InMemoryChapterRepository.php
Normal file
121
tests/Domain/Reader/Adapter/InMemoryChapterRepository.php
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Domain\Reader\Adapter;
|
||||||
|
|
||||||
|
use App\Domain\Reader\Domain\Contract\Repository\ChapterRepositoryInterface;
|
||||||
|
use App\Domain\Reader\Domain\Exception\ChapterNotFoundException;
|
||||||
|
use App\Domain\Reader\Domain\Exception\PageNotFoundException;
|
||||||
|
use App\Domain\Reader\Domain\Model\ChapterContext;
|
||||||
|
use App\Domain\Reader\Domain\Model\Page;
|
||||||
|
use App\Domain\Reader\Domain\Model\PageContent;
|
||||||
|
use App\Domain\Reader\Domain\ValueObject\ChapterId;
|
||||||
|
use App\Domain\Reader\Domain\ValueObject\PageNumber;
|
||||||
|
|
||||||
|
final class InMemoryChapterRepository implements ChapterRepositoryInterface
|
||||||
|
{
|
||||||
|
/** @var array<string, array{pages: array<Page>, context: ChapterContext}> */
|
||||||
|
private array $chapters = [];
|
||||||
|
|
||||||
|
public function addChapter(ChapterId $chapterId, ChapterContext $context, array $pages): void
|
||||||
|
{
|
||||||
|
$this->chapters[$chapterId->getValue()] = [
|
||||||
|
'pages' => $pages,
|
||||||
|
'context' => $context
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPagesForChapter(ChapterId $chapterId, int $page = 1, int $itemsPerPage = 20): array
|
||||||
|
{
|
||||||
|
if (!isset($this->chapters[$chapterId->getValue()])) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$pages = $this->chapters[$chapterId->getValue()]['pages'];
|
||||||
|
$offset = ($page - 1) * $itemsPerPage;
|
||||||
|
|
||||||
|
return array_slice($pages, $offset, $itemsPerPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChapterContext(ChapterId $chapterId): ChapterContext
|
||||||
|
{
|
||||||
|
if (!isset($this->chapters[$chapterId->getValue()])) {
|
||||||
|
throw ChapterNotFoundException::forChapter($chapterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->chapters[$chapterId->getValue()]['context'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalPagesForChapter(ChapterId $chapterId): int
|
||||||
|
{
|
||||||
|
if (!isset($this->chapters[$chapterId->getValue()])) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count($this->chapters[$chapterId->getValue()]['pages']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPreviousChapterId(ChapterId $chapterId): ?ChapterId
|
||||||
|
{
|
||||||
|
$currentChapter = $this->getChapterContext($chapterId);
|
||||||
|
$currentNumber = $currentChapter->getNumber();
|
||||||
|
|
||||||
|
$previousChapter = null;
|
||||||
|
$previousNumber = 0.0;
|
||||||
|
|
||||||
|
foreach ($this->chapters as $id => $chapter) {
|
||||||
|
$number = $chapter['context']->getNumber();
|
||||||
|
if ($number < $currentNumber && $number > $previousNumber) {
|
||||||
|
$previousChapter = $id;
|
||||||
|
$previousNumber = $number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $previousChapter ? new ChapterId($previousChapter) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNextChapterId(ChapterId $chapterId): ?ChapterId
|
||||||
|
{
|
||||||
|
$currentChapter = $this->getChapterContext($chapterId);
|
||||||
|
$currentNumber = $currentChapter->getNumber();
|
||||||
|
|
||||||
|
$nextChapter = null;
|
||||||
|
$nextNumber = PHP_FLOAT_MAX;
|
||||||
|
|
||||||
|
foreach ($this->chapters as $id => $chapter) {
|
||||||
|
$number = $chapter['context']->getNumber();
|
||||||
|
if ($number > $currentNumber && $number < $nextNumber) {
|
||||||
|
$nextChapter = $id;
|
||||||
|
$nextNumber = $number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $nextChapter ? new ChapterId($nextChapter) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPageContent(ChapterId $chapterId, PageNumber $pageNumber): PageContent
|
||||||
|
{
|
||||||
|
if (!isset($this->chapters[$chapterId->getValue()])) {
|
||||||
|
throw ChapterNotFoundException::forChapter($chapterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pages = $this->chapters[$chapterId->getValue()]['pages'];
|
||||||
|
$index = $pageNumber->getValue() - 1;
|
||||||
|
|
||||||
|
if (!isset($pages[$index])) {
|
||||||
|
throw PageNotFoundException::forPage($chapterId, $pageNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
$page = $pages[$index];
|
||||||
|
|
||||||
|
return new PageContent(
|
||||||
|
$page->getId(),
|
||||||
|
$page->getPageNumber(),
|
||||||
|
base64_encode('fake-image-content'),
|
||||||
|
'image/jpeg',
|
||||||
|
800,
|
||||||
|
600
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Domain\Reader\Application\QueryHandler;
|
||||||
|
|
||||||
|
use App\Domain\Reader\Application\Query\GetChapterPage;
|
||||||
|
use App\Domain\Reader\Application\QueryHandler\GetChapterPageHandler;
|
||||||
|
use App\Domain\Reader\Domain\Exception\ChapterNotFoundException;
|
||||||
|
use App\Domain\Reader\Domain\Exception\PageNotFoundException;
|
||||||
|
use App\Domain\Reader\Domain\Model\ChapterContext;
|
||||||
|
use App\Domain\Reader\Domain\Model\Page;
|
||||||
|
use App\Domain\Reader\Domain\ValueObject\ChapterId;
|
||||||
|
use App\Domain\Reader\Domain\ValueObject\PageNumber;
|
||||||
|
use App\Tests\Domain\Reader\Adapter\InMemoryChapterRepository;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class GetChapterPageHandlerTest extends TestCase
|
||||||
|
{
|
||||||
|
private InMemoryChapterRepository $repository;
|
||||||
|
private GetChapterPageHandler $handler;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->repository = new InMemoryChapterRepository();
|
||||||
|
$this->handler = new GetChapterPageHandler($this->repository);
|
||||||
|
|
||||||
|
// Préparation des données de test
|
||||||
|
$chapterId = new ChapterId('chapter-1');
|
||||||
|
$context = new ChapterContext(
|
||||||
|
$chapterId,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'Test Manga',
|
||||||
|
1.0,
|
||||||
|
'Chapter 1',
|
||||||
|
'path/to/cbz',
|
||||||
|
1,
|
||||||
|
10,
|
||||||
|
true,
|
||||||
|
new \DateTimeImmutable()
|
||||||
|
);
|
||||||
|
|
||||||
|
$pages = [];
|
||||||
|
for ($i = 1; $i <= 10; $i++) {
|
||||||
|
$pages[] = new Page(
|
||||||
|
sprintf('page-%d', $i),
|
||||||
|
new PageNumber($i),
|
||||||
|
sprintf('/api/chapters/chapter-1/pages/%d', $i),
|
||||||
|
800,
|
||||||
|
600
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->repository->addChapter($chapterId, $context, $pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItThrowsExceptionWhenChapterDoesNotExist(): void
|
||||||
|
{
|
||||||
|
$this->expectException(ChapterNotFoundException::class);
|
||||||
|
$this->handler->handle(new GetChapterPage('invalid-id', 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItThrowsExceptionWhenPageNumberExceedsTotalPages(): void
|
||||||
|
{
|
||||||
|
$this->expectException(PageNotFoundException::class);
|
||||||
|
$this->handler->handle(new GetChapterPage('chapter-1', 11));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItReturnsPageContentSuccessfully(): void
|
||||||
|
{
|
||||||
|
$response = $this->handler->handle(new GetChapterPage('chapter-1', 5));
|
||||||
|
|
||||||
|
$this->assertEquals('page-5', $response->getId());
|
||||||
|
$this->assertEquals(5, $response->getPageNumber());
|
||||||
|
$this->assertNotEmpty($response->getBase64Content());
|
||||||
|
$this->assertEquals('image/jpeg', $response->getMimeType());
|
||||||
|
$this->assertEquals(['width' => 800, 'height' => 600], $response->getDimensions());
|
||||||
|
}
|
||||||
|
}
|
||||||
85
tests/Feature/Reader/GetChapterPageTest.php
Normal file
85
tests/Feature/Reader/GetChapterPageTest.php
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Feature\Reader;
|
||||||
|
|
||||||
|
use App\Factory\ChapterFactory;
|
||||||
|
use App\Factory\MangaFactory;
|
||||||
|
use App\Tests\Feature\AbstractApiTestCase;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||||
|
|
||||||
|
final class GetChapterPageTest extends AbstractApiTestCase
|
||||||
|
{
|
||||||
|
use ResetDatabase;
|
||||||
|
|
||||||
|
private int $chapterId;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
// Création d'un manga et d'un chapitre avec les factories
|
||||||
|
$manga = MangaFactory::createOne([
|
||||||
|
'title' => 'Test Manga',
|
||||||
|
'slug' => 'test-manga'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$chapter = ChapterFactory::createOne([
|
||||||
|
'manga' => $manga,
|
||||||
|
'title' => 'Chapter 1',
|
||||||
|
'number' => 1.0,
|
||||||
|
'volume' => 1,
|
||||||
|
'visible' => true,
|
||||||
|
'cbzPath' => __DIR__ . '/../../Fixtures/chapter.cbz'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->chapterId = $chapter->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItReturnsNotFoundWhenChapterDoesNotExist(): void
|
||||||
|
{
|
||||||
|
$response = static::createClient()->request('GET', '/api/reader/chapter/999/page/1');
|
||||||
|
|
||||||
|
$this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
|
||||||
|
$this->assertJsonContains([
|
||||||
|
'detail' => 'Le chapitre 999 n\'existe pas'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItReturnsNotFoundWhenPageDoesNotExist(): void
|
||||||
|
{
|
||||||
|
$response = static::createClient()->request('GET', sprintf('/api/reader/chapter/%d/page/999', $this->chapterId));
|
||||||
|
|
||||||
|
$this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
|
||||||
|
$this->assertJsonContains([
|
||||||
|
'detail' => sprintf('La page 999 du chapitre %d n\'existe pas', $this->chapterId)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItReturnsPageContentSuccessfully(): void
|
||||||
|
{
|
||||||
|
$response = static::createClient()->request('GET', sprintf('/api/reader/chapter/%d/page/1', $this->chapterId));
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
|
||||||
|
|
||||||
|
// $this->assertJsonContains([
|
||||||
|
// 'id' => '01.jpg',
|
||||||
|
// 'pageNumber' => 1,
|
||||||
|
// 'mimeType' => 'image/jpeg',
|
||||||
|
// 'dimensions' => [
|
||||||
|
// 'hydra:member' => [
|
||||||
|
// 800,
|
||||||
|
// 1169
|
||||||
|
// ]
|
||||||
|
// ]
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
$content = $response->toArray();
|
||||||
|
$this->assertArrayHasKey('base64Content', $content);
|
||||||
|
$this->assertNotEmpty($content['base64Content']);
|
||||||
|
$this->assertTrue(base64_decode($content['base64Content'], true) !== false);
|
||||||
|
}
|
||||||
|
}
|
||||||
121
tests/Feature/Reader/GetChapterPagesTest.php.old
Normal file
121
tests/Feature/Reader/GetChapterPagesTest.php.old
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Feature\Reader;
|
||||||
|
|
||||||
|
use App\Factory\ChapterFactory;
|
||||||
|
use App\Factory\MangaFactory;
|
||||||
|
use App\Tests\Feature\AbstractApiTestCase;
|
||||||
|
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||||
|
|
||||||
|
final class GetChapterPagesTest extends AbstractApiTestCase
|
||||||
|
{
|
||||||
|
use ResetDatabase;
|
||||||
|
|
||||||
|
public function testItReturnsPagesForChapter(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$manga = MangaFactory::createOne([
|
||||||
|
'slug' => 'manga-1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$chapter = ChapterFactory::createOne([
|
||||||
|
'manga' => $manga,
|
||||||
|
'title' => 'Chapter 1',
|
||||||
|
'number' => 1,
|
||||||
|
'cbzPath' => __DIR__ . '/../../Fixtures/chapter.cbz',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
static::createClient()->request('GET', '/api/reader/chapter/' . $chapter->getId() . '/pages');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
|
||||||
|
|
||||||
|
$response = static::createClient()->request('GET', '/api/reader/chapter/' . $chapter->getId() . '/pages')->toArray();
|
||||||
|
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('pages', $response);
|
||||||
|
$this->assertCount(1, $response['pages']);
|
||||||
|
$this->assertEquals(1, $response['pages'][0]['pageNumber']);
|
||||||
|
$this->assertArrayHasKey('dimensions', $response['pages'][0]);
|
||||||
|
$this->assertEquals(1, $response['totalItems']);
|
||||||
|
$this->assertEquals(1, $response['currentPage']);
|
||||||
|
$this->assertEquals(20, $response['itemsPerPage']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItReturnsPagesWithPagination(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$manga = MangaFactory::createOne([
|
||||||
|
'slug' => 'manga-1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$chapter = ChapterFactory::createOne([
|
||||||
|
'manga' => $manga,
|
||||||
|
'title' => 'Chapter with multiple pages',
|
||||||
|
'number' => 1,
|
||||||
|
'cbzPath' => __DIR__ . '/../../Fixtures/chapter-multiple.cbz',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = static::createClient()->request('GET', '/api/reader/chapter/' . $chapter->getId() . '/pages', [
|
||||||
|
'query' => [
|
||||||
|
'page' => 2,
|
||||||
|
'itemsPerPage' => 1
|
||||||
|
]
|
||||||
|
])->toArray();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
$this->assertCount(1, $response['pages']);
|
||||||
|
$this->assertEquals(2, $response['pages'][0]['pageNumber']);
|
||||||
|
$this->assertEquals(2, $response['totalItems']);
|
||||||
|
$this->assertEquals(2, $response['currentPage']);
|
||||||
|
$this->assertEquals(1, $response['itemsPerPage']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItReturns404ForNonExistentChapter(): void
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
static::createClient()->request('GET', '/api/reader/chapter/0/pages');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertResponseStatusCodeSame(404);
|
||||||
|
$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',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItReturnsEmptyPagesForChapterWithoutCbz(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$manga = MangaFactory::createOne([
|
||||||
|
'slug' => 'manga-1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$chapter = ChapterFactory::createOne([
|
||||||
|
'manga' => $manga,
|
||||||
|
'title' => 'Chapter without CBZ',
|
||||||
|
'number' => 1,
|
||||||
|
'cbzPath' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
static::createClient()->request('GET', '/api/reader/chapter/' . $chapter->getId() . '/pages');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
$this->assertJsonContains([
|
||||||
|
'pages' => [],
|
||||||
|
'totalItems' => 0,
|
||||||
|
'currentPage' => 1,
|
||||||
|
'itemsPerPage' => 20
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
tests/Fixtures/chapter-multiple.cbz
Normal file
BIN
tests/Fixtures/chapter-multiple.cbz
Normal file
Binary file not shown.
BIN
tests/Fixtures/chapter.cbz
Normal file
BIN
tests/Fixtures/chapter.cbz
Normal file
Binary file not shown.
Reference in New Issue
Block a user