Added more strict checks on API errors tests

This commit is contained in:
Alejandro Celaya 2019-11-27 20:48:35 +01:00
parent 5266743a0c
commit d83d2f82bd
8 changed files with 156 additions and 40 deletions

View File

@ -10,6 +10,7 @@ use Shlinkio\Shlink\TestUtils\ApiTest\ApiTestCase;
use function Functional\map; use function Functional\map;
use function range; use function range;
use function sprintf;
class CreateShortUrlActionTest extends ApiTestCase class CreateShortUrlActionTest extends ApiTestCase
{ {
@ -40,10 +41,25 @@ class CreateShortUrlActionTest extends ApiTestCase
*/ */
public function failsToCreateShortUrlWithDuplicatedSlug(string $slug, ?string $domain): void public function failsToCreateShortUrlWithDuplicatedSlug(string $slug, ?string $domain): void
{ {
$suffix = $domain === null ? '' : sprintf(' for domain "%s"', $domain);
$detail = sprintf('Provided slug "%s" is already in use%s.', $slug, $suffix);
[$statusCode, $payload] = $this->createShortUrl(['customSlug' => $slug, 'domain' => $domain]); [$statusCode, $payload] = $this->createShortUrl(['customSlug' => $slug, 'domain' => $domain]);
$this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode); $this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode);
$this->assertEquals('INVALID_SLUG', $payload['error']); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
$this->assertEquals($detail, $payload['detail']);
$this->assertEquals($detail, $payload['message']); // Deprecated
$this->assertEquals('INVALID_SLUG', $payload['type']);
$this->assertEquals('INVALID_SLUG', $payload['error']); // Deprecated
$this->assertEquals('Invalid custom slug', $payload['title']);
$this->assertEquals($slug, $payload['customSlug']);
if ($domain !== null) {
$this->assertEquals($domain, $payload['domain']);
} else {
$this->assertArrayNotHasKey('domain', $payload);
}
} }
/** @test */ /** @test */
@ -203,10 +219,19 @@ class CreateShortUrlActionTest extends ApiTestCase
/** @test */ /** @test */
public function failsToCreateShortUrlWithInvalidOriginalUrl(): void public function failsToCreateShortUrlWithInvalidOriginalUrl(): void
{ {
[$statusCode, $payload] = $this->createShortUrl(['longUrl' => 'https://this-has-to-be-invalid.com']); $url = 'https://this-has-to-be-invalid.com';
$expectedDetail = sprintf('Provided URL %s is invalid. Try with a different one.', $url);
[$statusCode, $payload] = $this->createShortUrl(['longUrl' => $url]);
$this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode); $this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode);
$this->assertEquals('INVALID_URL', $payload['error']); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
$this->assertEquals('INVALID_URL', $payload['type']);
$this->assertEquals('INVALID_URL', $payload['error']); // Deprecated
$this->assertEquals($expectedDetail, $payload['detail']);
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
$this->assertEquals('Invalid URL', $payload['title']);
$this->assertEquals($url, $payload['url']);
} }
/** /**

View File

@ -11,11 +11,19 @@ class DeleteShortUrlActionTest extends ApiTestCase
/** @test */ /** @test */
public function notFoundErrorIsReturnWhenDeletingInvalidUrl(): void public function notFoundErrorIsReturnWhenDeletingInvalidUrl(): void
{ {
$expectedDetail = 'No URL found with short code "invalid"';
$resp = $this->callApiWithKey(self::METHOD_DELETE, '/short-urls/invalid'); $resp = $this->callApiWithKey(self::METHOD_DELETE, '/short-urls/invalid');
['error' => $error] = $this->getJsonResponsePayload($resp); $payload = $this->getJsonResponsePayload($resp);
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
$this->assertEquals('INVALID_SHORTCODE', $error); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
$this->assertEquals($expectedDetail, $payload['detail']);
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
$this->assertEquals('Short URL not found', $payload['title']);
$this->assertEquals('invalid', $payload['shortCode']);
} }
/** @test */ /** @test */
@ -25,11 +33,17 @@ class DeleteShortUrlActionTest extends ApiTestCase
for ($i = 0; $i < 20; $i++) { for ($i = 0; $i < 20; $i++) {
$this->assertEquals(self::STATUS_FOUND, $this->callShortUrl('abc123')->getStatusCode()); $this->assertEquals(self::STATUS_FOUND, $this->callShortUrl('abc123')->getStatusCode());
} }
$expectedDetail = 'Impossible to delete short URL with short code "abc123" since it has more than "15" visits.';
$resp = $this->callApiWithKey(self::METHOD_DELETE, '/short-urls/abc123'); $resp = $this->callApiWithKey(self::METHOD_DELETE, '/short-urls/abc123');
['error' => $error] = $this->getJsonResponsePayload($resp); $payload = $this->getJsonResponsePayload($resp);
$this->assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $resp->getStatusCode()); $this->assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $resp->getStatusCode());
$this->assertEquals('INVALID_SHORTCODE_DELETION', $error); $this->assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $payload['status']);
$this->assertEquals('INVALID_SHORTCODE_DELETION', $payload['type']);
$this->assertEquals('INVALID_SHORTCODE_DELETION', $payload['error']); // Deprecated
$this->assertEquals($expectedDetail, $payload['detail']);
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
$this->assertEquals('Cannot delete short URL', $payload['title']);
} }
} }

