tests(graphql): added non-eloquent queries tests (#1004)

This commit is contained in:
Kyrch
2025-11-14 14:55:50 -03:00
committed by GitHub
parent 0f290b1677
commit 4a6739b8cd
11 changed files with 453 additions and 13 deletions
@@ -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
{
+10 -3
View File
@@ -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']],
],
]);
});