added the foundations for fixture based tests

This commit is contained in:
pushrbx 2022-12-04 11:38:11 +00:00
parent 2254ef7bf2
commit 8f102d2b56
20 changed files with 589 additions and 3 deletions

View File

@ -7,7 +7,6 @@ indent_style = space
indent_size = 2 indent_size = 2
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
end_of_line = lf
[*.md] [*.md]
max_line_length = off max_line_length = off

View File

@ -5,9 +5,12 @@ namespace App;
use App\Http\HttpHelper; use App\Http\HttpHelper;
use Jikan\Jikan; use Jikan\Jikan;
use Jikan\Request\Anime\AnimeRequest; use Jikan\Request\Anime\AnimeRequest;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Anime extends JikanApiSearchableModel class Anime extends JikanApiSearchableModel
{ {
use HasFactory;
// note that here we skip "score", "min_score", "max_score", "rating" and others because they need special logic // note that here we skip "score", "min_score", "max_score", "rating" and others because they need special logic
// to set the correct filtering on the ORM. // to set the correct filtering on the ORM.
protected array $filters = ["order_by", "status", "type", "sort"]; protected array $filters = ["order_by", "status", "type", "sort"];

View File

@ -4,9 +4,12 @@ namespace App;
use Jikan\Jikan; use Jikan\Jikan;
use Jikan\Request\Character\CharacterRequest; use Jikan\Request\Character\CharacterRequest;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Character extends JikanApiSearchableModel class Character extends JikanApiSearchableModel
{ {
use HasFactory;
protected array $filters = ["order_by", "sort"]; protected array $filters = ["order_by", "sort"];
/** /**

View File

@ -4,6 +4,7 @@ namespace App;
use Jenssegers\Mongodb\Eloquent\Model; use Jenssegers\Mongodb\Eloquent\Model;
use Jikan\Request\Genre\AnimeGenresRequest; use Jikan\Request\Genre\AnimeGenresRequest;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/** /**
* Class Magazine * Class Magazine
@ -11,6 +12,8 @@ use Jikan\Request\Genre\AnimeGenresRequest;
*/ */
class GenreAnime extends JikanApiSearchableModel class GenreAnime extends JikanApiSearchableModel
{ {
use HasFactory;
protected array $filters = ["order_by", "sort"]; protected array $filters = ["order_by", "sort"];
/** /**

View File

@ -4,6 +4,7 @@ namespace App;
use Jenssegers\Mongodb\Eloquent\Model; use Jenssegers\Mongodb\Eloquent\Model;
use Jikan\Request\Genre\AnimeGenresRequest; use Jikan\Request\Genre\AnimeGenresRequest;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/** /**
* Class Magazine * Class Magazine
@ -11,6 +12,8 @@ use Jikan\Request\Genre\AnimeGenresRequest;
*/ */
class GenreManga extends JikanApiSearchableModel class GenreManga extends JikanApiSearchableModel
{ {
use HasFactory;
protected array $filters = ["order_by", "sort"]; protected array $filters = ["order_by", "sort"];
/** /**

View File

@ -5,9 +5,12 @@ namespace App;
use App\Http\HttpHelper; use App\Http\HttpHelper;
use Jikan\Jikan; use Jikan\Jikan;
use Jikan\Request\Manga\MangaRequest; use Jikan\Request\Manga\MangaRequest;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Manga extends JikanApiSearchableModel class Manga extends JikanApiSearchableModel
{ {
use HasFactory;
// note that here we skip "score", "min_score", "max_score", "rating" and others because they need special logic // note that here we skip "score", "min_score", "max_score", "rating" and others because they need special logic
// to set the correct filtering on the ORM. // to set the correct filtering on the ORM.
protected array $filters = ["order_by", "status", "type", "sort"]; protected array $filters = ["order_by", "status", "type", "sort"];

View File

@ -5,9 +5,11 @@ namespace App;
use Jikan\Jikan; use Jikan\Jikan;
use Jikan\Request\Person\PersonRequest; use Jikan\Request\Person\PersonRequest;
use function Symfony\Component\Translation\t; use function Symfony\Component\Translation\t;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Person extends JikanApiSearchableModel class Person extends JikanApiSearchableModel
{ {
use HasFactory;
protected array $filters = ["order_by", "sort"]; protected array $filters = ["order_by", "sort"];
/** /**

View File

@ -39,6 +39,7 @@
"zircote/swagger-php": "3.*" "zircote/swagger-php": "3.*"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.20",
"mockery/mockery": "^1.3.1", "mockery/mockery": "^1.3.1",
"phpunit/phpunit": "^8.5" "phpunit/phpunit": "^8.5"
}, },

71
composer.lock generated
View File

@ -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": "cf4c0d150bce75398ce4300c27d1cdf6", "content-hash": "3471a91d2125954a5be97999b1c3ce12",
"packages": [ "packages": [
{ {
"name": "amphp/amp", "name": "amphp/amp",
@ -10945,6 +10945,73 @@
} }
], ],
"packages-dev": [ "packages-dev": [
{
"name": "fakerphp/faker",
"version": "v1.20.0",
"source": {
"type": "git",
"url": "https://github.com/FakerPHP/Faker.git",
"reference": "37f751c67a5372d4e26353bd9384bc03744ec77b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/FakerPHP/Faker/zipball/37f751c67a5372d4e26353bd9384bc03744ec77b",
"reference": "37f751c67a5372d4e26353bd9384bc03744ec77b",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0",
"psr/container": "^1.0 || ^2.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
},
"conflict": {
"fzaninotto/faker": "*"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1",
"doctrine/persistence": "^1.3 || ^2.0",
"ext-intl": "*",
"symfony/phpunit-bridge": "^4.4 || ^5.2"
},
"suggest": {
"doctrine/orm": "Required to use Faker\\ORM\\Doctrine",
"ext-curl": "Required by Faker\\Provider\\Image to download images.",
"ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.",
"ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.",
"ext-mbstring": "Required for multibyte Unicode string functionality."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "v1.20-dev"
}
},
"autoload": {
"psr-4": {
"Faker\\": "src/Faker/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "François Zaninotto"
}
],
"description": "Faker is a PHP library that generates fake data for you.",
"keywords": [
"data",
"faker",
"fixtures"
],
"support": {
"issues": "https://github.com/FakerPHP/Faker/issues",
"source": "https://github.com/FakerPHP/Faker/tree/v1.20.0"
},
"time": "2022-07-20T13:12:54+00:00"
},
{ {
"name": "hamcrest/hamcrest-php", "name": "hamcrest/hamcrest-php",
"version": "v2.0.1", "version": "v2.0.1",
@ -12426,5 +12493,5 @@
"ext-mongodb": "*" "ext-mongodb": "*"
}, },
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "2.2.0" "plugin-api-version": "2.3.0"
} }

View File

@ -0,0 +1,102 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Anime;
class AnimeFactory extends Factory
{
use JikanDataGenerator;
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Anime::class;
public function definition(): array
{
$mal_id = $this->createMalId();
$title = $this->createTitle();
$status = $this->faker->randomElement(["Currently Airing", "Completed", "Upcoming"]);
[$aired_from, $aired_to] = $this->createActiveDateRange($status, "Currently Airing");
return [
"mal_id" => $mal_id,
"url" => $this->createUrl($mal_id, "anime"),
"titles" => [
[
"type" => "Default",
"title" => $title
]
],
"title" => $title,
"title_english" => $title,
"title_japanese" => $title,
"title_synonyms" => [$title],
"type" => $this->faker->randomElement(["TV", "Movie", "OVA"]),
"source" => $this->faker->randomElement(["Manga", "Original", "Novel"]),
"episodes" => $this->faker->randomElement([1, 12, 13, 16, 24, 48, 96, 128, 366]),
"status" => $status,
"airing" => $status == "Currently Airing",
"aired" => [
"from" => $aired_from->toAtomString(),
"to" => $aired_to,
],
"duration" => "",
"rating" => $this->faker->randomElement(["R - 17+ (violence & profanity)", "PG"]),
"score" => $this->faker->randomFloat(2, 1.00, 9.99),
"scored_by" => $this->faker->randomDigitNotNull(),
"rank" => $this->faker->randomDigitNotNull(),
"popularity" => $this->faker->randomDigitNotNull(),
"members" => $this->faker->randomDigitNotNull(),
"favorites" => $this->faker->randomDigitNotNull(),
"synopsis" => "test",
"background" => "test",
"season" => $this->faker->randomElement(["winter", "spring", "fall", "summer"]),
"broadcast" => [
"day" => "",
"time" => "",
"timezone" => "Asia/Tokyo",
"string" => "Tuesdays at 00:00 (JST)"
],
"producers" => [
[
"mal_id" => 16,
"type" => "anime",
"name" => "TV Tokyo",
"url" => "https://myanimelist.net/anime/producer/16/TV_Tokyo"
]
],
"lincesors" => [
[
"mal_id" => 119,
"type" => "anime",
"name" => "VIZ Media",
"url" => "https://myanimelist.net/anime/producer/119/VIZ_Media"
]
],
"studios" => [],
"genres" => [
[
"mal_id" => 1,
"type" => "anime",
"name" => "Action",
"url" => "https://myanimelist.net/anime/genre/1/Action"
]
],
"explicit_genres" => [],
"themes" => [],
"demographics" => [
[
"mal_id" => 27,
"type" => "anime",
"name" => "Shounen",
"url" => "https://myanimelist.net/anime/genre/27/Shounen"
]
]
];
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Character;
class CharacterFactory extends Factory
{
use JikanDataGenerator;
protected $model = Character::class;
public function definition()
{
$mal_id = $this->createMalId();
$url = $this->createUrl($mal_id, "character");
return [
"mal_id" => $mal_id,
"url" => $url,
"images" => [],
"name" => $this->faker->name(),
"name_kanji" => "",
"nicknames" => [],
"favorites" => $this->faker->randomDigitNotNull(),
"about" => "test"
];
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace Database\Factories;
use App\GenreAnime;
class GenreAnimeFactory extends GenreFactory
{
protected $model = GenreAnime::class;
protected string $mediaType = "anime";
}

View File

@ -0,0 +1,24 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
abstract class GenreFactory extends Factory
{
use JikanDataGenerator;
protected string $mediaType = "";
public function definition(): array
{
$mal_id = $this->createMalId();
$name = $this->getRandomGenreName();
$url = $this->createUrl($mal_id, $this->mediaType . "/genre");
return [
"mal_id" => $mal_id,
"name" => $name,
"url" => $url,
"count" => $this->faker->randomDigit()
];
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace Database\Factories;
use App\GenreManga;
class GenreMangaFactory extends GenreFactory
{
protected $model = GenreManga::class;
protected string $mediaType = "manga";
}

View File

@ -0,0 +1,129 @@
<?php
namespace Database\Factories;
use Faker\Generator;
use Illuminate\Support\Carbon;
trait JikanDataGenerator
{
/**
* The current Faker instance.
*
* @var Generator
*/
protected $faker;
private array $dummyGenres = [
"Action",
"Adventure",
"Avant Garde",
"Award Winning",
"Boys Love",
"Comedy",
"Drama",
"Fantasy",
"Girls Love",
"Gourmet",
"Horror",
"Mystery",
"Romance",
"Sci-Fi",
"Slice of Life",
"Sports",
"Supernatural",
"Suspense",
"Ecchi",
"Erotica",
"Hentai",
"Adult Cast",
"Anthropomorphic",
"CGDCT",
"Childcare",
"Combat Sports",
"Crossdressing",
"Delinquents",
"Detective",
"Educational",
"Gag Humor",
"Gore",
"Harem",
"High Stakes Game",
"Historical",
"Idols (Female)",
"Idols (Male)",
"Isekai",
"Iyashikei",
"Love Polygon",
"Magical Sex Shift",
"Mahou Shoujo",
"Martial Arts",
"Mecha",
"Medical",
"Military",
"Music",
"Mythology",
"Organized Crime",
"Otaku Culture",
"Parody",
"Performing Arts",
"Pets",
"Psychological",
"Racing",
"Reincarnation",
"Reverse Harem",
"Romantic Subtext",
"Samurai",
"School",
"Showbiz",
"Space",
"Strategy Game",
"Super Power",
"Survival",
"Team Sports",
"Time Travel",
"Vampire",
"Video Game",
"Visual Arts",
"Workplace",
"Josei",
"Kids",
"Seinen",
"Shoujo",
"Shounen"
];
private function createUrl($malId, $type): string
{
return "https://myanimelist.net/" . $type . "/" . $malId . "/x";
}
private function createMalId(): int
{
return $this->faker->numberBetween(1, 99999);
}
private function createTitle(): string
{
return $this->faker->name();
}
private function createRandomDateTime($startDate = "-30 years"): Carbon
{
return Carbon::createFromTimestamp($this->faker->dateTimeBetween($startDate)->getTimestamp());
}
private function createActiveDateRange($status, $activeStatus): array
{
$from = $this->createRandomDateTime("-15 years");
$to = $status != $activeStatus ? $from->addDays($this->faker->numberBetween(1, 368))->toAtomString() : null;
return [$from, $to];
}
private function getRandomGenreName(): string
{
return $this->faker->randomElement($this->dummyGenres);
}
private function getRandomGenreNames($count = 1): array
{
return $this->faker->randomElements($this->dummyGenres, $count);
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Manga;
class MangaFactory extends Factory
{
use JikanDataGenerator;
protected $model = Manga::class;
public function definition()
{
$mal_id = $this->createMalId();
$title = $this->createTitle();
$status = $this->faker->randomElement(["Finished", "Publishing", "Upcoming"]);
[$published_from, $published_to] = $this->createActiveDateRange($status, "Publishing");
return [
"mal_id" => $mal_id,
"url" => $this->createUrl($mal_id, "manga"),
"titles" => [
[
"type" => "Default",
"title" => $title
]
],
"title" => $title,
"title_english" => $title,
"title_japanese" => $title,
"title_synonyms" => [$title],
"type" => $this->faker->randomElement(["Manga", "Light Novel", "Web Novel"]),
"chapters" => $this->faker->numberBetween(1, 255),
"volumes" => $this->faker->numberBetween(0, 55),
"status" => $status,
"publishing" => $status === "Finished",
"published" => [
"from" => $published_from->toAtomString(),
"to" => $published_to
],
"score" => $this->faker->randomFloat(2, 1.00, 9.99),
"scored_by" => $this->faker->randomDigitNotNull(),
"rank" => $this->faker->randomDigitNotNull(),
"popularity" => $this->faker->randomDigitNotNull(),
"members" => $this->faker->randomDigitNotNull(),
"favorites" => $this->faker->randomDigitNotNull(),
"synopsis" => "test",
"background" => "test",
"authors" => [
[
"mal_id" => 1874,
"type" => "people",
"name" => "Arakawa, Hiromu",
"url" => "https://myanimelist.net/people/1847/Hiromu_Arakawa"
]
],
"serializations" => [
[
"mal_id" => 13,
"type" => "manga",
"name" => "Shounen Gangan",
"url" => "https://myanimelist.net/manga/magazine/13/Shounen_Gangan"
]
],
"genres" => [
[
"mal_id" => 1,
"type" => "anime",
"name" => "Action",
"url" => "https://myanimelist.net/anime/genre/1/Action"
]
],
"explicit_genres" => [],
"themes" => [],
"demographics" => [
[
"mal_id" => 27,
"type" => "manga",
"name" => "Shounen",
"url" => "https://myanimelist.net/manga/genre/27/Shounen"
]
]
];
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Person;
class PersonFactory extends Factory
{
use JikanDataGenerator;
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Person::class;
public function definition(): array
{
$mal_id = $this->createMalId();
$name = $this->faker->name();
$given_name = $this->faker->firstName();
$family_name = $this->faker->lastName();
return [
"mal_id" => $mal_id,
"url" => $this->createUrl($mal_id, "people"),
"website_url" => "https://webiste.example",
"images" => [],
"name" => $name,
"given_name" => $given_name,
"family_name" => $family_name,
"alternate_names" => [],
"birthday" => $this->createRandomDateTime("-80 years")->toAtomString(),
"favorites" => $this->faker->randomDigitNotNull(),
"about" => "test"
];
}
}

View File

@ -0,0 +1,57 @@
<?php
use Laravel\Lumen\Testing\DatabaseMigrations;
use App\Anime;
class AnimeSearchEndpointTest extends TestCase
{
use DatabaseMigrations;
public function limitParameterCombinationsProvider(): array
{
return [
[5, []],
[5, ["type" => "tv"]],
[5, ["type" => "tv", "min_score" => 7]],
[5, ["type" => "tv", "max_score" => 6]],
[5, ["type" => "tv", "status" => "complete", "max_score" => 8]],
[5, ["type" => "movie", "status" => "complete", "max_score" => 8]]
];
}
/**
* @test
*/
public function shouldReturnMethodNotAllowedResponseIfMethodNotAllowed()
{
$this->json("POST", "/v4/anime", ["title" => "Dum"])
->seeStatusCode(405);
}
/**
* @dataProvider limitParameterCombinationsProvider
*/
public function testLimitParameter(int $limitCount, array $additionalParams)
{
$f = Anime::factory()->count(25);
$overrides = [];
// let's make all database items the same type
if (array_key_exists("type", $additionalParams)) {
$overrides["type"] = $additionalParams["type"];
}
$f->make($overrides);
$parameters = http_build_query([
"limit" => $limitCount,
...$additionalParams
]);
$uri = "/v4/anime?" . $parameters;
$sut = $this->getJson($uri);
$content = $sut->response->json();
$sut->seeStatusCode(200);
$sut->response->assertJsonPath("pagination.items.count", $limitCount);
$sut->response->assertJsonPath("pagination.items.total", 25);
$sut->response->assertJsonPath("pagination.items.per_page", $limitCount);
$this->assertIsArray($content["data"]);
$this->assertCount($limitCount, $content["data"]);
}
}

View File

@ -0,0 +1,15 @@
<?php
trait MakesHttpRequestsEx
{
/**
* Visit the given URI with a JSON GET request.
* @param string $uri
* @param array $headers
* @return TestCase
*/
public function getJson(string $uri, array $headers = []): TestCase
{
return $this->json('GET', $uri, [], $headers);
}
}

View File

@ -2,6 +2,8 @@
abstract class TestCase extends Laravel\Lumen\Testing\TestCase abstract class TestCase extends Laravel\Lumen\Testing\TestCase
{ {
use MakesHttpRequestsEx;
/** /**
* Creates the application. * Creates the application.
* *