View File

@ -12,22 +12,37 @@ class EditShortUrlActionTest extends ApiTestCase
/** @test */ /** @test */
public function tryingToEditInvalidUrlReturnsNotFoundError(): void public function tryingToEditInvalidUrlReturnsNotFoundError(): void
{ {
$expectedDetail = 'No URL found with short code "invalid"';
$resp = $this->callApiWithKey(self::METHOD_PATCH, '/short-urls/invalid', [RequestOptions::JSON => []]); $resp = $this->callApiWithKey(self::METHOD_PATCH, '/short-urls/invalid', [RequestOptions::JSON => []]);
['error' => $error] = $this->getJsonResponsePayload($resp); $payload = $this->getJsonResponsePayload($resp);
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
$this->assertEquals('INVALID_SHORTCODE', $error); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
$this->assertEquals($expectedDetail, $payload['detail']);
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
$this->assertEquals('Short URL not found', $payload['title']);
$this->assertEquals('invalid', $payload['shortCode']);
} }
/** @test */ /** @test */
public function providingInvalidDataReturnsBadRequest(): void public function providingInvalidDataReturnsBadRequest(): void
{ {
$expectedDetail = 'Provided data is not valid';
$resp = $this->callApiWithKey(self::METHOD_PATCH, '/short-urls/invalid', [RequestOptions::JSON => [ $resp = $this->callApiWithKey(self::METHOD_PATCH, '/short-urls/invalid', [RequestOptions::JSON => [
'maxVisits' => 'not_a_number', 'maxVisits' => 'not_a_number',
]]); ]]);
['error' => $error] = $this->getJsonResponsePayload($resp); $payload = $this->getJsonResponsePayload($resp);
$this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); $this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode());
$this->assertEquals('INVALID_ARGUMENT', $error); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
$this->assertEquals('INVALID_ARGUMENT', $payload['type']);
$this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated
$this->assertEquals($expectedDetail, $payload['detail']);
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
$this->assertEquals('Invalid data', $payload['title']);
} }
} }

View File

@ -12,22 +12,37 @@ class EditShortUrlTagsActionTest extends ApiTestCase
/** @test */ /** @test */
public function notProvidingTagsReturnsBadRequest(): void public function notProvidingTagsReturnsBadRequest(): void
{ {
$expectedDetail = 'Provided data is not valid';
$resp = $this->callApiWithKey(self::METHOD_PUT, '/short-urls/abc123/tags', [RequestOptions::JSON => []]); $resp = $this->callApiWithKey(self::METHOD_PUT, '/short-urls/abc123/tags', [RequestOptions::JSON => []]);
['error' => $error] = $this->getJsonResponsePayload($resp); $payload = $this->getJsonResponsePayload($resp);
$this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); $this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode());
$this->assertEquals('INVALID_ARGUMENT', $error); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
$this->assertEquals('INVALID_ARGUMENT', $payload['type']);
$this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated
$this->assertEquals($expectedDetail, $payload['detail']);
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
$this->assertEquals('Invalid data', $payload['title']);
} }
/** @test */ /** @test */
public function providingInvalidShortCodeReturnsBadRequest(): void public function providingInvalidShortCodeReturnsBadRequest(): void
{ {
$expectedDetail = 'No URL found with short code "invalid"';
$resp = $this->callApiWithKey(self::METHOD_PUT, '/short-urls/invalid/tags', [RequestOptions::JSON => [ $resp = $this->callApiWithKey(self::METHOD_PUT, '/short-urls/invalid/tags', [RequestOptions::JSON => [
'tags' => ['foo', 'bar'], 'tags' => ['foo', 'bar'],
]]); ]]);
['error' => $error] = $this->getJsonResponsePayload($resp); $payload = $this->getJsonResponsePayload($resp);
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
$this->assertEquals('INVALID_SHORTCODE', $error); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
$this->assertEquals($expectedDetail, $payload['detail']);
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
$this->assertEquals('Short URL not found', $payload['title']);
$this->assertEquals('invalid', $payload['shortCode']);
} }
} }

View File

