mirror of
https://github.com/jikan-me/jikan-rest.git
synced 2025-02-20 11:23:35 +08:00
added more unit tests and configured test coverage discovery
This commit is contained in:
parent
75e04c72da
commit
331d5e821b
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
class CustomTestException extends \Exception
|
class CustomTestException extends \Exception
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Support;
|
namespace App\Support;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to provide strongly typed options about the cache configuration.
|
||||||
|
*/
|
||||||
final class CacheOptions
|
final class CacheOptions
|
||||||
{
|
{
|
||||||
private ?int $ttl = null;
|
private ?int $ttl = null;
|
||||||
|
@ -50,11 +50,6 @@ final class CachedData implements ArrayAccess
|
|||||||
return self::fromArray($model->toArray());
|
return self::fromArray($model->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function collect(): Collection
|
|
||||||
{
|
|
||||||
return $this->scraperResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isEmpty(): bool
|
public function isEmpty(): bool
|
||||||
{
|
{
|
||||||
return $this->scraperResult->isEmpty();
|
return $this->scraperResult->isEmpty();
|
||||||
@ -85,11 +80,6 @@ final class CachedData implements ArrayAccess
|
|||||||
return $modifiedAt !== null ? $ttl + $modifiedAt : $ttl;
|
return $modifiedAt !== null ? $ttl + $modifiedAt : $ttl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cacheTtl(): int
|
|
||||||
{
|
|
||||||
return $this->cacheTimeToLive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function lastModified(): ?int
|
public function lastModified(): ?int
|
||||||
{
|
{
|
||||||
if ($this->scraperResult->isEmpty()) {
|
if ($this->scraperResult->isEmpty()) {
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Support;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
*/
|
|
||||||
class Lazy
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param \Closure<T> $callback
|
|
||||||
*/
|
|
||||||
public function __construct(private readonly \Closure $callback)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return T
|
|
||||||
*/
|
|
||||||
public function value()
|
|
||||||
{
|
|
||||||
$callback = $this->callback;
|
|
||||||
return $callback();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace App\Testing\Concerns;
|
namespace App\Testing\Concerns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
trait MakesHttpRequestsEx
|
trait MakesHttpRequestsEx
|
||||||
{
|
{
|
||||||
protected function getBaseUri(): string
|
protected function getBaseUri(): string
|
||||||
|
@ -4,6 +4,9 @@ use Faker\Generator;
|
|||||||
use Faker\UniqueGenerator;
|
use Faker\UniqueGenerator;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
trait JikanDataGenerator
|
trait JikanDataGenerator
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -4,6 +4,9 @@ namespace App\Testing;
|
|||||||
|
|
||||||
use Typesense\LaravelTypesense\Typesense;
|
use Typesense\LaravelTypesense\Typesense;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
trait ScoutFlush
|
trait ScoutFlush
|
||||||
{
|
{
|
||||||
protected array $searchIndexModelCleanupList = [
|
protected array $searchIndexModelCleanupList = [
|
||||||
|
@ -8,6 +8,7 @@ use Illuminate\Support\Facades\DB;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A trait for test cases which want to clear the database after each test
|
* A trait for test cases which want to clear the database after each test
|
||||||
|
* @codeCoverageIgnore
|
||||||
*/
|
*/
|
||||||
trait SyntheticMongoDbTransaction
|
trait SyntheticMongoDbTransaction
|
||||||
{
|
{
|
||||||
|
@ -10,6 +10,7 @@ use TypeError;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* An exception handler class which helps to highlight errors during tests.
|
* An exception handler class which helps to highlight errors during tests.
|
||||||
|
* @codeCoverageIgnore
|
||||||
*/
|
*/
|
||||||
class TestExceptionsHandler extends Handler
|
class TestExceptionsHandler extends Handler
|
||||||
{
|
{
|
||||||
|
@ -66,9 +66,7 @@ class IntegrationTestListener implements TestListener
|
|||||||
try {
|
try {
|
||||||
$kernel->call('migrate:fresh', []);
|
$kernel->call('migrate:fresh', []);
|
||||||
} catch (\Exception $ex) {
|
} catch (\Exception $ex) {
|
||||||
print_r($ex->getMessage());
|
dd($ex);
|
||||||
print_r($ex);
|
|
||||||
throw $ex;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
81
tests/Unit/CachedDataTest.php
Normal file
81
tests/Unit/CachedDataTest.php
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
/** @noinspection PhpIllegalPsrClassPathInspection */
|
||||||
|
namespace Tests\Unit;
|
||||||
|
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use MongoDB\BSON\UTCDateTime;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
final class CachedDataTest extends TestCase
|
||||||
|
{
|
||||||
|
public function dateTimeProvider(): array
|
||||||
|
{
|
||||||
|
$now = Carbon::now();
|
||||||
|
return [
|
||||||
|
"mongodb bson date time" => [
|
||||||
|
$now, new UTCDateTime($now->getPreciseTimestamp(3))
|
||||||
|
],
|
||||||
|
"built-in datetime" => [
|
||||||
|
$now, $now->toDateTime()
|
||||||
|
],
|
||||||
|
"atom string datetime" => [
|
||||||
|
$now, $now->toAtomString()
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function invalidModifiedAtValuesProvider(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"number value" => [
|
||||||
|
["modifiedAt" => 1]
|
||||||
|
],
|
||||||
|
"float value" => [
|
||||||
|
["modifiedAt" => 1.3]
|
||||||
|
],
|
||||||
|
"bool value" => [
|
||||||
|
["modifiedAt" => true]
|
||||||
|
],
|
||||||
|
"modifiedAt key not present" => [
|
||||||
|
["someotherkey" => 1]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLastModifiedReturnsNullIfInternalCollectionIsEmpty()
|
||||||
|
{
|
||||||
|
$sut = CachedData::from(collect());
|
||||||
|
$this->assertNull($sut->lastModified());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider invalidModifiedAtValuesProvider
|
||||||
|
*/
|
||||||
|
public function testLastModifiedReturnsNullIfModifiedAtIsUnknownFormat(array $contents)
|
||||||
|
{
|
||||||
|
$sut = CachedData::from(collect($contents));
|
||||||
|
$this->assertNull($sut->lastModified());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider dateTimeProvider
|
||||||
|
*/
|
||||||
|
public function testLastModifiedReturnsModifiedTime($now, $dateTime)
|
||||||
|
{
|
||||||
|
$sut = CachedData::from(collect(["modifiedAt" => $dateTime]));
|
||||||
|
$this->assertEquals($now->getTimestamp(), $sut->lastModified());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsEmptyReturnsTrueIfEmpty()
|
||||||
|
{
|
||||||
|
$sut = CachedData::from(collect());
|
||||||
|
$this->assertEquals(true, $sut->isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsExpiredReturnTrueIfEmpty()
|
||||||
|
{
|
||||||
|
$sut = CachedData::from(collect());
|
||||||
|
$this->assertEquals(true, $sut->isExpired());
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
/** @noinspection PhpIllegalPsrClassPathInspection */
|
||||||
namespace Unit;
|
namespace Tests\Unit;
|
||||||
|
|
||||||
use App\Anime;
|
use App\Anime;
|
||||||
use App\Contracts\Repository;
|
use App\Contracts\Repository;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\Services\DefaultCachedScraperService;
|
use App\Services\DefaultCachedScraperService;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Jenssegers\Mongodb\Eloquent\Builder;
|
use Jenssegers\Mongodb\Eloquent\Builder;
|
||||||
use Jikan\MyAnimeList\MalClient;
|
use Jikan\MyAnimeList\MalClient;
|
||||||
use JMS\Serializer\SerializerInterface;
|
use JMS\Serializer\SerializerInterface;
|
||||||
@ -17,8 +16,9 @@ use Tests\TestCase;
|
|||||||
|
|
||||||
final class DefaultCachedScraperServiceTest extends TestCase
|
final class DefaultCachedScraperServiceTest extends TestCase
|
||||||
{
|
{
|
||||||
public function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
|
parent::tearDown();
|
||||||
// reset time pinning
|
// reset time pinning
|
||||||
Carbon::setTestNow();
|
Carbon::setTestNow();
|
||||||
}
|
}
|
||||||
@ -35,8 +35,9 @@ final class DefaultCachedScraperServiceTest extends TestCase
|
|||||||
$serializerMock = Mockery::mock(SerializerInterface::class);
|
$serializerMock = Mockery::mock(SerializerInterface::class);
|
||||||
|
|
||||||
$repositoryMock
|
$repositoryMock
|
||||||
->expects()
|
->allows()
|
||||||
->where("request_hash", $this->requestHash())
|
->where("request_hash", $this->requestHash())
|
||||||
|
->atMost()
|
||||||
->times($repositoryWhereCallCount)
|
->times($repositoryWhereCallCount)
|
||||||
->andReturn($queryBuilderMock);
|
->andReturn($queryBuilderMock);
|
||||||
|
|
||||||
@ -240,6 +241,7 @@ final class DefaultCachedScraperServiceTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
[$queryBuilderMock, $repositoryMock, $serializerMock] = $this->makeCtorArgMocks();
|
[$queryBuilderMock, $repositoryMock, $serializerMock] = $this->makeCtorArgMocks();
|
||||||
|
// stale record in db
|
||||||
$repositoryMock->expects()->getAllByMalId($malId)->andReturns(collect([
|
$repositoryMock->expects()->getAllByMalId($malId)->andReturns(collect([
|
||||||
$mockModel
|
$mockModel
|
||||||
]));
|
]));
|
||||||
@ -294,7 +296,7 @@ final class DefaultCachedScraperServiceTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
[$queryBuilderMock, $repositoryMock, $serializerMock] = $this->makeCtorArgMocks();
|
[$queryBuilderMock, $repositoryMock, $serializerMock] = $this->makeCtorArgMocks();
|
||||||
$repositoryMock->expects()->where("username", $username)->andReturns($queryBuilderMock);
|
$repositoryMock->expects()->where("username", $username)->atMost()->times(2)->andReturns($queryBuilderMock);
|
||||||
// nothing in db
|
// nothing in db
|
||||||
$queryBuilderMock->expects()->get()->andReturns(collect());
|
$queryBuilderMock->expects()->get()->andReturns(collect());
|
||||||
// scrape returns data
|
// scrape returns data
|
||||||
@ -311,4 +313,46 @@ final class DefaultCachedScraperServiceTest extends TestCase
|
|||||||
|
|
||||||
$this->assertEquals($mockModel->toArray(), $result->toArray());
|
$this->assertEquals($mockModel->toArray(), $result->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testIfFindByKeyUpdatesCache()
|
||||||
|
{
|
||||||
|
$malId = 1;
|
||||||
|
$username = "kompot";
|
||||||
|
$testRequestHash = $this->requestHash();
|
||||||
|
$now = Carbon::now();
|
||||||
|
$mockModel = Profile::factory()->makeOne([
|
||||||
|
"mal_id" => $malId,
|
||||||
|
"username" => $username,
|
||||||
|
"modifiedAt" => new UTCDateTime($now->sub("3 days")->getPreciseTimestamp(3)),
|
||||||
|
"createdAt" => new UTCDateTime($now->sub("3 days")->getPreciseTimestamp(3))
|
||||||
|
]);
|
||||||
|
$now = Carbon::now();
|
||||||
|
Carbon::setTestNow($now);
|
||||||
|
$updatedMockModel = Profile::factory()->makeOne([
|
||||||
|
...$mockModel->toArray(),
|
||||||
|
"location" => "North Pole",
|
||||||
|
"modifiedAt" => new UTCDateTime(Carbon::now()->getPreciseTimestamp(3)),
|
||||||
|
"createdAt" => new UTCDateTime(Carbon::now()->getPreciseTimestamp(3))
|
||||||
|
]);
|
||||||
|
[$queryBuilderMock, $repositoryMock, $serializerMock] = $this->makeCtorArgMocks();
|
||||||
|
// stale record in db
|
||||||
|
$repositoryMock->expects()->where("username", $username)->atMost()->times(3)->andReturns($queryBuilderMock);
|
||||||
|
$queryBuilderMock->expects()->get()->andReturns(collect([
|
||||||
|
$mockModel
|
||||||
|
]));
|
||||||
|
$repositoryMock->expects()->scrape($username)->andReturns(
|
||||||
|
collect($updatedMockModel->toArray())->except(["request_hash", "modifiedAt", "createdAt"])->toArray()
|
||||||
|
);
|
||||||
|
// mock out update
|
||||||
|
$queryBuilderMock->expects()->update(Mockery::any())->andReturns($malId);
|
||||||
|
// second call to ->get() should return the updated value
|
||||||
|
$queryBuilderMock->expects()->get()->andReturns(collect([
|
||||||
|
$updatedMockModel
|
||||||
|
]));
|
||||||
|
|
||||||
|
$sut = new DefaultCachedScraperService($repositoryMock, new MalClient(), $serializerMock);
|
||||||
|
$result = $sut->findByKey("username", $username, $testRequestHash);
|
||||||
|
|
||||||
|
$this->assertEquals($updatedMockModel->toArray(), $result->toArray());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
37
tests/Unit/DefaultMediatorTest.php
Normal file
37
tests/Unit/DefaultMediatorTest.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
/** @noinspection PhpIllegalPsrClassPathInspection */
|
||||||
|
namespace Tests\Unit;
|
||||||
|
|
||||||
|
use App\Contracts\DataRequest;
|
||||||
|
use App\Contracts\RequestHandler;
|
||||||
|
use App\Dto\AnimeSearchCommand;
|
||||||
|
use App\Support\DefaultMediator;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
final class DefaultMediatorTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testSendShouldReturnErrorResponseIfHandlerClassDoesntExist()
|
||||||
|
{
|
||||||
|
$mockHandler = \Mockery::mock(RequestHandler::class);
|
||||||
|
$mockHandler->allows()->requestClass()->andReturns(AnimeSearchCommand::class);
|
||||||
|
$sut = new DefaultMediator($mockHandler);
|
||||||
|
|
||||||
|
$response = $sut->send(\Mockery::mock(DataRequest::class));
|
||||||
|
$this->assertEquals(500, $response->status());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSendShouldCallHandleOnHandlerIfFound()
|
||||||
|
{
|
||||||
|
$mockHandler = \Mockery::mock(RequestHandler::class);
|
||||||
|
$mockRequest = \Mockery::mock(DataRequest::class);
|
||||||
|
$mockHandler->allows()->requestClass()->andReturns(get_class($mockRequest));
|
||||||
|
$mockHandler->expects()->handle($mockRequest)->once()->andReturns(response()->json([
|
||||||
|
"message" => "success"
|
||||||
|
]));
|
||||||
|
|
||||||
|
$sut = new DefaultMediator($mockHandler);
|
||||||
|
$response = $sut->send($mockRequest);
|
||||||
|
$this->assertEquals(200, $response->status());
|
||||||
|
$this->assertEquals(json_encode(["message" => "success"]), $response->content());
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user