diff --git a/CHANGELOG.md b/CHANGELOG.md index 6394408b..2a7617a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this #### Changed * [#492](https://github.com/shlinkio/shlink/issues/492) Updated to monolog 2, together with other dependencies, like Symfony 5 and infection-php. +* [#527](https://github.com/shlinkio/shlink/issues/527) Increased minimum required mutation score for unit tests to 80%. #### Deprecated diff --git a/composer.json b/composer.json index fd3840bb..0ded66fe 100644 --- a/composer.json +++ b/composer.json @@ -130,9 +130,9 @@ "test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite", "test:api": "bin/test/run-api-tests.sh", "test:unit:pretty": "phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage", - "infect": "infection --threads=4 --min-msi=75 --log-verbosity=default --only-covered", - "infect:ci": "infection --threads=4 --min-msi=75 --log-verbosity=default --only-covered --coverage=build", - "infect:show": "infection --threads=4 --min-msi=75 --log-verbosity=default --only-covered --show-mutations", + "infect": "infection --threads=4 --min-msi=80 --log-verbosity=default --only-covered", + "infect:ci": "infection --threads=4 --min-msi=80 --log-verbosity=default --only-covered --coverage=build", + "infect:show": "infection --threads=4 --min-msi=80 --log-verbosity=default --only-covered --show-mutations", "infect:test": [ "@test:unit:ci", "@infect:ci" diff --git a/module/Core/test/Exception/DeleteShortUrlExceptionTest.php b/module/Core/test/Exception/DeleteShortUrlExceptionTest.php index 0e2012a2..f2207c54 100644 --- a/module/Core/test/Exception/DeleteShortUrlExceptionTest.php +++ b/module/Core/test/Exception/DeleteShortUrlExceptionTest.php @@ -27,6 +27,14 @@ class DeleteShortUrlExceptionTest extends TestCase $this->assertEquals($threshold, $e->getVisitsThreshold()); $this->assertEquals($expectedMessage, $e->getMessage()); + $this->assertEquals($expectedMessage, $e->getDetail()); + $this->assertEquals([ + 'shortCode' => $shortCode, + 'threshold' => $threshold, + ], $e->getAdditionalData()); + $this->assertEquals('Cannot delete short URL', $e->getTitle()); + $this->assertEquals('INVALID_SHORTCODE_DELETION', $e->getType()); + $this->assertEquals(422, $e->getStatus()); } public function provideThresholds(): array diff --git a/module/Core/test/Exception/InvalidUrlExceptionTest.php b/module/Core/test/Exception/InvalidUrlExceptionTest.php index 1b7de449..cb0a08bc 100644 --- a/module/Core/test/Exception/InvalidUrlExceptionTest.php +++ b/module/Core/test/Exception/InvalidUrlExceptionTest.php @@ -10,6 +10,8 @@ use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Throwable; +use function sprintf; + class InvalidUrlExceptionTest extends TestCase { /** @@ -18,10 +20,17 @@ class InvalidUrlExceptionTest extends TestCase */ public function properlyCreatesExceptionFromUrl(?Throwable $prev): void { - $e = InvalidUrlException::fromUrl('http://the_url.com', $prev); + $url = 'http://the_url.com'; + $expectedMessage = sprintf('Provided URL %s is invalid. Try with a different one.', $url); + $e = InvalidUrlException::fromUrl($url, $prev); - $this->assertEquals('Provided URL http://the_url.com is invalid. Try with a different one.', $e->getMessage()); + $this->assertEquals($expectedMessage, $e->getMessage()); + $this->assertEquals($expectedMessage, $e->getDetail()); + $this->assertEquals('Invalid URL', $e->getTitle()); + $this->assertEquals('INVALID_URL', $e->getType()); + $this->assertEquals(['url' => $url], $e->getAdditionalData()); $this->assertEquals(StatusCodeInterface::STATUS_BAD_REQUEST, $e->getCode()); + $this->assertEquals(StatusCodeInterface::STATUS_BAD_REQUEST, $e->getStatus()); $this->assertEquals($prev, $e->getPrevious()); } diff --git a/module/Core/test/Exception/NonUniqueSlugExceptionTest.php b/module/Core/test/Exception/NonUniqueSlugExceptionTest.php index d2008621..00efa3cf 100644 --- a/module/Core/test/Exception/NonUniqueSlugExceptionTest.php +++ b/module/Core/test/Exception/NonUniqueSlugExceptionTest.php @@ -15,8 +15,19 @@ class NonUniqueSlugExceptionTest extends TestCase */ public function properlyCreatesExceptionFromSlug(string $expectedMessage, string $slug, ?string $domain): void { + $expectedAdditional = ['customSlug' => $slug]; + if ($domain !== null) { + $expectedAdditional['domain'] = $domain; + } + $e = NonUniqueSlugException::fromSlug($slug, $domain); + $this->assertEquals($expectedMessage, $e->getMessage()); + $this->assertEquals($expectedMessage, $e->getDetail()); + $this->assertEquals('Invalid custom slug', $e->getTitle()); + $this->assertEquals('INVALID_SLUG', $e->getType()); + $this->assertEquals(400, $e->getStatus()); + $this->assertEquals($expectedAdditional, $e->getAdditionalData()); } public function provideMessages(): iterable diff --git a/module/Core/test/Exception/ShortUrlNotFoundExceptionTest.php b/module/Core/test/Exception/ShortUrlNotFoundExceptionTest.php index 6f0d58f4..be02a66c 100644 --- a/module/Core/test/Exception/ShortUrlNotFoundExceptionTest.php +++ b/module/Core/test/Exception/ShortUrlNotFoundExceptionTest.php @@ -18,8 +18,19 @@ class ShortUrlNotFoundExceptionTest extends TestCase string $shortCode, ?string $domain ): void { + $expectedAdditional = ['shortCode' => $shortCode]; + if ($domain !== null) { + $expectedAdditional['domain'] = $domain; + } + $e = ShortUrlNotFoundException::fromNotFoundShortCode($shortCode, $domain); + $this->assertEquals($expectedMessage, $e->getMessage()); + $this->assertEquals($expectedMessage, $e->getDetail()); + $this->assertEquals('Short URL not found', $e->getTitle()); + $this->assertEquals('INVALID_SHORTCODE', $e->getType()); + $this->assertEquals(404, $e->getStatus()); + $this->assertEquals($expectedAdditional, $e->getAdditionalData()); } public function provideMessages(): iterable diff --git a/module/Core/test/Exception/TagNotFoundExceptionTest.php b/module/Core/test/Exception/TagNotFoundExceptionTest.php new file mode 100644 index 00000000..ccee7b38 --- /dev/null +++ b/module/Core/test/Exception/TagNotFoundExceptionTest.php @@ -0,0 +1,28 @@ +assertEquals($expectedMessage, $e->getMessage()); + $this->assertEquals($expectedMessage, $e->getDetail()); + $this->assertEquals('Tag not found', $e->getTitle()); + $this->assertEquals('TAG_NOT_FOUND', $e->getType()); + $this->assertEquals(['tag' => $tag], $e->getAdditionalData()); + $this->assertEquals(404, $e->getStatus()); + } +} diff --git a/module/Rest/src/ConfigProvider.php b/module/Rest/src/ConfigProvider.php index d942cf51..0c0e99a5 100644 --- a/module/Rest/src/ConfigProvider.php +++ b/module/Rest/src/ConfigProvider.php @@ -4,19 +4,26 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest; -use Zend\Config\Factory; -use Zend\Stdlib\Glob; - +use function Shlinkio\Shlink\Common\loadConfigFromGlob; use function sprintf; class ConfigProvider { private const ROUTES_PREFIX = '/rest/v{version:1|2}'; + /** @var callable */ + private $loadConfig; + + public function __construct(?callable $loadConfig = null) + { + $this->loadConfig = $loadConfig ?? function (string $glob) { + return loadConfigFromGlob($glob); + }; + } + public function __invoke() { - /** @var array $config */ - $config = Factory::fromFiles(Glob::glob(__DIR__ . '/../config/{,*.}config.php', Glob::GLOB_BRACE)); + $config = ($this->loadConfig)(__DIR__ . '/../config/{,*.}config.php'); return $this->applyRoutesPrefix($config); } diff --git a/module/Rest/src/Exception/MissingAuthenticationException.php b/module/Rest/src/Exception/MissingAuthenticationException.php index 6ed76e2a..c00cb3e0 100644 --- a/module/Rest/src/Exception/MissingAuthenticationException.php +++ b/module/Rest/src/Exception/MissingAuthenticationException.php @@ -21,7 +21,7 @@ class MissingAuthenticationException extends RuntimeException implements Problem public static function fromExpectedTypes(array $expectedTypes): self { $e = new self(sprintf( - 'Expected one of the following authentication headers, but none were provided, ["%s"]', + 'Expected one of the following authentication headers, ["%s"], but none were provided', implode('", "', $expectedTypes) )); diff --git a/module/Rest/test-api/Middleware/AuthenticationTest.php b/module/Rest/test-api/Middleware/AuthenticationTest.php index 3526c5f5..d92f4a44 100644 --- a/module/Rest/test-api/Middleware/AuthenticationTest.php +++ b/module/Rest/test-api/Middleware/AuthenticationTest.php @@ -17,7 +17,7 @@ class AuthenticationTest extends ApiTestCase public function authorizationErrorIsReturnedIfNoApiKeyIsSent(): void { $expectedDetail = sprintf( - 'Expected one of the following authentication headers, but none were provided, ["%s"]', + 'Expected one of the following authentication headers, ["%s"], but none were provided', implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS) ); diff --git a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php index 92a3c2aa..37f737f9 100644 --- a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php @@ -4,14 +4,17 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; +use Cake\Chronos\Chronos; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\ValidationException; +use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Rest\Action\ShortUrl\CreateShortUrlAction; use Zend\Diactoros\ServerRequest; +use Zend\Diactoros\ServerRequestFactory; use Zend\Diactoros\Uri; use function strpos; @@ -41,20 +44,41 @@ class CreateShortUrlActionTest extends TestCase $this->action->handle(new ServerRequest()); } - /** @test */ - public function properShortcodeConversionReturnsData(): void + /** + * @test + * @dataProvider provideRequestBodies + */ + public function properShortcodeConversionReturnsData(array $body, ShortUrlMeta $expectedMeta): void { $shortUrl = new ShortUrl(''); - $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), Argument::cetera()) - ->willReturn($shortUrl) - ->shouldBeCalledOnce(); + $shorten = $this->urlShortener->urlToShortCode( + Argument::type(Uri::class), + Argument::type('array'), + $expectedMeta + )->willReturn($shortUrl); - $request = (new ServerRequest())->withParsedBody([ - 'longUrl' => 'http://www.domain.com/foo/bar', - ]); + $request = ServerRequestFactory::fromGlobals()->withParsedBody($body); $response = $this->action->handle($request); + $this->assertEquals(200, $response->getStatusCode()); $this->assertTrue(strpos($response->getBody()->getContents(), $shortUrl->toString(self::DOMAIN_CONFIG)) > 0); + $shorten->shouldHaveBeenCalledOnce(); + } + + public function provideRequestBodies(): iterable + { + $fullMeta = [ + 'longUrl' => 'http://www.domain.com/foo/bar', + 'validSince' => Chronos::now()->toAtomString(), + 'validUntil' => Chronos::now()->toAtomString(), + 'customSlug' => 'foo-bar-baz', + 'maxVisits' => 50, + 'findIfExists' => true, + 'domain' => 'my-domain.com', + ]; + + yield [['longUrl' => 'http://www.domain.com/foo/bar'], ShortUrlMeta::createEmpty()]; + yield [$fullMeta, ShortUrlMeta::createFromRawData($fullMeta)]; } /** diff --git a/module/Rest/test/Authentication/RequestToAuthPluginTest.php b/module/Rest/test/Authentication/RequestToAuthPluginTest.php index 8d11b4d8..a49a4e19 100644 --- a/module/Rest/test/Authentication/RequestToAuthPluginTest.php +++ b/module/Rest/test/Authentication/RequestToAuthPluginTest.php @@ -37,7 +37,7 @@ class RequestToAuthPluginTest extends TestCase $this->expectException(MissingAuthenticationException::class); $this->expectExceptionMessage(sprintf( - 'Expected one of the following authentication headers, but none were provided, ["%s"]', + 'Expected one of the following authentication headers, ["%s"], but none were provided', implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS) )); diff --git a/module/Rest/test/ConfigProviderTest.php b/module/Rest/test/ConfigProviderTest.php index 3cd574b3..2922faad 100644 --- a/module/Rest/test/ConfigProviderTest.php +++ b/module/Rest/test/ConfigProviderTest.php @@ -20,9 +20,31 @@ class ConfigProviderTest extends TestCase /** @test */ public function properConfigIsReturned(): void { - $config = $this->configProvider->__invoke(); + $config = ($this->configProvider)(); $this->assertArrayHasKey('routes', $config); $this->assertArrayHasKey('dependencies', $config); } + + /** @test */ + public function routesAreProperlyPrefixed(): void + { + $configProvider = new ConfigProvider(function () { + return [ + 'routes' => [ + ['path' => '/foo'], + ['path' => '/bar'], + ['path' => '/baz/foo'], + ], + ]; + }); + + $config = $configProvider(); + + $this->assertEquals([ + ['path' => '/rest/v{version:1|2}/foo'], + ['path' => '/rest/v{version:1|2}/bar'], + ['path' => '/rest/v{version:1|2}/baz/foo'], + ], $config['routes']); + } } diff --git a/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php b/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php new file mode 100644 index 00000000..84c72e75 --- /dev/null +++ b/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php @@ -0,0 +1,43 @@ +assertEquals($expectedMessage, $e->getMessage()); + $this->assertEquals($expectedMessage, $e->getDetail()); + $this->assertEquals('Invalid authorization', $e->getTitle()); + $this->assertEquals('INVALID_AUTHORIZATION', $e->getType()); + $this->assertEquals(401, $e->getStatus()); + $this->assertEquals(['expectedTypes' => $expectedTypes], $e->getAdditionalData()); + } + + public function provideExpectedTypes(): iterable + { + yield [['foo', 'bar']]; + yield [['something']]; + yield [[]]; + yield [['foo', 'bar', 'baz']]; + } +} diff --git a/module/Rest/test/Middleware/BodyParserMiddlewareTest.php b/module/Rest/test/Middleware/BodyParserMiddlewareTest.php index fb69c695..829b4b59 100644 --- a/module/Rest/test/Middleware/BodyParserMiddlewareTest.php +++ b/module/Rest/test/Middleware/BodyParserMiddlewareTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Rest\Middleware; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\Prophecy\ProphecyInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Rest\Middleware\BodyParserMiddleware; @@ -31,7 +32,10 @@ class BodyParserMiddlewareTest extends TestCase */ public function requestsFromOtherMethodsJustFallbackToNextMiddleware(string $method): void { - $request = (new ServerRequest())->withMethod($method); + $request = $this->prophesize(ServerRequestInterface::class); + $request->getMethod()->willReturn($method); + $request->getParsedBody()->willReturn([]); + $this->assertHandlingRequestJustFallsBackToNext($request); } @@ -45,18 +49,25 @@ class BodyParserMiddlewareTest extends TestCase /** @test */ public function requestsWithNonEmptyBodyJustFallbackToNextMiddleware(): void { - $request = (new ServerRequest())->withParsedBody(['foo' => 'bar'])->withMethod('POST'); + $request = $this->prophesize(ServerRequestInterface::class); + $request->getMethod()->willReturn('POST'); + $request->getParsedBody()->willReturn(['foo' => 'bar']); + $this->assertHandlingRequestJustFallsBackToNext($request); } - private function assertHandlingRequestJustFallsBackToNext(ServerRequestInterface $request): void + private function assertHandlingRequestJustFallsBackToNext(ProphecyInterface $requestMock): void { + $getContentType = $requestMock->getHeaderLine('Content-type')->willReturn(''); + $request = $requestMock->reveal(); + $nextHandler = $this->prophesize(RequestHandlerInterface::class); $handle = $nextHandler->handle($request)->willReturn(new Response()); $this->middleware->process($request, $nextHandler->reveal()); $handle->shouldHaveBeenCalledOnce(); + $getContentType->shouldNotHaveBeenCalled(); } /** @test */ diff --git a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php index ebc88080..1716c19e 100644 --- a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php +++ b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php @@ -8,12 +8,14 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Server\RequestHandlerInterface; +use Shlinkio\Shlink\Rest\Authentication; use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware; use Zend\Diactoros\Response; use Zend\Diactoros\ServerRequest; use Zend\Expressive\Router\Route; use Zend\Expressive\Router\RouteResult; +use function implode; use function Zend\Stratigility\middleware; class CrossDomainMiddlewareTest extends TestCase @@ -39,6 +41,7 @@ class CrossDomainMiddlewareTest extends TestCase $this->assertSame($originalResponse, $response); $headers = $response->getHeaders(); + $this->assertArrayNotHasKey('Access-Control-Allow-Origin', $headers); $this->assertArrayNotHasKey('Access-Control-Expose-Headers', $headers); $this->assertArrayNotHasKey('Access-Control-Allow-Methods', $headers); @@ -59,8 +62,12 @@ class CrossDomainMiddlewareTest extends TestCase $this->assertNotSame($originalResponse, $response); $headers = $response->getHeaders(); - $this->assertArrayHasKey('Access-Control-Allow-Origin', $headers); - $this->assertArrayHasKey('Access-Control-Expose-Headers', $headers); + + $this->assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin')); + $this->assertEquals(implode(', ', [ + Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME, + Authentication\Plugin\AuthorizationHeaderPlugin::HEADER_NAME, + ]), $response->getHeaderLine('Access-Control-Expose-Headers')); $this->assertArrayNotHasKey('Access-Control-Allow-Methods', $headers); $this->assertArrayNotHasKey('Access-Control-Max-Age', $headers); $this->assertArrayNotHasKey('Access-Control-Allow-Headers', $headers); @@ -70,18 +77,25 @@ class CrossDomainMiddlewareTest extends TestCase public function optionsRequestIncludesMoreHeaders(): void { $originalResponse = new Response(); - $request = (new ServerRequest())->withMethod('OPTIONS')->withHeader('Origin', 'local'); + $request = (new ServerRequest()) + ->withMethod('OPTIONS') + ->withHeader('Origin', 'local') + ->withHeader('Access-Control-Request-Headers', 'foo, bar, baz'); $this->handler->handle(Argument::any())->willReturn($originalResponse)->shouldBeCalledOnce(); $response = $this->middleware->process($request, $this->handler->reveal()); $this->assertNotSame($originalResponse, $response); $headers = $response->getHeaders(); - $this->assertArrayHasKey('Access-Control-Allow-Origin', $headers); - $this->assertArrayHasKey('Access-Control-Expose-Headers', $headers); + + $this->assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin')); + $this->assertEquals(implode(', ', [ + Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME, + Authentication\Plugin\AuthorizationHeaderPlugin::HEADER_NAME, + ]), $response->getHeaderLine('Access-Control-Expose-Headers')); $this->assertArrayHasKey('Access-Control-Allow-Methods', $headers); - $this->assertArrayHasKey('Access-Control-Max-Age', $headers); - $this->assertArrayHasKey('Access-Control-Allow-Headers', $headers); + $this->assertEquals('1000', $response->getHeaderLine('Access-Control-Max-Age')); + $this->assertEquals('foo, bar, baz', $response->getHeaderLine('Access-Control-Allow-Headers')); } /** diff --git a/module/Rest/test/Service/ApiKeyServiceTest.php b/module/Rest/test/Service/ApiKeyServiceTest.php index d79cea41..caa50bf1 100644 --- a/module/Rest/test/Service/ApiKeyServiceTest.php +++ b/module/Rest/test/Service/ApiKeyServiceTest.php @@ -27,65 +27,49 @@ class ApiKeyServiceTest extends TestCase $this->service = new ApiKeyService($this->em->reveal()); } - /** @test */ - public function keyIsProperlyCreated() + /** + * @test + * @dataProvider provideCreationDate + */ + public function apiKeyIsProperlyCreated(?Chronos $date): void { $this->em->flush()->shouldBeCalledOnce(); $this->em->persist(Argument::type(ApiKey::class))->shouldBeCalledOnce(); - $key = $this->service->create(); - $this->assertNull($key->getExpirationDate()); - } - - /** @test */ - public function keyIsProperlyCreatedWithExpirationDate() - { - $this->em->flush()->shouldBeCalledOnce(); - $this->em->persist(Argument::type(ApiKey::class))->shouldBeCalledOnce(); - - $date = Chronos::parse('2030-01-01'); $key = $this->service->create($date); - $this->assertSame($date, $key->getExpirationDate()); + + $this->assertEquals($date, $key->getExpirationDate()); } - /** @test */ - public function checkReturnsFalseWhenKeyIsInvalid() + public function provideCreationDate(): iterable + { + yield 'no expiration date' => [null]; + yield 'expiration date' => [Chronos::parse('2030-01-01')]; + } + + /** + * @test + * @dataProvider provideInvalidApiKeys + */ + public function checkReturnsFalseForInvalidApiKeys(?ApiKey $invalidKey): void { $repo = $this->prophesize(EntityRepository::class); - $repo->findOneBy(['key' => '12345'])->willReturn(null) + $repo->findOneBy(['key' => '12345'])->willReturn($invalidKey) ->shouldBeCalledOnce(); $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); $this->assertFalse($this->service->check('12345')); } - /** @test */ - public function checkReturnsFalseWhenKeyIsDisabled() + public function provideInvalidApiKeys(): iterable { - $key = new ApiKey(); - $key->disable(); - $repo = $this->prophesize(EntityRepository::class); - $repo->findOneBy(['key' => '12345'])->willReturn($key) - ->shouldBeCalledOnce(); - $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); - - $this->assertFalse($this->service->check('12345')); + yield 'non-existent api key' => [null]; + yield 'disabled api key' => [(new ApiKey())->disable()]; + yield 'expired api key' => [new ApiKey(Chronos::now()->subDay())]; } /** @test */ - public function checkReturnsFalseWhenKeyIsExpired() - { - $key = new ApiKey(Chronos::now()->subDay()); - $repo = $this->prophesize(EntityRepository::class); - $repo->findOneBy(['key' => '12345'])->willReturn($key) - ->shouldBeCalledOnce(); - $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); - - $this->assertFalse($this->service->check('12345')); - } - - /** @test */ - public function checkReturnsTrueWhenConditionsAreFavorable() + public function checkReturnsTrueWhenConditionsAreFavorable(): void { $repo = $this->prophesize(EntityRepository::class); $repo->findOneBy(['key' => '12345'])->willReturn(new ApiKey()) @@ -96,7 +80,7 @@ class ApiKeyServiceTest extends TestCase } /** @test */ - public function disableThrowsExceptionWhenNoTokenIsFound() + public function disableThrowsExceptionWhenNoApiKeyIsFound(): void { $repo = $this->prophesize(EntityRepository::class); $repo->findOneBy(['key' => '12345'])->willReturn(null) @@ -104,11 +88,12 @@ class ApiKeyServiceTest extends TestCase $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); $this->expectException(InvalidArgumentException::class); + $this->service->disable('12345'); } /** @test */ - public function disableReturnsDisabledKeyWhenFOund() + public function disableReturnsDisabledApiKeyWhenFound(): void { $key = new ApiKey(); $repo = $this->prophesize(EntityRepository::class); @@ -125,24 +110,32 @@ class ApiKeyServiceTest extends TestCase } /** @test */ - public function listFindsAllApiKeys() + public function listFindsAllApiKeys(): void { + $expectedApiKeys = [new ApiKey(), new ApiKey(), new ApiKey()]; + $repo = $this->prophesize(EntityRepository::class); - $repo->findBy([])->willReturn([]) + $repo->findBy([])->willReturn($expectedApiKeys) ->shouldBeCalledOnce(); $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); - $this->service->listKeys(); + $result = $this->service->listKeys(); + + $this->assertEquals($expectedApiKeys, $result); } /** @test */ - public function listEnabledFindsOnlyEnabledApiKeys() + public function listEnabledFindsOnlyEnabledApiKeys(): void { + $expectedApiKeys = [new ApiKey(), new ApiKey(), new ApiKey()]; + $repo = $this->prophesize(EntityRepository::class); - $repo->findBy(['enabled' => true])->willReturn([]) + $repo->findBy(['enabled' => true])->willReturn($expectedApiKeys) ->shouldBeCalledOnce(); $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); - $this->service->listKeys(true); + $result = $this->service->listKeys(true); + + $this->assertEquals($expectedApiKeys, $result); } }