@ -11,10 +11,18 @@ class GetVisitsActionTest extends ApiTestCase
/** @test */ /** @test */
public function tryingToGetVisitsForInvalidUrlReturnsNotFoundError(): void public function tryingToGetVisitsForInvalidUrlReturnsNotFoundError(): void
{ {
$expectedDetail = 'No URL found with short code "invalid"';
$resp = $this->callApiWithKey(self::METHOD_GET, '/short-urls/invalid/visits'); $resp = $this->callApiWithKey(self::METHOD_GET, '/short-urls/invalid/visits');
['error' => $error] = $this->getJsonResponsePayload($resp); $payload = $this->getJsonResponsePayload($resp);
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
$this->assertEquals('INVALID_SHORTCODE', $error); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
$this->assertEquals($expectedDetail, $payload['detail']);
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
$this->assertEquals('Short URL not found', $payload['title']);
$this->assertEquals('invalid', $payload['shortCode']);
} }
} }

View File

@ -11,10 +11,18 @@ class ResolveShortUrlActionTest extends ApiTestCase
/** @test */ /** @test */
public function tryingToResolveInvalidUrlReturnsNotFoundError(): void public function tryingToResolveInvalidUrlReturnsNotFoundError(): void
{ {
$expectedDetail = 'No URL found with short code "invalid"';
$resp = $this->callApiWithKey(self::METHOD_GET, '/short-urls/invalid'); $resp = $this->callApiWithKey(self::METHOD_GET, '/short-urls/invalid');
['error' => $error] = $this->getJsonResponsePayload($resp); $payload = $this->getJsonResponsePayload($resp);
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
$this->assertEquals('INVALID_SHORTCODE', $error); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
$this->assertEquals('INVALID_SHORTCODE', $payload['type']);
$this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated
$this->assertEquals($expectedDetail, $payload['detail']);
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
$this->assertEquals('Short URL not found', $payload['title']);
$this->assertEquals('invalid', $payload['shortCode']);
} }
} }

View File

@ -15,11 +15,18 @@ class UpdateTagActionTest extends ApiTestCase
*/ */
public function notProvidingTagsReturnsBadRequest(array $body): void public function notProvidingTagsReturnsBadRequest(array $body): void
{ {
$expectedDetail = 'Provided data is not valid';
$resp = $this->callApiWithKey(self::METHOD_PUT, '/tags', [RequestOptions::JSON => $body]); $resp = $this->callApiWithKey(self::METHOD_PUT, '/tags', [RequestOptions::JSON => $body]);
['error' => $error] = $this->getJsonResponsePayload($resp); $payload = $this->getJsonResponsePayload($resp);
$this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); $this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode());
$this->assertEquals('INVALID_ARGUMENT', $error); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
$this->assertEquals('INVALID_ARGUMENT', $payload['type']);
$this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated
$this->assertEquals($expectedDetail, $payload['detail']);
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
$this->assertEquals('Invalid data', $payload['title']);
} }
public function provideInvalidBody(): iterable public function provideInvalidBody(): iterable
@ -32,13 +39,20 @@ class UpdateTagActionTest extends ApiTestCase
/** @test */ /** @test */
public function tryingToRenameInvalidTagReturnsNotFound(): void public function tryingToRenameInvalidTagReturnsNotFound(): void
{ {
$expectedDetail = 'Tag with name "invalid_tag" could not be found';
$resp = $this->callApiWithKey(self::METHOD_PUT, '/tags', [RequestOptions::JSON => [ $resp = $this->callApiWithKey(self::METHOD_PUT, '/tags', [RequestOptions::JSON => [
'oldName' => 'invalid_tag', 'oldName' => 'invalid_tag',
'newName' => 'foo', 'newName' => 'foo',
]]); ]]);
['error' => $error] = $this->getJsonResponsePayload($resp); $payload = $this->getJsonResponsePayload($resp);
$this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode());
$this->assertEquals('TAG_NOT_FOUND', $error); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']);
$this->assertEquals('TAG_NOT_FOUND', $payload['type']);
$this->assertEquals('TAG_NOT_FOUND', $payload['error']); // Deprecated
$this->assertEquals($expectedDetail, $payload['detail']);
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
$this->assertEquals('Tag not found', $payload['title']);
} }
} }

View File

