Merge pull request #998 from acelaya-forks/feature/fix-base-path-with-domain

Feature/fix base path with domain
This commit is contained in:
Alejandro Celaya 2021-02-01 23:32:16 +01:00 committed by GitHub
commit 36a172308a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 291 additions and 135 deletions

View file

@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
### Fixed
* [#988](https://github.com/shlinkio/shlink/issues/988) Fixed serving zero-byte static files in apache and apache-compatible web servers.
* [#990](https://github.com/shlinkio/shlink/issues/990) Fixed short URLs not properly composed in REST API endpoints when both custom domain and custom base path are used.
## [2.5.2] - 2021-01-24

View file

@ -11,6 +11,8 @@ use Laminas\ServiceManager\Factory\InvokableFactory;
use Shlinkio\Shlink\Common\Doctrine\NoDbNameConnectionFactory;
use Shlinkio\Shlink\Core\Domain\DomainService;
use Shlinkio\Shlink\Core\Service;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Core\Tag\TagService;
use Shlinkio\Shlink\Core\Visit;
use Shlinkio\Shlink\Installer\Factory\ProcessHelperFactory;
@ -64,11 +66,14 @@ return [
Command\ShortUrl\GenerateShortUrlCommand::class => [
Service\UrlShortener::class,
'config.url_shortener.domain',
ShortUrlStringifier::class,
'config.url_shortener.default_short_codes_length',
],
Command\ShortUrl\ResolveUrlCommand::class => [Service\ShortUrl\ShortUrlResolver::class],
Command\ShortUrl\ListShortUrlsCommand::class => [Service\ShortUrlService::class, 'config.url_shortener.domain'],
Command\ShortUrl\ListShortUrlsCommand::class => [
Service\ShortUrlService::class,
ShortUrlDataTransformer::class,
],
Command\ShortUrl\GetVisitsCommand::class => [Service\VisitsTracker::class],
Command\ShortUrl\DeleteShortUrlCommand::class => [Service\ShortUrl\DeleteShortUrlService::class],

View file

@ -10,6 +10,7 @@ use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use Shlinkio\Shlink\Core\Validation\ShortUrlInputFilter;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@ -30,14 +31,17 @@ class GenerateShortUrlCommand extends BaseCommand
public const NAME = 'short-url:generate';
private UrlShortenerInterface $urlShortener;
private array $domainConfig;
private ShortUrlStringifierInterface $stringifier;
private int $defaultShortCodeLength;
public function __construct(UrlShortenerInterface $urlShortener, array $domainConfig, int $defaultShortCodeLength)
{
public function __construct(
UrlShortenerInterface $urlShortener,
ShortUrlStringifierInterface $stringifier,
int $defaultShortCodeLength
) {
parent::__construct();
$this->urlShortener = $urlShortener;
$this->domainConfig = $domainConfig;
$this->stringifier = $stringifier;
$this->defaultShortCodeLength = $defaultShortCodeLength;
}
@ -163,7 +167,7 @@ class GenerateShortUrlCommand extends BaseCommand
$io->writeln([
sprintf('Processed long URL: <info>%s</info>', $longUrl),
sprintf('Generated short URL: <info>%s</info>', $shortUrl->toString($this->domainConfig)),
sprintf('Generated short URL: <info>%s</info>', $this->stringifier->stringify($shortUrl)),
]);
return ExitCodes::EXIT_SUCCESS;
} catch (InvalidUrlException | NonUniqueSlugException $e) {

View file

@ -9,10 +9,10 @@ use Shlinkio\Shlink\CLI\Util\ExitCodes;
use Shlinkio\Shlink\CLI\Util\ShlinkTable;
use Shlinkio\Shlink\Common\Paginator\Paginator;
use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtilsTrait;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Core\Validation\ShortUrlsParamsInputFilter;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@ -42,13 +42,13 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
];
private ShortUrlServiceInterface $shortUrlService;
private ShortUrlDataTransformer $transformer;
private DataTransformerInterface $transformer;
public function __construct(ShortUrlServiceInterface $shortUrlService, array $domainConfig)
public function __construct(ShortUrlServiceInterface $shortUrlService, DataTransformerInterface $transformer)
{
parent::__construct();
$this->shortUrlService = $shortUrlService;
$this->transformer = new ShortUrlDataTransformer($domainConfig);
$this->transformer = $transformer;
}
protected function doConfigure(): void

View file

@ -16,6 +16,7 @@ use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Service\UrlShortener;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
@ -23,18 +24,17 @@ class GenerateShortUrlCommandTest extends TestCase
{
use ProphecyTrait;
private const DOMAIN_CONFIG = [
'schema' => 'http',
'hostname' => 'foo.com',
];
private CommandTester $commandTester;
private ObjectProphecy $urlShortener;
private ObjectProphecy $stringifier;
public function setUp(): void
{
$this->urlShortener = $this->prophesize(UrlShortener::class);
$command = new GenerateShortUrlCommand($this->urlShortener->reveal(), self::DOMAIN_CONFIG, 5);
$this->stringifier = $this->prophesize(ShortUrlStringifierInterface::class);
$this->stringifier->stringify(Argument::type(ShortUrl::class))->willReturn('');
$command = new GenerateShortUrlCommand($this->urlShortener->reveal(), $this->stringifier->reveal(), 5);
$app = new Application();
$app->add($command);
$this->commandTester = new CommandTester($command);
@ -45,6 +45,7 @@ class GenerateShortUrlCommandTest extends TestCase
{
$shortUrl = ShortUrl::createEmpty();
$urlToShortCode = $this->urlShortener->shorten(Argument::cetera())->willReturn($shortUrl);
$stringify = $this->stringifier->stringify($shortUrl)->willReturn('stringified_short_url');
$this->commandTester->execute([
'longUrl' => 'http://domain.com/foo/bar',
@ -53,8 +54,9 @@ class GenerateShortUrlCommandTest extends TestCase
$output = $this->commandTester->getDisplay();
self::assertEquals(ExitCodes::EXIT_SUCCESS, $this->commandTester->getStatusCode());
self::assertStringContainsString($shortUrl->toString(self::DOMAIN_CONFIG), $output);
self::assertStringContainsString('stringified_short_url', $output);
$urlToShortCode->shouldHaveBeenCalledOnce();
$stringify->shouldHaveBeenCalledOnce();
}
/** @test */
@ -97,6 +99,7 @@ class GenerateShortUrlCommandTest extends TestCase
return true;
}),
)->willReturn($shortUrl);
$stringify = $this->stringifier->stringify($shortUrl)->willReturn('stringified_short_url');
$this->commandTester->execute([
'longUrl' => 'http://domain.com/foo/bar',
@ -105,8 +108,9 @@ class GenerateShortUrlCommandTest extends TestCase
$output = $this->commandTester->getDisplay();
self::assertEquals(ExitCodes::EXIT_SUCCESS, $this->commandTester->getStatusCode());
self::assertStringContainsString($shortUrl->toString(self::DOMAIN_CONFIG), $output);
self::assertStringContainsString('stringified_short_url', $output);
$urlToShortCode->shouldHaveBeenCalledOnce();
$stringify->shouldHaveBeenCalledOnce();
}
/**

View file

@ -15,6 +15,8 @@ use Shlinkio\Shlink\Common\Paginator\Paginator;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
@ -31,7 +33,9 @@ class ListShortUrlsCommandTest extends TestCase
{
$this->shortUrlService = $this->prophesize(ShortUrlServiceInterface::class);
$app = new Application();
$command = new ListShortUrlsCommand($this->shortUrlService->reveal(), []);
$command = new ListShortUrlsCommand($this->shortUrlService->reveal(), new ShortUrlDataTransformer(
new ShortUrlStringifier([]),
));
$app->add($command);
$this->commandTester = new CommandTester($command);
}
@ -56,6 +60,7 @@ class ListShortUrlsCommandTest extends TestCase
self::assertStringContainsString('Continue with page 2?', $output);
self::assertStringContainsString('Continue with page 3?', $output);
self::assertStringContainsString('Continue with page 4?', $output);
self::assertStringNotContainsString('Continue with page 5?', $output);
}
/** @test */

View file

@ -43,6 +43,8 @@ return [
Action\QrCodeAction::class => ConfigAbstractFactory::class,
ShortUrl\Resolver\PersistenceShortUrlRelationResolver::class => ConfigAbstractFactory::class,
ShortUrl\Helper\ShortUrlStringifier::class => ConfigAbstractFactory::class,
ShortUrl\Transformer\ShortUrlDataTransformer::class => ConfigAbstractFactory::class,
Mercure\MercureUpdatesGenerator::class => ConfigAbstractFactory::class,
@ -114,13 +116,15 @@ return [
],
Action\QrCodeAction::class => [
Service\ShortUrl\ShortUrlResolver::class,
'config.url_shortener.domain',
ShortUrl\Helper\ShortUrlStringifier::class,
'Logger_Shlink',
],
ShortUrl\Resolver\PersistenceShortUrlRelationResolver::class => ['em'],
ShortUrl\Helper\ShortUrlStringifier::class => ['config.url_shortener.domain', 'config.router.base_path'],
ShortUrl\Transformer\ShortUrlDataTransformer::class => [ShortUrl\Helper\ShortUrlStringifier::class],
Mercure\MercureUpdatesGenerator::class => ['config.url_shortener.domain'],
Mercure\MercureUpdatesGenerator::class => [ShortUrl\Transformer\ShortUrlDataTransformer::class],
Importer\ImportedLinksProcessor::class => [
'em',

View file

@ -53,7 +53,7 @@ return [
'em',
'Logger_Shlink',
'config.url_shortener.visits_webhooks',
'config.url_shortener.domain',
ShortUrl\Transformer\ShortUrlDataTransformer::class,
Options\AppOptions::class,
],
EventDispatcher\NotifyVisitToMercure::class => [

View file

@ -16,6 +16,7 @@ use Shlinkio\Shlink\Common\Response\QrCodeResponse;
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
class QrCodeAction implements MiddlewareInterface
{
@ -24,17 +25,17 @@ class QrCodeAction implements MiddlewareInterface
private const MAX_SIZE = 1000;
private ShortUrlResolverInterface $urlResolver;
private array $domainConfig;
private ShortUrlStringifierInterface $stringifier;
private LoggerInterface $logger;
public function __construct(
ShortUrlResolverInterface $urlResolver,
array $domainConfig,
ShortUrlStringifierInterface $stringifier,
?LoggerInterface $logger = null
) {
$this->urlResolver = $urlResolver;
$this->domainConfig = $domainConfig;
$this->logger = $logger ?? new NullLogger();
$this->stringifier = $stringifier;
}
public function process(Request $request, RequestHandlerInterface $handler): Response
@ -52,7 +53,7 @@ class QrCodeAction implements MiddlewareInterface
// Size attribute is deprecated
$size = $this->normalizeSize((int) $request->getAttribute('size', $query['size'] ?? self::DEFAULT_SIZE));
$qrCode = new QrCode($shortUrl->toString($this->domainConfig));
$qrCode = new QrCode($this->stringifier->stringify($shortUrl));
$qrCode->setSize($size);
$qrCode->setMargin(0);

View file

@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Core\Entity;
use Cake\Chronos\Chronos;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Laminas\Diactoros\Uri;
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException;
use Shlinkio\Shlink\Core\Model\ShortUrlEdit;
@ -128,6 +127,36 @@ class ShortUrl extends AbstractEntity
return $this->tags;
}
public function getValidSince(): ?Chronos
{
return $this->validSince;
}
public function getValidUntil(): ?Chronos
{
return $this->validUntil;
}
public function getVisitsCount(): int
{
return count($this->visits);
}
/**
* @param Collection|Visit[] $visits
* @internal
*/
public function setVisits(Collection $visits): self
{
$this->visits = $visits;
return $this;
}
public function getMaxVisits(): ?int
{
return $this->maxVisits;
}
public function update(
ShortUrlEdit $shortUrlEdit,
?ShortUrlRelationResolverInterface $relationResolver = null
@ -168,36 +197,6 @@ class ShortUrl extends AbstractEntity
$this->shortCode = generateRandomShortCode($this->shortCodeLength);
}
public function getValidSince(): ?Chronos
{
return $this->validSince;
}
public function getValidUntil(): ?Chronos
{
return $this->validUntil;
}
public function getVisitsCount(): int
{
return count($this->visits);
}
/**
* @param Collection|Visit[] $visits
* @internal
*/
public function setVisits(Collection $visits): self
{
$this->visits = $visits;
return $this;
}
public function getMaxVisits(): ?int
{
return $this->maxVisits;
}
public function isEnabled(): bool
{
$maxVisitsReached = $this->maxVisits !== null && $this->getVisitsCount() >= $this->maxVisits;
@ -218,21 +217,4 @@ class ShortUrl extends AbstractEntity
return true;
}
public function toString(array $domainConfig): string
{
return (new Uri())->withPath($this->shortCode)
->withScheme($domainConfig['schema'] ?? 'http')
->withHost($this->resolveDomain($domainConfig['hostname'] ?? ''))
->__toString();
}
private function resolveDomain(string $fallback = ''): string
{
if ($this->domain === null) {
return $fallback;
}
return $this->domain->getAuthority();
}
}

View file

@ -12,10 +12,10 @@ use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\RequestOptions;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\EventDispatcher\Event\VisitLocated;
use Shlinkio\Shlink\Core\Options\AppOptions;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Throwable;
use function Functional\map;
@ -29,7 +29,7 @@ class NotifyVisitToWebHooks
private LoggerInterface $logger;
/** @var string[] */
private array $webhooks;
private ShortUrlDataTransformer $transformer;
private DataTransformerInterface $transformer;
private AppOptions $appOptions;
public function __construct(
@ -37,14 +37,14 @@ class NotifyVisitToWebHooks
EntityManagerInterface $em,
LoggerInterface $logger,
array $webhooks,
array $domainConfig,
DataTransformerInterface $transformer,
AppOptions $appOptions
) {
$this->httpClient = $httpClient;
$this->em = $em;
$this->logger = $logger;
$this->webhooks = $webhooks;
$this->transformer = new ShortUrlDataTransformer($domainConfig);
$this->transformer = $transformer;
$this->appOptions = $appOptions;
}

View file

@ -4,8 +4,8 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Mercure;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Symfony\Component\Mercure\Update;
use function json_encode;
@ -17,11 +17,11 @@ final class MercureUpdatesGenerator implements MercureUpdatesGeneratorInterface
{
private const NEW_VISIT_TOPIC = 'https://shlink.io/new-visit';
private ShortUrlDataTransformer $transformer;
private DataTransformerInterface $transformer;
public function __construct(array $domainConfig)
public function __construct(DataTransformerInterface $transformer)
{
$this->transformer = new ShortUrlDataTransformer($domainConfig);
$this->transformer = $transformer;
}
public function newVisitUpdate(Visit $visit): Update

View file

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Helper;
use Laminas\Diactoros\Uri;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use function sprintf;
class ShortUrlStringifier implements ShortUrlStringifierInterface
{
private array $domainConfig;
private string $basePath;
public function __construct(array $domainConfig, string $basePath = '')
{
$this->domainConfig = $domainConfig;
$this->basePath = $basePath;
}
public function stringify(ShortUrl $shortUrl): string
{
return (new Uri())->withPath($shortUrl->getShortCode())
->withScheme($this->domainConfig['schema'] ?? 'http')
->withHost($this->resolveDomain($shortUrl))
->__toString();
}
private function resolveDomain(ShortUrl $shortUrl): string
{
$domain = $shortUrl->getDomain();
if ($domain === null) {
return $this->domainConfig['hostname'] ?? '';
}
return sprintf('%s%s', $domain->getAuthority(), $this->basePath);
}
}

View file

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Helper;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
interface ShortUrlStringifierInterface
{
public function stringify(ShortUrl $shortUrl): string;
}

View file

@ -2,21 +2,22 @@
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Transformer;
namespace Shlinkio\Shlink\Core\ShortUrl\Transformer;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use function Functional\invoke;
use function Functional\invoke_if;
class ShortUrlDataTransformer implements DataTransformerInterface
{
private array $domainConfig;
private ShortUrlStringifierInterface $stringifier;
public function __construct(array $domainConfig)
public function __construct(ShortUrlStringifierInterface $stringifier)
{
$this->domainConfig = $domainConfig;
$this->stringifier = $stringifier;
}
/**
@ -26,7 +27,7 @@ class ShortUrlDataTransformer implements DataTransformerInterface
{
return [
'shortCode' => $shortUrl->getShortCode(),
'shortUrl' => $shortUrl->toString($this->domainConfig),
'shortUrl' => $this->stringifier->stringify($shortUrl),
'longUrl' => $shortUrl->getLongUrl(),
'dateCreated' => $shortUrl->getDateCreated()->toAtomString(),
'visitsCount' => $shortUrl->getVisitsCount(),

View file

@ -20,6 +20,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use function getimagesizefromstring;
@ -37,7 +38,10 @@ class QrCodeActionTest extends TestCase
$this->urlResolver = $this->prophesize(ShortUrlResolverInterface::class);
$this->action = new QrCodeAction($this->urlResolver->reveal(), ['domain' => 'doma.in']);
$this->action = new QrCodeAction(
$this->urlResolver->reveal(),
new ShortUrlStringifier(['domain' => 'doma.in']),
);
}
/** @test */

View file

@ -23,6 +23,8 @@ use Shlinkio\Shlink\Core\EventDispatcher\Event\VisitLocated;
use Shlinkio\Shlink\Core\EventDispatcher\NotifyVisitToWebHooks;
use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Options\AppOptions;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
use function count;
use function Functional\contains;
@ -127,7 +129,7 @@ class NotifyVisitToWebHooksTest extends TestCase
$this->em->reveal(),
$this->logger->reveal(),
$webhooks,
[],
new ShortUrlDataTransformer(new ShortUrlStringifier([])),
new AppOptions(['name' => 'Shlink', 'version' => '1.2.3']),
);
}

View file

@ -10,6 +10,8 @@ use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Mercure\MercureUpdatesGenerator;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
use function Shlinkio\Shlink\Common\json_decode;
@ -19,7 +21,7 @@ class MercureUpdatesGeneratorTest extends TestCase
public function setUp(): void
{
$this->generator = new MercureUpdatesGenerator([]);
$this->generator = new MercureUpdatesGenerator(new ShortUrlDataTransformer(new ShortUrlStringifier([])));
}
/**

View file

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Core\ShortUrl\Helper;
use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
class ShortUrlStringifierTest extends TestCase
{
/**
* @test
* @dataProvider provideConfigAndShortUrls
*/
public function generatesExpectedOutputBasedOnConfigAndShortUrl(
array $config,
string $basePath,
ShortUrl $shortUrl,
string $expected
): void {
$stringifier = new ShortUrlStringifier($config, $basePath);
self::assertEquals($expected, $stringifier->stringify($shortUrl));
}
public function provideConfigAndShortUrls(): iterable
{
$shortUrlWithShortCode = fn (string $shortCode, ?string $domain = null) => ShortUrl::fromMeta(
ShortUrlMeta::fromRawData([
'longUrl' => '',
'customSlug' => $shortCode,
'domain' => $domain,
]),
);
yield 'no config' => [[], '', $shortUrlWithShortCode('foo'), 'http:/foo'];
yield 'hostname in config' => [
['hostname' => 'example.com'],
'',
$shortUrlWithShortCode('bar'),
'http://example.com/bar',
];
yield 'hostname with base path in config' => [
['hostname' => 'example.com/foo/bar'],
'',
$shortUrlWithShortCode('abc'),
'http://example.com/foo/bar/abc',
];
yield 'full config' => [
['schema' => 'https', 'hostname' => 'foo.com'],
'',
$shortUrlWithShortCode('baz'),
'https://foo.com/baz',
];
yield 'custom domain' => [
['schema' => 'https', 'hostname' => 'foo.com'],
'',
$shortUrlWithShortCode('baz', 'mydom.es'),
'https://mydom.es/baz',
];
yield 'custom domain with base path' => [
['schema' => 'https', 'hostname' => 'foo.com'],
'/foo/bar',
$shortUrlWithShortCode('baz', 'mydom.es'),
'https://mydom.es/foo/bar/baz',
];
}
}

View file

@ -2,13 +2,14 @@
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Core\Transformer;
namespace ShlinkioTest\Shlink\Core\ShortUrl\Transformer;
use Cake\Chronos\Chronos;
use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
use function random_int;
@ -18,7 +19,7 @@ class ShortUrlDataTransformerTest extends TestCase
public function setUp(): void
{
$this->transformer = new ShortUrlDataTransformer([]);
$this->transformer = new ShortUrlDataTransformer(new ShortUrlStringifier([]));
}
/**

View file

@ -11,6 +11,7 @@ use Shlinkio\Shlink\Common\Mercure\LcobucciJwtProvider;
use Shlinkio\Shlink\Core\Domain\DomainService;
use Shlinkio\Shlink\Core\Options\AppOptions;
use Shlinkio\Shlink\Core\Service;
use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Core\Tag\TagService;
use Shlinkio\Shlink\Core\Visit;
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
@ -54,21 +55,21 @@ return [
Action\HealthAction::class => ['em', AppOptions::class],
Action\MercureInfoAction::class => [LcobucciJwtProvider::class, 'config.mercure'],
Action\ShortUrl\CreateShortUrlAction::class => [Service\UrlShortener::class, 'config.url_shortener.domain'],
Action\ShortUrl\CreateShortUrlAction::class => [Service\UrlShortener::class, ShortUrlDataTransformer::class],
Action\ShortUrl\SingleStepCreateShortUrlAction::class => [
Service\UrlShortener::class,
'config.url_shortener.domain',
ShortUrlDataTransformer::class,
],
Action\ShortUrl\EditShortUrlAction::class => [Service\ShortUrlService::class, 'config.url_shortener.domain'],
Action\ShortUrl\EditShortUrlAction::class => [Service\ShortUrlService::class, ShortUrlDataTransformer::class],
Action\ShortUrl\DeleteShortUrlAction::class => [Service\ShortUrl\DeleteShortUrlService::class],
Action\ShortUrl\ResolveShortUrlAction::class => [
Service\ShortUrl\ShortUrlResolver::class,
'config.url_shortener.domain',
ShortUrlDataTransformer::class,
],
Action\Visit\ShortUrlVisitsAction::class => [Service\VisitsTracker::class],
Action\Visit\TagVisitsAction::class => [Service\VisitsTracker::class],
Action\Visit\GlobalVisitsAction::class => [Visit\VisitsStatsHelper::class],
Action\ShortUrl\ListShortUrlsAction::class => [Service\ShortUrlService::class, 'config.url_shortener.domain'],
Action\ShortUrl\ListShortUrlsAction::class => [Service\ShortUrlService::class, ShortUrlDataTransformer::class],
Action\ShortUrl\EditShortUrlTagsAction::class => [Service\ShortUrlService::class],
Action\Tag\ListTagsAction::class => [TagService::class],
Action\Tag\DeleteTagsAction::class => [TagService::class],

View file

@ -7,21 +7,21 @@ namespace Shlinkio\Shlink\Rest\Action\ShortUrl;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
abstract class AbstractCreateShortUrlAction extends AbstractRestAction
{
private UrlShortenerInterface $urlShortener;
private ShortUrlDataTransformer $transformer;
private DataTransformerInterface $transformer;
public function __construct(UrlShortenerInterface $urlShortener, array $domainConfig)
public function __construct(UrlShortenerInterface $urlShortener, DataTransformerInterface $transformer)
{
$this->urlShortener = $urlShortener;
$this->transformer = new ShortUrlDataTransformer($domainConfig);
$this->transformer = $transformer;
}
public function handle(Request $request): Response

View file

@ -7,10 +7,10 @@ namespace Shlinkio\Shlink\Rest\Action\ShortUrl;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Model\ShortUrlEdit;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware;
@ -20,12 +20,12 @@ class EditShortUrlAction extends AbstractRestAction
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_PATCH, self::METHOD_PUT];
private ShortUrlServiceInterface $shortUrlService;
private ShortUrlDataTransformer $transformer;
private DataTransformerInterface $transformer;
public function __construct(ShortUrlServiceInterface $shortUrlService, array $domainConfig)
public function __construct(ShortUrlServiceInterface $shortUrlService, DataTransformerInterface $transformer)
{
$this->shortUrlService = $shortUrlService;
$this->transformer = new ShortUrlDataTransformer($domainConfig);
$this->transformer = $transformer;
}
public function handle(ServerRequestInterface $request): ResponseInterface

View file

@ -8,9 +8,9 @@ use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtilsTrait;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware;
@ -22,12 +22,12 @@ class ListShortUrlsAction extends AbstractRestAction
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
private ShortUrlServiceInterface $shortUrlService;
private ShortUrlDataTransformer $transformer;
private DataTransformerInterface $transformer;
public function __construct(ShortUrlServiceInterface $shortUrlService, array $domainConfig)
public function __construct(ShortUrlServiceInterface $shortUrlService, DataTransformerInterface $transformer)
{
$this->shortUrlService = $shortUrlService;
$this->transformer = new ShortUrlDataTransformer($domainConfig);
$this->transformer = $transformer;
}
public function handle(Request $request): Response

View file

@ -7,9 +7,9 @@ namespace Shlinkio\Shlink\Rest\Action\ShortUrl;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware;
@ -19,12 +19,12 @@ class ResolveShortUrlAction extends AbstractRestAction
protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET];
private ShortUrlResolverInterface $urlResolver;
private ShortUrlDataTransformer $transformer;
private DataTransformerInterface $transformer;
public function __construct(ShortUrlResolverInterface $urlResolver, array $domainConfig)
public function __construct(ShortUrlResolverInterface $urlResolver, DataTransformerInterface $transformer)
{
$this->urlResolver = $urlResolver;
$this->transformer = new ShortUrlDataTransformer($domainConfig);
$this->transformer = $transformer;
}
public function handle(Request $request): Response

View file

@ -5,12 +5,14 @@ declare(strict_types=1);
namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl;
use Cake\Chronos\Chronos;
use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Diactoros\ServerRequest;
use Laminas\Diactoros\ServerRequestFactory;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
@ -18,24 +20,21 @@ use Shlinkio\Shlink\Core\Service\UrlShortener;
use Shlinkio\Shlink\Rest\Action\ShortUrl\CreateShortUrlAction;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
use function strpos;
class CreateShortUrlActionTest extends TestCase
{
use ProphecyTrait;
private const DOMAIN_CONFIG = [
'schema' => 'http',
'hostname' => 'foo.com',
];
private CreateShortUrlAction $action;
private ObjectProphecy $urlShortener;
private ObjectProphecy $transformer;
public function setUp(): void
{
$this->urlShortener = $this->prophesize(UrlShortener::class);
$this->action = new CreateShortUrlAction($this->urlShortener->reveal(), self::DOMAIN_CONFIG);
$this->transformer = $this->prophesize(DataTransformerInterface::class);
$this->transformer->transform(Argument::type(ShortUrl::class))->willReturn([]);
$this->action = new CreateShortUrlAction($this->urlShortener->reveal(), $this->transformer->reveal());
}
/** @test */
@ -55,14 +54,18 @@ class CreateShortUrlActionTest extends TestCase
$expectedMeta['apiKey'] = $apiKey;
$shorten = $this->urlShortener->shorten(ShortUrlMeta::fromRawData($expectedMeta))->willReturn($shortUrl);
$transform = $this->transformer->transform($shortUrl)->willReturn(['shortUrl' => 'stringified_short_url']);
$request = ServerRequestFactory::fromGlobals()->withParsedBody($body)->withAttribute(ApiKey::class, $apiKey);
/** @var JsonResponse $response */
$response = $this->action->handle($request);
$payload = $response->getPayload();
self::assertEquals(200, $response->getStatusCode());
self::assertTrue(strpos($response->getBody()->getContents(), $shortUrl->toString(self::DOMAIN_CONFIG)) > 0);
self::assertEquals('stringified_short_url', $payload['shortUrl']);
$shorten->shouldHaveBeenCalledOnce();
$transform->shouldHaveBeenCalledOnce();
}
/**

View file

@ -12,6 +12,8 @@ use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Rest\Action\ShortUrl\EditShortUrlAction;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
@ -25,7 +27,9 @@ class EditShortUrlActionTest extends TestCase
public function setUp(): void
{
$this->shortUrlService = $this->prophesize(ShortUrlServiceInterface::class);
$this->action = new EditShortUrlAction($this->shortUrlService->reveal(), []);
$this->action = new EditShortUrlAction($this->shortUrlService->reveal(), new ShortUrlDataTransformer(
new ShortUrlStringifier([]),
));
}
/** @test */

View file

@ -14,6 +14,8 @@ use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Common\Paginator\Paginator;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\Service\ShortUrlService;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Rest\Action\ShortUrl\ListShortUrlsAction;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
@ -28,10 +30,12 @@ class ListShortUrlsActionTest extends TestCase
{
$this->service = $this->prophesize(ShortUrlService::class);
$this->action = new ListShortUrlsAction($this->service->reveal(), [
'hostname' => 'doma.in',
'schema' => 'https',
]);
$this->action = new ListShortUrlsAction($this->service->reveal(), new ShortUrlDataTransformer(
new ShortUrlStringifier([
'hostname' => 'doma.in',
'schema' => 'https',
]),
));
}
/**

View file

@ -11,6 +11,8 @@ use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Rest\Action\ShortUrl\ResolveShortUrlAction;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
@ -26,7 +28,9 @@ class ResolveShortUrlActionTest extends TestCase
public function setUp(): void
{
$this->urlResolver = $this->prophesize(ShortUrlResolverInterface::class);
$this->action = new ResolveShortUrlAction($this->urlResolver->reveal(), []);
$this->action = new ResolveShortUrlAction($this->urlResolver->reveal(), new ShortUrlDataTransformer(
new ShortUrlStringifier([]),
));
}
/** @test */

View file

@ -6,8 +6,10 @@ namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl;
use Laminas\Diactoros\ServerRequest;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
@ -20,18 +22,17 @@ class SingleStepCreateShortUrlActionTest extends TestCase
private SingleStepCreateShortUrlAction $action;
private ObjectProphecy $urlShortener;
private ObjectProphecy $apiKeyService;
private ObjectProphecy $transformer;
public function setUp(): void
{
$this->urlShortener = $this->prophesize(UrlShortenerInterface::class);
$this->transformer = $this->prophesize(DataTransformerInterface::class);
$this->transformer->transform(Argument::type(ShortUrl::class))->willReturn([]);
$this->action = new SingleStepCreateShortUrlAction(
$this->urlShortener->reveal(),
[
'schema' => 'http',
'hostname' => 'foo.com',
],
$this->transformer->reveal(),
);
}