From 09e81b00c559a5615eff2d2108dd9d5f8321aa5d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 24 Feb 2024 23:10:08 +0100 Subject: [PATCH] Create component to resolve the long URL to redirect to for a short URL --- module/Core/config/dependencies.config.php | 8 ++- .../ShortUrlRedirectionResolver.php | 23 +++++++ .../ShortUrlRedirectionResolverInterface.php | 11 ++++ .../Helper/ShortUrlRedirectionBuilder.php | 13 ++-- .../ShortUrlRedirectionResolverTest.php | 60 +++++++++++++++++++ .../Helper/ShortUrlRedirectionBuilderTest.php | 37 ++++-------- 6 files changed, 121 insertions(+), 31 deletions(-) create mode 100644 module/Core/src/RedirectRule/ShortUrlRedirectionResolver.php create mode 100644 module/Core/src/RedirectRule/ShortUrlRedirectionResolverInterface.php create mode 100644 module/Core/test/RedirectRule/ShortUrlRedirectionResolverTest.php diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 5f9ae565..6246b307 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -32,6 +32,8 @@ return [ Options\QrCodeOptions::class => [ValinorConfigFactory::class, 'config.qr_codes'], Options\RabbitMqOptions::class => [ValinorConfigFactory::class, 'config.rabbitmq'], + RedirectRule\ShortUrlRedirectionResolver::class => ConfigAbstractFactory::class, + ShortUrl\UrlShortener::class => ConfigAbstractFactory::class, ShortUrl\ShortUrlService::class => ConfigAbstractFactory::class, ShortUrl\ShortUrlListService::class => ConfigAbstractFactory::class, @@ -156,6 +158,7 @@ return [ Util\RedirectResponseHelper::class => [Options\RedirectOptions::class], Config\NotFoundRedirectResolver::class => [Util\RedirectResponseHelper::class, 'Logger_Shlink'], + RedirectRule\ShortUrlRedirectionResolver::class => ['em'], Action\RedirectAction::class => [ ShortUrl\ShortUrlResolver::class, @@ -179,7 +182,10 @@ return [ ], ShortUrl\Helper\ShortUrlStringifier::class => ['config.url_shortener.domain', 'config.router.base_path'], ShortUrl\Helper\ShortUrlTitleResolutionHelper::class => ['httpClient', Options\UrlShortenerOptions::class], - ShortUrl\Helper\ShortUrlRedirectionBuilder::class => [Options\TrackingOptions::class], + ShortUrl\Helper\ShortUrlRedirectionBuilder::class => [ + Options\TrackingOptions::class, + RedirectRule\ShortUrlRedirectionResolver::class, + ], ShortUrl\Transformer\ShortUrlDataTransformer::class => [ShortUrl\Helper\ShortUrlStringifier::class], ShortUrl\Middleware\ExtraPathRedirectMiddleware::class => [ ShortUrl\ShortUrlResolver::class, diff --git a/module/Core/src/RedirectRule/ShortUrlRedirectionResolver.php b/module/Core/src/RedirectRule/ShortUrlRedirectionResolver.php new file mode 100644 index 00000000..dc2ae131 --- /dev/null +++ b/module/Core/src/RedirectRule/ShortUrlRedirectionResolver.php @@ -0,0 +1,23 @@ +getHeaderLine('User-Agent')); + return $shortUrl->longUrlForDevice($device); + } +} diff --git a/module/Core/src/RedirectRule/ShortUrlRedirectionResolverInterface.php b/module/Core/src/RedirectRule/ShortUrlRedirectionResolverInterface.php new file mode 100644 index 00000000..a1dd92a2 --- /dev/null +++ b/module/Core/src/RedirectRule/ShortUrlRedirectionResolverInterface.php @@ -0,0 +1,11 @@ +redirectionResolver->resolveLongUrl($shortUrl, $request)); $currentQuery = $request->getQueryParams(); - $device = DeviceType::matchFromUserAgent($request->getHeaderLine('User-Agent')); - $uri = new Uri($shortUrl->longUrlForDevice($device)); $shouldForwardQuery = $shortUrl->forwardQuery(); return $uri diff --git a/module/Core/test/RedirectRule/ShortUrlRedirectionResolverTest.php b/module/Core/test/RedirectRule/ShortUrlRedirectionResolverTest.php new file mode 100644 index 00000000..03a5ce6c --- /dev/null +++ b/module/Core/test/RedirectRule/ShortUrlRedirectionResolverTest.php @@ -0,0 +1,60 @@ +em = $this->createMock(EntityManagerInterface::class); + $this->resolver = new ShortUrlRedirectionResolver($this->em); + } + + #[Test, DataProvider('provideData')] + public function resolveLongUrlReturnsExpectedValue(ServerRequestInterface $request, string $expectedUrl): void + { + $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([ + 'longUrl' => 'https://example.com/foo/bar', + 'deviceLongUrls' => [ + DeviceType::ANDROID->value => 'https://example.com/android', + DeviceType::IOS->value => 'https://example.com/ios', + ], + ])); + + $result = $this->resolver->resolveLongUrl($shortUrl, $request); + + self::assertEquals($expectedUrl, $result); + } + + public static function provideData(): iterable + { + $request = static fn (string $userAgent = '') => ServerRequestFactory::fromGlobals()->withHeader( + 'User-Agent', + $userAgent, + ); + + yield 'unknown user agent' => [$request('Unknown'), 'https://example.com/foo/bar']; + yield 'desktop user agent' => [$request(DESKTOP_USER_AGENT), 'https://example.com/foo/bar']; + yield 'android user agent' => [$request(ANDROID_USER_AGENT), 'https://example.com/android']; + yield 'ios user agent' => [$request(IOS_USER_AGENT), 'https://example.com/ios']; + } +} diff --git a/module/Core/test/ShortUrl/Helper/ShortUrlRedirectionBuilderTest.php b/module/Core/test/ShortUrl/Helper/ShortUrlRedirectionBuilderTest.php index cf88db35..53a86322 100644 --- a/module/Core/test/ShortUrl/Helper/ShortUrlRedirectionBuilderTest.php +++ b/module/Core/test/ShortUrl/Helper/ShortUrlRedirectionBuilderTest.php @@ -7,26 +7,26 @@ namespace ShlinkioTest\Shlink\Core\ShortUrl\Helper; use Laminas\Diactoros\ServerRequestFactory; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; -use Shlinkio\Shlink\Core\Model\DeviceType; use Shlinkio\Shlink\Core\Options\TrackingOptions; +use Shlinkio\Shlink\Core\RedirectRule\ShortUrlRedirectionResolverInterface; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlRedirectionBuilder; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; -use const ShlinkioTest\Shlink\ANDROID_USER_AGENT; -use const ShlinkioTest\Shlink\DESKTOP_USER_AGENT; -use const ShlinkioTest\Shlink\IOS_USER_AGENT; - class ShortUrlRedirectionBuilderTest extends TestCase { private ShortUrlRedirectionBuilder $redirectionBuilder; + private ShortUrlRedirectionResolverInterface & MockObject $redirectionResolver; protected function setUp(): void { $trackingOptions = new TrackingOptions(disableTrackParam: 'foobar'); - $this->redirectionBuilder = new ShortUrlRedirectionBuilder($trackingOptions); + $this->redirectionResolver = $this->createMock(ShortUrlRedirectionResolverInterface::class); + + $this->redirectionBuilder = new ShortUrlRedirectionBuilder($trackingOptions, $this->redirectionResolver); } #[Test, DataProvider('provideData')] @@ -39,11 +39,12 @@ class ShortUrlRedirectionBuilderTest extends TestCase $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'https://domain.com/foo/bar?some=thing', 'forwardQuery' => $forwardQuery, - 'deviceLongUrls' => [ - DeviceType::ANDROID->value => 'https://domain.com/android', - DeviceType::IOS->value => 'https://domain.com/ios', - ], ])); + $this->redirectionResolver->expects($this->once())->method('resolveLongUrl')->with( + $shortUrl, + $request, + )->willReturn($shortUrl->getLongUrl()); + $result = $this->redirectionBuilder->buildShortUrlRedirect($shortUrl, $request, $extraPath); self::assertEquals($expectedUrl, $result); @@ -72,7 +73,7 @@ class ShortUrlRedirectionBuilderTest extends TestCase ]; yield [ 'https://domain.com/foo/bar?some=overwritten', - $request(['foobar' => 'notrack', 'some' => 'overwritten'])->withHeader('User-Agent', 'Unknown'), + $request(['foobar' => 'notrack', 'some' => 'overwritten']), null, true, ]; @@ -91,7 +92,7 @@ class ShortUrlRedirectionBuilderTest extends TestCase yield ['https://domain.com/foo/bar/something/else-baz?some=thing', $request(), '/something/else-baz', true]; yield [ 'https://domain.com/foo/bar/something/else-baz?some=thing&hello=world', - $request(['hello' => 'world'])->withHeader('User-Agent', DESKTOP_USER_AGENT), + $request(['hello' => 'world']), '/something/else-baz', true, ]; @@ -107,17 +108,5 @@ class ShortUrlRedirectionBuilderTest extends TestCase '/something/else-baz', false, ]; - yield [ - 'https://domain.com/android/something', - $request(['foo' => 'bar'])->withHeader('User-Agent', ANDROID_USER_AGENT), - '/something', - false, - ]; - yield [ - 'https://domain.com/ios?foo=bar', - $request(['foo' => 'bar'])->withHeader('User-Agent', IOS_USER_AGENT), - null, - null, - ]; } }