@ -16,18 +16,21 @@ class AuthenticationTest extends ApiTestCase
/** @test */ /** @test */
public function authorizationErrorIsReturnedIfNoApiKeyIsSent(): void public function authorizationErrorIsReturnedIfNoApiKeyIsSent(): void
{ {
$expectedDetail = sprintf(
'Expected one of the following authentication headers, but none were provided, ["%s"]',
implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS)
);
$resp = $this->callApi(self::METHOD_GET, '/short-codes'); $resp = $this->callApi(self::METHOD_GET, '/short-codes');
['error' => $error, 'message' => $message] = $this->getJsonResponsePayload($resp); $payload = $this->getJsonResponsePayload($resp);
$this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode()); $this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode());
$this->assertEquals('INVALID_AUTHORIZATION', $error); $this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']);
$this->assertEquals( $this->assertEquals('INVALID_AUTHORIZATION', $payload['type']);
sprintf( $this->assertEquals('INVALID_AUTHORIZATION', $payload['error']); // Deprecated
'Expected one of the following authentication headers, but none were provided, ["%s"]', $this->assertEquals($expectedDetail, $payload['detail']);
implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS) $this->assertEquals($expectedDetail, $payload['message']); // Deprecated
), $this->assertEquals('Invalid authorization', $payload['title']);
$message
);
} }
/** /**
@ -36,16 +39,22 @@ class AuthenticationTest extends ApiTestCase
*/ */
public function apiKeyErrorIsReturnedWhenProvidedApiKeyIsInvalid(string $apiKey): void public function apiKeyErrorIsReturnedWhenProvidedApiKeyIsInvalid(string $apiKey): void
{ {
$expectedDetail = 'Provided API key does not exist or is invalid.';
$resp = $this->callApi(self::METHOD_GET, '/short-codes', [ $resp = $this->callApi(self::METHOD_GET, '/short-codes', [
'headers' => [ 'headers' => [
Plugin\ApiKeyHeaderPlugin::HEADER_NAME => $apiKey, Plugin\ApiKeyHeaderPlugin::HEADER_NAME => $apiKey,
], ],
]); ]);
['error' => $error, 'message' => $message] = $this->getJsonResponsePayload($resp); $payload = $this->getJsonResponsePayload($resp);
$this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode()); $this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode());
$this->assertEquals('INVALID_API_KEY', $error); $this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']);
$this->assertEquals('Provided API key does not exist or is invalid.', $message); $this->assertEquals('INVALID_API_KEY', $payload['type']);
$this->assertEquals('INVALID_API_KEY', $payload['error']); // Deprecated
$this->assertEquals($expectedDetail, $payload['detail']);
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
$this->assertEquals('Invalid API key', $payload['title']);
} }
public function provideInvalidApiKeys(): iterable public function provideInvalidApiKeys(): iterable
@ -61,19 +70,24 @@ class AuthenticationTest extends ApiTestCase
*/ */
public function authorizationErrorIsReturnedIfInvalidDataIsProvided( public function authorizationErrorIsReturnedIfInvalidDataIsProvided(
string $authValue, string $authValue,
string $expectedMessage, string $expectedDetail,
string $expectedError string $expectedType,
string $expectedTitle
): void { ): void {
$resp = $this->callApi(self::METHOD_GET, '/short-codes', [ $resp = $this->callApi(self::METHOD_GET, '/short-codes', [
'headers' => [ 'headers' => [
Plugin\AuthorizationHeaderPlugin::HEADER_NAME => $authValue, Plugin\AuthorizationHeaderPlugin::HEADER_NAME => $authValue,
], ],
]); ]);
['error' => $error, 'message' => $message] = $this->getJsonResponsePayload($resp); $payload = $this->getJsonResponsePayload($resp);
$this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode()); $this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode());
$this->assertEquals($expectedError, $error); $this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']);
$this->assertEquals($expectedMessage, $message); $this->assertEquals($expectedType, $payload['type']);
$this->assertEquals($expectedType, $payload['error']); // Deprecated
$this->assertEquals($expectedDetail, $payload['detail']);
$this->assertEquals($expectedDetail, $payload['message']); // Deprecated
$this->assertEquals($expectedTitle, $payload['title']);
} }
public function provideInvalidAuthorizations(): iterable public function provideInvalidAuthorizations(): iterable
@ -82,17 +96,20 @@ class AuthenticationTest extends ApiTestCase
'invalid', 'invalid',
'You need to provide the Bearer type in the Authorization header.', 'You need to provide the Bearer type in the Authorization header.',
'INVALID_AUTHORIZATION', 'INVALID_AUTHORIZATION',
'Invalid authorization',
]; ];
yield 'invalid type' => [ yield 'invalid type' => [
'Basic invalid', 'Basic invalid',
'Provided authorization type Basic is not supported. Use Bearer instead.', 'Provided authorization type Basic is not supported. Use Bearer instead.',
'INVALID_AUTHORIZATION', 'INVALID_AUTHORIZATION',
'Invalid authorization',
]; ];
yield 'invalid JWT' => [ yield 'invalid JWT' => [
'Bearer invalid', 'Bearer invalid',
'Missing or invalid auth token provided. Perform a new authentication request and send provided ' 'Missing or invalid auth token provided. Perform a new authentication request and send provided '
. 'token on every new request on the Authorization header', . 'token on every new request on the Authorization header',
'INVALID_AUTH_TOKEN', 'INVALID_AUTH_TOKEN',
'Invalid auth token',
]; ];
} }
} }