mirror of
https://github.com/shlinkio/shlink.git
synced 2024-11-24 09:50:17 -06:00
commit
05695e8cd6
@ -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
14
bin/test/run-api-tests.sh
Executable 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
|
3
build.sh
3
build.sh
@ -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}"
|
||||
|
@ -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</>",
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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;
|
26
config/test/bootstrap_api_tests.php
Normal file
26
config/test/bootstrap_api_tests.php
Normal 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'));
|
19
config/test/bootstrap_db_tests.php
Normal file
19
config/test/bootstrap_db_tests.php
Normal 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'));
|
57
config/test/test_config.global.php
Normal file
57
config/test/test_config.global.php
Normal 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',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
@ -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');
|
51
module/Common/test-db/ApiTest/ApiTestCase.php
Normal file
51
module/Common/test-db/ApiTest/ApiTestCase.php
Normal 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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
44
module/Common/test-db/TestHelper.php
Normal file
44
module/Common/test-db/TestHelper.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
|
64
module/Rest/test-api/Action/ListShortUrlsTest.php
Normal file
64
module/Rest/test-api/Action/ListShortUrlsTest.php
Normal 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);
|
||||
}
|
||||
}
|
36
module/Rest/test-api/Fixtures/ApiKeyFixture.php
Normal file
36
module/Rest/test-api/Fixtures/ApiKeyFixture.php
Normal 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;
|
||||
}
|
||||
}
|
52
module/Rest/test-api/Fixtures/ShortUrlsFixture.php
Normal file
52
module/Rest/test-api/Fixtures/ShortUrlsFixture.php
Normal 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;
|
||||
}
|
||||
}
|
37
module/Rest/test-api/Fixtures/TagsFixture.php
Normal file
37
module/Rest/test-api/Fixtures/TagsFixture.php
Normal 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();
|
||||
}
|
||||
}
|
35
module/Rest/test-api/Fixtures/VisitsFixture.php
Normal file
35
module/Rest/test-api/Fixtures/VisitsFixture.php
Normal 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();
|
||||
}
|
||||
}
|
68
module/Rest/test-api/Middleware/AuthenticationTest.php
Normal file
68
module/Rest/test-api/Middleware/AuthenticationTest.php
Normal 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
19
phpunit-api.xml
Normal 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>
|
@ -2,8 +2,9 @@
|
||||
<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">
|
||||
bootstrap="./config/test/bootstrap_db_tests.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Shlink database tests">
|
||||
<directory>./module/*/test-db</directory>
|
||||
|
Loading…
Reference in New Issue
Block a user