From eabaa94e06fbb82081c54f918d0340f667bb482f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 15 Jul 2021 19:37:09 +0200 Subject: [PATCH] Created ExtraPathRedirectMiddleware test --- CHANGELOG.md | 6 + .../ExtraPathRedirectMiddlewareTest.php | 147 ++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 module/Core/test/ShortUrl/Middleware/ExtraPathRedirectMiddlewareTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 76e14a26..63d9c6fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this Now, when calling the `GET /{shorCode}/qr-code` URL, you can pass the `errorCorrection` query param with values `L` for Low, `M` for Medium, `Q` for Quartile or `H` for High. +* [#1080](https://github.com/shlinkio/shlink/issues/1080) Added support to redirect to URLs as soon as the path starts with a valid short code, appending the rest of the path to the redirected long URL. + + With this, if you have the `https://example.com/abc123` short URL redirecting to `https://www.twitter.com`, a visit to `https://example.com/abc123/shlinkio` will take you to `https://www.twitter.com/shlinkio`. + + This behavior needs to be actively opted in, via installer config options or env vars. + ### Changed * *Nothing* diff --git a/module/Core/test/ShortUrl/Middleware/ExtraPathRedirectMiddlewareTest.php b/module/Core/test/ShortUrl/Middleware/ExtraPathRedirectMiddlewareTest.php new file mode 100644 index 00000000..24917366 --- /dev/null +++ b/module/Core/test/ShortUrl/Middleware/ExtraPathRedirectMiddlewareTest.php @@ -0,0 +1,147 @@ +resolver = $this->prophesize(ShortUrlResolverInterface::class); + $this->requestTracker = $this->prophesize(RequestTrackerInterface::class); + $this->redirectionBuilder = $this->prophesize(ShortUrlRedirectionBuilderInterface::class); + $this->redirectResponseHelper = $this->prophesize(RedirectResponseHelperInterface::class); + $this->options = new UrlShortenerOptions(['append_extra_path' => true]); + + $this->middleware = new ExtraPathRedirectMiddleware( + $this->resolver->reveal(), + $this->requestTracker->reveal(), + $this->redirectionBuilder->reveal(), + $this->redirectResponseHelper->reveal(), + $this->options, + ); + + $this->handler = $this->prophesize(RequestHandlerInterface::class); + $this->handler->handle(Argument::cetera())->willReturn(new RedirectResponse('')); + } + + /** + * @test + * @dataProvider provideNonRedirectingRequests + */ + public function handlerIsCalledWhenConfigPreventsRedirectWithExtraPath( + bool $appendExtraPath, + ServerRequestInterface $request + ): void { + $this->options->appendExtraPath = $appendExtraPath; + + $this->middleware->process($request, $this->handler->reveal()); + + $this->resolver->resolveEnabledShortUrl(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->requestTracker->trackIfApplicable(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->redirectionBuilder->buildShortUrlRedirect(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->redirectResponseHelper->buildRedirectResponse(Argument::cetera())->shouldNotHaveBeenCalled(); + } + + public function provideNonRedirectingRequests(): iterable + { + $baseReq = ServerRequestFactory::fromGlobals(); + $buildReq = static fn (?NotFoundType $type): ServerRequestInterface => + $baseReq->withAttribute(NotFoundType::class, $type); + + yield 'disabled option' => [false, $buildReq(NotFoundType::fromRequest($baseReq, '/foo/bar'))]; + yield 'base_url error' => [true, $buildReq(NotFoundType::fromRequest($baseReq, ''))]; + yield 'invalid_short_url error' => [ + true, + $buildReq(NotFoundType::fromRequest($baseReq, ''))->withAttribute( + RouteResult::class, + RouteResult::fromRoute(new Route( + '', + $this->prophesize(MiddlewareInterface::class)->reveal(), + ['GET'], + )), + ), + ]; + yield 'no error type' => [true, $buildReq(null)]; + } + + /** @test */ + public function handlerIsCalledWhenNoShortUrlIsFound(): void + { + $type = $this->prophesize(NotFoundType::class); + $type->isRegularNotFound()->willReturn(true); + $request = ServerRequestFactory::fromGlobals()->withAttribute(NotFoundType::class, $type->reveal()) + ->withUri(new Uri('/shortCode/bar/baz')); + + $resolve = $this->resolver->resolveEnabledShortUrl(Argument::cetera())->willThrow( + ShortUrlNotFoundException::class, + ); + + $this->middleware->process($request, $this->handler->reveal()); + + $resolve->shouldHaveBeenCalledOnce(); + $this->requestTracker->trackIfApplicable(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->redirectionBuilder->buildShortUrlRedirect(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->redirectResponseHelper->buildRedirectResponse(Argument::cetera())->shouldNotHaveBeenCalled(); + } + + /** @test */ + public function visitIsTrackedAndRedirectIsReturnedWhenShortUrlIsFound(): void + { + $type = $this->prophesize(NotFoundType::class); + $type->isRegularNotFound()->willReturn(true); + $request = ServerRequestFactory::fromGlobals()->withAttribute(NotFoundType::class, $type->reveal()) + ->withUri(new Uri('https://doma.in/shortCode/bar/baz')); + $shortUrl = ShortUrl::withLongUrl(''); + $identifier = ShortUrlIdentifier::fromShortCodeAndDomain('shortCode', 'doma.in'); + + $resolve = $this->resolver->resolveEnabledShortUrl($identifier)->willReturn($shortUrl); + $buildLongUrl = $this->redirectionBuilder->buildShortUrlRedirect($shortUrl, [], '/bar/baz')->willReturn( + 'the_built_long_url', + ); + $buildResp = $this->redirectResponseHelper->buildRedirectResponse('the_built_long_url')->willReturn( + new RedirectResponse(''), + ); + + $this->middleware->process($request, $this->handler->reveal()); + + $resolve->shouldHaveBeenCalledOnce(); + $buildLongUrl->shouldHaveBeenCalledOnce(); + $buildResp->shouldHaveBeenCalledOnce(); + $this->requestTracker->trackIfApplicable($shortUrl, $request)->shouldHaveBeenCalledOnce(); + } +}