Load specific env file when running API tests

This commit is contained in:
Alejandro Celaya
2024-10-23 09:16:52 +02:00
parent c0200317dd
commit 46601443f5
25 changed files with 59 additions and 83 deletions

View File

@@ -23,3 +23,4 @@ php*xml*
build* build*
**/.* **/.*
!config/roadrunner/.rr.yml !config/roadrunner/.rr.yml
*.env*

View File

@@ -17,8 +17,12 @@ touch $OUTPUT_LOGS
# Try to stop server just in case it hanged in last execution # Try to stop server just in case it hanged in last execution
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -f -w . [ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -f -w .
# Resolve .env file absolute path, as it fails to load with relative paths
TESTS_DOTENV="${PWD}/config/test/shlink-test.env"
echo 'Starting server...' echo 'Starting server...'
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr serve -p -w . -c=config/roadrunner/.rr.test.yml \ [ "$TEST_RUNTIME" = 'rr' ] && bin/rr serve -p -w . -c=config/roadrunner/.rr.test.yml \
--dotenv "$TESTS_DOTENV" \
-o=logs.output="${PWD}/${OUTPUT_LOGS}" \ -o=logs.output="${PWD}/${OUTPUT_LOGS}" \
-o=logs.channels.http.output="${PWD}/${OUTPUT_LOGS}" \ -o=logs.channels.http.output="${PWD}/${OUTPUT_LOGS}" \
-o=logs.channels.server.output="${PWD}/${OUTPUT_LOGS}" & -o=logs.channels.server.output="${PWD}/${OUTPUT_LOGS}" &

View File

@@ -0,0 +1,8 @@
# URL shortener
DEFAULT_DOMAIN=s.test
IS_HTTPS_ENABLED=false
# Disable mercure integration during E2E tests
MERCURE_PUBLIC_HUB_URL=null
MERCURE_INTERNAL_HUB_URL=null
MERCURE_JWT_SECRET=null

View File

@@ -93,13 +93,6 @@ return [
ConfigAggregator::ENABLE_CACHE => false, ConfigAggregator::ENABLE_CACHE => false,
FastRouteRouter::CONFIG_CACHE_ENABLED => false, FastRouteRouter::CONFIG_CACHE_ENABLED => false,
'url_shortener' => [
'domain' => [
'schema' => 'http',
'hostname' => 's.test',
],
],
'routes' => [ 'routes' => [
// This route is used to test that title resolution is skipped if the long URL times out // This route is used to test that title resolution is skipped if the long URL times out
[ [
@@ -120,13 +113,6 @@ return [
], ],
], ],
// Disable mercure integration during E2E tests
'mercure' => [
'public_hub_url' => null,
'internal_hub_url' => null,
'jwt_secret' => null,
],
'dependencies' => [ 'dependencies' => [
'services' => [ 'services' => [
'shlink_test_api_client' => new Client([ 'shlink_test_api_client' => new Client([

View File

@@ -73,5 +73,7 @@ CMD \
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \ if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
# Download roadrunner binary # Download roadrunner binary
if [[ ! -f "./bin/rr" ]]; then ./vendor/bin/rr get --no-interaction --no-config --location bin/ && chmod +x bin/rr ; fi && \ if [[ ! -f "./bin/rr" ]]; then ./vendor/bin/rr get --no-interaction --no-config --location bin/ && chmod +x bin/rr ; fi && \
# Create .env file if it does not exist yet
if [[ ! -f "./shlink-dev.env" ]]; then cp ./shlink-dev.env.dist ./shlink-dev.env ; fi && \
# Run with `exec` so that signals are properly handled # Run with `exec` so that signals are properly handled
exec ./bin/rr serve --dotenv /home/shlink/shlink-dev.env -c config/roadrunner/.rr.dev.yml exec ./bin/rr serve --dotenv /home/shlink/shlink-dev.env -c config/roadrunner/.rr.dev.yml

View File

@@ -61,8 +61,6 @@ services:
- shlink_mercure_proxy - shlink_mercure_proxy
- shlink_rabbitmq - shlink_rabbitmq
- shlink_matomo - shlink_matomo
environment:
DEFAULT_DOMAIN: localhost:8800
extra_hosts: extra_hosts:
- 'host.docker.internal:host-gateway' - 'host.docker.internal:host-gateway'

View File

@@ -40,7 +40,7 @@ readonly class RoleResolver implements RoleResolverInterface
private function resolveRoleForAuthority(string $domainAuthority): RoleDefinition private function resolveRoleForAuthority(string $domainAuthority): RoleDefinition
{ {
if ($domainAuthority === $this->urlShortenerOptions->defaultDomain()) { if ($domainAuthority === $this->urlShortenerOptions->defaultDomain) {
throw InvalidRoleConfigException::forDomainOnlyWithDefaultDomain(); throw InvalidRoleConfigException::forDomainOnlyWithDefaultDomain();
} }

View File

@@ -25,10 +25,7 @@ class RoleResolverTest extends TestCase
protected function setUp(): void protected function setUp(): void
{ {
$this->domainService = $this->createMock(DomainServiceInterface::class); $this->domainService = $this->createMock(DomainServiceInterface::class);
$this->resolver = new RoleResolver( $this->resolver = new RoleResolver($this->domainService, new UrlShortenerOptions('default.com'));
$this->domainService,
new UrlShortenerOptions(domain: ['hostname' => 'default.com']),
);
} }
#[Test, DataProvider('provideRoles')] #[Test, DataProvider('provideRoles')]

View File

@@ -37,10 +37,7 @@ class CreateShortUrlCommandTest extends TestCase
$command = new CreateShortUrlCommand( $command = new CreateShortUrlCommand(
$this->urlShortener, $this->urlShortener,
$this->stringifier, $this->stringifier,
new UrlShortenerOptions( new UrlShortenerOptions(defaultDomain: 'example.com', defaultShortCodesLength: 5),
domain: ['hostname' => 'example.com', 'schema' => ''],
defaultShortCodesLength: 5,
),
); );
$this->commandTester = CliTestUtils::testerForCommand($command); $this->commandTester = CliTestUtils::testerForCommand($command);
} }

View File

@@ -15,10 +15,11 @@ use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH;
final readonly class UrlShortenerOptions final readonly class UrlShortenerOptions
{ {
/** /**
* @param array{schema: ?string, hostname: ?string} $domain * @param 'http'|'https' $schema
*/ */
public function __construct( public function __construct(
public array $domain = ['schema' => null, 'hostname' => null], public string $defaultDomain = '',
public string $schema = 'http',
public int $defaultShortCodesLength = DEFAULT_SHORT_CODES_LENGTH, public int $defaultShortCodesLength = DEFAULT_SHORT_CODES_LENGTH,
public bool $autoResolveTitles = false, public bool $autoResolveTitles = false,
public bool $appendExtraPath = false, public bool $appendExtraPath = false,
@@ -37,10 +38,8 @@ final readonly class UrlShortenerOptions
$mode = EnvVars::SHORT_URL_MODE->loadFromEnv(); $mode = EnvVars::SHORT_URL_MODE->loadFromEnv();
return new self( return new self(
domain: [ defaultDomain: EnvVars::DEFAULT_DOMAIN->loadFromEnv(),
'schema' => ((bool) EnvVars::IS_HTTPS_ENABLED->loadFromEnv()) ? 'https' : 'http', schema: ((bool) EnvVars::IS_HTTPS_ENABLED->loadFromEnv()) ? 'https' : 'http',
'hostname' => EnvVars::DEFAULT_DOMAIN->loadFromEnv(),
],
defaultShortCodesLength: $shortCodesLength, defaultShortCodesLength: $shortCodesLength,
autoResolveTitles: (bool) EnvVars::AUTO_RESOLVE_TITLES->loadFromEnv(), autoResolveTitles: (bool) EnvVars::AUTO_RESOLVE_TITLES->loadFromEnv(),
appendExtraPath: (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH->loadFromEnv(), appendExtraPath: (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH->loadFromEnv(),
@@ -54,9 +53,4 @@ final readonly class UrlShortenerOptions
{ {
return $this->mode === ShortUrlMode::LOOSE; return $this->mode === ShortUrlMode::LOOSE;
} }
public function defaultDomain(): string
{
return $this->domain['hostname'] ?? '';
}
} }

View File

@@ -37,7 +37,7 @@ readonly class DomainService implements DomainServiceInterface
return [ return [
DomainItem::forDefaultDomain( DomainItem::forDefaultDomain(
$this->urlShortenerOptions->defaultDomain(), $this->urlShortenerOptions->defaultDomain,
$default ?? new EmptyNotFoundRedirectConfig(), $default ?? new EmptyNotFoundRedirectConfig(),
), ),
...$mappedDomains, ...$mappedDomains,
@@ -56,7 +56,7 @@ readonly class DomainService implements DomainServiceInterface
$restOfDomains = []; $restOfDomains = [];
foreach ($allDomains as $domain) { foreach ($allDomains as $domain) {
if ($domain->authority === $this->urlShortenerOptions->defaultDomain()) { if ($domain->authority === $this->urlShortenerOptions->defaultDomain) {
$defaultDomain = $domain; $defaultDomain = $domain;
} else { } else {
$restOfDomains[] = $domain; $restOfDomains[] = $domain;

View File

@@ -20,8 +20,7 @@ readonly class ShortUrlStringifier implements ShortUrlStringifierInterface
public function stringify(ShortUrl $shortUrl): string public function stringify(ShortUrl $shortUrl): string
{ {
$domainConfig = $this->urlShortenerOptions->domain; $uriWithoutShortCode = (new Uri())->withScheme($this->urlShortenerOptions->schema)
$uriWithoutShortCode = (new Uri())->withScheme($domainConfig['schema'] ?? 'http')
->withHost($this->resolveDomain($shortUrl)) ->withHost($this->resolveDomain($shortUrl))
->withPath($this->basePath) ->withPath($this->basePath)
->__toString(); ->__toString();
@@ -32,7 +31,6 @@ readonly class ShortUrlStringifier implements ShortUrlStringifierInterface
private function resolveDomain(ShortUrl $shortUrl): string private function resolveDomain(ShortUrl $shortUrl): string
{ {
$domainConfig = $this->urlShortenerOptions->domain; return $shortUrl->getDomain()?->authority ?? $this->urlShortenerOptions->defaultDomain;
return $shortUrl->getDomain()?->authority ?? $domainConfig['hostname'] ?? '';
} }
} }

View File

@@ -40,7 +40,7 @@ class PersistenceShortUrlRelationResolver implements ShortUrlRelationResolverInt
public function resolveDomain(?string $domain): ?Domain public function resolveDomain(?string $domain): ?Domain
{ {
if ($domain === null || $domain === $this->options->defaultDomain()) { if ($domain === null || $domain === $this->options->defaultDomain) {
return null; return null;
} }

View File

@@ -24,7 +24,7 @@ readonly class ShortUrlListService implements ShortUrlListServiceInterface
*/ */
public function listShortUrls(ShortUrlsParams $params, ?ApiKey $apiKey = null): Paginator public function listShortUrls(ShortUrlsParams $params, ?ApiKey $apiKey = null): Paginator
{ {
$defaultDomain = $this->urlShortenerOptions->defaultDomain(); $defaultDomain = $this->urlShortenerOptions->defaultDomain;
$paginator = new Paginator(new ShortUrlRepositoryAdapter($this->repo, $params, $apiKey, $defaultDomain)); $paginator = new Paginator(new ShortUrlRepositoryAdapter($this->repo, $params, $apiKey, $defaultDomain));
$paginator->setMaxPerPage($params->itemsPerPage) $paginator->setMaxPerPage($params->itemsPerPage)
->setCurrentPage($params->page); ->setCurrentPage($params->page);

View File

@@ -17,7 +17,6 @@ use Psr\Log\NullLogger;
use Shlinkio\Shlink\Common\Response\QrCodeResponse; use Shlinkio\Shlink\Common\Response\QrCodeResponse;
use Shlinkio\Shlink\Core\Action\QrCodeAction; use Shlinkio\Shlink\Core\Action\QrCodeAction;
use Shlinkio\Shlink\Core\Config\Options\QrCodeOptions; use Shlinkio\Shlink\Core\Config\Options\QrCodeOptions;
use Shlinkio\Shlink\Core\Config\Options\UrlShortenerOptions;
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;

View File

@@ -29,10 +29,7 @@ class DomainServiceTest extends TestCase
protected function setUp(): void protected function setUp(): void
{ {
$this->em = $this->createMock(EntityManagerInterface::class); $this->em = $this->createMock(EntityManagerInterface::class);
$this->domainService = new DomainService( $this->domainService = new DomainService($this->em, new UrlShortenerOptions(defaultDomain: 'default.com'));
$this->em,
new UrlShortenerOptions(domain: ['hostname' => 'default.com']),
);
} }
#[Test, DataProvider('provideExcludedDomains')] #[Test, DataProvider('provideExcludedDomains')]

View File

@@ -37,7 +37,7 @@ class MatomoVisitSenderTest extends TestCase
$this->visitSender = new MatomoVisitSender( $this->visitSender = new MatomoVisitSender(
$this->trackerBuilder, $this->trackerBuilder,
new ShortUrlStringifier(new UrlShortenerOptions(domain: ['hostname' => 's2.test'])), new ShortUrlStringifier(new UrlShortenerOptions(defaultDomain: 's2.test')),
$this->visitIterationRepository, $this->visitIterationRepository,
); );
} }

View File

@@ -14,14 +14,18 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
class ShortUrlStringifierTest extends TestCase class ShortUrlStringifierTest extends TestCase
{ {
/**
* @param 'http'|'https' $schema
*/
#[Test, DataProvider('provideConfigAndShortUrls')] #[Test, DataProvider('provideConfigAndShortUrls')]
public function generatesExpectedOutputBasedOnConfigAndShortUrl( public function generatesExpectedOutputBasedOnConfigAndShortUrl(
array $domainConfig, string $defaultDomain,
string $schema,
string $basePath, string $basePath,
ShortUrl $shortUrl, ShortUrl $shortUrl,
string $expected, string $expected,
): void { ): void {
$stringifier = new ShortUrlStringifier(new UrlShortenerOptions($domainConfig), $basePath); $stringifier = new ShortUrlStringifier(new UrlShortenerOptions($defaultDomain, $schema), $basePath);
self::assertEquals($expected, $stringifier->stringify($shortUrl)); self::assertEquals($expected, $stringifier->stringify($shortUrl));
} }
@@ -36,45 +40,45 @@ class ShortUrlStringifierTest extends TestCase
]), ]),
); );
yield 'no config' => [[], '', $shortUrlWithShortCode('foo'), 'http:/foo']; yield 'no default domain' => ['', 'http', '', $shortUrlWithShortCode('foo'), 'http:/foo'];
yield 'hostname in config' => [ yield 'default domain' => [
['hostname' => 'example.com'], 'example.com',
'http',
'', '',
$shortUrlWithShortCode('bar'), $shortUrlWithShortCode('bar'),
'http://example.com/bar', 'http://example.com/bar',
]; ];
yield 'special chars in short code' => [ yield 'special chars in short code' => [
['hostname' => 'example.com'], 'example.com',
'http',
'', '',
$shortUrlWithShortCode('グーグル'), $shortUrlWithShortCode('グーグル'),
'http://example.com/グーグル', 'http://example.com/グーグル',
]; ];
yield 'emojis in short code' => [ yield 'emojis in short code' => [
['hostname' => 'example.com'], 'example.com',
'http',
'', '',
$shortUrlWithShortCode('🦣-🍅'), $shortUrlWithShortCode('🦣-🍅'),
'http://example.com/🦣-🍅', 'http://example.com/🦣-🍅',
]; ];
yield 'hostname with base path in config' => [ yield 'default domain with base path' => [
['hostname' => 'example.com/foo/bar'], 'example.com/foo/bar',
'http',
'', '',
$shortUrlWithShortCode('abc'), $shortUrlWithShortCode('abc'),
'http://example.com/foo/bar/abc', 'http://example.com/foo/bar/abc',
]; ];
yield 'full config' => [
['schema' => 'https', 'hostname' => 'foo.com'],
'',
$shortUrlWithShortCode('baz'),
'https://foo.com/baz',
];
yield 'custom domain' => [ yield 'custom domain' => [
['schema' => 'https', 'hostname' => 'foo.com'], 'foo.com',
'https',
'', '',
$shortUrlWithShortCode('baz', 'mydom.es'), $shortUrlWithShortCode('baz', 'mydom.es'),
'https://mydom.es/baz', 'https://mydom.es/baz',
]; ];
yield 'custom domain with base path' => [ yield 'custom domain with base path' => [
['schema' => 'https', 'hostname' => 'foo.com'], 'foo.com',
'https',
'/foo/bar', '/foo/bar',
$shortUrlWithShortCode('baz', 'mydom.es'), $shortUrlWithShortCode('baz', 'mydom.es'),
'https://mydom.es/foo/bar/baz', 'https://mydom.es/foo/bar/baz',

View File

@@ -29,9 +29,7 @@ class PersistenceShortUrlRelationResolverTest extends TestCase
$this->em = $this->createMock(EntityManagerInterface::class); $this->em = $this->createMock(EntityManagerInterface::class);
$this->em->method('getEventManager')->willReturn(new EventManager()); $this->em->method('getEventManager')->willReturn(new EventManager());
$this->resolver = new PersistenceShortUrlRelationResolver($this->em, new UrlShortenerOptions( $this->resolver = new PersistenceShortUrlRelationResolver($this->em, new UrlShortenerOptions('default.com'));
domain: ['schema' => 'https', 'hostname' => 'default.com'],
));
} }
#[Test, DataProvider('provideDomainsThatEmpty')] #[Test, DataProvider('provideDomainsThatEmpty')]

View File

@@ -38,7 +38,7 @@ class DomainVisitsAction extends AbstractRestAction
private function resolveDomainParam(Request $request): string private function resolveDomainParam(Request $request): string
{ {
$domainParam = $request->getAttribute('domain', ''); $domainParam = $request->getAttribute('domain', '');
if ($domainParam === $this->urlShortenerOptions->defaultDomain()) { if ($domainParam === $this->urlShortenerOptions->defaultDomain) {
return 'DEFAULT'; return 'DEFAULT';
} }

View File

@@ -28,7 +28,7 @@ readonly class DropDefaultDomainFromRequestMiddleware implements MiddlewareInter
private function sanitizeDomainFromPayload(array $payload): array private function sanitizeDomainFromPayload(array $payload): array
{ {
if (isset($payload['domain']) && $payload['domain'] === $this->urlShortenerOptions->defaultDomain()) { if (isset($payload['domain']) && $payload['domain'] === $this->urlShortenerOptions->defaultDomain) {
unset($payload['domain']); unset($payload['domain']);
} }

View File

@@ -31,10 +31,7 @@ class ListShortUrlsActionTest extends TestCase
$this->service = $this->createMock(ShortUrlListServiceInterface::class); $this->service = $this->createMock(ShortUrlListServiceInterface::class);
$this->action = new ListShortUrlsAction($this->service, new ShortUrlDataTransformer( $this->action = new ListShortUrlsAction($this->service, new ShortUrlDataTransformer(
new ShortUrlStringifier(new UrlShortenerOptions(domain: [ new ShortUrlStringifier(new UrlShortenerOptions('s.test')),
'hostname' => 's.test',
'schema' => 'https',
])),
)); ));
} }

View File

@@ -25,10 +25,7 @@ class DomainVisitsActionTest extends TestCase
protected function setUp(): void protected function setUp(): void
{ {
$this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class); $this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class);
$this->action = new DomainVisitsAction( $this->action = new DomainVisitsAction($this->visitsHelper, new UrlShortenerOptions('the_default.com'));
$this->visitsHelper,
new UrlShortenerOptions(domain: ['hostname' => 'the_default.com']),
);
} }
#[Test, DataProvider('provideDomainAuthorities')] #[Test, DataProvider('provideDomainAuthorities')]

View File

@@ -24,9 +24,7 @@ class DropDefaultDomainFromRequestMiddlewareTest extends TestCase
protected function setUp(): void protected function setUp(): void
{ {
$this->next = $this->createMock(RequestHandlerInterface::class); $this->next = $this->createMock(RequestHandlerInterface::class);
$this->middleware = new DropDefaultDomainFromRequestMiddleware( $this->middleware = new DropDefaultDomainFromRequestMiddleware(new UrlShortenerOptions('s.test'));
new UrlShortenerOptions(domain: ['hostname' => 's.test']),
);
} }
#[Test, DataProvider('provideQueryParams')] #[Test, DataProvider('provideQueryParams')]

View File

@@ -3,6 +3,7 @@ LC_ALL=C
#GEOLITE_LICENSE_KEY= #GEOLITE_LICENSE_KEY=
# URL shortener # URL shortener
DEFAULT_DOMAIN=localhost:8800
IS_HTTPS_ENABLED=false IS_HTTPS_ENABLED=false
# Database - MySQL # Database - MySQL