Added some API tests for v3 API errors

This commit is contained in:
Alejandro Celaya 2022-08-13 17:48:55 +02:00
parent 905f51fbd0
commit 40bbcb3250
8 changed files with 105 additions and 20 deletions

View file

@ -10,8 +10,8 @@ return [
'problem-details' => [
'default_types_map' => [
404 => 'NOT_FOUND',
500 => 'INTERNAL_SERVER_ERROR',
404 => 'NOT_FOUND', // TODO Define new values, with backwards compatibility if possible
500 => 'INTERNAL_SERVER_ERROR', // TODO Define new values, with backwards compatibility if possible
],
],

View file

@ -26,7 +26,6 @@ return [
'path' => '/rest',
'middleware' => [
ProblemDetails\ProblemDetailsMiddleware::class,
Rest\Middleware\ErrorHandler\BackwardsCompatibleProblemDetailsHandler::class,
],
],
@ -46,6 +45,7 @@ return [
'rest' => [
'path' => '/rest',
'middleware' => [
Rest\Middleware\ErrorHandler\BackwardsCompatibleProblemDetailsHandler::class,
Router\Middleware\ImplicitOptionsMiddleware::class,
Rest\Middleware\BodyParserMiddleware::class,
Rest\Middleware\AuthenticationMiddleware::class,

View file

@ -6,6 +6,7 @@
"schema": {
"type": "string",
"enum": [
"3",
"2",
"1"
]

View file

@ -3,7 +3,7 @@
"info": {
"title": "Shlink",
"description": "Shlink, the self-hosted URL shortener",
"version": "2.0"
"version": "3.0"
},
"externalDocs": {

View file

@ -60,6 +60,25 @@ class CreateShortUrlTest extends ApiTestCase
}
}
/**
* @test
* @dataProvider provideDuplicatedSlugApiVersions
*/
public function expectedTypeIsReturnedForConflictingSlugBasedOnApiVersion(
string $version,
string $expectedType,
): void {
[, $payload] = $this->createShortUrl(['customSlug' => 'custom'], version: $version);
self::assertEquals($expectedType, $payload['type']);
}
public function provideDuplicatedSlugApiVersions(): iterable
{
yield ['1', 'INVALID_SLUG'];
yield ['2', 'INVALID_SLUG'];
yield ['3', 'https://shlink.io/api/error/non-unique-slug'];
}
/**
* @test
* @dataProvider provideTags
@ -226,15 +245,15 @@ class CreateShortUrlTest extends ApiTestCase
* @test
* @dataProvider provideInvalidUrls
*/
public function failsToCreateShortUrlWithInvalidLongUrl(string $url): void
public function failsToCreateShortUrlWithInvalidLongUrl(string $url, string $version, string $expectedType): void
{
$expectedDetail = sprintf('Provided URL %s is invalid. Try with a different one.', $url);
[$statusCode, $payload] = $this->createShortUrl(['longUrl' => $url, 'validateUrl' => true]);
[$statusCode, $payload] = $this->createShortUrl(['longUrl' => $url, 'validateUrl' => true], version: $version);
self::assertEquals(self::STATUS_BAD_REQUEST, $statusCode);
self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
self::assertEquals('INVALID_URL', $payload['type']);
self::assertEquals($expectedType, $payload['type']);
self::assertEquals($expectedDetail, $payload['detail']);
self::assertEquals('Invalid URL', $payload['title']);
self::assertEquals($url, $payload['url']);
@ -242,23 +261,37 @@ class CreateShortUrlTest extends ApiTestCase
public function provideInvalidUrls(): iterable
{
yield 'empty URL' => [''];
yield 'non-reachable URL' => ['https://this-has-to-be-invalid.com'];
yield 'empty URL' => ['', '2', 'INVALID_URL'];
yield 'non-reachable URL' => ['https://this-has-to-be-invalid.com', '2', 'INVALID_URL'];
yield 'API version 3' => ['', '3', 'https://shlink.io/api/error/invalid-url'];
}
/** @test */
public function failsToCreateShortUrlWithoutLongUrl(): void
/**
* @test
* @dataProvider provideInvalidArgumentApiVersions
*/
public function failsToCreateShortUrlWithoutLongUrl(string $version, string $expectedType): void
{
$resp = $this->callApiWithKey(self::METHOD_POST, '/short-urls', [RequestOptions::JSON => []]);
$resp = $this->callApiWithKey(
self::METHOD_POST,
sprintf('/rest/v%s/short-urls', $version),
[RequestOptions::JSON => []],
);
$payload = $this->getJsonResponsePayload($resp);
self::assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode());
self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']);
self::assertEquals('INVALID_ARGUMENT', $payload['type']);
self::assertEquals($expectedType, $payload['type']);
self::assertEquals('Provided data is not valid', $payload['detail']);
self::assertEquals('Invalid data', $payload['title']);
}
public function provideInvalidArgumentApiVersions(): iterable
{
yield ['2', 'INVALID_ARGUMENT'];
yield ['3', 'https://shlink.io/api/error/invalid-data'];
}
/** @test */
public function defaultDomainIsDroppedIfProvided(): void
{
@ -332,12 +365,17 @@ class CreateShortUrlTest extends ApiTestCase
/**
* @return array{int $statusCode, array $payload}
*/
private function createShortUrl(array $body = [], string $apiKey = 'valid_api_key'): array
private function createShortUrl(array $body = [], string $apiKey = 'valid_api_key', string $version = '2'): array
{
if (! isset($body['longUrl'])) {
$body['longUrl'] = 'https://app.shlink.io';
}
$resp = $this->callApiWithKey(self::METHOD_POST, '/short-urls', [RequestOptions::JSON => $body], $apiKey);
$resp = $this->callApiWithKey(
self::METHOD_POST,
sprintf('/rest/v%s/short-urls', $version),
[RequestOptions::JSON => $body],
$apiKey,
);
$payload = $this->getJsonResponsePayload($resp);
return [$resp->getStatusCode(), $payload];

View file

@ -7,6 +7,8 @@ namespace ShlinkioApiTest\Shlink\Rest\Action;
use Shlinkio\Shlink\TestUtils\ApiTest\ApiTestCase;
use ShlinkioApiTest\Shlink\Rest\Utils\NotFoundUrlHelpersTrait;
use function sprintf;
class DeleteShortUrlTest extends ApiTestCase
{
use NotFoundUrlHelpersTrait;
@ -33,6 +35,28 @@ class DeleteShortUrlTest extends ApiTestCase
self::assertEquals($domain, $payload['domain'] ?? null);
}
/**
* @test
* @dataProvider provideApiVersions
*/
public function expectedTypeIsReturnedBasedOnApiVersion(string $version, string $expectedType): void
{
$resp = $this->callApiWithKey(
self::METHOD_DELETE,
sprintf('/rest/v%s/short-urls/invalid-short-code', $version),
);
$payload = $this->getJsonResponsePayload($resp);
self::assertEquals($expectedType, $payload['type']);
}
public function provideApiVersions(): iterable
{
yield ['1', 'INVALID_SHORTCODE'];
yield ['2', 'INVALID_SHORTCODE'];
yield ['3', 'https://shlink.io/api/error/short-url-not-found'];
}
/** @test */
public function properShortUrlIsDeletedWhenDomainIsProvided(): void
{

View file

@ -7,29 +7,32 @@ namespace ShlinkioApiTest\Shlink\Rest\Action;
use GuzzleHttp\RequestOptions;
use Shlinkio\Shlink\TestUtils\ApiTest\ApiTestCase;
use function sprintf;
class DeleteTagsTest extends ApiTestCase
{
/**
* @test
* @dataProvider provideNonAdminApiKeys
*/
public function anErrorIsReturnedWithNonAdminApiKeys(string $apiKey): void
public function anErrorIsReturnedWithNonAdminApiKeys(string $apiKey, string $version, string $expectedType): void
{
$resp = $this->callApiWithKey(self::METHOD_DELETE, '/tags', [
$resp = $this->callApiWithKey(self::METHOD_DELETE, sprintf('/rest/v%s/tags', $version), [
RequestOptions::QUERY => ['tags' => ['foo']],
], $apiKey);
$payload = $this->getJsonResponsePayload($resp);
self::assertEquals(self::STATUS_FORBIDDEN, $resp->getStatusCode());
self::assertEquals(self::STATUS_FORBIDDEN, $payload['status']);
self::assertEquals('FORBIDDEN_OPERATION', $payload['type']);
self::assertEquals($expectedType, $payload['type']);
self::assertEquals('You are not allowed to delete tags', $payload['detail']);
self::assertEquals('Forbidden tag operation', $payload['title']);
}
public function provideNonAdminApiKeys(): iterable
{
yield 'author' => ['author_api_key'];
yield 'domain' => ['domain_api_key'];
yield 'author' => ['author_api_key', '2', 'FORBIDDEN_OPERATION'];
yield 'domain' => ['domain_api_key', '2', 'FORBIDDEN_OPERATION'];
yield 'version 3' => ['domain_api_key', '3', 'https://shlink.io/api/error/forbidden-tag-operation'];
}
}

View file

@ -65,4 +65,23 @@ class DomainVisitsTest extends ApiTestCase
yield 'domain API key with not-owned valid domain' => ['domain_api_key', 'this_domain_is_detached.com'];
yield 'author API key with valid domain not used in URLs' => ['author_api_key', 'this_domain_is_detached.com'];
}
/**
* @test
* @dataProvider provideApiVersions
*/
public function expectedNotFoundTypeIsReturnedForApiVersion(string $version, string $expectedType): void
{
$resp = $this->callApiWithKey(self::METHOD_GET, sprintf('/rest/v%s/domains/invalid.com/visits', $version));
$payload = $this->getJsonResponsePayload($resp);
self::assertEquals($expectedType, $payload['type']);
}
public function provideApiVersions(): iterable
{
yield ['1', 'DOMAIN_NOT_FOUND'];
yield ['2', 'DOMAIN_NOT_FOUND'];
yield ['3', 'https://shlink.io/api/error/domain-not-found'];
}
}