mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-05 23:03:11 +08:00
Compare commits
32 Commits
v2.4.1
...
v2.5.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
179ddc5bd7 | ||
|
|
bfd886604e | ||
|
|
f34033aa9c | ||
|
|
e54745b250 | ||
|
|
1975a35837 | ||
|
|
5db66dcf0e | ||
|
|
cfdf2f9480 | ||
|
|
c13adb04ef | ||
|
|
4f1ab977a1 | ||
|
|
fe59a5ad86 | ||
|
|
a72dc16d85 | ||
|
|
74108a19e5 | ||
|
|
abe0fc16df | ||
|
|
39bda5113b | ||
|
|
49ea5cc78b | ||
|
|
8acde332b2 | ||
|
|
600f7a7388 | ||
|
|
fd007ea4a9 | ||
|
|
b66922b3d5 | ||
|
|
7d981434e1 | ||
|
|
c672d35b4a | ||
|
|
6259c73b33 | ||
|
|
e4b00e832a | ||
|
|
a452aeaf7e | ||
|
|
6e83b90028 | ||
|
|
45ffdce312 | ||
|
|
5485efc9ae | ||
|
|
850360dd2b | ||
|
|
8d3ceaf462 | ||
|
|
bb6c5de697 | ||
|
|
ca4c1b00dc | ||
|
|
dda6d30c12 |
41
CHANGELOG.md
41
CHANGELOG.md
@@ -4,6 +4,47 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
* [#869](https://github.com/shlinkio/shlink/issues/869) Added support for Mercure Hub 0.10.
|
||||
* [#833](https://github.com/shlinkio/shlink/issues/833) Added support to connect through unix socket when using an external MySQL, MariaDB or Postgres database.
|
||||
|
||||
It can be provided during the installation, or as the `DB_UNIX_SOCKET` env var for the docker image.
|
||||
|
||||
### Changed
|
||||
* [#912](https://github.com/shlinkio/shlink/issues/912) Changed error templates to be plain html files, removing the dependency on `league/plates` package.
|
||||
|
||||
### Deprecated
|
||||
* [#917](https://github.com/shlinkio/shlink/issues/917) Deprecated `/{shortCode}/qr-code/{size}` URL, in favor of providing the size in the query instead, `/{shortCode}/qr-code?size={size}`.
|
||||
* [#924](https://github.com/shlinkio/shlink/issues/924) Deprecated mechanism to provide config options to the docker image through volumes. Use the env vars instead as a direct replacement.
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* *Nothing*
|
||||
|
||||
|
||||
## [2.4.2] - 2020-11-22
|
||||
### Added
|
||||
* *Nothing*
|
||||
|
||||
### Changed
|
||||
* *Nothing*
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [#904](https://github.com/shlinkio/shlink/issues/904) Explicitly added missing "Domains" and "Integrations" tags to swagger docs.
|
||||
* [#901](https://github.com/shlinkio/shlink/issues/901) Ensured domains which are not in use on any short URL are not returned on the list of domains.
|
||||
* [#899](https://github.com/shlinkio/shlink/issues/899) Avoided filesystem errors produced while downloading geolite DB files on several shlink instances that share the same filesystem.
|
||||
* [#827](https://github.com/shlinkio/shlink/issues/827) Fixed swoole config getting loaded in config cache if a console command is run before any web execution, when swoole extension is enabled, making subsequent non-swoole web requests fail.
|
||||
|
||||
|
||||
## [2.4.1] - 2020-11-10
|
||||
### Added
|
||||
* *Nothing*
|
||||
|
||||
@@ -32,13 +32,12 @@
|
||||
"laminas/laminas-paginator": "^2.8",
|
||||
"laminas/laminas-servicemanager": "^3.4",
|
||||
"laminas/laminas-stdlib": "^3.2",
|
||||
"lcobucci/jwt": "^4.0@alpha",
|
||||
"lcobucci/jwt": "^4.0",
|
||||
"league/uri": "^6.2",
|
||||
"lstrojny/functional-php": "^1.9",
|
||||
"mezzio/mezzio": "^3.2",
|
||||
"mezzio/mezzio-fastroute": "^3.0",
|
||||
"mezzio/mezzio-helpers": "^5.3",
|
||||
"mezzio/mezzio-platesrenderer": "^2.1",
|
||||
"mezzio/mezzio-problem-details": "^1.1",
|
||||
"mezzio/mezzio-swoole": "^2.6.4",
|
||||
"monolog/monolog": "^2.0",
|
||||
@@ -49,16 +48,16 @@
|
||||
"predis/predis": "^1.1",
|
||||
"pugx/shortid-php": "^0.5",
|
||||
"ramsey/uuid": "^3.9",
|
||||
"shlinkio/shlink-common": "^3.3.0",
|
||||
"shlinkio/shlink-common": "^3.3.2",
|
||||
"shlinkio/shlink-config": "^1.0",
|
||||
"shlinkio/shlink-event-dispatcher": "^1.4",
|
||||
"shlinkio/shlink-importer": "^2.0.1",
|
||||
"shlinkio/shlink-installer": "^5.1.0",
|
||||
"shlinkio/shlink-installer": "^5.2.0",
|
||||
"shlinkio/shlink-ip-geolocation": "^1.5",
|
||||
"symfony/console": "^5.1",
|
||||
"symfony/filesystem": "^5.1",
|
||||
"symfony/lock": "^5.1",
|
||||
"symfony/mercure": "^0.3.0",
|
||||
"symfony/mercure": "^0.4.0",
|
||||
"symfony/process": "^5.1",
|
||||
"symfony/string": "^5.1"
|
||||
},
|
||||
|
||||
@@ -7,6 +7,9 @@ use Laminas\ConfigAggregator\ConfigAggregator;
|
||||
return [
|
||||
|
||||
'debug' => false,
|
||||
ConfigAggregator::ENABLE_CACHE => true,
|
||||
|
||||
// Disabling config cache for cli, ensures it's never used for swoole and also that console commands don't generate
|
||||
// a cache file that's then used by non-swoole web executions
|
||||
ConfigAggregator::ENABLE_CACHE => PHP_SAPI !== 'cli',
|
||||
|
||||
];
|
||||
|
||||
@@ -6,8 +6,8 @@ return [
|
||||
|
||||
'geolite2' => [
|
||||
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
||||
'temp_dir' => sys_get_temp_dir(),
|
||||
'license_key' => 'G4Lm0C60yJsnkdPi',
|
||||
'temp_dir' => __DIR__ . '/../../data',
|
||||
'license_key' => 'G4Lm0C60yJsnkdPi', // Deprecated. Remove hardcoded license on v3
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -14,6 +14,7 @@ return [
|
||||
Option\Database\DatabasePortConfigOption::class,
|
||||
Option\Database\DatabaseUserConfigOption::class,
|
||||
Option\Database\DatabasePasswordConfigOption::class,
|
||||
Option\Database\DatabaseUnixSocketConfigOption::class,
|
||||
Option\Database\DatabaseSqlitePathConfigOption::class,
|
||||
Option\Database\DatabaseMySqlOptionsConfigOption::class,
|
||||
Option\UrlShortener\ShortDomainHostConfigOption::class,
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'templates' => [
|
||||
'extension' => 'phtml',
|
||||
],
|
||||
|
||||
'plates' => [
|
||||
'extensions' => [
|
||||
// extension service names or instances
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -15,7 +15,6 @@ return (new ConfigAggregator\ConfigAggregator([
|
||||
Mezzio\ConfigProvider::class,
|
||||
Mezzio\Router\ConfigProvider::class,
|
||||
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
||||
Mezzio\Plates\ConfigProvider::class,
|
||||
Mezzio\Swoole\ConfigProvider::class,
|
||||
ProblemDetails\ConfigProvider::class,
|
||||
Diactoros\ConfigProvider::class,
|
||||
|
||||
@@ -131,7 +131,7 @@ services:
|
||||
|
||||
shlink_mercure:
|
||||
container_name: shlink_mercure
|
||||
image: dunglas/mercure:v0.9
|
||||
image: dunglas/mercure:v0.10
|
||||
ports:
|
||||
- "3080:80"
|
||||
environment:
|
||||
|
||||
@@ -157,6 +157,7 @@ This is the complete list of supported env vars:
|
||||
* **mysql** or **maria** -> `3306`
|
||||
* **postgres** -> `5432`
|
||||
* **mssql** -> `1433`
|
||||
* `DB_UNIX_SOCKET`: Alternatively to the `DB_HOST`, you can provide this to connect through unix sockets when using `mysql`, `maria` or `postgres` drivers.
|
||||
* `DISABLE_TRACK_PARAM`: The name of a query param that can be used to visit short URLs avoiding the visit to be tracked. This feature won't be available if not value is provided.
|
||||
* `DELETE_SHORT_URL_THRESHOLD`: The amount of visits on short URLs which will not allow them to be deleted. Defaults to `15`.
|
||||
* `VALIDATE_URLS`: Boolean which tells if shlink should validate a status 20x is returned (after following redirects) when trying to shorten a URL. Defaults to `false`.
|
||||
@@ -215,7 +216,11 @@ docker run \
|
||||
shlinkio/shlink:stable
|
||||
```
|
||||
|
||||
## Provide config via volumes
|
||||
## [DEPRECATED] Provide config via volumes
|
||||
|
||||
> As of v2.5.0, providing config through volumes is deprecated, and no new options will be added anymore. Use env vars instead.
|
||||
>
|
||||
> Support for config options through volumes will be removed in Shlink v3.0.0
|
||||
|
||||
Rather than providing custom configuration via env vars, it is also possible ot provide config files in json format.
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ $helper = new class {
|
||||
public function getDbConfig(): array
|
||||
{
|
||||
$driver = env('DB_DRIVER');
|
||||
$isMysql = contains(['maria', 'mysql'], $driver);
|
||||
if ($driver === null || $driver === 'sqlite') {
|
||||
return [
|
||||
'driver' => 'pdo_sqlite',
|
||||
@@ -41,7 +42,7 @@ $helper = new class {
|
||||
];
|
||||
}
|
||||
|
||||
$driverOptions = ! contains(['maria', 'mysql'], $driver) ? [] : [
|
||||
$driverOptions = ! $isMysql ? [] : [
|
||||
// 1002 -> PDO::MYSQL_ATTR_INIT_COMMAND
|
||||
1002 => 'SET NAMES utf8',
|
||||
// 1000 -> PDO::MYSQL_ATTR_USE_BUFFERED_QUERY
|
||||
@@ -52,9 +53,10 @@ $helper = new class {
|
||||
'dbname' => env('DB_NAME', 'shlink'),
|
||||
'user' => env('DB_USER'),
|
||||
'password' => env('DB_PASSWORD'),
|
||||
'host' => env('DB_HOST'),
|
||||
'host' => env('DB_HOST', $driver === 'postgres' ? env('DB_UNIX_SOCKET') : null),
|
||||
'port' => env('DB_PORT', self::DB_PORTS_MAP[$driver]),
|
||||
'driverOptions' => $driverOptions,
|
||||
'unix_socket' => $isMysql ? env('DB_UNIX_SOCKET') : null,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -99,8 +101,6 @@ $helper = new class {
|
||||
|
||||
return [
|
||||
|
||||
'config_cache_enabled' => false,
|
||||
|
||||
'app_options' => [
|
||||
'disable_track_param' => env('DISABLE_TRACK_PARAM'),
|
||||
],
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
{
|
||||
"name": "size",
|
||||
"in": "path",
|
||||
"in": "query",
|
||||
"description": "The size of the image to be returned.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
|
||||
66
docs/swagger/paths/{shortCode}_qr-code_{size}.json
Normal file
66
docs/swagger/paths/{shortCode}_qr-code_{size}.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"get": {
|
||||
"operationId": "shortUrlQrCodeSize",
|
||||
"deprecated": true,
|
||||
"tags": [
|
||||
"URL Shortener"
|
||||
],
|
||||
"summary": "Short URL QR code",
|
||||
"description": "Generates a QR code image pointing to a short URL",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "shortCode",
|
||||
"in": "path",
|
||||
"description": "The short code to resolve.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "size",
|
||||
"in": "path",
|
||||
"description": "The size of the image to be returned.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"minimum": 50,
|
||||
"maximum": 1000,
|
||||
"default": 300
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "format",
|
||||
"in": "query",
|
||||
"description": "The format for the QR code image, being valid values png and svg. Not providing the param or providing any other value will fall back to png.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"png",
|
||||
"svg"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "QR code in PNG format",
|
||||
"content": {
|
||||
"image/png": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
}
|
||||
},
|
||||
"image/svg+xml": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,14 @@
|
||||
"name": "Visits",
|
||||
"description": "Operations to manage visits on short URLs"
|
||||
},
|
||||
{
|
||||
"name": "Domains",
|
||||
"description": "Operations to manage domains used on short URLs"
|
||||
},
|
||||
{
|
||||
"name": "Integrations",
|
||||
"description": "Handle services with which shlink is integrated"
|
||||
},
|
||||
{
|
||||
"name": "Monitoring",
|
||||
"description": "Public endpoints designed to monitor the service"
|
||||
@@ -108,6 +116,9 @@
|
||||
},
|
||||
"/{shortCode}/qr-code": {
|
||||
"$ref": "paths/{shortCode}_qr-code.json"
|
||||
},
|
||||
"/{shortCode}/qr-code/{size}": {
|
||||
"$ref": "paths/{shortCode}_qr-code_{size}.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\CLI;
|
||||
|
||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\CLI\ConfigProvider;
|
||||
|
||||
@@ -21,7 +22,9 @@ class ConfigProviderTest extends TestCase
|
||||
{
|
||||
$config = ($this->configProvider)();
|
||||
|
||||
self::assertCount(3, $config);
|
||||
self::assertArrayHasKey('cli', $config);
|
||||
self::assertArrayHasKey('dependencies', $config);
|
||||
self::assertArrayHasKey(ConfigAbstractFactory::class, $config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Core;
|
||||
|
||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Mezzio\Template\TemplateRendererInterface;
|
||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Shlinkio\Shlink\Core\ErrorHandler;
|
||||
use Shlinkio\Shlink\Core\Options\NotFoundRedirectOptions;
|
||||
@@ -16,7 +16,7 @@ return [
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
ErrorHandler\NotFoundRedirectHandler::class => ConfigAbstractFactory::class,
|
||||
ErrorHandler\NotFoundTemplateHandler::class => ConfigAbstractFactory::class,
|
||||
ErrorHandler\NotFoundTemplateHandler::class => InvokableFactory::class,
|
||||
|
||||
Options\AppOptions::class => ConfigAbstractFactory::class,
|
||||
Options\DeleteShortUrlsOptions::class => ConfigAbstractFactory::class,
|
||||
@@ -60,7 +60,6 @@ return [
|
||||
Util\RedirectResponseHelper::class,
|
||||
'config.router.base_path',
|
||||
],
|
||||
ErrorHandler\NotFoundTemplateHandler::class => [TemplateRendererInterface::class],
|
||||
|
||||
Options\AppOptions::class => ['config.app_options'],
|
||||
Options\DeleteShortUrlsOptions::class => ['config.delete_short_urls'],
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'mezzio' => [
|
||||
'error_handler' => [
|
||||
'template_404' => 'ShlinkCore::error/404',
|
||||
'template_error' => 'ShlinkCore::error/error',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -29,7 +29,17 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => Action\QrCodeAction::class,
|
||||
'path' => '/{shortCode}/qr-code[/{size:[0-9]+}]',
|
||||
'path' => '/{shortCode}/qr-code',
|
||||
'middleware' => [
|
||||
Action\QrCodeAction::class,
|
||||
],
|
||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
||||
],
|
||||
|
||||
// Deprecated
|
||||
[
|
||||
'name' => 'old_' . Action\QrCodeAction::class,
|
||||
'path' => '/{shortCode}/qr-code/{size:[0-9]+}',
|
||||
'middleware' => [
|
||||
Action\QrCodeAction::class,
|
||||
],
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'templates' => [
|
||||
'paths' => [
|
||||
'ShlinkCore' => __DIR__ . '/../templates',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -41,7 +41,7 @@ abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMet
|
||||
$this->urlResolver = $urlResolver;
|
||||
$this->visitTracker = $visitTracker;
|
||||
$this->appOptions = $appOptions;
|
||||
$this->logger = $logger ?: new NullLogger();
|
||||
$this->logger = $logger ?? new NullLogger();
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
|
||||
@@ -34,7 +34,7 @@ class QrCodeAction implements MiddlewareInterface
|
||||
) {
|
||||
$this->urlResolver = $urlResolver;
|
||||
$this->domainConfig = $domainConfig;
|
||||
$this->logger = $logger ?: new NullLogger();
|
||||
$this->logger = $logger ?? new NullLogger();
|
||||
}
|
||||
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
@@ -48,11 +48,15 @@ class QrCodeAction implements MiddlewareInterface
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
$query = $request->getQueryParams();
|
||||
// Size attribute is deprecated
|
||||
$size = $this->normalizeSize((int) $request->getAttribute('size', $query['size'] ?? self::DEFAULT_SIZE));
|
||||
|
||||
$qrCode = new QrCode($shortUrl->toString($this->domainConfig));
|
||||
$qrCode->setSize($this->getSizeParam($request));
|
||||
$qrCode->setSize($size);
|
||||
$qrCode->setMargin(0);
|
||||
|
||||
$format = $request->getQueryParams()['format'] ?? 'png';
|
||||
$format = $query['format'] ?? 'png';
|
||||
if ($format === 'svg') {
|
||||
$qrCode->setWriter(new SvgWriter());
|
||||
}
|
||||
@@ -60,9 +64,8 @@ class QrCodeAction implements MiddlewareInterface
|
||||
return new QrCodeResponse($qrCode);
|
||||
}
|
||||
|
||||
private function getSizeParam(Request $request): int
|
||||
private function normalizeSize(int $size): int
|
||||
{
|
||||
$size = (int) $request->getAttribute('size', self::DEFAULT_SIZE);
|
||||
if ($size < self::MIN_SIZE) {
|
||||
return self::MIN_SIZE;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use function Functional\contains;
|
||||
use function Functional\reduce_left;
|
||||
use function uksort;
|
||||
|
||||
/** @deprecated */
|
||||
class SimplifiedConfigParser
|
||||
{
|
||||
private const SIMPLIFIED_CONFIG_MAPPING = [
|
||||
|
||||
@@ -5,7 +5,9 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Core\Domain\Repository;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Query\Expr\Join;
|
||||
use Shlinkio\Shlink\Core\Entity\Domain;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
|
||||
class DomainRepository extends EntityRepository implements DomainRepositoryInterface
|
||||
{
|
||||
@@ -14,7 +16,9 @@ class DomainRepository extends EntityRepository implements DomainRepositoryInter
|
||||
*/
|
||||
public function findDomainsWithout(?string $excludedAuthority = null): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('d')->orderBy('d.authority', 'ASC');
|
||||
$qb = $this->createQueryBuilder('d');
|
||||
$qb->join(ShortUrl::class, 's', Join::WITH, 's.domain = d')
|
||||
->orderBy('d.authority', 'ASC');
|
||||
|
||||
if ($excludedAuthority !== null) {
|
||||
$qb->where($qb->expr()->neq('d.authority', ':excludedAuthority'))
|
||||
|
||||
@@ -4,40 +4,37 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\ErrorHandler;
|
||||
|
||||
use Closure;
|
||||
use Fig\Http\Message\StatusCodeInterface;
|
||||
use InvalidArgumentException;
|
||||
use Laminas\Diactoros\Response;
|
||||
use Mezzio\Router\RouteResult;
|
||||
use Mezzio\Template\TemplateRendererInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
use function file_get_contents;
|
||||
use function sprintf;
|
||||
|
||||
class NotFoundTemplateHandler implements RequestHandlerInterface
|
||||
{
|
||||
public const NOT_FOUND_TEMPLATE = 'ShlinkCore::error/404';
|
||||
public const INVALID_SHORT_CODE_TEMPLATE = 'ShlinkCore::invalid-short-code';
|
||||
private const TEMPLATES_BASE_DIR = __DIR__ . '/../../templates';
|
||||
public const NOT_FOUND_TEMPLATE = '404.html';
|
||||
public const INVALID_SHORT_CODE_TEMPLATE = 'invalid-short-code.html';
|
||||
private Closure $readFile;
|
||||
|
||||
private TemplateRendererInterface $renderer;
|
||||
|
||||
public function __construct(TemplateRendererInterface $renderer)
|
||||
public function __construct(?callable $readFile = null)
|
||||
{
|
||||
$this->renderer = $renderer;
|
||||
$this->readFile = $readFile ? Closure::fromCallable($readFile) : fn (string $file) => file_get_contents($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the next available middleware and return the response.
|
||||
*
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
/** @var RouteResult $routeResult */
|
||||
$routeResult = $request->getAttribute(RouteResult::class, RouteResult::fromRouteFailure(null));
|
||||
$routeResult = $request->getAttribute(RouteResult::class) ?? RouteResult::fromRouteFailure(null);
|
||||
$status = StatusCodeInterface::STATUS_NOT_FOUND;
|
||||
|
||||
$template = $routeResult->isFailure() ? self::NOT_FOUND_TEMPLATE : self::INVALID_SHORT_CODE_TEMPLATE;
|
||||
return new Response\HtmlResponse($this->renderer->render($template), $status);
|
||||
$templateContent = ($this->readFile)(sprintf('%s/%s', self::TEMPLATES_BASE_DIR, $template));
|
||||
return new Response\HtmlResponse($templateContent, $status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,6 @@ class ShortUrlRepositoryAdapter implements AdapterInterface
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of items for a page.
|
||||
*
|
||||
* @param int $offset Page offset
|
||||
* @param int $itemCountPerPage Number of items per page
|
||||
*/
|
||||
public function getItems($offset, $itemCountPerPage): array // phpcs:ignore
|
||||
{
|
||||
return $this->repository->findList(
|
||||
@@ -37,15 +31,6 @@ class ShortUrlRepositoryAdapter implements AdapterInterface
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of an object
|
||||
* @link http://php.net/manual/en/countable.count.php
|
||||
* @return int The custom count as an integer.
|
||||
* </p>
|
||||
* <p>
|
||||
* The return value is cast to an integer.
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return $this->repository->countList(
|
||||
|
||||
@@ -33,15 +33,9 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
|
||||
?DateRange $dateRange = null
|
||||
): array {
|
||||
$qb = $this->createListQueryBuilder($searchTerm, $tags, $dateRange);
|
||||
$qb->select('DISTINCT s');
|
||||
|
||||
// Set limit and offset
|
||||
if ($limit !== null) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
if ($offset !== null) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
$qb->select('DISTINCT s')
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($offset);
|
||||
|
||||
// In case the ordering has been specified, the query could be more complex. Process it
|
||||
if ($orderBy !== null && $orderBy->hasOrderField()) {
|
||||
@@ -147,7 +141,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
|
||||
WHERE s.shortCode = :shortCode
|
||||
AND (s.domain IS NULL OR d.authority = :domain)
|
||||
ORDER BY s.domain {$ordering}
|
||||
DQL;
|
||||
DQL;
|
||||
|
||||
$query = $this->getEntityManager()->createQuery($dql);
|
||||
$query->setMaxResults(1)
|
||||
@@ -220,9 +214,8 @@ DQL;
|
||||
}
|
||||
if ($meta->hasValidUntil()) {
|
||||
$qb->andWhere($qb->expr()->eq('s.validUntil', ':validUntil'))
|
||||
->setParameter('validUntil', $meta->getValidUntil());
|
||||
->setParameter('validUntil', $meta->getValidUntil());
|
||||
}
|
||||
|
||||
if ($meta->hasDomain()) {
|
||||
$qb->join('s.domain', 'd')
|
||||
->andWhere($qb->expr()->eq('d.authority', ':domain'))
|
||||
|
||||
27
module/Core/templates/404.html
Normal file
27
module/Core/templates/404.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Not Found | Shlink</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
|
||||
<link rel="shortcut icon" href="/favicon.ico">
|
||||
<style>
|
||||
html, body {height: 100%}
|
||||
.app {height: 100vh; display: flex; align-items: center; justify-content: center; flex-flow: column;}
|
||||
p {margin-bottom: 20px;}
|
||||
body {text-align: center;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<main class="container">
|
||||
<h1>404</h1>
|
||||
<hr>
|
||||
<h3>Page not found.</h3>
|
||||
<p>The page you requested could not be found.</p>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php $this->layout('ShlinkCore::layout/default') ?>
|
||||
|
||||
<?php $this->start('title') ?>
|
||||
Not Found
|
||||
<?php $this->end() ?>
|
||||
|
||||
<?php $this->start('stylesheets') ?>
|
||||
<style>
|
||||
p {margin-bottom: 20px;}
|
||||
body {text-align: center;}
|
||||
</style>
|
||||
<?php $this->end() ?>
|
||||
|
||||
<?php $this->start('main') ?>
|
||||
<h1>404</h1>
|
||||
<hr>
|
||||
<h3>Page not found.</h3>
|
||||
<p>The page you requested could not be found.</p>
|
||||
<?php $this->end() ?>
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php $this->layout('ShlinkCore::layout/default') ?>
|
||||
|
||||
<?php $this->start('title') ?>
|
||||
<?= $this->e($status . ' ' . $reason) ?>
|
||||
<?php $this->end() ?>
|
||||
|
||||
<?php $this->start('stylesheets') ?>
|
||||
<style>
|
||||
p {margin-bottom: 20px;}
|
||||
body {text-align: center;}
|
||||
</style>
|
||||
<?php $this->end() ?>
|
||||
|
||||
<?php $this->start('main') ?>
|
||||
<h1>Oops!</h1>
|
||||
<hr>
|
||||
|
||||
<?php if ($status !== 404): ?>
|
||||
<p><?= sprintf('We encountered a %s %s error.', $status, $reason) ?></p>
|
||||
<?php else: ?>
|
||||
<p>'This short URL doesn't seem to be valid.</p>
|
||||
<p>'Make sure you included all the characters, with no extra punctuation.</p>
|
||||
<?php endif; ?>
|
||||
<?php $this->end() ?>
|
||||
|
||||
27
module/Core/templates/invalid-short-code.html
Normal file
27
module/Core/templates/invalid-short-code.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Invalid Short URL | Shlink</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
|
||||
<link rel="shortcut icon" href="/favicon.ico">
|
||||
<style>
|
||||
html, body {height: 100%}
|
||||
.app {height: 100vh; display: flex; align-items: center; justify-content: center; flex-flow: column;}
|
||||
p {margin-bottom: 20px;}
|
||||
body {text-align: center;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<main class="container">
|
||||
<h1>Oops!</h1>
|
||||
<hr>
|
||||
<p>This short URL doesn't seem to be valid.</p>
|
||||
<p>Make sure you included all the characters, with no extra punctuation.</p>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php $this->layout('ShlinkCore::layout/default') ?>
|
||||
|
||||
<?php $this->start('title') ?>
|
||||
Invalid Short URL
|
||||
<?php $this->end() ?>
|
||||
|
||||
<?php $this->start('stylesheets') ?>
|
||||
<style>
|
||||
p {margin-bottom: 20px;}
|
||||
body {text-align: center;}
|
||||
</style>
|
||||
<?php $this->end() ?>
|
||||
|
||||
<?php $this->start('main') ?>
|
||||
<h1>Oops!</h1>
|
||||
<hr>
|
||||
<p>This short URL doesn't seem to be valid.</p>
|
||||
<p>Make sure you included all the characters, with no extra punctuation.</p>
|
||||
<?php $this->end() ?>
|
||||
@@ -1,23 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title><?= $this->section('title', '') ?> | Shlink</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
|
||||
<link rel="shortcut icon" href="/favicon.ico">
|
||||
<style>
|
||||
html, body {height: 100%}
|
||||
.app {height: 100vh; display: flex; align-items: center; justify-content: center; flex-flow: column;}
|
||||
</style>
|
||||
<?= $this->section('stylesheets', '') ?>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<main class="container">
|
||||
<?= $this->section('main', '') ?>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -6,11 +6,15 @@ namespace ShlinkioTest\Shlink\Core\Domain\Repository;
|
||||
|
||||
use Shlinkio\Shlink\Core\Domain\Repository\DomainRepository;
|
||||
use Shlinkio\Shlink\Core\Entity\Domain;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase;
|
||||
|
||||
class DomainRepositoryTest extends DatabaseTestCase
|
||||
{
|
||||
protected const ENTITIES_TO_EMPTY = [Domain::class];
|
||||
protected const ENTITIES_TO_EMPTY = [ShortUrl::class, Domain::class];
|
||||
|
||||
private DomainRepository $repo;
|
||||
|
||||
@@ -23,12 +27,23 @@ class DomainRepositoryTest extends DatabaseTestCase
|
||||
public function findDomainsReturnsExpectedResult(): void
|
||||
{
|
||||
$fooDomain = new Domain('foo.com');
|
||||
$barDomain = new Domain('bar.com');
|
||||
$bazDomain = new Domain('baz.com');
|
||||
|
||||
$this->getEntityManager()->persist($fooDomain);
|
||||
$fooShortUrl = $this->createShortUrl($fooDomain);
|
||||
$this->getEntityManager()->persist($fooShortUrl);
|
||||
|
||||
$barDomain = new Domain('bar.com');
|
||||
$this->getEntityManager()->persist($barDomain);
|
||||
$barShortUrl = $this->createShortUrl($barDomain);
|
||||
$this->getEntityManager()->persist($barShortUrl);
|
||||
|
||||
$bazDomain = new Domain('baz.com');
|
||||
$this->getEntityManager()->persist($bazDomain);
|
||||
$bazShortUrl = $this->createShortUrl($bazDomain);
|
||||
$this->getEntityManager()->persist($bazShortUrl);
|
||||
|
||||
$detachedDomain = new Domain('detached.com');
|
||||
$this->getEntityManager()->persist($detachedDomain);
|
||||
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
self::assertEquals([$barDomain, $bazDomain, $fooDomain], $this->repo->findDomainsWithout());
|
||||
@@ -36,4 +51,30 @@ class DomainRepositoryTest extends DatabaseTestCase
|
||||
self::assertEquals([$bazDomain, $fooDomain], $this->repo->findDomainsWithout('bar.com'));
|
||||
self::assertEquals([$barDomain, $fooDomain], $this->repo->findDomainsWithout('baz.com'));
|
||||
}
|
||||
|
||||
private function createShortUrl(Domain $domain): ShortUrl
|
||||
{
|
||||
return new ShortUrl(
|
||||
'foo',
|
||||
ShortUrlMeta::fromRawData(['domain' => $domain->getAuthority()]),
|
||||
new class ($domain) implements ShortUrlRelationResolverInterface {
|
||||
private Domain $domain;
|
||||
|
||||
public function __construct(Domain $domain)
|
||||
{
|
||||
$this->domain = $domain;
|
||||
}
|
||||
|
||||
public function resolveDomain(?string $domain): ?Domain
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
public function resolveApiKey(?string $key): ?ApiKey
|
||||
{
|
||||
return null;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,13 @@ namespace ShlinkioTest\Shlink\Core\Action;
|
||||
|
||||
use Laminas\Diactoros\Response;
|
||||
use Laminas\Diactoros\ServerRequest;
|
||||
use Laminas\Diactoros\ServerRequestFactory;
|
||||
use Mezzio\Router\RouterInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Common\Response\QrCodeResponse;
|
||||
use Shlinkio\Shlink\Core\Action\QrCodeAction;
|
||||
@@ -19,6 +21,8 @@ use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
|
||||
|
||||
use function getimagesizefromstring;
|
||||
|
||||
class QrCodeActionTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
@@ -51,21 +55,6 @@ class QrCodeActionTest extends TestCase
|
||||
$process->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function anInvalidShortCodeWillReturnNotFoundResponse(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, ''))
|
||||
->willThrow(ShortUrlNotFoundException::class)
|
||||
->shouldBeCalledOnce();
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
$process = $delegate->handle(Argument::any())->willReturn(new Response());
|
||||
|
||||
$this->action->process((new ServerRequest())->withAttribute('shortCode', $shortCode), $delegate->reveal());
|
||||
|
||||
$process->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function aCorrectRequestReturnsTheQrCodeResponse(): void
|
||||
{
|
||||
@@ -110,4 +99,31 @@ class QrCodeActionTest extends TestCase
|
||||
yield 'svg format' => [['format' => 'svg'], 'image/svg+xml'];
|
||||
yield 'unsupported format' => [['format' => 'jpg'], 'image/png'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideRequestsWithSize
|
||||
*/
|
||||
public function imageIsReturnedWithExpectedSize(ServerRequestInterface $req, int $expectedSize): void
|
||||
{
|
||||
$code = 'abc123';
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn(new ShortUrl(''));
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
|
||||
$resp = $this->action->process($req->withAttribute('shortCode', $code), $delegate->reveal());
|
||||
[$size] = getimagesizefromstring((string) $resp->getBody());
|
||||
|
||||
self::assertEquals($expectedSize, $size);
|
||||
}
|
||||
|
||||
public function provideRequestsWithSize(): iterable
|
||||
{
|
||||
yield 'no size' => [ServerRequestFactory::fromGlobals(), 300];
|
||||
yield 'size in attr' => [ServerRequestFactory::fromGlobals()->withAttribute('size', '400'), 400];
|
||||
yield 'size in query' => [ServerRequestFactory::fromGlobals()->withQueryParams(['size' => '123']), 123];
|
||||
yield 'size in query and attr' => [
|
||||
ServerRequestFactory::fromGlobals()->withAttribute('size', '350')->withQueryParams(['size' => '123']),
|
||||
350,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Core;
|
||||
|
||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Core\ConfigProvider;
|
||||
|
||||
@@ -19,11 +20,13 @@ class ConfigProviderTest extends TestCase
|
||||
/** @test */
|
||||
public function properConfigIsReturned(): void
|
||||
{
|
||||
$config = $this->configProvider->__invoke();
|
||||
$config = ($this->configProvider)();
|
||||
|
||||
self::assertCount(5, $config);
|
||||
self::assertArrayHasKey('routes', $config);
|
||||
self::assertArrayHasKey('dependencies', $config);
|
||||
self::assertArrayHasKey('templates', $config);
|
||||
self::assertArrayHasKey('mezzio', $config);
|
||||
self::assertArrayHasKey('entity_manager', $config);
|
||||
self::assertArrayHasKey('events', $config);
|
||||
self::assertArrayHasKey(ConfigAbstractFactory::class, $config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,29 +4,30 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Core\ErrorHandler;
|
||||
|
||||
use Closure;
|
||||
use Laminas\Diactoros\Response;
|
||||
use Laminas\Diactoros\ServerRequestFactory;
|
||||
use Mezzio\Router\Route;
|
||||
use Mezzio\Router\RouteResult;
|
||||
use Mezzio\Template\TemplateRendererInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Shlinkio\Shlink\Core\ErrorHandler\NotFoundTemplateHandler;
|
||||
|
||||
class NotFoundTemplateHandlerTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
private NotFoundTemplateHandler $handler;
|
||||
private ObjectProphecy $renderer;
|
||||
private Closure $readFile;
|
||||
private bool $readFileCalled;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->renderer = $this->prophesize(TemplateRendererInterface::class);
|
||||
$this->handler = new NotFoundTemplateHandler($this->renderer->reveal());
|
||||
$this->readFileCalled = false;
|
||||
$this->readFile = function (string $fileName): string {
|
||||
$this->readFileCalled = true;
|
||||
return $fileName;
|
||||
};
|
||||
$this->handler = new NotFoundTemplateHandler($this->readFile);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,13 +36,11 @@ class NotFoundTemplateHandlerTest extends TestCase
|
||||
*/
|
||||
public function properErrorTemplateIsRendered(ServerRequestInterface $request, string $expectedTemplate): void
|
||||
{
|
||||
$request = $request->withHeader('Accept', 'text/html');
|
||||
$render = $this->renderer->render($expectedTemplate)->willReturn('');
|
||||
|
||||
$resp = $this->handler->handle($request);
|
||||
$resp = $this->handler->handle($request->withHeader('Accept', 'text/html'));
|
||||
|
||||
self::assertInstanceOf(Response\HtmlResponse::class, $resp);
|
||||
$render->shouldHaveBeenCalledOnce();
|
||||
self::assertStringContainsString($expectedTemplate, (string) $resp->getBody());
|
||||
self::assertTrue($this->readFileCalled);
|
||||
}
|
||||
|
||||
public function provideTemplates(): iterable
|
||||
|
||||
19
module/Rest/test-api/Fixtures/DomainFixture.php
Normal file
19
module/Rest/test-api/Fixtures/DomainFixture.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioApiTest\Shlink\Rest\Fixtures;
|
||||
|
||||
use Doctrine\Common\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Shlinkio\Shlink\Core\Entity\Domain;
|
||||
|
||||
class DomainFixture extends AbstractFixture
|
||||
{
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
$orphanDomain = new Domain('this_domain_is_detached.com');
|
||||
$manager->persist($orphanDomain);
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Rest;
|
||||
|
||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Rest\ConfigProvider;
|
||||
|
||||
@@ -21,8 +22,12 @@ class ConfigProviderTest extends TestCase
|
||||
{
|
||||
$config = ($this->configProvider)();
|
||||
|
||||
self::assertCount(5, $config);
|
||||
self::assertArrayHasKey('routes', $config);
|
||||
self::assertArrayHasKey('dependencies', $config);
|
||||
self::assertArrayHasKey('auth', $config);
|
||||
self::assertArrayHasKey('entity_manager', $config);
|
||||
self::assertArrayHasKey(ConfigAbstractFactory::class, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user