mirror of
https://github.com/AnimeThemes/animethemes-server.git
synced 2026-07-11 01:24:46 +02:00
tests(graphql): added non-eloquent queries tests (#1004)
This commit is contained in:
@@ -4,13 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\GraphQL;
|
||||
|
||||
use Exception;
|
||||
use GraphQL\Error\ClientAware;
|
||||
use GraphQL\Error\Error;
|
||||
|
||||
/**
|
||||
* Thrown when query is not allowed.
|
||||
*/
|
||||
class ClientForbiddenException extends Exception implements ClientAware
|
||||
class ClientForbiddenException extends Error
|
||||
{
|
||||
public function isClientSafe(): bool
|
||||
{
|
||||
|
||||
@@ -4,13 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\GraphQL;
|
||||
|
||||
use Exception;
|
||||
use GraphQL\Error\ClientAware;
|
||||
use GraphQL\Error\Error;
|
||||
|
||||
/**
|
||||
* Thrown when client arguments are missing or wrong.
|
||||
*/
|
||||
class ClientValidationException extends Exception implements ClientAware
|
||||
class ClientValidationException extends Error
|
||||
{
|
||||
public function isClientSafe(): bool
|
||||
{
|
||||
|
||||
@@ -8,10 +8,11 @@ use App\Actions\Http\Api\DestroyAction;
|
||||
use App\Actions\Http\Api\StoreAction;
|
||||
use App\Actions\Http\Api\UpdateAction;
|
||||
use App\GraphQL\Schema\Mutations\BaseMutation;
|
||||
use Dotenv\Exception\ValidationException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Rebing\GraphQL\Error\ValidationError;
|
||||
|
||||
/**
|
||||
* @template TModel of \Illuminate\Database\Eloquent\Model
|
||||
@@ -37,13 +38,19 @@ abstract class BaseController
|
||||
* @param class-string<BaseMutation> $mutation
|
||||
* @return array<string, mixed>
|
||||
*
|
||||
* @throws ValidationException
|
||||
* @throws ValidationError
|
||||
*/
|
||||
public function validated(array $args, string $mutation): array
|
||||
{
|
||||
$mutationInstance = App::make($mutation);
|
||||
|
||||
$validated = Validator::make($args, $mutationInstance->rulesForValidation($args))->validated();
|
||||
$validator = Validator::make($args, $mutationInstance->rulesForValidation($args));
|
||||
|
||||
try {
|
||||
$validated = $validator->validated();
|
||||
} catch (ValidationException $e) {
|
||||
throw new ValidationError($e->getMessage(), $validator);
|
||||
}
|
||||
|
||||
return [
|
||||
...$validated,
|
||||
|
||||
@@ -21,7 +21,11 @@ class SyncExternalProfileController extends BaseController
|
||||
/** @var ExternalProfile $profile */
|
||||
$profile = Arr::pull($args, self::MODEL);
|
||||
|
||||
throw_unless($profile->canBeSynced(), ClientForbiddenException::class, 'This external profile cannot be synced at the moment.');
|
||||
throw_unless(
|
||||
$profile->canBeSynced(),
|
||||
ClientForbiddenException::class,
|
||||
'This external profile cannot be synced at the moment.'
|
||||
);
|
||||
|
||||
$profile->dispatchSyncJob();
|
||||
|
||||
|
||||
@@ -35,8 +35,11 @@ class AnimeYearsController extends BaseController
|
||||
$fieldSelection = $resolveInfo->getFieldSelection(1);
|
||||
|
||||
// Restrict 'animes' field to a unique year.
|
||||
throw_if(($year === null || count($year) > 1)
|
||||
&& (Arr::get($fieldSelection, 'season.anime') || Arr::get($fieldSelection, 'seasons.anime')), ClientValidationException::class, "Please provide a unique 'year' argument to query the animes field.");
|
||||
throw_if(
|
||||
($year === null || count($year) > 1) && (Arr::get($fieldSelection, 'season.anime') || Arr::get($fieldSelection, 'seasons.anime')),
|
||||
ClientValidationException::class,
|
||||
"Please provide a unique 'year' argument to query the animes field."
|
||||
);
|
||||
|
||||
return Anime::query()
|
||||
->distinct([Anime::ATTRIBUTE_YEAR, Anime::ATTRIBUTE_SEASON])
|
||||
|
||||
@@ -79,7 +79,7 @@ class FindAnimeByExternalSiteQuery extends BaseQuery
|
||||
|
||||
throw_if(is_null($externalId) && is_null($link), ClientValidationException::class, 'At least "id" or "link" is required.');
|
||||
|
||||
throw_if(count($externalId) > 100, ClientValidationException::class, 'The "Id" parameter cannot contain more than 100 integer values.');
|
||||
throw_if($externalId !== null && count($externalId) > 100, ClientValidationException::class, 'The "Id" parameter cannot contain more than 100 integer values.');
|
||||
|
||||
$builder->whereRelation(Anime::RELATION_RESOURCES, function (Builder $query) use ($site, $externalId, $link): void {
|
||||
$query->where(ExternalResource::ATTRIBUTE_SITE, $site);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
|
||||
use function Pest\Laravel\post;
|
||||
|
||||
test('current featured theme', function () {
|
||||
FeaturedTheme::factory()
|
||||
->sequence(fn () => [
|
||||
FeaturedTheme::ATTRIBUTE_START_AT => now()->addDays(fake()->numberBetween(1, 10)),
|
||||
FeaturedTheme::ATTRIBUTE_END_AT => now()->addDays(fake()->numberBetween(11, 20)),
|
||||
])
|
||||
->count(fake()->randomDigitNotNull())
|
||||
->create();
|
||||
|
||||
$featuredTheme = FeaturedTheme::factory()->create();
|
||||
|
||||
$response = post(route('graphql'), [
|
||||
'query' => '
|
||||
query {
|
||||
currentfeaturedtheme {
|
||||
id
|
||||
}
|
||||
}
|
||||
',
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'currentfeaturedtheme' => [
|
||||
'id' => $featuredTheme->getKey(),
|
||||
],
|
||||
],
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\Auth\User;
|
||||
|
||||
use function Pest\Laravel\actingAs;
|
||||
use function Pest\Laravel\post;
|
||||
|
||||
test('unauthenticated returns null', function () {
|
||||
$response = post(route('graphql'), [
|
||||
'query' => '
|
||||
query {
|
||||
me {
|
||||
id
|
||||
}
|
||||
}
|
||||
',
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'me' => null,
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('authenticated returns user', function () {
|
||||
$user = User::factory()->createOne();
|
||||
|
||||
actingAs($user);
|
||||
|
||||
$response = post(route('graphql'), [
|
||||
'query' => '
|
||||
query {
|
||||
me {
|
||||
id
|
||||
}
|
||||
}
|
||||
',
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'me' => [
|
||||
'id' => $user->getKey(),
|
||||
],
|
||||
],
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
use function Pest\Laravel\post;
|
||||
|
||||
test('searches attributes', function () {
|
||||
Config::set('scout.driver', 'collection');
|
||||
|
||||
$response = post(route('graphql'), [
|
||||
'query' => '
|
||||
query($search: String!) {
|
||||
search(search: $search) {
|
||||
anime {
|
||||
id
|
||||
}
|
||||
artists {
|
||||
id
|
||||
}
|
||||
animethemes {
|
||||
id
|
||||
}
|
||||
playlists {
|
||||
id
|
||||
}
|
||||
series {
|
||||
id
|
||||
}
|
||||
songs {
|
||||
id
|
||||
}
|
||||
studios {
|
||||
id
|
||||
}
|
||||
videos {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
',
|
||||
'variables' => [
|
||||
'search' => fake()->word(),
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertJsonStructure([
|
||||
'data' => [
|
||||
'search' => [
|
||||
'anime',
|
||||
'artists',
|
||||
'animethemes',
|
||||
'playlists',
|
||||
'series',
|
||||
'songs',
|
||||
'studios',
|
||||
'videos',
|
||||
],
|
||||
],
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\Wiki\Anime;
|
||||
|
||||
use function Pest\Laravel\post;
|
||||
|
||||
test('query season & seasons field', function () {
|
||||
$animes = Anime::factory()
|
||||
->count(fake()->randomDigitNotNull())
|
||||
->create();
|
||||
|
||||
$response = post(route('graphql'), [
|
||||
'query' => '
|
||||
query($season: AnimeSeason!) {
|
||||
animeyears {
|
||||
year
|
||||
season(season: $season) {
|
||||
season
|
||||
}
|
||||
seasons {
|
||||
season
|
||||
}
|
||||
}
|
||||
}
|
||||
',
|
||||
'variables' => [
|
||||
'season' => $animes->random()->getAttribute(Anime::ATTRIBUTE_SEASON)->name,
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
|
||||
$response->assertJsonStructure([
|
||||
'data' => [
|
||||
'animeyears' => [[
|
||||
'year',
|
||||
'season' => [
|
||||
'season',
|
||||
],
|
||||
]],
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('fails query season anime field without year', function () {
|
||||
$animes = Anime::factory()
|
||||
->count(fake()->randomDigitNotNull())
|
||||
->create();
|
||||
|
||||
$response = post(route('graphql'), [
|
||||
'query' => '
|
||||
query($season: AnimeSeason!) {
|
||||
animeyears {
|
||||
year
|
||||
season(season: $season) {
|
||||
season
|
||||
anime {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
seasons {
|
||||
season {
|
||||
anime {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
',
|
||||
'variables' => [
|
||||
'season' => $animes->random()->getAttribute(Anime::ATTRIBUTE_SEASON)->name,
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonCount(1, 'errors');
|
||||
$response->assertJsonStructure([
|
||||
'errors' => [['message']],
|
||||
]);
|
||||
});
|
||||
|
||||
test('query season anime field with year', function () {
|
||||
$animes = Anime::factory()
|
||||
->count(fake()->randomDigitNotNull())
|
||||
->create();
|
||||
|
||||
$random = $animes->random();
|
||||
|
||||
$response = post(route('graphql'), [
|
||||
'query' => '
|
||||
query($season: AnimeSeason!, $year: [Int!]) {
|
||||
animeyears(year: $year) {
|
||||
year
|
||||
season(season: $season) {
|
||||
season
|
||||
anime {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
seasons {
|
||||
season
|
||||
anime {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
',
|
||||
'variables' => [
|
||||
'season' => $random->getAttribute(Anime::ATTRIBUTE_SEASON)->name,
|
||||
'year' => $random->getAttribute(Anime::ATTRIBUTE_YEAR),
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonStructure([
|
||||
'data' => [
|
||||
'animeyears' => [[
|
||||
'year',
|
||||
'season' => [
|
||||
'season',
|
||||
'anime' => [
|
||||
'data' => [['id']],
|
||||
],
|
||||
],
|
||||
'seasons' => [[
|
||||
'season',
|
||||
'anime' => [
|
||||
'data' => [['id']],
|
||||
],
|
||||
]],
|
||||
]],
|
||||
],
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\Models\Wiki\ResourceSite;
|
||||
use App\Models\Wiki\ExternalResource;
|
||||
use App\Pivots\Morph\Resourceable;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use function Pest\Laravel\post;
|
||||
|
||||
test('fails without id or link', function () {
|
||||
$resourceSite = Arr::random(ResourceSite::cases());
|
||||
|
||||
$response = post(route('graphql'), [
|
||||
'query' => '
|
||||
query($site: ResourceSite!) {
|
||||
findAnimeByExternalSite(site: $site) {
|
||||
id
|
||||
}
|
||||
}
|
||||
',
|
||||
'variables' => [
|
||||
'site' => $resourceSite->name,
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonStructure([
|
||||
'errors' => [['message']],
|
||||
]);
|
||||
});
|
||||
|
||||
test('fails with for than 100 ids', function () {
|
||||
$resourceSite = Arr::random(ResourceSite::cases());
|
||||
|
||||
$response = post(route('graphql'), [
|
||||
'query' => '
|
||||
query($site: ResourceSite!, ids: [Int!]) {
|
||||
findAnimeByExternalSite(site: $site, id: $ids) {
|
||||
id
|
||||
}
|
||||
}
|
||||
',
|
||||
'variables' => [
|
||||
'site' => $resourceSite->name,
|
||||
'ids' => Collection::times(101, fn (int $int) => $int + 1)->toArray(),
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonStructure([
|
||||
'errors' => [['message']],
|
||||
]);
|
||||
});
|
||||
|
||||
test('passes with id', function () {
|
||||
Resourceable::factory()
|
||||
->for(
|
||||
ExternalResource::factory()->create([
|
||||
ExternalResource::ATTRIBUTE_EXTERNAL_ID => $externalId = fake()->randomDigitNotNull(),
|
||||
]),
|
||||
Resourceable::RELATION_RESOURCE
|
||||
)
|
||||
->forAnime()
|
||||
->create();
|
||||
|
||||
$resourceSite = ExternalResource::query()
|
||||
->first()
|
||||
->getAttribute(ExternalResource::ATTRIBUTE_SITE);
|
||||
|
||||
$response = post(route('graphql'), [
|
||||
'query' => '
|
||||
query($site: ResourceSite!, $externalId: [Int!]) {
|
||||
findAnimeByExternalSite(site: $site, id: $externalId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
',
|
||||
'variables' => [
|
||||
'site' => $resourceSite->name,
|
||||
'externalId' => $externalId,
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertExactJsonStructure([
|
||||
'data' => [
|
||||
'findAnimeByExternalSite' => [['id']],
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('passes with link', function () {
|
||||
Resourceable::factory()
|
||||
->for(
|
||||
ExternalResource::factory()->create([
|
||||
ExternalResource::ATTRIBUTE_LINK => $link = fake()->url(),
|
||||
]),
|
||||
Resourceable::RELATION_RESOURCE
|
||||
)
|
||||
->forAnime()
|
||||
->create();
|
||||
|
||||
$resourceSite = ExternalResource::query()
|
||||
->first()
|
||||
->getAttribute(ExternalResource::ATTRIBUTE_SITE);
|
||||
|
||||
$response = post(route('graphql'), [
|
||||
'query' => '
|
||||
query($site: ResourceSite!, $link: String) {
|
||||
findAnimeByExternalSite(site: $site, link: $link) {
|
||||
id
|
||||
}
|
||||
}
|
||||
',
|
||||
'variables' => [
|
||||
'site' => $resourceSite->name,
|
||||
'link' => (string) $link,
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonStructure([
|
||||
'data' => [
|
||||
'findAnimeByExternalSite' =>[['id']],
|
||||
],
|
||||
]);
|
||||
});
|
||||
Reference in New Issue
Block a user