feat: GetMangaList endpoint + tests + test db
This commit is contained in:
parent
073439163b
commit
e3d380eadd
@@ -5,5 +5,6 @@ SYMFONY_DEPRECATIONS_HELPER=999999
|
|||||||
PANTHER_APP_ENV=panther
|
PANTHER_APP_ENV=panther
|
||||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
||||||
|
|
||||||
POSTGRES_DB=app
|
# Configuration PostgreSQL pour les tests
|
||||||
POSTGRE_VERSION=16
|
POSTGRES_DB=app_test
|
||||||
|
POSTGRES_VERSION=16
|
||||||
|
|||||||
@@ -105,6 +105,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
"dama/doctrine-test-bundle": "^8.2",
|
||||||
"dbrekelmans/bdi": "^1.3",
|
"dbrekelmans/bdi": "^1.3",
|
||||||
"deployer/deployer": "^7.5",
|
"deployer/deployer": "^7.5",
|
||||||
"doctrine/doctrine-fixtures-bundle": "^3.5",
|
"doctrine/doctrine-fixtures-bundle": "^3.5",
|
||||||
|
|||||||
69
composer.lock
generated
69
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "49014ec06c069804432e6a13701e46a4",
|
"content-hash": "e84863f24aa342f98beebd3cd364f698",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "api-platform/core",
|
"name": "api-platform/core",
|
||||||
@@ -9436,6 +9436,73 @@
|
|||||||
],
|
],
|
||||||
"time": "2022-02-25T21:32:43+00:00"
|
"time": "2022-02-25T21:32:43+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "dama/doctrine-test-bundle",
|
||||||
|
"version": "v8.2.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/dmaicher/doctrine-test-bundle.git",
|
||||||
|
"reference": "eefe54fdf00d910f808efea9cfce9cc261064a0a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/eefe54fdf00d910f808efea9cfce9cc261064a0a",
|
||||||
|
"reference": "eefe54fdf00d910f808efea9cfce9cc261064a0a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"doctrine/dbal": "^3.3 || ^4.0",
|
||||||
|
"doctrine/doctrine-bundle": "^2.11.0",
|
||||||
|
"php": "^7.4 || ^8.0",
|
||||||
|
"psr/cache": "^1.0 || ^2.0 || ^3.0",
|
||||||
|
"symfony/cache": "^5.4 || ^6.3 || ^7.0",
|
||||||
|
"symfony/framework-bundle": "^5.4 || ^6.3 || ^7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"behat/behat": "^3.0",
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.27",
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpunit/phpunit": "^8.0 || ^9.0 || ^10.0 || ^11.0",
|
||||||
|
"symfony/phpunit-bridge": "^7.2",
|
||||||
|
"symfony/process": "^5.4 || ^6.3 || ^7.0",
|
||||||
|
"symfony/yaml": "^5.4 || ^6.3 || ^7.0"
|
||||||
|
},
|
||||||
|
"type": "symfony-bundle",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "8.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"DAMA\\DoctrineTestBundle\\": "src/DAMA/DoctrineTestBundle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "David Maicher",
|
||||||
|
"email": "mail@dmaicher.de"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony bundle to isolate doctrine database tests and improve test performance",
|
||||||
|
"keywords": [
|
||||||
|
"doctrine",
|
||||||
|
"isolation",
|
||||||
|
"performance",
|
||||||
|
"symfony",
|
||||||
|
"testing",
|
||||||
|
"tests"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/dmaicher/doctrine-test-bundle/issues",
|
||||||
|
"source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v8.2.2"
|
||||||
|
},
|
||||||
|
"time": "2025-02-04T14:37:36+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "dbrekelmans/bdi",
|
"name": "dbrekelmans/bdi",
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ return [
|
|||||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||||
Symfony\Bundle\MercureBundle\MercureBundle::class => ['all' => true],
|
Symfony\Bundle\MercureBundle\MercureBundle::class => ['all' => true],
|
||||||
Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
|
Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
|
||||||
|
DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -26,5 +26,6 @@ api_platform:
|
|||||||
mapping:
|
mapping:
|
||||||
paths:
|
paths:
|
||||||
- '%kernel.project_dir%/src/Domain/Scraping/Infrastructure/ApiPlatform/Dto'
|
- '%kernel.project_dir%/src/Domain/Scraping/Infrastructure/ApiPlatform/Dto'
|
||||||
|
- '%kernel.project_dir%/src/Domain/Manga/Infrastructure/ApiPlatform/Resource'
|
||||||
patch_formats:
|
patch_formats:
|
||||||
json: ['application/merge-patch+json']
|
json: ['application/merge-patch+json']
|
||||||
|
|||||||
5
config/packages/dama_doctrine_test_bundle.yaml
Normal file
5
config/packages/dama_doctrine_test_bundle.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
when@test:
|
||||||
|
dama_doctrine_test:
|
||||||
|
enable_static_connection: true
|
||||||
|
enable_static_meta_data_cache: true
|
||||||
|
enable_static_query_cache: true
|
||||||
@@ -32,8 +32,11 @@ doctrine:
|
|||||||
when@test:
|
when@test:
|
||||||
doctrine:
|
doctrine:
|
||||||
dbal:
|
dbal:
|
||||||
# "TEST_TOKEN" is typically set by ParaTest
|
connections:
|
||||||
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
|
default:
|
||||||
|
use_savepoints: true
|
||||||
|
# "TEST_TOKEN" is typically set by ParaTest
|
||||||
|
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
|
||||||
|
|
||||||
when@prod:
|
when@prod:
|
||||||
doctrine:
|
doctrine:
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
<extensions>
|
<extensions>
|
||||||
|
<bootstrap class="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension" />
|
||||||
<bootstrap class="Symfony\Component\Panther\ServerExtension" />
|
<bootstrap class="Symfony\Component\Panther\ServerExtension" />
|
||||||
<bootstrap class="Zenstruck\Browser\Test\BrowserExtension"/>
|
<bootstrap class="Zenstruck\Browser\Test\BrowserExtension"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|||||||
13
src/Domain/Manga/Application/Query/GetMangaList.php
Normal file
13
src/Domain/Manga/Application/Query/GetMangaList.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Application\Query;
|
||||||
|
|
||||||
|
readonly class GetMangaList
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public ?int $page = 1,
|
||||||
|
public ?int $limit = 20,
|
||||||
|
public ?string $sortBy = 'title',
|
||||||
|
public ?string $sortOrder = 'asc'
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Application\QueryHandler;
|
||||||
|
|
||||||
|
use App\Domain\Manga\Application\Query\GetMangaList;
|
||||||
|
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||||
|
use App\Domain\Manga\Application\Response\MangaListResponse;
|
||||||
|
|
||||||
|
readonly class GetMangaListHandler
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private MangaRepositoryInterface $mangaRepository
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function handle(GetMangaList $query): MangaListResponse
|
||||||
|
{
|
||||||
|
$mangas = $this->mangaRepository->findAll(
|
||||||
|
page: $query->page,
|
||||||
|
limit: $query->limit,
|
||||||
|
sortBy: $query->sortBy,
|
||||||
|
sortOrder: $query->sortOrder
|
||||||
|
);
|
||||||
|
|
||||||
|
$total = $this->mangaRepository->count();
|
||||||
|
|
||||||
|
return new MangaListResponse(
|
||||||
|
mangas: $mangas,
|
||||||
|
total: $total,
|
||||||
|
page: $query->page,
|
||||||
|
limit: $query->limit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/Domain/Manga/Application/Response/MangaListResponse.php
Normal file
28
src/Domain/Manga/Application/Response/MangaListResponse.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Application\Response;
|
||||||
|
|
||||||
|
readonly class MangaListResponse
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public array $mangas,
|
||||||
|
public int $total,
|
||||||
|
public int $page,
|
||||||
|
public int $limit
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getTotalPages(): int
|
||||||
|
{
|
||||||
|
return (int) ceil($this->total / $this->limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasNextPage(): bool
|
||||||
|
{
|
||||||
|
return $this->page < $this->getTotalPages();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasPreviousPage(): bool
|
||||||
|
{
|
||||||
|
return $this->page > 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Domain\Contract\Repository;
|
||||||
|
|
||||||
|
use App\Domain\Manga\Domain\Model\Manga;
|
||||||
|
|
||||||
|
interface MangaRepositoryInterface
|
||||||
|
{
|
||||||
|
public function findAll(int $page = 1, int $limit = 20, string $sortBy = 'title', string $sortOrder = 'asc'): array;
|
||||||
|
public function count(): int;
|
||||||
|
public function findById(string $id): ?Manga;
|
||||||
|
public function save(Manga $manga): void;
|
||||||
|
public function delete(Manga $manga): void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Domain\Exception;
|
||||||
|
|
||||||
|
class InvalidExternalIdException extends MangaDomainException
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Domain\Exception;
|
||||||
|
|
||||||
|
class InvalidMangaIdException extends MangaDomainException
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Domain\Exception;
|
||||||
|
|
||||||
|
class InvalidMangaSlugException extends MangaDomainException
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Domain\Exception;
|
||||||
|
|
||||||
|
class InvalidMangaTitleException extends MangaDomainException
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Domain\Exception;
|
||||||
|
|
||||||
|
class MangaDomainException extends \DomainException
|
||||||
|
{
|
||||||
|
}
|
||||||
80
src/Domain/Manga/Domain/Model/Manga.php
Normal file
80
src/Domain/Manga/Domain/Model/Manga.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Domain\Model;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
readonly class Manga
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private MangaId $id,
|
||||||
|
private MangaTitle $title,
|
||||||
|
private MangaSlug $slug,
|
||||||
|
private string $description,
|
||||||
|
private string $author,
|
||||||
|
private int $publicationYear,
|
||||||
|
private array $genres,
|
||||||
|
private string $status,
|
||||||
|
private ?ExternalId $externalId = null,
|
||||||
|
private ?string $imageUrl = null,
|
||||||
|
private ?float $rating = null,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getId(): MangaId
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): MangaTitle
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSlug(): MangaSlug
|
||||||
|
{
|
||||||
|
return $this->slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthor(): string
|
||||||
|
{
|
||||||
|
return $this->author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPublicationYear(): int
|
||||||
|
{
|
||||||
|
return $this->publicationYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGenres(): array
|
||||||
|
{
|
||||||
|
return $this->genres;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatus(): string
|
||||||
|
{
|
||||||
|
return $this->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExternalId(): ?ExternalId
|
||||||
|
{
|
||||||
|
return $this->externalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getImageUrl(): ?string
|
||||||
|
{
|
||||||
|
return $this->imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRating(): ?float
|
||||||
|
{
|
||||||
|
return $this->rating;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/Domain/Manga/Domain/Model/ValueObject/ExternalId.php
Normal file
21
src/Domain/Manga/Domain/Model/ValueObject/ExternalId.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Domain\Model\ValueObject;
|
||||||
|
|
||||||
|
use App\Domain\Manga\Domain\Exception\InvalidExternalIdException;
|
||||||
|
|
||||||
|
readonly class ExternalId
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $value
|
||||||
|
) {
|
||||||
|
if (empty($value)) {
|
||||||
|
throw new InvalidExternalIdException('External ID cannot be empty');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValue(): string
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/Domain/Manga/Domain/Model/ValueObject/MangaId.php
Normal file
26
src/Domain/Manga/Domain/Model/ValueObject/MangaId.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Domain\Model\ValueObject;
|
||||||
|
|
||||||
|
use App\Domain\Manga\Domain\Exception\InvalidMangaIdException;
|
||||||
|
|
||||||
|
readonly class MangaId
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $value
|
||||||
|
) {
|
||||||
|
if (empty($value)) {
|
||||||
|
throw new InvalidMangaIdException('Manga ID cannot be empty');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValue(): string
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function equals(self $other): bool
|
||||||
|
{
|
||||||
|
return $this->value === $other->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/Domain/Manga/Domain/Model/ValueObject/MangaSlug.php
Normal file
25
src/Domain/Manga/Domain/Model/ValueObject/MangaSlug.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Domain\Model\ValueObject;
|
||||||
|
|
||||||
|
use App\Domain\Manga\Domain\Exception\InvalidMangaSlugException;
|
||||||
|
|
||||||
|
readonly class MangaSlug
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $value
|
||||||
|
) {
|
||||||
|
if (empty(trim($value))) {
|
||||||
|
throw new InvalidMangaSlugException('Manga slug cannot be empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preg_match('/^[a-z0-9]+(?:-[a-z0-9]+)*$/', $value)) {
|
||||||
|
throw new InvalidMangaSlugException('Invalid manga slug format');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValue(): string
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/Domain/Manga/Domain/Model/ValueObject/MangaTitle.php
Normal file
21
src/Domain/Manga/Domain/Model/ValueObject/MangaTitle.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Domain\Model\ValueObject;
|
||||||
|
|
||||||
|
use App\Domain\Manga\Domain\Exception\InvalidMangaTitleException;
|
||||||
|
|
||||||
|
readonly class MangaTitle
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $value
|
||||||
|
) {
|
||||||
|
if (empty(trim($value))) {
|
||||||
|
throw new InvalidMangaTitleException('Manga title cannot be empty');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValue(): string
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Dto;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiProperty;
|
||||||
|
|
||||||
|
readonly class MangaCollection
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
/** @var MangaListItem[] */
|
||||||
|
public array $items,
|
||||||
|
public int $total,
|
||||||
|
public int $page,
|
||||||
|
public int $limit,
|
||||||
|
public bool $hasNextPage,
|
||||||
|
public bool $hasPreviousPage
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Dto;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiProperty;
|
||||||
|
|
||||||
|
readonly class MangaListItem
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[ApiProperty(identifier: true)]
|
||||||
|
public string $id,
|
||||||
|
public string $title,
|
||||||
|
public string $slug,
|
||||||
|
public ?string $imageUrl,
|
||||||
|
public string $author,
|
||||||
|
public int $publicationYear,
|
||||||
|
public array $genres,
|
||||||
|
public string $status,
|
||||||
|
public ?float $rating
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaCollection;
|
||||||
|
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaListStateProvider;
|
||||||
|
|
||||||
|
#[ApiResource(
|
||||||
|
shortName: 'Manga',
|
||||||
|
operations: [
|
||||||
|
new GetCollection(
|
||||||
|
uriTemplate: '/mangas',
|
||||||
|
provider: GetMangaListStateProvider::class,
|
||||||
|
output: MangaCollection::class
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
class MangaListResource
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\Operation;
|
||||||
|
use ApiPlatform\State\ProviderInterface;
|
||||||
|
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\Infrastructure\ApiPlatform\Dto\MangaCollection;
|
||||||
|
use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaListItem;
|
||||||
|
|
||||||
|
readonly class GetMangaListStateProvider implements ProviderInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private GetMangaListHandler $handler
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function provide(Operation $operation, array $uriVariables = [], array $context = []): MangaCollection
|
||||||
|
{
|
||||||
|
$page = $context['filters']['page'] ?? 1;
|
||||||
|
$limit = $context['filters']['limit'] ?? 20;
|
||||||
|
$sortBy = $context['filters']['sortBy'] ?? 'title';
|
||||||
|
$sortOrder = $context['filters']['sortOrder'] ?? 'asc';
|
||||||
|
|
||||||
|
$query = new GetMangaList($page, $limit, $sortBy, $sortOrder);
|
||||||
|
$response = $this->handler->handle($query);
|
||||||
|
|
||||||
|
return new MangaCollection(
|
||||||
|
items: array_map(
|
||||||
|
fn (Manga $manga) => $this->createMangaListItem($manga),
|
||||||
|
$response->mangas
|
||||||
|
),
|
||||||
|
total: $response->total,
|
||||||
|
page: $response->page,
|
||||||
|
limit: $response->limit,
|
||||||
|
hasNextPage: $response->hasNextPage(),
|
||||||
|
hasPreviousPage: $response->hasPreviousPage()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createMangaListItem(Manga $manga): MangaListItem
|
||||||
|
{
|
||||||
|
return new MangaListItem(
|
||||||
|
id: $manga->getId()->getValue(),
|
||||||
|
title: $manga->getTitle()->getValue(),
|
||||||
|
slug: $manga->getSlug()->getValue(),
|
||||||
|
imageUrl: $manga->getImageUrl(),
|
||||||
|
author: $manga->getAuthor(),
|
||||||
|
publicationYear: $manga->getPublicationYear(),
|
||||||
|
genres: $manga->getGenres(),
|
||||||
|
status: $manga->getStatus(),
|
||||||
|
rating: $manga->getRating()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domain\Manga\Infrastructure\Persistence;
|
||||||
|
|
||||||
|
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||||
|
use App\Domain\Manga\Domain\Model\Manga as DomainManga;
|
||||||
|
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\Entity\Manga as EntityManga;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private EntityManagerInterface $entityManager
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function findAll(int $page = 1, int $limit = 20, string $sortBy = 'title', string $sortOrder = 'asc'): array
|
||||||
|
{
|
||||||
|
$offset = ($page - 1) * $limit;
|
||||||
|
|
||||||
|
$queryBuilder = $this->entityManager->createQueryBuilder()
|
||||||
|
->select('m')
|
||||||
|
->from(EntityManga::class, 'm')
|
||||||
|
->orderBy("m.$sortBy", $sortOrder)
|
||||||
|
->setFirstResult($offset)
|
||||||
|
->setMaxResults($limit);
|
||||||
|
|
||||||
|
return array_map(
|
||||||
|
fn (EntityManga $entity) => $this->toDomain($entity),
|
||||||
|
$queryBuilder->getQuery()->getResult()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return $this->entityManager->createQueryBuilder()
|
||||||
|
->select('COUNT(m.id)')
|
||||||
|
->from(EntityManga::class, 'm')
|
||||||
|
->getQuery()
|
||||||
|
->getSingleScalarResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findById(string $id): ?DomainManga
|
||||||
|
{
|
||||||
|
$entity = $this->entityManager->find(EntityManga::class, $id);
|
||||||
|
|
||||||
|
return $entity ? $this->toDomain($entity) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(DomainManga $manga): void
|
||||||
|
{
|
||||||
|
$entity = $this->entityManager->find(EntityManga::class, $manga->getId()->getValue())
|
||||||
|
?? new EntityManga();
|
||||||
|
|
||||||
|
$this->updateEntity($entity, $manga);
|
||||||
|
|
||||||
|
$this->entityManager->persist($entity);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(DomainManga $manga): void
|
||||||
|
{
|
||||||
|
$entity = $this->entityManager->find(EntityManga::class, $manga->getId()->getValue());
|
||||||
|
|
||||||
|
if ($entity) {
|
||||||
|
$this->entityManager->remove($entity);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function toDomain(EntityManga $entity): DomainManga
|
||||||
|
{
|
||||||
|
return new DomainManga(
|
||||||
|
new MangaId((string) $entity->getId()),
|
||||||
|
new MangaTitle($entity->getTitle()),
|
||||||
|
new MangaSlug($entity->getSlug()),
|
||||||
|
$entity->getDescription(),
|
||||||
|
$entity->getAuthor(),
|
||||||
|
$entity->getPublicationYear(),
|
||||||
|
$entity->getGenres(),
|
||||||
|
$entity->getStatus(),
|
||||||
|
$entity->getExternalId() ? new ExternalId($entity->getExternalId()) : null,
|
||||||
|
$entity->getImageUrl(),
|
||||||
|
$entity->getRating()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateEntity(EntityManga $entity, DomainManga $manga): void
|
||||||
|
{
|
||||||
|
$entity->setTitle($manga->getTitle()->getValue())
|
||||||
|
->setSlug($manga->getSlug()->getValue())
|
||||||
|
->setDescription($manga->getDescription())
|
||||||
|
->setAuthor($manga->getAuthor())
|
||||||
|
->setPublicationYear($manga->getPublicationYear())
|
||||||
|
->setGenres($manga->getGenres())
|
||||||
|
->setStatus($manga->getStatus());
|
||||||
|
|
||||||
|
if ($manga->getExternalId()) {
|
||||||
|
$entity->setExternalId($manga->getExternalId()->getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($manga->getImageUrl()) {
|
||||||
|
$entity->setImageUrl($manga->getImageUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($manga->getRating()) {
|
||||||
|
$entity->setRating($manga->getRating());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
symfony.lock
12
symfony.lock
@@ -13,6 +13,18 @@
|
|||||||
"src/ApiResource/.gitignore"
|
"src/ApiResource/.gitignore"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"dama/doctrine-test-bundle": {
|
||||||
|
"version": "8.2",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes-contrib",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "7.2",
|
||||||
|
"ref": "896306d79d4ee143af9eadf9b09fd34a8c391b70"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/dama_doctrine_test_bundle.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"doctrine/doctrine-bundle": {
|
"doctrine/doctrine-bundle": {
|
||||||
"version": "2.11",
|
"version": "2.11",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
|||||||
73
tests/Domain/Manga/Adapter/InMemoryMangaRepository.php
Normal file
73
tests/Domain/Manga/Adapter/InMemoryMangaRepository.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Domain\Manga\Adapter;
|
||||||
|
|
||||||
|
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||||
|
use App\Domain\Manga\Domain\Model\Manga;
|
||||||
|
|
||||||
|
class InMemoryMangaRepository implements MangaRepositoryInterface
|
||||||
|
{
|
||||||
|
/** @var Manga[] */
|
||||||
|
private array $mangas = [];
|
||||||
|
|
||||||
|
public function findAll(int $page = 1, int $limit = 20, string $sortBy = 'title', string $sortOrder = 'asc'): array
|
||||||
|
{
|
||||||
|
$sortedMangas = $this->mangas;
|
||||||
|
|
||||||
|
usort($sortedMangas, function (Manga $a, Manga $b) use ($sortBy, $sortOrder) {
|
||||||
|
$valueA = $this->getPropertyValue($a, $sortBy);
|
||||||
|
$valueB = $this->getPropertyValue($b, $sortBy);
|
||||||
|
|
||||||
|
return $sortOrder === 'asc'
|
||||||
|
? $valueA <=> $valueB
|
||||||
|
: $valueB <=> $valueA;
|
||||||
|
});
|
||||||
|
|
||||||
|
$offset = ($page - 1) * $limit;
|
||||||
|
|
||||||
|
return array_slice($sortedMangas, $offset, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return count($this->mangas);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findById(string $id): ?Manga
|
||||||
|
{
|
||||||
|
foreach ($this->mangas as $manga) {
|
||||||
|
if ($manga->getId()->getValue() === $id) {
|
||||||
|
return $manga;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(Manga $manga): void
|
||||||
|
{
|
||||||
|
$this->mangas[] = $manga;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(Manga $manga): void
|
||||||
|
{
|
||||||
|
$this->mangas = array_filter(
|
||||||
|
$this->mangas,
|
||||||
|
fn(Manga $existingManga) => !$existingManga->getId()->equals($manga->getId())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPropertyValue(Manga $manga, string $property): mixed
|
||||||
|
{
|
||||||
|
return match($property) {
|
||||||
|
'title' => $manga->getTitle()->getValue(),
|
||||||
|
'publicationYear' => $manga->getPublicationYear(),
|
||||||
|
default => throw new \InvalidArgumentException("Unknown sort property: $property")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clear(): void
|
||||||
|
{
|
||||||
|
$this->mangas = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Domain\Manga\Application\QueryHandler;
|
||||||
|
|
||||||
|
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\Tests\Domain\Manga\Adapter\InMemoryMangaRepository;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class GetMangaListHandlerTest extends TestCase
|
||||||
|
{
|
||||||
|
private InMemoryMangaRepository $repository;
|
||||||
|
private GetMangaListHandler $handler;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->repository = new InMemoryMangaRepository();
|
||||||
|
$this->handler = new GetMangaListHandler($this->repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
$this->assertEquals(20, $response->limit);
|
||||||
|
$this->assertFalse($response->hasNextPage());
|
||||||
|
$this->assertFalse($response->hasPreviousPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHandleReturnsPaginatedList(): void
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
$this->assertEquals(2, $response->page);
|
||||||
|
$this->assertEquals(10, $response->limit);
|
||||||
|
$this->assertTrue($response->hasNextPage());
|
||||||
|
$this->assertTrue($response->hasPreviousPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHandleSortsMangas(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$this->repository->save($this->createManga('1', 'Manga B', 2020));
|
||||||
|
$this->repository->save($this->createManga('2', 'Manga A', 2021));
|
||||||
|
$this->repository->save($this->createManga('3', 'Manga C', 2019));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$query = new GetMangaList(sortBy: 'title', sortOrder: 'asc');
|
||||||
|
$response = $this->handler->handle($query);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertCount(3, $response->mangas);
|
||||||
|
$this->assertEquals('Manga A', $response->mangas[0]->getTitle()->getValue());
|
||||||
|
$this->assertEquals('Manga B', $response->mangas[1]->getTitle()->getValue());
|
||||||
|
$this->assertEquals('Manga C', $response->mangas[2]->getTitle()->getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function givenMangasExist(int $count): void
|
||||||
|
{
|
||||||
|
for ($i = 1; $i <= $count; $i++) {
|
||||||
|
$this->repository->save(
|
||||||
|
$this->createManga((string)$i, "Manga $i", 2020)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createManga(string $id, string $title, int $year): Manga
|
||||||
|
{
|
||||||
|
return new Manga(
|
||||||
|
new MangaId($id),
|
||||||
|
new MangaTitle($title),
|
||||||
|
new MangaSlug(strtolower(str_replace(' ', '-', $title))),
|
||||||
|
'Description',
|
||||||
|
'Author',
|
||||||
|
$year,
|
||||||
|
[],
|
||||||
|
'ongoing'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
$this->repository->clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
25
tests/Feature/AbstractApiTestCase.php
Normal file
25
tests/Feature/AbstractApiTestCase.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Feature;
|
||||||
|
|
||||||
|
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
|
||||||
|
abstract class AbstractApiTestCase extends ApiTestCase
|
||||||
|
{
|
||||||
|
protected ?EntityManagerInterface $entityManager;
|
||||||
|
protected ?ContainerInterface $container;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->container = static::getContainer();
|
||||||
|
$this->entityManager = $this->container->get(EntityManagerInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
107
tests/Feature/Manga/GetMangaListTest.php
Normal file
107
tests/Feature/Manga/GetMangaListTest.php
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Feature\Manga;
|
||||||
|
|
||||||
|
use App\Entity\Manga;
|
||||||
|
use App\Tests\Feature\AbstractApiTestCase;
|
||||||
|
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||||
|
|
||||||
|
class GetMangaListTest extends AbstractApiTestCase
|
||||||
|
{
|
||||||
|
use ResetDatabase;
|
||||||
|
|
||||||
|
public function testGetEmptyMangaList(): void
|
||||||
|
{
|
||||||
|
// When
|
||||||
|
$client = static::createClient();
|
||||||
|
$response = $client->request('GET', '/api/mangas');
|
||||||
|
|
||||||
|
// Then
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
$this->assertJsonContains([
|
||||||
|
'total' => 0,
|
||||||
|
'page' => 1,
|
||||||
|
'limit' => 20,
|
||||||
|
'hasNextPage' => false,
|
||||||
|
'hasPreviousPage' => false,
|
||||||
|
'items' => []
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMangaListWithPagination(): void
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
$this->createMangas(25);
|
||||||
|
|
||||||
|
// When
|
||||||
|
$client = static::createClient();
|
||||||
|
$response = $client->request('GET', '/api/mangas', [
|
||||||
|
'query' => [
|
||||||
|
'page' => 2,
|
||||||
|
'limit' => 10
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
$data = $response->toArray();
|
||||||
|
|
||||||
|
$this->assertCount(10, $data['items']);
|
||||||
|
$this->assertEquals(25, $data['total']);
|
||||||
|
$this->assertEquals(2, $data['page']);
|
||||||
|
$this->assertEquals(10, $data['limit']);
|
||||||
|
$this->assertTrue($data['hasNextPage']);
|
||||||
|
$this->assertTrue($data['hasPreviousPage']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMangaListWithSorting(): void
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
$this->createManga('Manga B');
|
||||||
|
$this->createManga('Manga A');
|
||||||
|
$this->createManga('Manga C');
|
||||||
|
|
||||||
|
// When
|
||||||
|
$client = static::createClient();
|
||||||
|
$response = $client->request('GET', '/api/mangas', [
|
||||||
|
'query' => [
|
||||||
|
'sortBy' => 'title',
|
||||||
|
'sortOrder' => 'asc'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
$data = $response->toArray();
|
||||||
|
|
||||||
|
$this->assertCount(3, $data['items']);
|
||||||
|
$this->assertEquals('Manga A', $data['items'][0]['title']);
|
||||||
|
$this->assertEquals('Manga B', $data['items'][1]['title']);
|
||||||
|
$this->assertEquals('Manga C', $data['items'][2]['title']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createMangas(int $count): void
|
||||||
|
{
|
||||||
|
for ($i = 1; $i <= $count; $i++) {
|
||||||
|
$this->createManga("Manga $i");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createManga(string $title): void
|
||||||
|
{
|
||||||
|
$manga = new Manga();
|
||||||
|
$manga->setTitle($title)
|
||||||
|
->setSlug(strtolower(str_replace(' ', '-', $title)))
|
||||||
|
->setDescription('Description test')
|
||||||
|
->setAuthor('Author test')
|
||||||
|
->setPublicationYear(2020)
|
||||||
|
->setGenres(['action'])
|
||||||
|
->setStatus('ongoing')
|
||||||
|
->setRating(4.5)
|
||||||
|
->setMonitored(false)
|
||||||
|
;
|
||||||
|
|
||||||
|
$this->entityManager->persist($manga);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Tests\Feature\Scraping;
|
|
||||||
|
|
||||||
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
|
|
||||||
|
|
||||||
abstract class AbstractApiTestCase extends ApiTestCase
|
|
||||||
{
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function tearDown(): void
|
|
||||||
{
|
|
||||||
parent::tearDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ namespace App\Tests\Feature\Scraping;
|
|||||||
|
|
||||||
use App\Domain\Scraping\Application\Command\ScrapeChapter;
|
use App\Domain\Scraping\Application\Command\ScrapeChapter;
|
||||||
use App\Tests\Domain\Scraping\Adapter\InMemoryMessageBus;
|
use App\Tests\Domain\Scraping\Adapter\InMemoryMessageBus;
|
||||||
|
use App\Tests\Feature\AbstractApiTestCase;
|
||||||
use Symfony\Component\Messenger\MessageBusInterface;
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
|
|
||||||
class ScrapeChapterTest extends AbstractApiTestCase
|
class ScrapeChapterTest extends AbstractApiTestCase
|
||||||
|
|||||||
Reference in New Issue
Block a user