tests: add missing model tests & sort tests (#1082)

This commit is contained in:
Kyrch
2026-02-05 19:24:38 -03:00
committed by GitHub
parent 2257d4b6ed
commit 5817636003
19 changed files with 323 additions and 60 deletions
+3 -4
View File
@@ -12,7 +12,6 @@ use App\GraphQL\Argument\SortArgument;
use App\GraphQL\Criteria\Sort\RelationSortCriteria;
use App\GraphQL\Criteria\Sort\SortCriteria;
use App\GraphQL\Schema\Enums\SortableColumns;
use App\GraphQL\Schema\Fields\StringField;
use App\GraphQL\Schema\Types\BaseType;
use App\Rules\GraphQL\Argument\FirstArgumentRule;
use App\Search\Criteria;
@@ -76,15 +75,15 @@ class IndexAction
/** @var SortCriteria $criterion */
$criterion = Arr::get($criteria, $sort);
$column = $criterion->getField()->getColumn();
$column = $criterion->getSort()->getColumn();
$direction = $criterion->getDirection();
$isString = $criterion->getField() instanceof StringField;
$isString = $criterion->isStringField();
if ($criterion instanceof RelationSortCriteria) {
$sortsRaw[$column] = [
'direction' => $direction->value,
'isString' => $isString,
'relation' => $criterion->relation,
'relation' => $criterion->getRelation(),
];
} else {
$sortsRaw[$column] = [
@@ -13,7 +13,7 @@ class FieldSortCriteria extends SortCriteria
*/
public function sort(Builder $builder): Builder
{
$sort = $this->field->getSort();
$sort = $this->getSort();
$column = $sort->shouldQualifyColumn()
? $builder->qualifyColumn($sort->getColumn())
+12 -10
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace App\GraphQL\Criteria\Sort;
use App\Contracts\GraphQL\Fields\SortableField;
use App\Enums\GraphQL\Sort\SortDirection;
use App\GraphQL\Schema\Fields\Field;
use App\GraphQL\Sort\Sort;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Str;
@@ -14,11 +13,12 @@ use Illuminate\Support\Str;
class PivotSortCriteria extends SortCriteria
{
public function __construct(
protected Field&SortableField $field,
protected Sort $sort,
protected SortDirection $direction = SortDirection::ASC,
protected ?BelongsToMany $relation = null,
protected bool $isStringField = false,
) {
parent::__construct($field, $direction);
parent::__construct($sort, $direction, $isStringField);
}
/**
@@ -28,12 +28,12 @@ class PivotSortCriteria extends SortCriteria
*/
public function __toString(): string
{
$name = Str::of($this->field->getName())
$name = Str::of($this->getSort()->getName())
->snake()
->upper()
->prepend('PIVOT_');
return (string) match ($this->direction) {
return (string) match ($this->getDirection()) {
SortDirection::ASC => $name,
SortDirection::DESC => $name->append('_DESC'),
};
@@ -44,10 +44,12 @@ class PivotSortCriteria extends SortCriteria
*/
public function sort(Builder $builder): Builder
{
$column = $this->field->getSort()->shouldQualifyColumn()
? $this->relation?->qualifyPivotColumn($this->field->getColumn())
: $this->field->getColumn();
$sort = $this->getSort();
return $builder->orderBy($column, $this->direction->value);
$column = $sort->shouldQualifyColumn()
? $this->relation?->qualifyPivotColumn($sort->getColumn())
: $sort->getColumn();
return $builder->orderBy($column, $this->getDirection()->value);
}
}
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\GraphQL\Criteria\Sort;
use App\GraphQL\Schema\Fields\Base\CreatedAtField;
use App\GraphQL\Sort\Sort;
use Illuminate\Database\Eloquent\Builder;
class RandomSortCriteria extends SortCriteria
@@ -12,7 +12,7 @@ class RandomSortCriteria extends SortCriteria
public function __construct()
{
// Random Sort doesn't need a field so we fake it.
parent::__construct(new CreatedAtField);
parent::__construct(new Sort(''));
}
/**
@@ -4,20 +4,25 @@ declare(strict_types=1);
namespace App\GraphQL\Criteria\Sort;
use App\Contracts\GraphQL\Fields\SortableField;
use App\Enums\GraphQL\Sort\SortDirection;
use App\GraphQL\Schema\Fields\Field;
use App\GraphQL\Sort\Sort;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
class RelationSortCriteria extends SortCriteria
{
public function __construct(
protected Field&SortableField $field,
public string $relation,
protected SortDirection $direction = SortDirection::ASC
protected Sort $sort,
protected string $relation,
protected SortDirection $direction = SortDirection::ASC,
protected bool $isStringField = false,
) {
parent::__construct($field, $direction);
parent::__construct($sort, $direction, $isStringField);
}
public function getRelation(): string
{
return $this->relation;
}
/**
@@ -27,9 +32,9 @@ class RelationSortCriteria extends SortCriteria
*/
public function __toString(): string
{
$name = Str::of($this->relation)
$name = Str::of($this->getRelation())
->append('_')
->append($this->field->getName())
->append($this->getSort()->getName())
->snake()
->upper();
@@ -44,8 +49,8 @@ class RelationSortCriteria extends SortCriteria
*/
public function sort(Builder $builder): Builder
{
$column = $this->field->getColumn();
$column = $this->getSort()->getColumn();
return $builder->orderBy("{$this->relation}_$column", $this->direction->value);
return $builder->orderBy("{$this->getRelation()}_$column", $this->direction->value);
}
}
+12 -7
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace App\GraphQL\Criteria\Sort;
use App\Contracts\GraphQL\Fields\SortableField;
use App\Enums\GraphQL\Sort\SortDirection;
use App\GraphQL\Schema\Fields\Field;
use App\GraphQL\Sort\Sort;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
use Stringable;
@@ -14,13 +13,14 @@ use Stringable;
abstract class SortCriteria implements Stringable
{
public function __construct(
protected Field&SortableField $field,
protected SortDirection $direction = SortDirection::ASC
protected Sort $sort,
protected SortDirection $direction = SortDirection::ASC,
protected bool $isStringField = false,
) {}
public function getField(): Field&SortableField
public function getSort(): Sort
{
return $this->field;
return $this->sort;
}
public function getDirection(): SortDirection
@@ -28,6 +28,11 @@ abstract class SortCriteria implements Stringable
return $this->direction;
}
public function isStringField(): bool
{
return $this->isStringField;
}
/**
* Build the enum case for a direction.
* Template: {FIELD_NAME}.
@@ -35,7 +40,7 @@ abstract class SortCriteria implements Stringable
*/
public function __toString(): string
{
$name = Str::of($this->field->getName())
$name = Str::of($this->getSort()->getName())
->snake()
->upper();
+9 -8
View File
@@ -14,6 +14,7 @@ use App\GraphQL\Criteria\Sort\SortCriteria;
use App\GraphQL\Schema\Fields\Field;
use App\GraphQL\Schema\Fields\Relations\BelongsToRelation;
use App\GraphQL\Schema\Fields\Relations\Relation;
use App\GraphQL\Schema\Fields\StringField;
use App\GraphQL\Schema\Types\BaseType;
use App\GraphQL\Schema\Types\Pivot\PivotType;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -69,9 +70,9 @@ class SortableColumns extends EnumType
private function getFieldSortCriteria(): Collection
{
return $this->getSortableFields()
->map(fn (Field $field): array => [
new FieldSortCriteria($field),
new FieldSortCriteria($field, SortDirection::DESC),
->map(fn (Field&SortableField $field): array => [
new FieldSortCriteria($field->getSort(), SortDirection::ASC, $field instanceof StringField),
new FieldSortCriteria($field->getSort(), SortDirection::DESC, $field instanceof StringField),
])
->flatten();
}
@@ -80,9 +81,9 @@ class SortableColumns extends EnumType
{
return collect($this->pivotType?->fieldClasses() ?? [])
->filter(fn (Field $field): bool => $field instanceof SortableField)
->map(fn (Field $field): array => [
new PivotSortCriteria($field, SortDirection::ASC, $this->relation),
new PivotSortCriteria($field, SortDirection::DESC, $this->relation),
->map(fn (Field&SortableField $field): array => [
new PivotSortCriteria($field->getSort(), SortDirection::ASC, $this->relation, $field instanceof StringField),
new PivotSortCriteria($field->getSort(), SortDirection::DESC, $this->relation, $field instanceof StringField),
])
->flatten();
}
@@ -91,8 +92,8 @@ class SortableColumns extends EnumType
{
return $this->getRelations($this->type)
->flatMap(fn (Collection $fields, $relation) => $fields->map(fn (Field&SortableField $field): array => [
new RelationSortCriteria($field, $relation),
new RelationSortCriteria($field, $relation, SortDirection::DESC),
new RelationSortCriteria($field->getSort(), $relation, SortDirection::ASC, $field instanceof StringField),
new RelationSortCriteria($field->getSort(), $relation, SortDirection::DESC, $field instanceof StringField),
]))
->flatten();
}
@@ -58,7 +58,7 @@ abstract class AggregateField extends Field implements FilterableField, Sortable
$sortCriteria = Arr::get(new SortableColumns($type)->getAttributes(), 'criteria');
$sorts = Arr::get($args, 'sort', []);
foreach ($sortCriteria as $sortCriterion) {
if (in_array($sortCriterion->__toString(), $sorts) && $sortCriterion->getField()->getName() === $this->getName()) {
if (in_array($sortCriterion->__toString(), $sorts) && $sortCriterion->getSort()->getName() === $this->getName()) {
return true;
}
}
+4 -4
View File
@@ -9,7 +9,7 @@ use App\Enums\GraphQL\QualifyColumn;
class Sort
{
public function __construct(
protected readonly string $key,
protected readonly string $name,
protected readonly ?string $column = null,
protected readonly QualifyColumn $qualifyColumn = QualifyColumn::YES
) {}
@@ -17,9 +17,9 @@ class Sort
/**
* Get sort key value.
*/
public function getKey(): string
public function getName(): string
{
return $this->key;
return $this->name;
}
/**
@@ -27,7 +27,7 @@ class Sort
*/
public function getColumn(): string
{
return $this->column ?? $this->key;
return $this->column ?? $this->getName();
}
/**
+6 -4
View File
@@ -160,9 +160,9 @@ return [
'APP_KEY',
'DB_PASSWORD',
'REDIS_PASSWORD',
'MAL_BEARER_TOKEN',
'MAL_CLIENT_SECRET',
'ANILIST_CLIENT_SECRET',
'ELASTIC_PASS',
'DO_BEARER_TOKEN',
'IMAGE_ACCESS_KEY_ID',
'IMAGE_SECRET_ACCESS_KEY',
'VIDEO_ACCESS_KEY_ID',
@@ -174,14 +174,15 @@ return [
'MAILGUN_SECRET',
'POSTMARK_TOKEN',
'DISCORD_BOT_API_TOKEN',
'DISCORD_BOT_API_KEY',
],
'_SERVER' => [
'APP_KEY',
'DB_PASSWORD',
'REDIS_PASSWORD',
'MAL_BEARER_TOKEN',
'MAL_CLIENT_SECRET',
'ANILIST_CLIENT_SECRET',
'ELASTIC_PASS',
'DO_BEARER_TOKEN',
'IMAGE_ACCESS_KEY_ID',
'IMAGE_SECRET_ACCESS_KEY',
'VIDEO_ACCESS_KEY_ID',
@@ -193,6 +194,7 @@ return [
'MAILGUN_SECRET',
'POSTMARK_TOKEN',
'DISCORD_BOT_API_TOKEN',
'DISCORD_BOT_API_KEY',
],
'_POST' => [
'password',
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Database\Factories\Wiki\Song;
use App\Models\Wiki\Artist;
use App\Models\Wiki\Song\Membership;
use Illuminate\Database\Eloquent\Factories\Factory;
@@ -32,6 +33,8 @@ class MembershipFactory extends Factory
return [
Membership::ATTRIBUTE_ALIAS => fake()->text(),
Membership::ATTRIBUTE_AS => fake()->text(),
Membership::ATTRIBUTE_ARTIST => Artist::factory(),
Membership::ATTRIBUTE_MEMBER => Artist::factory(),
];
}
}
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Database\Factories\Wiki\Song;
use App\Models\Wiki\Artist;
use App\Models\Wiki\Song;
use App\Models\Wiki\Song\Membership;
use App\Models\Wiki\Song\Performance;
use Illuminate\Database\Eloquent\Factories\Factory;
@@ -35,6 +36,9 @@ class PerformanceFactory extends Factory
return [
Performance::ATTRIBUTE_ALIAS => fake()->text(),
Performance::ATTRIBUTE_AS => fake()->text(),
Performance::ATTRIBUTE_ARTIST_TYPE => Relation::getMorphAlias(Artist::class),
Performance::ATTRIBUTE_ARTIST_ID => Artist::factory(),
Performance::ATTRIBUTE_SONG => Song::factory(),
];
}
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
use App\Enums\GraphQL\Sort\SortDirection;
use App\GraphQL\Criteria\Sort\PivotSortCriteria;
use App\GraphQL\Sort\Sort;
it('formats for asc', function () {
$criteria = new class(new Sort(fake()->word()), SortDirection::ASC) extends PivotSortCriteria {};
$this->assertStringStartsWith('PIVOT_', $criteria->__toString());
$this->assertStringEndsNotWith('_DESC', $criteria->__toString());
});
it('formats for desc', function () {
$criteria = new class(new Sort(fake()->word()), SortDirection::DESC) extends PivotSortCriteria {};
$this->assertStringStartsWith('PIVOT_', $criteria->__toString());
$this->assertStringEndsWith('_DESC', $criteria->__toString());
});
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
use App\Enums\GraphQL\Sort\SortDirection;
use App\GraphQL\Criteria\Sort\RelationSortCriteria;
use App\GraphQL\Sort\Sort;
use Illuminate\Support\Str;
it('formats for asc', function () {
$relation = fake()->word();
$criteria = new class(new Sort(fake()->word()), $relation, SortDirection::ASC) extends RelationSortCriteria {};
$this->assertStringStartsWith(Str::upper($relation), $criteria->__toString());
$this->assertStringEndsNotWith('_DESC', $criteria->__toString());
});
it('formats for desc', function () {
$relation = fake()->word();
$criteria = new class(new Sort(fake()->word()), $relation, SortDirection::DESC) extends RelationSortCriteria {};
$this->assertStringStartsWith(Str::upper($relation), $criteria->__toString());
$this->assertStringEndsWith('_DESC', $criteria->__toString());
});
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
use App\Enums\GraphQL\Sort\SortDirection;
use App\GraphQL\Criteria\Sort\SortCriteria;
use App\GraphQL\Sort\Sort;
use Illuminate\Database\Eloquent\Builder;
it('formats for asc', function () {
$criteria = new class(new Sort(fake()->word()), SortDirection::ASC) extends SortCriteria
{
public function sort(Builder $builder): Builder
{
return $builder;
}
};
$this->assertStringEndsNotWith('_DESC', $criteria->__toString());
});
it('formats for desc', function () {
$criteria = new class(new Sort(fake()->word()), SortDirection::DESC) extends SortCriteria
{
public function sort(Builder $builder): Builder
{
return $builder;
}
};
$this->assertStringEndsWith('_DESC', $criteria->__toString());
});
+53 -9
View File
@@ -6,11 +6,15 @@ use App\Models\Wiki\Artist;
use App\Models\Wiki\ExternalResource;
use App\Models\Wiki\Image;
use App\Models\Wiki\Song;
use App\Models\Wiki\Song\Membership;
use App\Models\Wiki\Song\Performance;
use App\Pivots\Morph\Imageable;
use App\Pivots\Morph\Resourceable;
use App\Pivots\Wiki\ArtistMember;
use App\Pivots\Wiki\ArtistSong;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Foundation\Testing\WithFaker;
@@ -53,24 +57,51 @@ test('songs', function () {
$this->assertEquals(ArtistSong::class, $artist->songs()->getPivotClass());
});
test('external resources', function () {
$resourceCount = fake()->randomDigitNotNull();
test('performances', function () {
$performanceCount = fake()->randomDigitNotNull();
$artist = Artist::factory()
->has(ExternalResource::factory()->count($resourceCount), 'resources')
->createOne();
$this->assertInstanceOf(MorphToMany::class, $artist->resources());
$this->assertEquals($resourceCount, $artist->resources()->count());
$this->assertInstanceOf(ExternalResource::class, $artist->resources()->first());
$this->assertEquals(Resourceable::class, $artist->resources()->getPivotClass());
Performance::factory()
->artist($artist)
->count($performanceCount)
->create();
$this->assertInstanceOf(MorphMany::class, $artist->performances());
$this->assertEquals($performanceCount, $artist->performances()->count());
$this->assertInstanceOf(Performance::class, $artist->performances()->first());
});
test('memberships', function () {
$membershipCount = fake()->randomDigitNotNull();
$artist = Artist::factory()
->has(Membership::factory()->count($membershipCount), Artist::RELATION_MEMBERSHIPS)
->createOne();
$this->assertInstanceOf(HasMany::class, $artist->memberships());
$this->assertEquals($membershipCount, $artist->memberships()->count());
$this->assertInstanceOf(Membership::class, $artist->memberships()->first());
});
test('groupships', function () {
$groupshipCount = fake()->randomDigitNotNull();
$artist = Artist::factory()
->has(Membership::factory()->count($groupshipCount), Artist::RELATION_GROUPSHIPS)
->createOne();
$this->assertInstanceOf(HasMany::class, $artist->groupships());
$this->assertEquals($groupshipCount, $artist->groupships()->count());
$this->assertInstanceOf(Membership::class, $artist->groupships()->first());
});
test('members', function () {
$memberCount = fake()->randomDigitNotNull();
$artist = Artist::factory()
->has(Artist::factory()->count($memberCount), 'members')
->has(Artist::factory()->count($memberCount), Artist::RELATION_MEMBERS)
->createOne();
$this->assertInstanceOf(BelongsToMany::class, $artist->members());
@@ -83,7 +114,7 @@ test('groups', function () {
$groupCount = fake()->randomDigitNotNull();
$artist = Artist::factory()
->has(Artist::factory()->count($groupCount), 'groups')
->has(Artist::factory()->count($groupCount), Artist::RELATION_GROUPS)
->createOne();
$this->assertInstanceOf(BelongsToMany::class, $artist->groups());
@@ -104,3 +135,16 @@ test('images', function () {
$this->assertInstanceOf(Image::class, $artist->images()->first());
$this->assertEquals(Imageable::class, $artist->images()->getPivotClass());
});
test('external resources', function () {
$resourceCount = fake()->randomDigitNotNull();
$artist = Artist::factory()
->has(ExternalResource::factory()->count($resourceCount), 'resources')
->createOne();
$this->assertInstanceOf(MorphToMany::class, $artist->resources());
$this->assertEquals($resourceCount, $artist->resources()->count());
$this->assertInstanceOf(ExternalResource::class, $artist->resources()->first());
$this->assertEquals(Resourceable::class, $artist->resources()->getPivotClass());
});
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
use App\Models\Wiki\Artist;
use App\Models\Wiki\Song\Membership;
use App\Models\Wiki\Song\Performance;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Foundation\Testing\WithFaker;
uses(WithFaker::class);
test('nameable', function () {
$membership = Membership::factory()->createOne();
$this->assertIsString($membership->getName());
});
test('has subtitle', function () {
$membership = Membership::factory()->createOne();
$this->assertIsString($membership->getSubtitle());
});
test('group', function () {
$membership = Membership::factory()
->for(Artist::factory(), Membership::RELATION_GROUP)
->createOne();
$this->assertInstanceOf(BelongsTo::class, $membership->group());
$this->assertInstanceOf(Artist::class, $membership->group()->first());
});
test('member', function () {
$membership = Membership::factory()
->for(Artist::factory(), Membership::RELATION_MEMBER)
->createOne();
$this->assertInstanceOf(BelongsTo::class, $membership->member());
$this->assertInstanceOf(Artist::class, $membership->member()->first());
});
test('performances', function () {
$performanceCount = fake()->randomDigitNotNull();
$membership = Membership::factory()
->has(Performance::factory()->count($performanceCount))
->createOne();
$this->assertInstanceOf(MorphMany::class, $membership->performances());
$this->assertEquals($performanceCount, $membership->performances()->count());
$this->assertInstanceOf(Performance::class, $membership->performances()->first());
});
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
use App\Models\Wiki\Artist;
use App\Models\Wiki\Song;
use App\Models\Wiki\Song\Membership;
use App\Models\Wiki\Song\Performance;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Foundation\Testing\WithFaker;
uses(WithFaker::class);
test('nameable', function () {
$performance = Performance::factory()->createOne();
$this->assertIsString($performance->getName());
});
test('has subtitle', function () {
$performance = Performance::factory()->createOne();
$this->assertIsString($performance->getSubtitle());
});
test('song', function () {
$performance = Performance::factory()
->for(Song::factory())
->createOne();
$this->assertInstanceOf(BelongsTo::class, $performance->song());
$this->assertInstanceOf(Song::class, $performance->song()->first());
});
test('artist', function () {
$performance = Performance::factory()
->artist(Artist::factory()->createOne())
->createOne();
$this->assertInstanceOf(MorphTo::class, $performance->artist());
$this->assertInstanceOf(Artist::class, $performance->artist()->first());
});
test('membership', function () {
$performance = Performance::factory()
->artist(Membership::factory()->createOne())
->createOne();
$this->assertInstanceOf(MorphTo::class, $performance->membership());
$this->assertInstanceOf(Membership::class, $performance->membership()->first());
});
+13
View File
@@ -7,6 +7,7 @@ use App\Models\Wiki\Anime\AnimeTheme;
use App\Models\Wiki\Artist;
use App\Models\Wiki\ExternalResource;
use App\Models\Wiki\Song;
use App\Models\Wiki\Song\Performance;
use App\Pivots\Morph\Resourceable;
use App\Pivots\Wiki\ArtistSong;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -67,6 +68,18 @@ test('artists', function () {
$this->assertEquals(ArtistSong::class, $song->artists()->getPivotClass());
});
test('performances', function () {
$performanceCount = fake()->randomDigitNotNull();
$song = Song::factory()
->has(Performance::factory()->count($performanceCount))
->createOne();
$this->assertInstanceOf(HasMany::class, $song->performances());
$this->assertEquals($performanceCount, $song->performances()->count());
$this->assertInstanceOf(Performance::class, $song->performances()->first());
});
test('external resources', function () {
$resourceCount = fake()->randomDigitNotNull();