diff --git a/README.MD b/README.MD index 5c60c22..50fde60 100644 --- a/README.MD +++ b/README.MD @@ -1,7 +1,7 @@ [![Jikan](https://i.imgur.com/ccx3pxo.png)](#jikan-rest-api-v4---unofficial-myanimelistnet-rest-api) # Jikan REST API v4 - Unofficial MyAnimeList.net REST API -[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/jikan-me/jikan-rest.svg)](http://isitmaintained.com/project/jikan-me/jikan-rest "Average time to resolve an issue") [![Percentage of issues still open](http://isitmaintained.com/badge/open/jikan-me/jikan-rest.svg)](http://isitmaintained.com/project/jikan-me/jikan-rest "Percentage of issues still open") [![stable](https://img.shields.io/badge/PHP-^8.0-blue.svg?style=flat)]() [![Discord Server](https://img.shields.io/discord/460491088004907029.svg?style=flat&logo=discord)](https://discordapp.com/invite/4tvCr36) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/jikan-me/jikan-rest.svg)](http://isitmaintained.com/project/jikan-me/jikan-rest "Average time to resolve an issue") [![Percentage of issues still open](http://isitmaintained.com/badge/open/jikan-me/jikan-rest.svg)](http://isitmaintained.com/project/jikan-me/jikan-rest "Percentage of issues still open") [![stable](https://img.shields.io/badge/PHP-^8.1-blue.svg?style=flat)]() [![Discord Server](https://img.shields.io/discord/460491088004907029.svg?style=flat&logo=discord)](https://discordapp.com/invite/4tvCr36) Jikan is a REST API for [MyAnimeList.net](https://myanimelist.net). It scrapes the website to satisfy the need for API functionality that MyAnimeList.net lacks. @@ -25,7 +25,7 @@ Please read the [manual installation guide](https://github.com/jikan-me/jikan-re For any additional help, join our [Discord server](http://discord.jikan.moe/). ### 🐳 Docker Installation -We distribute the app as a container image so you can just run it: +We distribute the app as a container image, so you can just run it: ```bash docker run -d --name=jikan-rest -p 8080:8080 -v ./.env:/app/.env jikanme/jikan-rest:latest ``` diff --git a/app/Anime.php b/app/Anime.php index 802ef55..a3d369c 100644 --- a/app/Anime.php +++ b/app/Anime.php @@ -18,7 +18,7 @@ class Anime extends JikanApiSearchableModel { use HasFactory, MediaFilters, FilteredByLetter; - protected array $filters = ["order_by", "status", "type", "sort", "max_score", "min_score", "score", "rating", "start_date", "end_date", "producer", "producers", "letter"]; + protected array $filters = ["order_by", "status", "type", "sort", "max_score", "min_score", "score", "rating", "start_date", "end_date", "producer", "producers", "letter", "genres", "genres_exclude"]; /** * The attributes that are mass assignable. * @@ -133,12 +133,6 @@ class Anime extends JikanApiSearchableModel ]; } - /** @noinspection PhpUnused */ - public function filterByLetter(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, string $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder - { - return $query->where("title", "like", "{$value}%"); - } - /** @noinspection PhpUnused */ public function filterByType(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, AnimeTypeEnum $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder { @@ -152,15 +146,15 @@ class Anime extends JikanApiSearchableModel } /** @noinspection PhpUnused */ - public function filterByStartDate(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, CarbonImmutable $date): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder + public function filterByStartDate(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, CarbonImmutable $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder { - return $query->where("aired.from", $date->setTime(0, 0)->toAtomString()); + return $query->where("aired.from", ">=", $value->setTime(0, 0)->toAtomString()); } /** @noinspection PhpUnused */ - public function filterByEndDate(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, CarbonImmutable $date): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder + public function filterByEndDate(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, CarbonImmutable $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder { - return $query->where("aired.to", $date->setTime(0, 0)->toAtomString()); + return $query->where("aired.to", "<=", $value->setTime(0, 0)->toAtomString()); } public function filterByProducer(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, string $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder diff --git a/app/Concerns/MediaFilters.php b/app/Concerns/MediaFilters.php index 00c0323..0236f23 100644 --- a/app/Concerns/MediaFilters.php +++ b/app/Concerns/MediaFilters.php @@ -8,11 +8,21 @@ trait MediaFilters { public function filterByMaxScore(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, mixed $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder { + // if the client specifies the "max" possible value, ignore it, in that case they want everything included + // https://github.com/jikan-me/jikan-rest/issues/309 + if (floatval($value) == 10) { + return $query; + } return $query->where("score", "<=", floatval($value)); } public function filterByMinScore(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, mixed $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder { + // if the client specifies the "max" possible value, ignore it, in that case they want everything included + // https://github.com/jikan-me/jikan-rest/issues/309 + if (floatval($value) == 0) { + return $query; + } return $query->where("score", ">=", floatval($value)); } diff --git a/app/Concerns/ResolvesPaginatorParams.php b/app/Concerns/ResolvesPaginatorParams.php index 876649a..745c33a 100644 --- a/app/Concerns/ResolvesPaginatorParams.php +++ b/app/Concerns/ResolvesPaginatorParams.php @@ -10,6 +10,6 @@ trait ResolvesPaginatorParams $limit = $limit ?? $default_max_results_per_page; $page = $page ?? 1; - return compact($limit, $page); + return compact("limit", "page"); } } diff --git a/app/Dto/AnimeSearchCommand.php b/app/Dto/AnimeSearchCommand.php index 026d9cb..a36dfb9 100644 --- a/app/Dto/AnimeSearchCommand.php +++ b/app/Dto/AnimeSearchCommand.php @@ -7,6 +7,7 @@ use App\Enums\AnimeOrderByEnum; use App\Enums\AnimeRatingEnum; use App\Enums\AnimeStatusEnum; use App\Http\Resources\V4\AnimeCollection; +use App\Rules\Attributes\EnumValidation; use Spatie\Enum\Laravel\Rules\EnumRule; use Spatie\LaravelData\Attributes\MapInputName; use Spatie\LaravelData\Attributes\MapOutputName; @@ -23,10 +24,10 @@ use Spatie\LaravelData\Optional; */ final class AnimeSearchCommand extends MediaSearchCommand implements DataRequest { - #[WithCast(EnumCast::class, AnimeStatusEnum::class)] + #[WithCast(EnumCast::class, AnimeStatusEnum::class), EnumValidation(AnimeStatusEnum::class)] public AnimeStatusEnum|Optional $status; - #[WithCast(EnumCast::class, AnimeRatingEnum::class)] + #[WithCast(EnumCast::class, AnimeRatingEnum::class), EnumValidation(AnimeRatingEnum::class)] public AnimeRatingEnum|Optional $rating; #[IntegerType, Min(1)] @@ -35,16 +36,11 @@ final class AnimeSearchCommand extends MediaSearchCommand implements DataRequest #[Prohibits("producer"), StringType] public string|Optional $producers; - #[MapInputName("order_by"), MapOutputName("order_by"), WithCast(EnumCast::class, AnimeOrderByEnum::class)] + #[ + MapInputName("order_by"), + MapOutputName("order_by"), + WithCast(EnumCast::class, AnimeOrderByEnum::class), + EnumValidation(AnimeOrderByEnum::class) + ] public AnimeOrderByEnum|Optional $orderBy; - - public static function rules(): array - { - return [ - ...parent::rules(), - "status" => [new EnumRule(AnimeStatusEnum::class)], - "rating" => [new EnumRule(AnimeRatingEnum::class)], - "order_by" => [new EnumRule(AnimeOrderByEnum::class)] - ]; - } } diff --git a/app/Dto/MangaSearchCommand.php b/app/Dto/MangaSearchCommand.php index 16921b5..2086289 100644 --- a/app/Dto/MangaSearchCommand.php +++ b/app/Dto/MangaSearchCommand.php @@ -7,6 +7,7 @@ use App\Contracts\DataRequest; use App\Enums\MangaOrderByEnum; use App\Enums\MangaStatusEnum; use App\Http\Resources\V4\MangaCollection; +use App\Rules\Attributes\EnumValidation; use Spatie\Enum\Laravel\Rules\EnumRule; use Spatie\LaravelData\Attributes\MapInputName; use Spatie\LaravelData\Attributes\MapOutputName; @@ -19,21 +20,17 @@ use Spatie\LaravelData\Optional; */ final class MangaSearchCommand extends MediaSearchCommand implements DataRequest { - #[WithCast(EnumCast::class, MangaStatusEnum::class)] + #[WithCast(EnumCast::class, MangaStatusEnum::class), EnumValidation(MangaStatusEnum::class)] public MangaStatusEnum|Optional $status; #[StringType] public string|Optional $magazines; - #[MapInputName("order_by"), MapOutputName("order_by"), WithCast(EnumCast::class, MangaOrderByEnum::class)] + #[ + MapInputName("order_by"), + MapOutputName("order_by"), + EnumValidation(MangaOrderByEnum::class), + WithCast(EnumCast::class, MangaOrderByEnum::class) + ] public MangaOrderByEnum|Optional $orderBy; - - public static function rules(): array - { - return [ - ...parent::rules(), - "status" => new EnumRule(MangaStatusEnum::class), - "order_by" => new EnumRule(MangaOrderByEnum::class) - ]; - } } diff --git a/app/Dto/MediaSearchCommand.php b/app/Dto/MediaSearchCommand.php index 545e8c9..0c1c58c 100644 --- a/app/Dto/MediaSearchCommand.php +++ b/app/Dto/MediaSearchCommand.php @@ -3,16 +3,17 @@ namespace App\Dto; use Carbon\CarbonImmutable; +use Illuminate\Validation\Validator; use Spatie\LaravelData\Attributes\MapInputName; use Spatie\LaravelData\Attributes\MapOutputName; use Spatie\LaravelData\Attributes\Validation\AfterOrEqual; use Spatie\LaravelData\Attributes\Validation\BeforeOrEqual; use Spatie\LaravelData\Attributes\Validation\Between; use Spatie\LaravelData\Attributes\Validation\DateFormat; -use Spatie\LaravelData\Attributes\Validation\GreaterThanOrEqualTo; -use Spatie\LaravelData\Attributes\Validation\LessThanOrEqualTo; use Spatie\LaravelData\Attributes\Validation\Numeric; use Spatie\LaravelData\Attributes\Validation\Prohibits; +use Spatie\LaravelData\Attributes\Validation\Required; +use Spatie\LaravelData\Attributes\Validation\Sometimes; use Spatie\LaravelData\Attributes\WithCast; use Spatie\LaravelData\Attributes\WithTransformer; use Spatie\LaravelData\Casts\DateTimeInterfaceCast; @@ -21,10 +22,10 @@ use Spatie\LaravelData\Transformers\DateTimeInterfaceTransformer; class MediaSearchCommand extends SearchCommand { - #[MapInputName("min_score"), MapOutputName("min_score"), Between(1.00, 9.99), Numeric] + #[MapInputName("min_score"), MapOutputName("min_score"), Between(0.00, 10.00), Numeric] public float|Optional $minScore; - #[MapInputName("max_score"), MapOutputName("max_score"), Between(1.00, 9.99), Numeric] + #[MapInputName("max_score"), MapOutputName("max_score"), Between(1.00, 10.00), Numeric] public float|Optional $maxScore; #[Between(1.00, 9.99), Numeric, Prohibits(["min_score", "max_score"])] @@ -37,11 +38,29 @@ class MediaSearchCommand extends SearchCommand #[MapInputName("genres_exclude"), MapOutputName("genres_exclude")] public string|Optional $genresExclude; - #[WithCast(DateTimeInterfaceCast::class), WithTransformer(DateTimeInterfaceTransformer::class)] - #[BeforeOrEqual("end_date"), DateFormat("Y-m-d")] + #[ + BeforeOrEqual("end_date"), + DateFormat("Y-m-d"), + Sometimes, + Required, + WithCast(DateTimeInterfaceCast::class), + WithTransformer(DateTimeInterfaceTransformer::class) + ] public CarbonImmutable|Optional $start_date; - #[WithCast(DateTimeInterfaceCast::class), WithTransformer(DateTimeInterfaceTransformer::class)] - #[AfterOrEqual("start_date"), DateFormat("Y-m-d")] + #[ + AfterOrEqual("start_date"), + DateFormat("Y-m-d"), + Sometimes, + Required, + WithCast(DateTimeInterfaceCast::class), + WithTransformer(DateTimeInterfaceTransformer::class) + ] public CarbonImmutable|Optional $end_date; + + public static function withValidator(Validator $validator): void + { + $validator->sometimes("min_score", "lte:max_score", fn ($input) => !empty($input->max_score)); + $validator->sometimes("max_score", "gte:min_score", fn ($input) => !empty($input->min_score)); + } } diff --git a/app/Dto/SearchCommand.php b/app/Dto/SearchCommand.php index 74c1d0f..fadbddd 100644 --- a/app/Dto/SearchCommand.php +++ b/app/Dto/SearchCommand.php @@ -6,6 +6,7 @@ use App\Casts\EnumCast; use App\Dto\Concerns\HasLimitParameter; use App\Dto\Concerns\HasPageParameter; use App\Enums\SortDirection; +use App\Rules\Attributes\EnumValidation; use Spatie\Enum\Laravel\Rules\EnumRule; use Spatie\LaravelData\Attributes\Validation\Alpha; use Spatie\LaravelData\Attributes\Validation\Max; @@ -26,16 +27,9 @@ class SearchCommand extends Data #[Max(255), StringType] public string|Optional $q; - #[WithCast(EnumCast::class, SortDirection::class)] + #[WithCast(EnumCast::class, SortDirection::class), EnumValidation(SortDirection::class)] public SortDirection|Optional $sort; #[Size(1), StringType, Alpha] public string|Optional $letter; - - public static function rules(): array - { - return [ - "sort" => [new EnumRule(SortDirection::class)] - ]; - } } diff --git a/app/Dto/UsersSearchCommand.php b/app/Dto/UsersSearchCommand.php index e400dfc..b11b746 100644 --- a/app/Dto/UsersSearchCommand.php +++ b/app/Dto/UsersSearchCommand.php @@ -7,6 +7,7 @@ use App\Concerns\HasRequestFingerprint; use App\Contracts\DataRequest; use App\Enums\GenderEnum; use App\Http\Resources\V4\UserCollection; +use App\Rules\Attributes\EnumValidation; use Spatie\Enum\Laravel\Rules\EnumRule; use Spatie\LaravelData\Attributes\Validation\StringType; use Spatie\LaravelData\Attributes\WithCast; @@ -23,17 +24,9 @@ final class UsersSearchCommand extends SearchCommand implements DataRequest public int|Optional $maxAge; - #[WithCast(EnumCast::class, GenderEnum::class)] + #[WithCast(EnumCast::class, GenderEnum::class), EnumValidation(GenderEnum::class)] public GenderEnum|Optional $gender; #[StringType] public string|Optional $location; - - public static function rules(): array - { - return [ - ...parent::rules(), - "gender" => [new EnumRule(GenderEnum::class)] - ]; - } } diff --git a/app/Exceptions/GithubReport.php b/app/Exceptions/GithubReport.php index c9635a4..e408058 100644 --- a/app/Exceptions/GithubReport.php +++ b/app/Exceptions/GithubReport.php @@ -115,6 +115,7 @@ class GithubReport "Please fill out the details below.\n\n**Summary:**\n\n**Steps to reproduce:**\n\n\n\n ### Additional Details \n **Jikan Parser Version**: ```{$this->jikanVersion}```\n**PHP:** ```{$this->phpVersion}```\n**Redis**: ```{$this->redisRunning}```\n**Exception:** ```{$this->name}```\n**Code:** ```{$this->code}```\n**Message:** ```{$this->error}```\n**Trace:** ```{$this->trace}```\n**Request:** `{$this->requestMethod} {$this->requestUri}`\n" ); + // https://github.com/jikan-me/jikan-rest/issues/new?assignees=&labels=i%3A+bug%2C+i%3A+needs+triage&template=bug.md&title= return "https://github.com/{$this->repo}/issues/new?title={$title}&body={$body}"; } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index b2341e9..6513e0a 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -113,9 +113,9 @@ class Handler extends ExceptionHandler return response() ->json([ 'status' => 400, - 'type' => 'BadRequestException', - 'message' => $e->getMessage(), - 'error' => null + 'type' => 'ValidationException', + 'messages' => $e->validator->getMessageBag()->getMessages(), + 'error' => 'Invalid or incomplete request. Make sure your request is correct. https://docs.api.jikan.moe/' ], 400); } diff --git a/app/Filters/FilterResolver.php b/app/Filters/FilterResolver.php index ad8f861..dd9cd0e 100644 --- a/app/Filters/FilterResolver.php +++ b/app/Filters/FilterResolver.php @@ -18,6 +18,10 @@ trait FilterResolver private function resolveCustomFilter($filterName, $values) { + $filterMethodName = "filterBy" . ucfirst(Str::camel($filterName)); + if (method_exists($this, $filterMethodName)) { + $filterName = $filterMethodName; + } return $this->getClosure($this->makeCallable($filterName), $values); } diff --git a/app/Manga.php b/app/Manga.php index b2bf12f..86fc0ec 100644 --- a/app/Manga.php +++ b/app/Manga.php @@ -16,7 +16,7 @@ class Manga extends JikanApiSearchableModel { use HasFactory, MediaFilters, FilteredByLetter; - protected array $filters = ["order_by", "status", "type", "sort", "max_score", "min_score", "score", "start_date", "end_date", "magazine", "magazines", "letter"]; + protected array $filters = ["order_by", "status", "type", "sort", "max_score", "min_score", "score", "start_date", "end_date", "magazine", "magazines", "letter", "genres", "genres_exclude"]; /** * The attributes that are mass assignable. @@ -58,15 +58,15 @@ class Manga extends JikanApiSearchableModel } /** @noinspection PhpUnused */ - public function filterByStartDate(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, CarbonImmutable $date): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder + public function filterByStartDate(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, CarbonImmutable $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder { - return $query->where("published.from", $date->setTime(0, 0)->toAtomString()); + return $query->where("published.from", ">=", $value->setTime(0, 0)->toAtomString()); } /** @noinspection PhpUnused */ - public function filterByEndDate(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, CarbonImmutable $date): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder + public function filterByEndDate(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, CarbonImmutable $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder { - return $query->where("published.to", $date->setTime(0, 0)->toAtomString()); + return $query->where("published.to", "<=", $value->setTime(0, 0)->toAtomString()); } public function filterByMagazine(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, string $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 9679913..5624779 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -15,7 +15,6 @@ use App\Contracts\Repository; use App\Contracts\RequestHandler; use App\Contracts\UnitOfWork; use App\Contracts\UserRepository; -use App\Http\Middleware\EndpointCacheTtlMiddleware; use App\Macros\CollectionOffsetGetFirst; use App\Macros\ResponseJikanCacheFlags; use App\Macros\To2dArrayWithDottedKeys; @@ -30,6 +29,7 @@ use App\Repositories\DefaultPeopleRepository; use App\Repositories\DefaultProducerRepository; use App\Repositories\DefaultUserRepository; use App\Repositories\MangaGenresRepository; +use App\Services\DefaultBuilderPaginatorService; use App\Services\DefaultCachedScraperService; use App\Services\DefaultQueryBuilderService; use App\Services\DefaultScoutSearchService; @@ -82,11 +82,7 @@ class AppServiceProvider extends ServiceProvider // cache options class is used to share the request scope level cache settings $this->app->singleton(CacheOptions::class); $this->app->singleton(CachedScraperService::class, DefaultCachedScraperService::class); - if ($this->getSearchIndexesEnabledConfig($this->app)) { - $this->app->bind(QueryBuilderPaginatorService::class, ScoutBuilderPaginatorService::class); - } else { - $this->app->bind(QueryBuilderPaginatorService::class, EloquentBuilderPaginatorService::class); - } + $this->app->bind(QueryBuilderPaginatorService::class, DefaultBuilderPaginatorService::class); $this->registerModelRepositories(); $this->registerRequestHandlers(); } diff --git a/app/Rules/Attributes/EnumValidation.php b/app/Rules/Attributes/EnumValidation.php new file mode 100644 index 0000000..1a12fe9 --- /dev/null +++ b/app/Rules/Attributes/EnumValidation.php @@ -0,0 +1,16 @@ +scoutBuilderPaginatorService->paginate($builder, $limit, $page); + } + + return $this->eloquentBuilderPaginatorService->paginate($builder, $limit, $page); + } +} diff --git a/app/Services/ScoutBuilderPaginatorService.php b/app/Services/ScoutBuilderPaginatorService.php index 5fd45ac..9177c18 100644 --- a/app/Services/ScoutBuilderPaginatorService.php +++ b/app/Services/ScoutBuilderPaginatorService.php @@ -5,7 +5,7 @@ namespace App\Services; use App\Concerns\ResolvesPaginatorParams; use Illuminate\Contracts\Pagination\LengthAwarePaginator; -class ScoutBuilderPaginatorService implements QueryBuilderPaginatorService +final class ScoutBuilderPaginatorService implements QueryBuilderPaginatorService { use ResolvesPaginatorParams; diff --git a/database/factories/JikanMediaModelFactory.php b/database/factories/JikanMediaModelFactory.php index 44a8575..df5657b 100644 --- a/database/factories/JikanMediaModelFactory.php +++ b/database/factories/JikanMediaModelFactory.php @@ -3,7 +3,9 @@ namespace Database\Factories; use App\CarbonDateRange; -use App\Http\QueryBuilder\AnimeSearchQueryBuilder; +use App\Enums\AnimeRatingEnum; +use App\Enums\AnimeTypeEnum; +use App\Enums\MangaTypeEnum; use App\Testing\JikanDataGenerator; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Carbon; @@ -88,7 +90,7 @@ abstract class JikanMediaModelFactory extends JikanModelFactory implements Media return fn($i) => $randomDate->copy()->addDays($i); })()), "rating" => ((function() { - $validRatingItems = array_values(AnimeSearchQueryBuilder::MAP_RATING); + $validRatingItems = AnimeRatingEnum::toValues(); $validRatingItemsCount = count($validRatingItems); return fn($i) => $validRatingItems[$i % $validRatingItemsCount]; })()), @@ -98,7 +100,7 @@ abstract class JikanMediaModelFactory extends JikanModelFactory implements Media return fn($i) => $alphabet[$i % $alphabetCount]; })()), "type" => ((function() { - $types = array_values(AnimeSearchQueryBuilder::MAP_TYPES); + $types = $this->modelName() === "App\Anime" ? AnimeTypeEnum::toValues() : MangaTypeEnum::toValues(); $typesCount = count($types); return fn($i) => $types[$i % $typesCount]; })()), diff --git a/tests/Integration/AnimeSearchEndpointTest.php b/tests/Integration/AnimeSearchEndpointTest.php index 6999779..e29b27f 100644 --- a/tests/Integration/AnimeSearchEndpointTest.php +++ b/tests/Integration/AnimeSearchEndpointTest.php @@ -53,11 +53,19 @@ class AnimeSearchEndpointTest extends TestCase ]; } + public function partialDatesParameterProvider(): array + { + return [ + [["start_date" => "2012"]], + [["start_date" => "2012-05"]], + [["end_date" => "2015"]], + [["end_date" => "2015-05"]] + ]; + } + public function startDatesParameterProvider(): array { return [ - [["start_date" => "2022"]], - [["start_date" => "2012-05"]], [["start_date" => "2012-05-12"]], [["start_date" => "2012-04-01"]], [["start_date" => "2012-04-28"]], @@ -68,8 +76,6 @@ class AnimeSearchEndpointTest extends TestCase public function endDatesParameterProvider(): array { return [ - [["end_date" => "2022"]], - [["end_date" => "2012-05"]], [["end_date" => "2012-05-12"]], [["end_date" => "2012-05-12", "page" => 1]], ]; @@ -78,8 +84,6 @@ class AnimeSearchEndpointTest extends TestCase public function startAndEndDatesParameterProvider(): array { return [ - [["start_date" => "2021", "end_date" => "2022"]], - [["start_date" => "2021-01", "end_date" => "2021-02"]], [["start_date" => "2021-01-01", "end_date" => "2021-03-22"]], [["start_date" => "2021-01-01", "end_date" => "2021-03-22", "page" => 1]], ]; @@ -127,10 +131,10 @@ class AnimeSearchEndpointTest extends TestCase public function invalidScoreParameterProvider(): array { return [ - [["max_score" => "634638"], 15], - [["min_score" => "673473"], 0], - [["max_score" => "72344", "min_score" => "3532325"], 0], - [["max_score" => 1, "min_score" => 5], 0], + [["max_score" => "634638"]], + [["min_score" => "673473"]], + [["max_score" => "72344", "min_score" => "3532325"]], + [["max_score" => 1, "min_score" => 5]], ]; } @@ -198,15 +202,26 @@ class AnimeSearchEndpointTest extends TestCase /** * @dataProvider emptyDateRangeProvider */ - public function testSearchByEmptyDatesShouldDoNothing($params) + public function testSearchByEmptyDatesShouldRaiseValidationError($params) { $this->generateFiveSpecificAndTenRandomElementsInDb($params); $content = $this->getJsonResponse($params); - $this->seeStatusCode(200); - $this->assertPaginationData(15); - $this->assertCount(15, $content["data"]); + $this->seeStatusCode(400); + $this->assertEquals("ValidationException", data_get($content, "type")); + } + + /** + * @dataProvider partialDatesParameterProvider + */ + public function testSearchByStartDateShouldRaiseIfPartialDate($params) + { + $this->generateFiveSpecificAndTenRandomElementsInDb($params); + $content = $this->getJsonResponse($params); + + $this->seeStatusCode(400); + $this->assertEquals("ValidationException", data_get($content, "type")); } /** @@ -345,25 +360,20 @@ class AnimeSearchEndpointTest extends TestCase $this->generateFiveSpecificAndTenRandomElementsInDb($params); $content = $this->getJsonResponse($params); - $this->seeStatusCode(200); - $this->assertPaginationData(15); - $this->assertIsArray($content["data"]); - // it should return all, and disregard the gibberish filter - $this->assertCount(15, $content["data"]); + $this->seeStatusCode(400); + $this->assertEquals("ValidationException", data_get($content, "type")); } /** * @dataProvider invalidScoreParameterProvider */ - public function testSearchByInvalidScoreParameters($params, $expectedCount) + public function testSearchByInvalidScoreParameters($params) { $this->generateFiveSpecificAndTenRandomElementsInDb($params); $content = $this->getJsonResponse($params); - $this->seeStatusCode(200); - $this->assertPaginationData($expectedCount); - $this->assertIsArray($content["data"]); - $this->assertCount($expectedCount, $content["data"]); + $this->seeStatusCode(400); + $this->assertEquals("ValidationException", data_get($content, "type")); } /** @@ -489,10 +499,8 @@ class AnimeSearchEndpointTest extends TestCase $this->generateFiveSpecificAndTenRandomElementsInDb($params); $content = $this->getJsonResponse($params); - $this->seeStatusCode(200); - $this->assertPaginationData($expectedCount); - $this->assertIsArray($content["data"]); - $this->assertCount($expectedCount, $content["data"]); + $this->seeStatusCode(400); + $this->assertEquals("ValidationException", data_get($content, "type")); } /** @@ -511,7 +519,6 @@ class AnimeSearchEndpointTest extends TestCase public function testSearchByInvalidLetterParameter() { - $expectedCount = 0; $this->generateFiveSpecificAndTenRandomElementsInDb([ "letter" => "a" ]); @@ -519,10 +526,8 @@ class AnimeSearchEndpointTest extends TestCase "letter" => "asd" ]); - $this->seeStatusCode(200); - $this->assertPaginationData($expectedCount); - $this->assertIsArray($content["data"]); - $this->assertCount($expectedCount, $content["data"]); + $this->seeStatusCode(400); + $this->assertEquals("ValidationException", data_get($content, "type")); } public function testTypeSenseSearchPagination() diff --git a/tests/Integration/MangaSearchEndpointTest.php b/tests/Integration/MangaSearchEndpointTest.php index 64e51f2..c0a2ff9 100644 --- a/tests/Integration/MangaSearchEndpointTest.php +++ b/tests/Integration/MangaSearchEndpointTest.php @@ -55,11 +55,19 @@ class MangaSearchEndpointTest extends TestCase ]; } + public function partialDatesParameterProvider(): array + { + return [ + [["start_date" => "2012"]], + [["start_date" => "2012-05"]], + [["end_date" => "2015"]], + [["end_date" => "2015-05"]] + ]; + } + public function startDatesParameterProvider(): array { return [ - [["start_date" => "2022"]], - [["start_date" => "2012-05"]], [["start_date" => "2012-05-12"]], [["start_date" => "2012-04-01"]], [["start_date" => "2012-04-28"]], @@ -70,8 +78,6 @@ class MangaSearchEndpointTest extends TestCase public function endDatesParameterProvider(): array { return [ - [["end_date" => "2022"]], - [["end_date" => "2012-05"]], [["end_date" => "2012-05-12"]], [["end_date" => "2012-05-12", "page" => 1]], ]; @@ -80,8 +86,6 @@ class MangaSearchEndpointTest extends TestCase public function startAndEndDatesParameterProvider(): array { return [ - [["start_date" => "2021", "end_date" => "2022"]], - [["start_date" => "2021-01", "end_date" => "2021-02"]], [["start_date" => "2021-01-01", "end_date" => "2021-03-22"]], [["start_date" => "2021-01-01", "end_date" => "2021-03-22", "page" => 1]], ]; @@ -123,10 +127,10 @@ class MangaSearchEndpointTest extends TestCase public function invalidScoreParameterProvider(): array { return [ - [["max_score" => "634638"], 15], - [["min_score" => "673473"], 0], - [["max_score" => "72344", "min_score" => "3532325"], 0], - [["max_score" => 1, "min_score" => 5], 0], + [["max_score" => "634638"]], + [["min_score" => "673473"]], + [["max_score" => "72344", "min_score" => "3532325"]], + [["max_score" => 1, "min_score" => 5]], ]; } @@ -185,17 +189,29 @@ class MangaSearchEndpointTest extends TestCase /** * @dataProvider emptyDateRangeProvider */ - public function testSearchByEmptyDatesShouldDoNothing($params) + public function testSearchByEmptyDatesShouldRaiseValidationError($params) { $this->generateFiveSpecificAndTenRandomElementsInDb($params); $content = $this->getJsonResponse($params); - $this->seeStatusCode(200); - $this->assertPaginationData(15); - $this->assertCount(15, $content["data"]); + $this->seeStatusCode(400); + $this->assertEquals("ValidationException", data_get($content, "type")); } + /** + * @dataProvider partialDatesParameterProvider + */ + public function testSearchByStartDateShouldRaiseIfPartialDate($params) + { + $this->generateFiveSpecificAndTenRandomElementsInDb($params); + $content = $this->getJsonResponse($params); + + $this->seeStatusCode(400); + $this->assertEquals("ValidationException", data_get($content, "type")); + } + + /** * @dataProvider startDatesParameterProvider */ @@ -264,7 +280,10 @@ class MangaSearchEndpointTest extends TestCase // this is mainly focused on mongodb features $startDate = "2015-02-01"; $carbonStartDate = Carbon::parse($startDate); - Manga::factory(5)->create(); + $fo = Manga::factory(5); + $fo->create($fo->serializeStateDefinition([ + "published" => new CarbonDateRange(Carbon::parse("2002-01-01"), Carbon::parse("2002-02-02")) + ])); $f = Manga::factory(1); $f->create($f->serializeStateDefinition([ "published" => new CarbonDateRange($carbonStartDate, null) @@ -282,12 +301,15 @@ class MangaSearchEndpointTest extends TestCase public function testSearchWithEndDateEqualToParam() { // we test here whether the filtering works by start date - // if the start date parameter's value exactly matches + // if the end date parameter's value exactly matches // with one item in the database. // this is mainly focused on mongodb features $endDate = "2015-03-28"; $carbonEndDate = Carbon::parse($endDate); - Manga::factory(5)->create(); + $fo = Manga::factory(5); + $fo->create($fo->serializeStateDefinition([ + "published" => new CarbonDateRange(Carbon::parse("2022-01-01"), Carbon::parse("2022-02-02")) + ])); $f = Manga::factory(1); $f->create($f->serializeStateDefinition([ "published" => new CarbonDateRange(Carbon::parse("2015-01-05"), $carbonEndDate) @@ -326,25 +348,20 @@ class MangaSearchEndpointTest extends TestCase $this->generateFiveSpecificAndTenRandomElementsInDb($params); $content = $this->getJsonResponse($params); - $this->seeStatusCode(200); - $this->assertPaginationData(15); - $this->assertIsArray($content["data"]); - // it should return all, and disregard the gibberish filter - $this->assertCount(15, $content["data"]); + $this->seeStatusCode(400); + $this->assertEquals("ValidationException", data_get($content, "type")); } /** * @dataProvider invalidScoreParameterProvider */ - public function testSearchByInvalidScoreParameters($params, $expectedCount) + public function testSearchByInvalidScoreParameters($params) { $this->generateFiveSpecificAndTenRandomElementsInDb($params); $content = $this->getJsonResponse($params); - $this->seeStatusCode(200); - $this->assertPaginationData($expectedCount); - $this->assertIsArray($content["data"]); - $this->assertCount($expectedCount, $content["data"]); + $this->seeStatusCode(400); + $this->assertEquals("ValidationException", data_get($content, "type")); } /** @@ -487,10 +504,8 @@ class MangaSearchEndpointTest extends TestCase "letter" => "asd" ]); - $this->seeStatusCode(200); - $this->assertPaginationData($expectedCount); - $this->assertIsArray($content["data"]); - $this->assertCount($expectedCount, $content["data"]); + $this->seeStatusCode(400); + $this->assertEquals("ValidationException", data_get($content, "type")); } public function testTypeSenseSearchPagination()