Merge pull request #339 from acelaya/feature/api-test

Feature/api test
This commit is contained in:
Alejandro Celaya 2019-01-27 12:49:59 +01:00 committed by GitHub
commit 05695e8cd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 554 additions and 66 deletions

View File

@ -29,6 +29,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
The status code can be `200 OK` in case of success or `503 Service Unavailable` in case of error, while the `status` property will be one of `pass` or `fail`, as defined in the [Health check RFC](https://inadarei.github.io/rfc-healthcheck/).
* [#336](https://github.com/shlinkio/shlink/issues/336) Added an API test suite which performs API calls to an actual instance of the web service.
#### Changed
* [#320](https://github.com/shlinkio/shlink/issues/320) Replaced query builder by plain DQL for all queries which do not need to be dynamically generated.

14
bin/test/run-api-tests.sh Executable file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env sh
set -e
export APP_ENV=test
# Try to stop server just in case it hanged in last execution
vendor/bin/zend-expressive-swoole stop
echo 'Starting server...'
vendor/bin/zend-expressive-swoole start -d
sleep 2
vendor/bin/phpunit --order-by=random -c phpunit-api.xml
vendor/bin/zend-expressive-swoole stop

View File

@ -17,6 +17,7 @@ echo 'Copying project files...'
rm -rf "${builtcontent}"
mkdir -p "${builtcontent}"
rsync -av * "${builtcontent}" \
--exclude=bin/test \
--exclude=data/infra \
--exclude=data/travis \
--exclude=data/migrations_template.txt \
@ -28,11 +29,11 @@ rsync -av * "${builtcontent}" \
--exclude=docs \
--exclude=indocker \
--exclude=docker* \
--exclude=func_tests_bootstrap.php \
--exclude=php* \
--exclude=infection.json \
--exclude=phpstan.neon \
--exclude=config/autoload/*local* \
--exclude=config/test \
--exclude=**/test* \
--exclude=build*
cd "${builtcontent}"

View File

@ -51,6 +51,7 @@
},
"require-dev": {
"devster/ubench": "^2.0",
"doctrine/data-fixtures": "^1.3",
"filp/whoops": "^2.0",
"infection/infection": "^0.11.0",
"phpstan/phpstan": "^0.10.0",
@ -78,6 +79,7 @@
"psr-4": {
"ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test",
"ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test",
"ShlinkioApiTest\\Shlink\\Rest\\": "module/Rest/test-api",
"ShlinkioTest\\Shlink\\Core\\": [
"module/Core/test",
"module/Core/test-db"
@ -107,11 +109,13 @@
],
"test:ci": [
"@test:unit:ci",
"@test:db"
"@test:db",
"@test:api"
],
"test:unit": "phpdbg -qrr vendor/bin/phpunit --order-by=random --coverage-php build/coverage-unit.cov",
"test:unit:ci": "phpdbg -qrr vendor/bin/phpunit --order-by=random --coverage-php build/coverage-unit.cov --coverage-clover=build/clover.xml --coverage-xml=build/coverage-xml --log-junit=build/phpunit.junit.xml",
"test:db": "phpdbg -qrr vendor/bin/phpunit --order-by=random -c phpunit-db.xml --coverage-php build/coverage-db.cov",
"test:db": "APP_ENV=test phpdbg -qrr vendor/bin/phpunit --order-by=random -c phpunit-db.xml --coverage-php build/coverage-db.cov",
"test:api": "bin/test/run-api-tests.sh",
"test:pretty": [
"@test",
@ -138,6 +142,7 @@
"test:unit": "<fg=blue;options=bold>Runs unit test suites</>",
"test:unit:ci": "<fg=blue;options=bold>Runs unit test suites, generating all needed reports and logs for CI envs</>",
"test:db": "<fg=blue;options=bold>Runs database test suites (covering entity repositories)</>",
"test:api": "<fg=blue;options=bold>Runs API test suites</>",
"test:pretty": "<fg=blue;options=bold>Runs all test suites and generates an HTML code coverage report</>",
"test:unit:pretty": "<fg=blue;options=bold>Runs unit test suites and generates an HTML code coverage report</>",
"infect": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing</>",

View File

@ -6,18 +6,8 @@ use Doctrine\ORM\Tools\Console\ConsoleRunner;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\ServiceManager;
// If the "--test" flag was provided, we are on a test environment
$isTest = false;
foreach ($_SERVER['argv'] as $i => $arg) {
if ($arg === '--test') {
unset($_SERVER['argv'][$i]);
$isTest = true;
break;
}
}
/** @var ContainerInterface|ServiceManager $container */
$container = $isTest ? include __DIR__ . '/test-container.php' : include __DIR__ . '/container.php';
$container = include __DIR__ . '/container.php';
$em = $container->get(EntityManager::class);
return ConsoleRunner::createHelperSet($em);

View File

@ -6,6 +6,7 @@ namespace Shlinkio\Shlink;
use Acelaya\ExpressiveErrorHandler;
use Zend\ConfigAggregator;
use Zend\Expressive;
use function Shlinkio\Shlink\Common\env;
return (new ConfigAggregator\ConfigAggregator([
Expressive\ConfigProvider::class,
@ -21,4 +22,7 @@ return (new ConfigAggregator\ConfigAggregator([
Rest\ConfigProvider::class,
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
new ConfigAggregator\ZendConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
env('APP_ENV') === 'test'
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
: new ConfigAggregator\ArrayProvider([]),
], 'data/cache/app_config.php'))->getMergedConfig();

View File

@ -1,14 +0,0 @@
<?php
declare(strict_types=1);
$container = include __DIR__ . '/container.php';
$container->setAllowOverride(true);
$config = $container->get('config');
$config['entity_manager']['connection'] = [
'driver' => 'pdo_sqlite',
'path' => realpath(sys_get_temp_dir()) . '/shlink-tests.db',
];
$container->setService('config', $config);
return $container;

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Common;
use Doctrine\ORM\EntityManager;
use Psr\Container\ContainerInterface;
use function file_exists;
use function touch;
// Create an empty .env file
if (! file_exists('.env')) {
touch('.env');
}
/** @var ContainerInterface $container */
$container = require __DIR__ . '/../container.php';
$testHelper = $container->get(TestHelper::class);
$config = $container->get('config');
$testHelper->createTestDb();
$em = $container->get(EntityManager::class);
$testHelper->seedFixtures($em, $config['data_fixtures'] ?? []);
ApiTest\ApiTestCase::setApiClient($container->get('shlink_test_api_client'));

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Common;
use Psr\Container\ContainerInterface;
use function file_exists;
use function touch;
// Create an empty .env file
if (! file_exists('.env')) {
touch('.env');
}
/** @var ContainerInterface $container */
$container = require __DIR__ . '/../container.php';
$container->get(TestHelper::class)->createTestDb();
DbTest\DatabaseTestCase::setEntityManager($container->get('em'));

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink;
use GuzzleHttp\Client;
use Zend\ConfigAggregator\ConfigAggregator;
use Zend\ServiceManager\Factory\InvokableFactory;
use function realpath;
use function sys_get_temp_dir;
return [
'debug' => true,
ConfigAggregator::ENABLE_CACHE => false,
'url_shortener' => [
'domain' => [
'schema' => 'http',
'hostname' => 'doma.in',
],
],
'zend-expressive-swoole' => [
'swoole-http-server' => [
'port' => 9999,
'host' => '127.0.0.1',
'process-name' => 'shlink_test',
'options' => [
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
],
],
],
'dependencies' => [
'factories' => [
Common\TestHelper::class => InvokableFactory::class,
'shlink_test_api_client' => function () {
return new Client(['base_uri' => 'http://localhost:9999/']);
},
],
],
'entity_manager' => [
'connection' => [
'driver' => 'pdo_sqlite',
'path' => realpath(sys_get_temp_dir()) . '/shlink-tests.db',
],
],
'data_fixtures' => [
'paths' => [
__DIR__ . '/../../module/Rest/test-api/Fixtures',
],
],
];

View File

@ -1,26 +0,0 @@
<?php
declare(strict_types=1);
use Psr\Container\ContainerInterface;
use ShlinkioTest\Shlink\Common\DbUnit\DatabaseTestCase;
use Symfony\Component\Process\Process;
// Create an empty .env file
if (! file_exists('.env')) {
touch('.env');
}
$shlinkDbPath = realpath(sys_get_temp_dir()) . '/shlink-tests.db';
if (file_exists($shlinkDbPath)) {
unlink($shlinkDbPath);
}
/** @var ContainerInterface $container */
$container = require __DIR__ . '/config/test-container.php';
// Create database
$process = new Process(['vendor/bin/doctrine', 'orm:schema-tool:create', '--no-interaction', '-q', '--test'], __DIR__);
$process->inheritEnvironmentVariables()
->mustRun();
DatabaseTestCase::$em = $container->get('em');

View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Common\ApiTest;
use Fig\Http\Message\RequestMethodInterface;
use Fig\Http\Message\StatusCodeInterface;
use GuzzleHttp\ClientInterface;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Shlinkio\Shlink\Rest\Authentication\Plugin\ApiKeyHeaderPlugin;
use function Shlinkio\Shlink\Common\json_decode;
use function sprintf;
abstract class ApiTestCase extends TestCase implements StatusCodeInterface, RequestMethodInterface
{
private const PATH_PREFX = '/rest/v1';
/** @var ClientInterface */
private static $client;
public static function setApiClient(ClientInterface $client): void
{
self::$client = $client;
}
/**
* @throws \GuzzleHttp\Exception\GuzzleException
*/
protected function callApi(string $method, string $uri, array $options = []): ResponseInterface
{
return self::$client->request($method, sprintf('%s%s', self::PATH_PREFX, $uri), $options);
}
/**
* @throws \GuzzleHttp\Exception\GuzzleException
*/
protected function callApiWithKey(string $method, string $uri, array $options = []): ResponseInterface
{
$headers = $options['headers'] ?? [];
$headers[ApiKeyHeaderPlugin::HEADER_NAME] = 'valid_api_key';
$options['headers'] = $headers;
return $this->callApi($method, $uri, $options);
}
protected function getJsonResponsePayload(ResponseInterface $resp): array
{
return json_decode((string) $resp->getBody());
}
}

View File

@ -1,7 +1,7 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Common\DbUnit;
namespace ShlinkioTest\Shlink\Common\DbTest;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
@ -11,23 +11,26 @@ abstract class DatabaseTestCase extends TestCase
protected const ENTITIES_TO_EMPTY = [];
/** @var EntityManagerInterface */
public static $em;
private static $em;
public static function setEntityManager(EntityManagerInterface $em): void
{
self::$em = $em;
}
protected function getEntityManager(): EntityManagerInterface
{
return static::$em;
return self::$em;
}
public function tearDown()
{
// Empty all entity tables defined by this test after each test
foreach (static::ENTITIES_TO_EMPTY as $entityClass) {
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->delete($entityClass, 'x');
$qb->getQuery()->execute();
}
// Clear entity manager
$this->getEntityManager()->clear();
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Common;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Loader;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Process\Process;
use function file_exists;
use function realpath;
use function sys_get_temp_dir;
use function unlink;
class TestHelper
{
public function createTestDb(): void
{
$shlinkDbPath = realpath(sys_get_temp_dir()) . '/shlink-tests.db';
if (file_exists($shlinkDbPath)) {
unlink($shlinkDbPath);
}
$process = new Process(['vendor/bin/doctrine', 'orm:schema-tool:create', '--no-interaction', '-q']);
$process->inheritEnvironmentVariables()
->mustRun();
}
public function seedFixtures(EntityManagerInterface $em, array $config): void
{
$paths = $config['paths'] ?? [];
if (empty($paths)) {
return;
}
$loader = new Loader();
foreach ($paths as $path) {
$loader->loadFromDirectory($path);
}
$executor = new ORMExecutor($em);
$executor->execute($loader->getFixtures(), true);
}
}

View File

@ -11,7 +11,7 @@ use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
use ShlinkioTest\Shlink\Common\DbUnit\DatabaseTestCase;
use ShlinkioTest\Shlink\Common\DbTest\DatabaseTestCase;
use function count;
class ShortUrlRepositoryTest extends DatabaseTestCase

View File

@ -5,7 +5,7 @@ namespace ShlinkioTest\Shlink\Core\Repository;
use Shlinkio\Shlink\Core\Entity\Tag;
use Shlinkio\Shlink\Core\Repository\TagRepository;
use ShlinkioTest\Shlink\Common\DbUnit\DatabaseTestCase;
use ShlinkioTest\Shlink\Common\DbTest\DatabaseTestCase;
class TagRepositoryTest extends DatabaseTestCase
{

View File

@ -10,7 +10,7 @@ use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Entity\VisitLocation;
use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Repository\VisitRepository;
use ShlinkioTest\Shlink\Common\DbUnit\DatabaseTestCase;
use ShlinkioTest\Shlink\Common\DbTest\DatabaseTestCase;
use function sprintf;
class VisitRepositoryTest extends DatabaseTestCase

View File

@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace ShlinkioApiTest\Shlink\Rest\Action;
use ShlinkioTest\Shlink\Common\ApiTest\ApiTestCase;
class ListShortUrlsTest extends ApiTestCase
{
/**
* @test
*/
public function shortUrlsAreProperlyListed()
{
$resp = $this->callApiWithKey(self::METHOD_GET, '/short-urls');
$respPayload = $this->getJsonResponsePayload($resp);
$this->assertEquals(self::STATUS_OK, $resp->getStatusCode());
$this->assertEquals([
'shortUrls' => [
'data' => [
[
'shortCode' => 'abc123',
'shortUrl' => 'http://doma.in/abc123',
'longUrl' => 'https://shlink.io',
'dateCreated' => '2019-01-01T00:00:00+00:00',
'visitsCount' => 3,
'tags' => ['foo'],
'originalUrl' => 'https://shlink.io',
],
[
'shortCode' => 'def456',
'shortUrl' => 'http://doma.in/def456',
'longUrl' =>
'https://blog.alejandrocelaya.com/2017/12/09'
. '/acmailer-7-0-the-most-important-release-in-a-long-time/',
'dateCreated' => '2019-01-01T00:00:00+00:00',
'visitsCount' => 2,
'tags' => ['foo', 'bar'],
'originalUrl' =>
'https://blog.alejandrocelaya.com/2017/12/09'
. '/acmailer-7-0-the-most-important-release-in-a-long-time/',
],
[
'shortCode' => 'custom',
'shortUrl' => 'http://doma.in/custom',
'longUrl' => 'https://shlink.io',
'dateCreated' => '2019-01-01T00:00:00+00:00',
'visitsCount' => 0,
'tags' => [],
'originalUrl' => 'https://shlink.io',
],
],
'pagination' => [
'currentPage' => 1,
'pagesCount' => 1,
'itemsPerPage' => 10,
'itemsInCurrentPage' => 3,
'totalItems' => 3,
],
],
], $respPayload);
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace ShlinkioApiTest\Shlink\Rest\Fixtures;
use Cake\Chronos\Chronos;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use ReflectionObject;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
class ApiKeyFixture implements FixtureInterface
{
public function load(ObjectManager $manager): void
{
$manager->persist($this->buildApiKey('valid_api_key', true));
$manager->persist($this->buildApiKey('disabled_api_key', false));
$manager->persist($this->buildApiKey('expired_api_key', true, Chronos::now()->subDay()));
$manager->flush();
}
private function buildApiKey(string $key, bool $enabled, Chronos $expiresAt = null): ApiKey
{
$apiKey = new ApiKey($expiresAt);
$refObj = new ReflectionObject($apiKey);
$keyProp = $refObj->getProperty('key');
$keyProp->setAccessible(true);
$keyProp->setValue($apiKey, $key);
if (! $enabled) {
$apiKey->disable();
}
return $apiKey;
}
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace ShlinkioApiTest\Shlink\Rest\Fixtures;
use Cake\Chronos\Chronos;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\Persistence\ObjectManager;
use ReflectionObject;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
class ShortUrlsFixture extends AbstractFixture
{
/**
* Load data fixtures with the passed EntityManager
*
* @param ObjectManager $manager
*/
public function load(ObjectManager $manager): void
{
$abcShortUrl = $this->setShortUrlDate(new ShortUrl('https://shlink.io'))->setShortCode('abc123');
$manager->persist($abcShortUrl);
$defShortUrl = $this->setShortUrlDate(new ShortUrl(
'https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/',
ShortUrlMeta::createFromParams(Chronos::now()->addDays(3))
))->setShortCode('def456');
$manager->persist($defShortUrl);
$customShortUrl = $this->setShortUrlDate(new ShortUrl(
'https://shlink.io',
ShortUrlMeta::createFromParams(null, null, 'custom', 2)
));
$manager->persist($customShortUrl);
$manager->flush();
$this->addReference('abc123_short_url', $abcShortUrl);
$this->addReference('def456_short_url', $defShortUrl);
}
private function setShortUrlDate(ShortUrl $shortUrl): ShortUrl
{
$ref = new ReflectionObject($shortUrl);
$dateProp = $ref->getProperty('dateCreated');
$dateProp->setAccessible(true);
$dateProp->setValue($shortUrl, Chronos::create(2019, 1, 1, 0, 0, 0));
return $shortUrl;
}
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace ShlinkioApiTest\Shlink\Rest\Fixtures;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Entity\Tag;
class TagsFixture extends AbstractFixture implements DependentFixtureInterface
{
public function getDependencies(): array
{
return [ShortUrlsFixture::class];
}
public function load(ObjectManager $manager): void
{
$fooTag = new Tag('foo');
$manager->persist($fooTag);
$barTag = new Tag('bar');
$manager->persist($barTag);
/** @var ShortUrl $abcShortUrl */
$abcShortUrl = $this->getReference('abc123_short_url');
$abcShortUrl->setTags(new ArrayCollection([$fooTag]));
/** @var ShortUrl $defShortUrl */
$defShortUrl = $this->getReference('def456_short_url');
$defShortUrl->setTags(new ArrayCollection([$fooTag, $barTag]));
$manager->flush();
}
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace ShlinkioApiTest\Shlink\Rest\Fixtures;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Model\Visitor;
class VisitsFixture extends AbstractFixture implements DependentFixtureInterface
{
public function getDependencies(): array
{
return [ShortUrlsFixture::class];
}
public function load(ObjectManager $manager): void
{
/** @var ShortUrl $abcShortUrl */
$abcShortUrl = $this->getReference('abc123_short_url');
$manager->persist(new Visit($abcShortUrl, new Visitor('shlink-tests-agent', '', '44.55.66.77')));
$manager->persist(new Visit($abcShortUrl, new Visitor('shlink-tests-agent', 'https://google.com', '4.5.6.7')));
$manager->persist(new Visit($abcShortUrl, new Visitor('shlink-tests-agent', '', '1.2.3.4')));
/** @var ShortUrl $defShortUrl */
$defShortUrl = $this->getReference('def456_short_url');
$manager->persist(new Visit($defShortUrl, new Visitor('shlink-tests-agent', '', '127.0.0.1')));
$manager->persist(new Visit($defShortUrl, new Visitor('shlink-tests-agent', 'https://app.shlink.io', '')));
$manager->flush();
}
}

View File

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace ShlinkioApiTest\Shlink\Rest\Middleware;
use GuzzleHttp\Exception\ClientException;
use Shlinkio\Shlink\Rest\Authentication\Plugin\ApiKeyHeaderPlugin;
use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPlugin;
use Shlinkio\Shlink\Rest\Util\RestUtils;
use ShlinkioTest\Shlink\Common\ApiTest\ApiTestCase;
use function implode;
use function Shlinkio\Shlink\Common\json_decode;
use function sprintf;
class AuthenticationTest extends ApiTestCase
{
/**
* @test
*/
public function authorizationErrorIsReturnedIfNoApiKeyIsSent()
{
try {
$this->callApi(self::METHOD_GET, '/short-codes');
} catch (ClientException $e) {
['error' => $error, 'message' => $message] = $this->getJsonResponsePayload($e->getResponse());
$this->assertEquals(self::STATUS_UNAUTHORIZED, $e->getCode());
$this->assertEquals(RestUtils::INVALID_AUTHORIZATION_ERROR, $error);
$this->assertEquals(
sprintf(
'Expected one of the following authentication headers, but none were provided, ["%s"]',
implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS)
),
$message
);
}
}
/**
* @test
* @dataProvider provideInvalidApiKeys
*/
public function apiKeyErrorIsReturnedWhenProvidedApiKeyIsInvalid(string $apiKey)
{
try {
$this->callApi(self::METHOD_GET, '/short-codes', [
'headers' => [
ApiKeyHeaderPlugin::HEADER_NAME => $apiKey,
],
]);
} catch (ClientException $e) {
['error' => $error, 'message' => $message] = json_decode((string) $e->getResponse()->getBody());
$this->assertEquals(self::STATUS_UNAUTHORIZED, $e->getCode());
$this->assertEquals(RestUtils::INVALID_API_KEY_ERROR, $error);
$this->assertEquals('Provided API key does not exist or is invalid.', $message);
}
}
public function provideInvalidApiKeys(): array
{
return [
'key which does not exist' => ['invalid'],
'key which is expired' => ['expired_api_key'],
'key which is disabled' => ['disabled_api_key'],
];
}
}

19
phpunit-api.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.5/phpunit.xsd"
bootstrap="./config/test/bootstrap_api_tests.php"
colors="true"
>
<testsuites>
<testsuite name="Shlink API tests">
<directory>./module/*/test-api</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./module/*/src</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -1,9 +1,10 @@
<?xml version="1.0"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.5/phpunit.xsd"
bootstrap="./db_tests_bootstrap.php"
colors="true">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.5/phpunit.xsd"
bootstrap="./config/test/bootstrap_db_tests.php"
colors="true"
>
<testsuites>
<testsuite name="Shlink database tests">
<directory>./module/*/test-db</directory>