diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index bff5549c..e260896d 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -135,7 +135,6 @@ class ShortUrl extends AbstractEntity /** * @param Collection|Visit[] $visits - * @return ShortUrl * @internal */ public function setVisits(Collection $visits): self diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index be61d15b..ab45143a 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -10,7 +10,6 @@ use Shlinkio\Shlink\Core\Domain\Resolver\PersistenceDomainResolver; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; -use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; @@ -124,20 +123,4 @@ class UrlShortener implements UrlShortenerInterface $this->verifyShortCodeUniqueness($meta, $shortUrlToBeCreated); } } - - /** - * @throws ShortUrlNotFoundException - * @fixme Move this method to a different service - */ - public function shortCodeToUrl(string $shortCode, ?string $domain = null): ShortUrl - { - /** @var ShortUrlRepository $shortUrlRepo */ - $shortUrlRepo = $this->em->getRepository(ShortUrl::class); - $shortUrl = $shortUrlRepo->findOneByShortCode($shortCode, $domain); - if ($shortUrl === null) { - throw ShortUrlNotFoundException::fromNotFoundShortCode($shortCode, $domain); - } - - return $shortUrl; - } } diff --git a/module/Core/src/Service/UrlShortenerInterface.php b/module/Core/src/Service/UrlShortenerInterface.php index ee9cc343..802eb048 100644 --- a/module/Core/src/Service/UrlShortenerInterface.php +++ b/module/Core/src/Service/UrlShortenerInterface.php @@ -8,7 +8,6 @@ use Psr\Http\Message\UriInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; -use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; interface UrlShortenerInterface @@ -19,9 +18,4 @@ interface UrlShortenerInterface * @throws InvalidUrlException */ public function urlToShortCode(UriInterface $url, array $tags, ShortUrlMeta $meta): ShortUrl; - - /** - * @throws ShortUrlNotFoundException - */ - public function shortCodeToUrl(string $shortCode, ?string $domain = null): ShortUrl; } diff --git a/module/Core/test/Service/ShortUrl/ShortUrlResolverTest.php b/module/Core/test/Service/ShortUrl/ShortUrlResolverTest.php new file mode 100644 index 00000000..58f79606 --- /dev/null +++ b/module/Core/test/Service/ShortUrl/ShortUrlResolverTest.php @@ -0,0 +1,135 @@ +em = $this->prophesize(EntityManagerInterface::class); + $this->urlResolver = new ShortUrlResolver($this->em->reveal()); + } + + /** @test */ + public function shortCodeIsProperlyParsed(): void + { + $shortUrl = new ShortUrl('expected_url'); + $shortCode = $shortUrl->getShortCode(); + + $repo = $this->prophesize(ShortUrlRepositoryInterface::class); + $findOneByShortCode = $repo->findOneByShortCode($shortCode, null)->willReturn($shortUrl); + $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + + $result = $this->urlResolver->shortCodeToShortUrl($shortCode); + + $this->assertSame($shortUrl, $result); + $findOneByShortCode->shouldHaveBeenCalledOnce(); + $getRepo->shouldHaveBeenCalledOnce(); + } + + /** @test */ + public function exceptionIsThrownIfShortcodeIsNotFound(): void + { + $shortCode = 'abc123'; + + $repo = $this->prophesize(ShortUrlRepositoryInterface::class); + $findOneByShortCode = $repo->findOneByShortCode($shortCode, null)->willReturn(null); + $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + + $this->expectException(ShortUrlNotFoundException::class); + $findOneByShortCode->shouldBeCalledOnce(); + $getRepo->shouldBeCalledOnce(); + + $this->urlResolver->shortCodeToShortUrl($shortCode); + } + + /** @test */ + public function shortCodeToEnabledShortUrlProperlyParsesShortCode(): void + { + $shortUrl = new ShortUrl('expected_url'); + $shortCode = $shortUrl->getShortCode(); + + $repo = $this->prophesize(ShortUrlRepositoryInterface::class); + $findOneByShortCode = $repo->findOneByShortCode($shortCode, null)->willReturn($shortUrl); + $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + + $result = $this->urlResolver->shortCodeToEnabledShortUrl($shortCode); + + $this->assertSame($shortUrl, $result); + $findOneByShortCode->shouldHaveBeenCalledOnce(); + $getRepo->shouldHaveBeenCalledOnce(); + } + + /** + * @test + * @dataProvider provideDisabledShortUrls + */ + public function shortCodeToEnabledShortUrlThrowsExceptionIfUrlIsNotEnabled(ShortUrl $shortUrl): void + { + $shortCode = $shortUrl->getShortCode(); + + $repo = $this->prophesize(ShortUrlRepositoryInterface::class); + $findOneByShortCode = $repo->findOneByShortCode($shortCode, null)->willReturn($shortUrl); + $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + + $this->expectException(ShortUrlNotFoundException::class); + $findOneByShortCode->shouldBeCalledOnce(); + $getRepo->shouldBeCalledOnce(); + + $this->urlResolver->shortCodeToEnabledShortUrl($shortCode); + } + + public function provideDisabledShortUrls(): iterable + { + $now = Chronos::now(); + + yield 'maxVisits reached' => [(function () { + $shortUrl = new ShortUrl('', ShortUrlMeta::fromRawData(['maxVisits' => 3])); + $shortUrl->setVisits(new ArrayCollection(map( + range(0, 4), + fn () => new Visit($shortUrl, Visitor::emptyInstance()), + ))); + + return $shortUrl; + })()]; + yield 'future validSince' => [new ShortUrl('', ShortUrlMeta::fromRawData([ + 'validSince' => $now->addMonth()->toAtomString(), + ]))]; + yield 'past validUntil' => [new ShortUrl('', ShortUrlMeta::fromRawData([ + 'validUntil' => $now->subMonth()->toAtomString(), + ]))]; + yield 'mixed' => [(function () use ($now) { + $shortUrl = new ShortUrl('', ShortUrlMeta::fromRawData([ + 'maxVisits' => 3, + 'validUntil' => $now->subMonth()->toAtomString(), + ])); + $shortUrl->setVisits(new ArrayCollection(map( + range(0, 4), + fn () => new Visit($shortUrl, Visitor::emptyInstance()), + ))); + + return $shortUrl; + })()]; + } +} diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index 0b531ea1..a0392489 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -19,7 +19,6 @@ use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; -use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Core\Util\UrlValidatorInterface; @@ -260,18 +259,4 @@ class UrlShortenerTest extends TestCase $findExisting->shouldHaveBeenCalledOnce(); $getRepo->shouldHaveBeenCalledOnce(); } - - /** @test */ - public function shortCodeIsProperlyParsed(): void - { - $shortUrl = new ShortUrl('expected_url'); - $shortCode = $shortUrl->getShortCode(); - - $repo = $this->prophesize(ShortUrlRepositoryInterface::class); - $repo->findOneByShortCode($shortCode, null)->willReturn($shortUrl); - $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); - - $url = $this->urlShortener->shortCodeToUrl($shortCode); - $this->assertSame($shortUrl, $url); - } }