From 07b12fac3c8e1733626b84b393c8c2454dbb7b3d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 30 Jan 2021 14:18:44 +0100 Subject: [PATCH] Refactored short URL creation so that the long URL is part of the ShortUrlMeta --- .../ShortUrl/GenerateShortUrlCommand.php | 3 +- .../ShortUrl/GenerateShortUrlCommandTest.php | 8 +- .../Command/ShortUrl/GetVisitsCommandTest.php | 2 +- .../ShortUrl/ListShortUrlsCommandTest.php | 4 +- .../ShortUrl/ResolveUrlCommandTest.php | 2 +- .../Command/Visit/LocateVisitsCommandTest.php | 6 +- module/Core/src/Entity/ShortUrl.php | 48 ++++--- module/Core/src/Model/CreateShortUrlData.php | 9 +- module/Core/src/Model/ShortUrlMeta.php | 14 +- .../src/Repository/ShortUrlRepository.php | 4 +- .../ShortUrlRepositoryInterface.php | 2 +- module/Core/src/Service/UrlShortener.php | 14 +- .../src/Service/UrlShortenerInterface.php | 2 +- .../Validation/ShortUrlMetaInputFilter.php | 15 ++- .../Repository/DomainRepositoryTest.php | 5 +- .../Repository/ShortUrlRepositoryTest.php | 120 +++++++++++------- .../test-db/Repository/TagRepositoryTest.php | 11 +- .../Repository/VisitRepositoryTest.php | 19 ++- module/Core/test/Action/PixelActionTest.php | 2 +- module/Core/test/Action/QrCodeActionTest.php | 10 +- .../Core/test/Action/RedirectActionTest.php | 4 +- module/Core/test/Entity/ShortUrlTest.php | 10 +- module/Core/test/Entity/VisitTest.php | 4 +- .../LocateShortUrlVisitTest.php | 10 +- .../NotifyVisitToMercureTest.php | 4 +- .../NotifyVisitToWebHooksTest.php | 4 +- .../Mercure/MercureUpdatesGeneratorTest.php | 2 +- module/Core/test/Model/ShortUrlMetaTest.php | 11 +- .../ShortUrl/DeleteShortUrlServiceTest.php | 4 +- .../Service/ShortUrl/ShortUrlResolverTest.php | 21 +-- .../Core/test/Service/ShortUrlServiceTest.php | 10 +- module/Core/test/Service/UrlShortenerTest.php | 71 +++++------ .../Core/test/Service/VisitsTrackerTest.php | 6 +- .../ShortUrlDataTransformerTest.php | 19 ++- module/Core/test/Visit/VisitLocatorTest.php | 4 +- .../ShortUrl/AbstractCreateShortUrlAction.php | 3 +- .../Action/ShortUrl/CreateShortUrlAction.php | 8 +- .../SingleStepCreateShortUrlAction.php | 12 +- .../test-api/Action/CreateShortUrlTest.php | 27 +++- .../test-api/Fixtures/ShortUrlsFixture.php | 46 +++---- .../ShortUrl/CreateShortUrlActionTest.php | 42 ++---- .../ShortUrl/EditShortUrlActionTest.php | 2 +- .../ShortUrl/EditShortUrlTagsActionTest.php | 2 +- .../ShortUrl/ResolveShortUrlActionTest.php | 2 +- .../SingleStepCreateShortUrlActionTest.php | 21 +-- 45 files changed, 343 insertions(+), 306 deletions(-) diff --git a/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php index 7ceb0435..6fd82c1a 100644 --- a/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php @@ -145,7 +145,8 @@ class GenerateShortUrlCommand extends BaseCommand $doValidateUrl = $this->doValidateUrl($input); try { - $shortUrl = $this->urlShortener->shorten($longUrl, $tags, ShortUrlMeta::fromRawData([ + $shortUrl = $this->urlShortener->shorten($tags, ShortUrlMeta::fromRawData([ + ShortUrlMetaInputFilter::LONG_URL => $longUrl, ShortUrlMetaInputFilter::VALID_SINCE => $this->getOptionWithDeprecatedFallback($input, 'valid-since'), ShortUrlMetaInputFilter::VALID_UNTIL => $this->getOptionWithDeprecatedFallback($input, 'valid-until'), ShortUrlMetaInputFilter::CUSTOM_SLUG => $customSlug, diff --git a/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php index 3283dced..48d9421b 100644 --- a/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php @@ -43,7 +43,7 @@ class GenerateShortUrlCommandTest extends TestCase /** @test */ public function properShortCodeIsCreatedIfLongUrlIsCorrect(): void { - $shortUrl = new ShortUrl(''); + $shortUrl = ShortUrl::createEmpty(); $urlToShortCode = $this->urlShortener->shorten(Argument::cetera())->willReturn($shortUrl); $this->commandTester->execute([ @@ -89,9 +89,8 @@ class GenerateShortUrlCommandTest extends TestCase /** @test */ public function properlyProcessesProvidedTags(): void { - $shortUrl = new ShortUrl(''); + $shortUrl = ShortUrl::createEmpty(); $urlToShortCode = $this->urlShortener->shorten( - Argument::type('string'), Argument::that(function (array $tags) { Assert::assertEquals(['foo', 'bar', 'baz', 'boo', 'zar'], $tags); return $tags; @@ -116,9 +115,8 @@ class GenerateShortUrlCommandTest extends TestCase */ public function urlValidationHasExpectedValueBasedOnProvidedTags(array $options, ?bool $expectedValidateUrl): void { - $shortUrl = new ShortUrl(''); + $shortUrl = ShortUrl::createEmpty(); $urlToShortCode = $this->urlShortener->shorten( - Argument::type('string'), Argument::type('array'), Argument::that(function (ShortUrlMeta $meta) use ($expectedValidateUrl) { Assert::assertEquals($expectedValidateUrl, $meta->doValidateUrl()); diff --git a/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php b/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php index 51394414..3da492e3 100644 --- a/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php @@ -103,7 +103,7 @@ class GetVisitsCommandTest extends TestCase $shortCode = 'abc123'; $this->visitsTracker->info(new ShortUrlIdentifier($shortCode), Argument::any())->willReturn( new Paginator(new ArrayAdapter([ - (new Visit(new ShortUrl(''), new Visitor('bar', 'foo', '')))->locate( + (new Visit(ShortUrl::createEmpty(), new Visitor('bar', 'foo', '')))->locate( new VisitLocation(new Location('', 'Spain', '', '', 0, 0, '')), ), ])), diff --git a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php index 43047bbf..784391e0 100644 --- a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php @@ -42,7 +42,7 @@ class ListShortUrlsCommandTest extends TestCase // The paginator will return more than one page $data = []; for ($i = 0; $i < 50; $i++) { - $data[] = new ShortUrl('url_' . $i); + $data[] = ShortUrl::withLongUrl('url_' . $i); } $this->shortUrlService->listShortUrls(Argument::cetera()) @@ -64,7 +64,7 @@ class ListShortUrlsCommandTest extends TestCase // The paginator will return more than one page $data = []; for ($i = 0; $i < 30; $i++) { - $data[] = new ShortUrl('url_' . $i); + $data[] = ShortUrl::withLongUrl('url_' . $i); } $this->shortUrlService->listShortUrls(ShortUrlsParams::emptyInstance()) diff --git a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php index a84a1ee3..f0025b65 100644 --- a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php @@ -41,7 +41,7 @@ class ResolveUrlCommandTest extends TestCase { $shortCode = 'abc123'; $expectedUrl = 'http://domain.com/foo/bar'; - $shortUrl = new ShortUrl($expectedUrl); + $shortUrl = ShortUrl::withLongUrl($expectedUrl); $this->urlResolver->resolveShortUrl(new ShortUrlIdentifier($shortCode))->willReturn($shortUrl) ->shouldBeCalledOnce(); diff --git a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php index bb9f4715..fc64d643 100644 --- a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php @@ -77,7 +77,7 @@ class LocateVisitsCommandTest extends TestCase bool $expectWarningPrint, array $args ): void { - $visit = new Visit(new ShortUrl(''), new Visitor('', '', '1.2.3.4')); + $visit = new Visit(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4')); $location = new VisitLocation(Location::emptyInstance()); $mockMethodBehavior = $this->invokeHelperMethods($visit, $location); @@ -121,7 +121,7 @@ class LocateVisitsCommandTest extends TestCase */ public function localhostAndEmptyAddressesAreIgnored(?string $address, string $message): void { - $visit = new Visit(new ShortUrl(''), new Visitor('', '', $address)); + $visit = new Visit(ShortUrl::createEmpty(), new Visitor('', '', $address)); $location = new VisitLocation(Location::emptyInstance()); $locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will( @@ -154,7 +154,7 @@ class LocateVisitsCommandTest extends TestCase /** @test */ public function errorWhileLocatingIpIsDisplayed(): void { - $visit = new Visit(new ShortUrl(''), new Visitor('', '', '1.2.3.4')); + $visit = new Visit(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4')); $location = new VisitLocation(Location::emptyInstance()); $locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will( diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 67d41136..3c076585 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -40,26 +40,37 @@ class ShortUrl extends AbstractEntity private ?string $importOriginalShortCode = null; private ?ApiKey $authorApiKey = null; - public function __construct( - string $longUrl, - ?ShortUrlMeta $meta = null, + public static function createEmpty(): self + { + return self::fromMeta(ShortUrlMeta::createEmpty()); + } + + public static function withLongUrl(string $longUrl): self + { + return self::fromMeta(ShortUrlMeta::fromRawData([ShortUrlMetaInputFilter::LONG_URL => $longUrl])); + } + + public static function fromMeta( + ShortUrlMeta $meta, ?ShortUrlRelationResolverInterface $relationResolver = null - ) { - $meta = $meta ?? ShortUrlMeta::createEmpty(); + ): self { + $instance = new self(); $relationResolver = $relationResolver ?? new SimpleShortUrlRelationResolver(); - $this->longUrl = $longUrl; - $this->dateCreated = Chronos::now(); - $this->visits = new ArrayCollection(); - $this->tags = new ArrayCollection(); - $this->validSince = $meta->getValidSince(); - $this->validUntil = $meta->getValidUntil(); - $this->maxVisits = $meta->getMaxVisits(); - $this->customSlugWasProvided = $meta->hasCustomSlug(); - $this->shortCodeLength = $meta->getShortCodeLength(); - $this->shortCode = $meta->getCustomSlug() ?? generateRandomShortCode($this->shortCodeLength); - $this->domain = $relationResolver->resolveDomain($meta->getDomain()); - $this->authorApiKey = $meta->getApiKey(); + $instance->longUrl = $meta->getLongUrl(); + $instance->dateCreated = Chronos::now(); + $instance->visits = new ArrayCollection(); + $instance->tags = new ArrayCollection(); + $instance->validSince = $meta->getValidSince(); + $instance->validUntil = $meta->getValidUntil(); + $instance->maxVisits = $meta->getMaxVisits(); + $instance->customSlugWasProvided = $meta->hasCustomSlug(); + $instance->shortCodeLength = $meta->getShortCodeLength(); + $instance->shortCode = $meta->getCustomSlug() ?? generateRandomShortCode($instance->shortCodeLength); + $instance->domain = $relationResolver->resolveDomain($meta->getDomain()); + $instance->authorApiKey = $meta->getApiKey(); + + return $instance; } public static function fromImport( @@ -68,6 +79,7 @@ class ShortUrl extends AbstractEntity ?ShortUrlRelationResolverInterface $relationResolver = null ): self { $meta = [ + ShortUrlMetaInputFilter::LONG_URL => $url->longUrl(), ShortUrlMetaInputFilter::DOMAIN => $url->domain(), ShortUrlMetaInputFilter::VALIDATE_URL => false, ]; @@ -75,7 +87,7 @@ class ShortUrl extends AbstractEntity $meta[ShortUrlMetaInputFilter::CUSTOM_SLUG] = $url->shortCode(); } - $instance = new self($url->longUrl(), ShortUrlMeta::fromRawData($meta), $relationResolver); + $instance = self::fromMeta(ShortUrlMeta::fromRawData($meta), $relationResolver); $instance->importSource = $url->source(); $instance->importOriginalShortCode = $url->shortCode(); $instance->dateCreated = Chronos::instance($url->createdAt()); diff --git a/module/Core/src/Model/CreateShortUrlData.php b/module/Core/src/Model/CreateShortUrlData.php index 9b64302d..d944ac42 100644 --- a/module/Core/src/Model/CreateShortUrlData.php +++ b/module/Core/src/Model/CreateShortUrlData.php @@ -6,22 +6,15 @@ namespace Shlinkio\Shlink\Core\Model; final class CreateShortUrlData { - private string $longUrl; private array $tags; private ShortUrlMeta $meta; - public function __construct(string $longUrl, array $tags = [], ?ShortUrlMeta $meta = null) + public function __construct(array $tags = [], ?ShortUrlMeta $meta = null) { - $this->longUrl = $longUrl; $this->tags = $tags; $this->meta = $meta ?? ShortUrlMeta::createEmpty(); } - public function getLongUrl(): string - { - return $this->longUrl; - } - /** * @return string[] */ diff --git a/module/Core/src/Model/ShortUrlMeta.php b/module/Core/src/Model/ShortUrlMeta.php index 0df792be..9291a5e6 100644 --- a/module/Core/src/Model/ShortUrlMeta.php +++ b/module/Core/src/Model/ShortUrlMeta.php @@ -17,6 +17,7 @@ use const Shlinkio\Shlink\Core\DEFAULT_SHORT_CODES_LENGTH; final class ShortUrlMeta { + private string $longUrl; private ?Chronos $validSince = null; private ?Chronos $validUntil = null; private ?string $customSlug = null; @@ -34,7 +35,10 @@ final class ShortUrlMeta public static function createEmpty(): self { - return new self(); + $meta = new self(); + $meta->longUrl = ''; + + return $meta; } /** @@ -52,11 +56,12 @@ final class ShortUrlMeta */ private function validateAndInit(array $data): void { - $inputFilter = new ShortUrlMetaInputFilter($data); + $inputFilter = new ShortUrlMetaInputFilter($data, true); if (! $inputFilter->isValid()) { throw ValidationException::fromInputFilter($inputFilter); } + $this->longUrl = $inputFilter->getValue(ShortUrlMetaInputFilter::LONG_URL); $this->validSince = parseDateField($inputFilter->getValue(ShortUrlMetaInputFilter::VALID_SINCE)); $this->validUntil = parseDateField($inputFilter->getValue(ShortUrlMetaInputFilter::VALID_UNTIL)); $this->customSlug = $inputFilter->getValue(ShortUrlMetaInputFilter::CUSTOM_SLUG); @@ -71,6 +76,11 @@ final class ShortUrlMeta $this->apiKey = $inputFilter->getValue(ShortUrlMetaInputFilter::API_KEY); } + public function getLongUrl(): string + { + return $this->longUrl; + } + public function getValidSince(): ?Chronos { return $this->validSince; diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index ddfaa189..286a2fcb 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -201,14 +201,14 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU return $qb; } - public function findOneMatching(string $url, array $tags, ShortUrlMeta $meta): ?ShortUrl + public function findOneMatching(array $tags, ShortUrlMeta $meta): ?ShortUrl { $qb = $this->getEntityManager()->createQueryBuilder(); $qb->select('s') ->from(ShortUrl::class, 's') ->where($qb->expr()->eq('s.longUrl', ':longUrl')) - ->setParameter('longUrl', $url) + ->setParameter('longUrl', $meta->getLongUrl()) ->setMaxResults(1) ->orderBy('s.id'); diff --git a/module/Core/src/Repository/ShortUrlRepositoryInterface.php b/module/Core/src/Repository/ShortUrlRepositoryInterface.php index a0131f6f..53da12d4 100644 --- a/module/Core/src/Repository/ShortUrlRepositoryInterface.php +++ b/module/Core/src/Repository/ShortUrlRepositoryInterface.php @@ -38,7 +38,7 @@ interface ShortUrlRepositoryInterface extends ObjectRepository, EntitySpecificat public function shortCodeIsInUse(string $slug, ?string $domain, ?Specification $spec = null): bool; - public function findOneMatching(string $url, array $tags, ShortUrlMeta $meta): ?ShortUrl; + public function findOneMatching(array $tags, ShortUrlMeta $meta): ?ShortUrl; public function importedUrlExists(ImportedShlinkUrl $url): bool; } diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index 3ed4d2df..cc411ba0 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -43,18 +43,18 @@ class UrlShortener implements UrlShortenerInterface * @throws InvalidUrlException * @throws Throwable */ - public function shorten(string $url, array $tags, ShortUrlMeta $meta): ShortUrl + public function shorten(array $tags, ShortUrlMeta $meta): ShortUrl { // First, check if a short URL exists for all provided params - $existingShortUrl = $this->findExistingShortUrlIfExists($url, $tags, $meta); + $existingShortUrl = $this->findExistingShortUrlIfExists($tags, $meta); if ($existingShortUrl !== null) { return $existingShortUrl; } - $this->urlValidator->validateUrl($url, $meta->doValidateUrl()); + $this->urlValidator->validateUrl($meta->getLongUrl(), $meta->doValidateUrl()); - return $this->em->transactional(function () use ($url, $tags, $meta) { - $shortUrl = new ShortUrl($url, $meta, $this->relationResolver); + return $this->em->transactional(function () use ($tags, $meta) { + $shortUrl = ShortUrl::fromMeta($meta, $this->relationResolver); $shortUrl->setTags($this->tagNamesToEntities($this->em, $tags)); $this->verifyShortCodeUniqueness($meta, $shortUrl); @@ -64,7 +64,7 @@ class UrlShortener implements UrlShortenerInterface }); } - private function findExistingShortUrlIfExists(string $url, array $tags, ShortUrlMeta $meta): ?ShortUrl + private function findExistingShortUrlIfExists(array $tags, ShortUrlMeta $meta): ?ShortUrl { if (! $meta->findIfExists()) { return null; @@ -72,7 +72,7 @@ class UrlShortener implements UrlShortenerInterface /** @var ShortUrlRepositoryInterface $repo */ $repo = $this->em->getRepository(ShortUrl::class); - return $repo->findOneMatching($url, $tags, $meta); + return $repo->findOneMatching($tags, $meta); } private function verifyShortCodeUniqueness(ShortUrlMeta $meta, ShortUrl $shortUrlToBeCreated): void diff --git a/module/Core/src/Service/UrlShortenerInterface.php b/module/Core/src/Service/UrlShortenerInterface.php index 45b1eb8a..b0916ab3 100644 --- a/module/Core/src/Service/UrlShortenerInterface.php +++ b/module/Core/src/Service/UrlShortenerInterface.php @@ -16,5 +16,5 @@ interface UrlShortenerInterface * @throws NonUniqueSlugException * @throws InvalidUrlException */ - public function shorten(string $url, array $tags, ShortUrlMeta $meta): ShortUrl; + public function shorten(array $tags, ShortUrlMeta $meta): ShortUrl; } diff --git a/module/Core/src/Validation/ShortUrlMetaInputFilter.php b/module/Core/src/Validation/ShortUrlMetaInputFilter.php index ca29ad14..ba095c03 100644 --- a/module/Core/src/Validation/ShortUrlMetaInputFilter.php +++ b/module/Core/src/Validation/ShortUrlMetaInputFilter.php @@ -31,15 +31,26 @@ class ShortUrlMetaInputFilter extends InputFilter public const VALIDATE_URL = 'validateUrl'; public const API_KEY = 'apiKey'; - public function __construct(array $data) + private bool $requireLongUrl; + + public function __construct(array $data, bool $requireLongUrl = false) { + $this->requireLongUrl = $requireLongUrl; $this->initialize(); $this->setData($data); } private function initialize(): void { - $this->add($this->createInput(self::LONG_URL, false)); + $longUrlInput = $this->createInput(self::LONG_URL, $this->requireLongUrl); + $longUrlInput->getValidatorChain()->attach(new Validator\NotEmpty([ + Validator\NotEmpty::OBJECT, + Validator\NotEmpty::SPACE, + Validator\NotEmpty::NULL, + Validator\NotEmpty::EMPTY_ARRAY, + Validator\NotEmpty::BOOLEAN, + ])); + $this->add($longUrlInput); $validSince = $this->createInput(self::VALID_SINCE, false); $validSince->getValidatorChain()->attach(new Validator\Date(['format' => DateTime::ATOM])); diff --git a/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php b/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php index eae77154..3231eec4 100644 --- a/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php +++ b/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php @@ -88,9 +88,8 @@ class DomainRepositoryTest extends DatabaseTestCase private function createShortUrl(Domain $domain, ?ApiKey $apiKey = null): ShortUrl { - return new ShortUrl( - 'foo', - ShortUrlMeta::fromRawData(['domain' => $domain->getAuthority(), 'apiKey' => $apiKey]), + return ShortUrl::fromMeta( + ShortUrlMeta::fromRawData(['domain' => $domain->getAuthority(), 'apiKey' => $apiKey, 'longUrl' => 'foo']), new class ($domain) implements ShortUrlRelationResolverInterface { private Domain $domain; diff --git a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php index c942f61d..40205743 100644 --- a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php @@ -39,16 +39,16 @@ class ShortUrlRepositoryTest extends DatabaseTestCase /** @test */ public function findOneWithDomainFallbackReturnsProperData(): void { - $regularOne = new ShortUrl('foo', ShortUrlMeta::fromRawData(['customSlug' => 'foo'])); + $regularOne = ShortUrl::fromMeta(ShortUrlMeta::fromRawData(['customSlug' => 'foo', 'longUrl' => 'foo'])); $this->getEntityManager()->persist($regularOne); - $withDomain = new ShortUrl('foo', ShortUrlMeta::fromRawData( - ['domain' => 'example.com', 'customSlug' => 'domain-short-code'], + $withDomain = ShortUrl::fromMeta(ShortUrlMeta::fromRawData( + ['domain' => 'example.com', 'customSlug' => 'domain-short-code', 'longUrl' => 'foo'], )); $this->getEntityManager()->persist($withDomain); - $withDomainDuplicatingRegular = new ShortUrl('foo_with_domain', ShortUrlMeta::fromRawData( - ['domain' => 'doma.in', 'customSlug' => 'foo'], + $withDomainDuplicatingRegular = ShortUrl::fromMeta(ShortUrlMeta::fromRawData( + ['domain' => 'doma.in', 'customSlug' => 'foo', 'longUrl' => 'foo_with_domain'], )); $this->getEntityManager()->persist($withDomainDuplicatingRegular); @@ -80,7 +80,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase { $count = 5; for ($i = 0; $i < $count; $i++) { - $this->getEntityManager()->persist(new ShortUrl((string) $i)); + $this->getEntityManager()->persist(ShortUrl::withLongUrl((string) $i)); } $this->getEntityManager()->flush(); @@ -93,17 +93,17 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $tag = new Tag('bar'); $this->getEntityManager()->persist($tag); - $foo = new ShortUrl('foo'); + $foo = ShortUrl::withLongUrl('foo'); $foo->setTags(new ArrayCollection([$tag])); $this->getEntityManager()->persist($foo); - $bar = new ShortUrl('bar'); + $bar = ShortUrl::withLongUrl('bar'); $visit = new Visit($bar, Visitor::emptyInstance()); $this->getEntityManager()->persist($visit); $bar->setVisits(new ArrayCollection([$visit])); $this->getEntityManager()->persist($bar); - $foo2 = new ShortUrl('foo_2'); + $foo2 = ShortUrl::withLongUrl('foo_2'); $ref = new ReflectionObject($foo2); $dateProp = $ref->getProperty('dateCreated'); $dateProp->setAccessible(true); @@ -151,7 +151,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase { $urls = ['a', 'z', 'c', 'b']; foreach ($urls as $url) { - $this->getEntityManager()->persist(new ShortUrl($url)); + $this->getEntityManager()->persist(ShortUrl::withLongUrl($url)); } $this->getEntityManager()->flush(); @@ -170,12 +170,13 @@ class ShortUrlRepositoryTest extends DatabaseTestCase /** @test */ public function shortCodeIsInUseLooksForShortUrlInProperSetOfTables(): void { - $shortUrlWithoutDomain = new ShortUrl('foo', ShortUrlMeta::fromRawData(['customSlug' => 'my-cool-slug'])); + $shortUrlWithoutDomain = ShortUrl::fromMeta( + ShortUrlMeta::fromRawData(['customSlug' => 'my-cool-slug', 'longUrl' => 'foo']), + ); $this->getEntityManager()->persist($shortUrlWithoutDomain); - $shortUrlWithDomain = new ShortUrl( - 'foo', - ShortUrlMeta::fromRawData(['domain' => 'doma.in', 'customSlug' => 'another-slug']), + $shortUrlWithDomain = ShortUrl::fromMeta( + ShortUrlMeta::fromRawData(['domain' => 'doma.in', 'customSlug' => 'another-slug', 'longUrl' => 'foo']), ); $this->getEntityManager()->persist($shortUrlWithDomain); @@ -192,12 +193,13 @@ class ShortUrlRepositoryTest extends DatabaseTestCase /** @test */ public function findOneLooksForShortUrlInProperSetOfTables(): void { - $shortUrlWithoutDomain = new ShortUrl('foo', ShortUrlMeta::fromRawData(['customSlug' => 'my-cool-slug'])); + $shortUrlWithoutDomain = ShortUrl::fromMeta( + ShortUrlMeta::fromRawData(['customSlug' => 'my-cool-slug', 'longUrl' => 'foo']), + ); $this->getEntityManager()->persist($shortUrlWithoutDomain); - $shortUrlWithDomain = new ShortUrl( - 'foo', - ShortUrlMeta::fromRawData(['domain' => 'doma.in', 'customSlug' => 'another-slug']), + $shortUrlWithDomain = ShortUrl::fromMeta( + ShortUrlMeta::fromRawData(['domain' => 'doma.in', 'customSlug' => 'another-slug', 'longUrl' => 'foo']), ); $this->getEntityManager()->persist($shortUrlWithDomain); @@ -214,12 +216,16 @@ class ShortUrlRepositoryTest extends DatabaseTestCase /** @test */ public function findOneMatchingReturnsNullForNonExistingShortUrls(): void { - self::assertNull($this->repo->findOneMatching('', [], ShortUrlMeta::createEmpty())); - self::assertNull($this->repo->findOneMatching('foobar', [], ShortUrlMeta::createEmpty())); - self::assertNull($this->repo->findOneMatching('foobar', ['foo', 'bar'], ShortUrlMeta::createEmpty())); - self::assertNull($this->repo->findOneMatching('foobar', ['foo', 'bar'], ShortUrlMeta::fromRawData([ + self::assertNull($this->repo->findOneMatching([], ShortUrlMeta::createEmpty())); + self::assertNull($this->repo->findOneMatching([], ShortUrlMeta::fromRawData(['longUrl' => 'foobar']))); + self::assertNull($this->repo->findOneMatching( + ['foo', 'bar'], + ShortUrlMeta::fromRawData(['longUrl' => 'foobar']), + )); + self::assertNull($this->repo->findOneMatching(['foo', 'bar'], ShortUrlMeta::fromRawData([ 'validSince' => Chronos::parse('2020-03-05 20:18:30'), 'customSlug' => 'this_slug_does_not_exist', + 'longUrl' => 'foobar', ]))); } @@ -229,56 +235,65 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $start = Chronos::parse('2020-03-05 20:18:30'); $end = Chronos::parse('2021-03-05 20:18:30'); - $shortUrl = new ShortUrl('foo', ShortUrlMeta::fromRawData(['validSince' => $start])); + $shortUrl = ShortUrl::fromMeta(ShortUrlMeta::fromRawData(['validSince' => $start, 'longUrl' => 'foo'])); $shortUrl->setTags($this->tagNamesToEntities($this->getEntityManager(), ['foo', 'bar'])); $this->getEntityManager()->persist($shortUrl); - $shortUrl2 = new ShortUrl('bar', ShortUrlMeta::fromRawData(['validUntil' => $end])); + $shortUrl2 = ShortUrl::fromMeta(ShortUrlMeta::fromRawData(['validUntil' => $end, 'longUrl' => 'bar'])); $this->getEntityManager()->persist($shortUrl2); - $shortUrl3 = new ShortUrl('baz', ShortUrlMeta::fromRawData(['validSince' => $start, 'validUntil' => $end])); + $shortUrl3 = ShortUrl::fromMeta( + ShortUrlMeta::fromRawData(['validSince' => $start, 'validUntil' => $end, 'longUrl' => 'baz']), + ); $this->getEntityManager()->persist($shortUrl3); - $shortUrl4 = new ShortUrl('foo', ShortUrlMeta::fromRawData(['customSlug' => 'custom', 'validUntil' => $end])); + $shortUrl4 = ShortUrl::fromMeta( + ShortUrlMeta::fromRawData(['customSlug' => 'custom', 'validUntil' => $end, 'longUrl' => 'foo']), + ); $this->getEntityManager()->persist($shortUrl4); - $shortUrl5 = new ShortUrl('foo', ShortUrlMeta::fromRawData(['maxVisits' => 3])); + $shortUrl5 = ShortUrl::fromMeta(ShortUrlMeta::fromRawData(['maxVisits' => 3, 'longUrl' => 'foo'])); $this->getEntityManager()->persist($shortUrl5); - $shortUrl6 = new ShortUrl('foo', ShortUrlMeta::fromRawData(['domain' => 'doma.in'])); + $shortUrl6 = ShortUrl::fromMeta(ShortUrlMeta::fromRawData(['domain' => 'doma.in', 'longUrl' => 'foo'])); $this->getEntityManager()->persist($shortUrl6); $this->getEntityManager()->flush(); self::assertSame( $shortUrl, - $this->repo->findOneMatching('foo', ['foo', 'bar'], ShortUrlMeta::fromRawData(['validSince' => $start])), + $this->repo->findOneMatching( + ['foo', 'bar'], + ShortUrlMeta::fromRawData(['validSince' => $start, 'longUrl' => 'foo']), + ), ); self::assertSame( $shortUrl2, - $this->repo->findOneMatching('bar', [], ShortUrlMeta::fromRawData(['validUntil' => $end])), + $this->repo->findOneMatching([], ShortUrlMeta::fromRawData(['validUntil' => $end, 'longUrl' => 'bar'])), ); self::assertSame( $shortUrl3, - $this->repo->findOneMatching('baz', [], ShortUrlMeta::fromRawData([ + $this->repo->findOneMatching([], ShortUrlMeta::fromRawData([ 'validSince' => $start, 'validUntil' => $end, + 'longUrl' => 'baz', ])), ); self::assertSame( $shortUrl4, - $this->repo->findOneMatching('foo', [], ShortUrlMeta::fromRawData([ + $this->repo->findOneMatching([], ShortUrlMeta::fromRawData([ 'customSlug' => 'custom', 'validUntil' => $end, + 'longUrl' => 'foo', ])), ); self::assertSame( $shortUrl5, - $this->repo->findOneMatching('foo', [], ShortUrlMeta::fromRawData(['maxVisits' => 3])), + $this->repo->findOneMatching([], ShortUrlMeta::fromRawData(['maxVisits' => 3, 'longUrl' => 'foo'])), ); self::assertSame( $shortUrl6, - $this->repo->findOneMatching('foo', [], ShortUrlMeta::fromRawData(['domain' => 'doma.in'])), + $this->repo->findOneMatching([], ShortUrlMeta::fromRawData(['domain' => 'doma.in', 'longUrl' => 'foo'])), ); } @@ -286,25 +301,25 @@ class ShortUrlRepositoryTest extends DatabaseTestCase public function findOneMatchingReturnsOldestOneWhenThereAreMultipleMatches(): void { $start = Chronos::parse('2020-03-05 20:18:30'); - $meta = ['validSince' => $start, 'maxVisits' => 50]; + $meta = ShortUrlMeta::fromRawData(['validSince' => $start, 'maxVisits' => 50, 'longUrl' => 'foo']); $tags = ['foo', 'bar']; $tagEntities = $this->tagNamesToEntities($this->getEntityManager(), $tags); - $shortUrl1 = new ShortUrl('foo', ShortUrlMeta::fromRawData($meta)); + $shortUrl1 = ShortUrl::fromMeta($meta); $shortUrl1->setTags($tagEntities); $this->getEntityManager()->persist($shortUrl1); - $shortUrl2 = new ShortUrl('foo', ShortUrlMeta::fromRawData($meta)); + $shortUrl2 = ShortUrl::fromMeta($meta); $shortUrl2->setTags($tagEntities); $this->getEntityManager()->persist($shortUrl2); - $shortUrl3 = new ShortUrl('foo', ShortUrlMeta::fromRawData($meta)); + $shortUrl3 = ShortUrl::fromMeta($meta); $shortUrl3->setTags($tagEntities); $this->getEntityManager()->persist($shortUrl3); $this->getEntityManager()->flush(); - $result = $this->repo->findOneMatching('foo', $tags, ShortUrlMeta::fromRawData($meta)); + $result = $this->repo->findOneMatching($tags, $meta); self::assertSame($shortUrl1, $result); self::assertNotSame($shortUrl2, $result); @@ -332,8 +347,8 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $rightDomainApiKey = ApiKey::withRoles(RoleDefinition::forDomain($rightDomain)); $this->getEntityManager()->persist($rightDomainApiKey); - $shortUrl = new ShortUrl('foo', ShortUrlMeta::fromRawData( - ['validSince' => $start, 'apiKey' => $apiKey, 'domain' => $rightDomain->getAuthority()], + $shortUrl = ShortUrl::fromMeta(ShortUrlMeta::fromRawData( + ['validSince' => $start, 'apiKey' => $apiKey, 'domain' => $rightDomain->getAuthority(), 'longUrl' => 'foo'], ), new PersistenceShortUrlRelationResolver($this->getEntityManager())); $shortUrl->setTags($this->tagNamesToEntities($this->getEntityManager(), ['foo', 'bar'])); $this->getEntityManager()->persist($shortUrl); @@ -342,45 +357,54 @@ class ShortUrlRepositoryTest extends DatabaseTestCase self::assertSame( $shortUrl, - $this->repo->findOneMatching('foo', ['foo', 'bar'], ShortUrlMeta::fromRawData(['validSince' => $start])), + $this->repo->findOneMatching( + ['foo', 'bar'], + ShortUrlMeta::fromRawData(['validSince' => $start, 'longUrl' => 'foo']), + ), ); - self::assertSame($shortUrl, $this->repo->findOneMatching('foo', ['foo', 'bar'], ShortUrlMeta::fromRawData([ + self::assertSame($shortUrl, $this->repo->findOneMatching(['foo', 'bar'], ShortUrlMeta::fromRawData([ 'validSince' => $start, 'apiKey' => $apiKey, + 'longUrl' => 'foo', ]))); - self::assertNull($this->repo->findOneMatching('foo', ['foo', 'bar'], ShortUrlMeta::fromRawData([ + self::assertNull($this->repo->findOneMatching(['foo', 'bar'], ShortUrlMeta::fromRawData([ 'validSince' => $start, 'apiKey' => $otherApiKey, + 'longUrl' => 'foo', ]))); self::assertSame( $shortUrl, - $this->repo->findOneMatching('foo', ['foo', 'bar'], ShortUrlMeta::fromRawData([ + $this->repo->findOneMatching(['foo', 'bar'], ShortUrlMeta::fromRawData([ 'validSince' => $start, 'domain' => $rightDomain->getAuthority(), + 'longUrl' => 'foo', ])), ); self::assertSame( $shortUrl, - $this->repo->findOneMatching('foo', ['foo', 'bar'], ShortUrlMeta::fromRawData([ + $this->repo->findOneMatching(['foo', 'bar'], ShortUrlMeta::fromRawData([ 'validSince' => $start, 'domain' => $rightDomain->getAuthority(), 'apiKey' => $rightDomainApiKey, + 'longUrl' => 'foo', ])), ); self::assertSame( $shortUrl, - $this->repo->findOneMatching('foo', ['foo', 'bar'], ShortUrlMeta::fromRawData([ + $this->repo->findOneMatching(['foo', 'bar'], ShortUrlMeta::fromRawData([ 'validSince' => $start, 'domain' => $rightDomain->getAuthority(), 'apiKey' => $apiKey, + 'longUrl' => 'foo', ])), ); self::assertNull( - $this->repo->findOneMatching('foo', ['foo', 'bar'], ShortUrlMeta::fromRawData([ + $this->repo->findOneMatching(['foo', 'bar'], ShortUrlMeta::fromRawData([ 'validSince' => $start, 'domain' => $rightDomain->getAuthority(), 'apiKey' => $wrongDomainApiKey, + 'longUrl' => 'foo', ])), ); } diff --git a/module/Core/test-db/Repository/TagRepositoryTest.php b/module/Core/test-db/Repository/TagRepositoryTest.php index 59f53b6b..58f146f3 100644 --- a/module/Core/test-db/Repository/TagRepositoryTest.php +++ b/module/Core/test-db/Repository/TagRepositoryTest.php @@ -62,14 +62,14 @@ class TagRepositoryTest extends DatabaseTestCase [$firstUrlTags] = array_chunk($tags, 3); $secondUrlTags = [$tags[0]]; - $shortUrl = new ShortUrl(''); + $shortUrl = ShortUrl::createEmpty(); $shortUrl->setTags(new ArrayCollection($firstUrlTags)); $this->getEntityManager()->persist($shortUrl); $this->getEntityManager()->persist(new Visit($shortUrl, Visitor::emptyInstance())); $this->getEntityManager()->persist(new Visit($shortUrl, Visitor::emptyInstance())); $this->getEntityManager()->persist(new Visit($shortUrl, Visitor::emptyInstance())); - $shortUrl2 = new ShortUrl(''); + $shortUrl2 = ShortUrl::createEmpty(); $shortUrl2->setTags(new ArrayCollection($secondUrlTags)); $this->getEntityManager()->persist($shortUrl2); $this->getEntityManager()->persist(new Visit($shortUrl2, Visitor::emptyInstance())); @@ -119,13 +119,12 @@ class TagRepositoryTest extends DatabaseTestCase [$firstUrlTags, $secondUrlTags] = array_chunk($tags, 3); - $shortUrl = new ShortUrl('', ShortUrlMeta::fromRawData(['apiKey' => $authorApiKey])); + $shortUrl = ShortUrl::fromMeta(ShortUrlMeta::fromRawData(['apiKey' => $authorApiKey, 'longUrl' => ''])); $shortUrl->setTags(new ArrayCollection($firstUrlTags)); $this->getEntityManager()->persist($shortUrl); - $shortUrl2 = new ShortUrl( - '', - ShortUrlMeta::fromRawData(['domain' => $domain->getAuthority()]), + $shortUrl2 = ShortUrl::fromMeta( + ShortUrlMeta::fromRawData(['domain' => $domain->getAuthority(), 'longUrl' => '']), new PersistenceShortUrlRelationResolver($this->getEntityManager()), ); $shortUrl2->setTags(new ArrayCollection($secondUrlTags)); diff --git a/module/Core/test-db/Repository/VisitRepositoryTest.php b/module/Core/test-db/Repository/VisitRepositoryTest.php index 1cc1c895..ebdc2116 100644 --- a/module/Core/test-db/Repository/VisitRepositoryTest.php +++ b/module/Core/test-db/Repository/VisitRepositoryTest.php @@ -40,7 +40,7 @@ class VisitRepositoryTest extends DatabaseTestCase */ public function findVisitsReturnsProperVisits(int $blockSize): void { - $shortUrl = new ShortUrl(''); + $shortUrl = ShortUrl::createEmpty(); $this->getEntityManager()->persist($shortUrl); $countIterable = function (iterable $results): int { $resultsCount = 0; @@ -190,9 +190,8 @@ class VisitRepositoryTest extends DatabaseTestCase $apiKey1 = ApiKey::withRoles(RoleDefinition::forAuthoredShortUrls()); $this->getEntityManager()->persist($apiKey1); - $shortUrl = new ShortUrl( - '', - ShortUrlMeta::fromRawData(['apiKey' => $apiKey1, 'domain' => $domain->getAuthority()]), + $shortUrl = ShortUrl::fromMeta( + ShortUrlMeta::fromRawData(['apiKey' => $apiKey1, 'domain' => $domain->getAuthority(), 'longUrl' => '']), new PersistenceShortUrlRelationResolver($this->getEntityManager()), ); $this->getEntityManager()->persist($shortUrl); @@ -200,13 +199,12 @@ class VisitRepositoryTest extends DatabaseTestCase $apiKey2 = ApiKey::withRoles(RoleDefinition::forAuthoredShortUrls()); $this->getEntityManager()->persist($apiKey2); - $shortUrl2 = new ShortUrl('', ShortUrlMeta::fromRawData(['apiKey' => $apiKey2])); + $shortUrl2 = ShortUrl::fromMeta(ShortUrlMeta::fromRawData(['apiKey' => $apiKey2, 'longUrl' => ''])); $this->getEntityManager()->persist($shortUrl2); $this->createVisitsForShortUrl($shortUrl2, 5); - $shortUrl3 = new ShortUrl( - '', - ShortUrlMeta::fromRawData(['apiKey' => $apiKey2, 'domain' => $domain->getAuthority()]), + $shortUrl3 = ShortUrl::fromMeta( + ShortUrlMeta::fromRawData(['apiKey' => $apiKey2, 'domain' => $domain->getAuthority(), 'longUrl' => '']), new PersistenceShortUrlRelationResolver($this->getEntityManager()), ); $this->getEntityManager()->persist($shortUrl3); @@ -225,7 +223,7 @@ class VisitRepositoryTest extends DatabaseTestCase private function createShortUrlsAndVisits(bool $withDomain = true): array { - $shortUrl = new ShortUrl(''); + $shortUrl = ShortUrl::createEmpty(); $domain = 'example.com'; $shortCode = $shortUrl->getShortCode(); $this->getEntityManager()->persist($shortUrl); @@ -233,9 +231,10 @@ class VisitRepositoryTest extends DatabaseTestCase $this->createVisitsForShortUrl($shortUrl); if ($withDomain) { - $shortUrlWithDomain = new ShortUrl('', ShortUrlMeta::fromRawData([ + $shortUrlWithDomain = ShortUrl::fromMeta(ShortUrlMeta::fromRawData([ 'customSlug' => $shortCode, 'domain' => $domain, + 'longUrl' => '', ])); $this->getEntityManager()->persist($shortUrlWithDomain); $this->createVisitsForShortUrl($shortUrlWithDomain, 3); diff --git a/module/Core/test/Action/PixelActionTest.php b/module/Core/test/Action/PixelActionTest.php index b1edd9ec..cae74926 100644 --- a/module/Core/test/Action/PixelActionTest.php +++ b/module/Core/test/Action/PixelActionTest.php @@ -43,7 +43,7 @@ class PixelActionTest extends TestCase { $shortCode = 'abc123'; $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, ''))->willReturn( - new ShortUrl('http://domain.com/foo/bar'), + ShortUrl::withLongUrl('http://domain.com/foo/bar'), )->shouldBeCalledOnce(); $this->visitTracker->track(Argument::cetera())->shouldBeCalledOnce(); diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index 76daa406..5593be7c 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -60,7 +60,7 @@ class QrCodeActionTest extends TestCase { $shortCode = 'abc123'; $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, '')) - ->willReturn(new ShortUrl('')) + ->willReturn(ShortUrl::createEmpty()) ->shouldBeCalledOnce(); $delegate = $this->prophesize(RequestHandlerInterface::class); @@ -83,7 +83,9 @@ class QrCodeActionTest extends TestCase string $expectedContentType ): void { $code = 'abc123'; - $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn(new ShortUrl('')); + $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn( + ShortUrl::createEmpty(), + ); $delegate = $this->prophesize(RequestHandlerInterface::class); $req = (new ServerRequest())->withAttribute('shortCode', $code)->withQueryParams($query); @@ -107,7 +109,9 @@ class QrCodeActionTest extends TestCase public function imageIsReturnedWithExpectedSize(ServerRequestInterface $req, int $expectedSize): void { $code = 'abc123'; - $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn(new ShortUrl('')); + $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn( + ShortUrl::createEmpty(), + ); $delegate = $this->prophesize(RequestHandlerInterface::class); $resp = $this->action->process($req->withAttribute('shortCode', $code), $delegate->reveal()); diff --git a/module/Core/test/Action/RedirectActionTest.php b/module/Core/test/Action/RedirectActionTest.php index 99046e8c..411d9a50 100644 --- a/module/Core/test/Action/RedirectActionTest.php +++ b/module/Core/test/Action/RedirectActionTest.php @@ -54,7 +54,7 @@ class RedirectActionTest extends TestCase public function redirectionIsPerformedToLongUrl(string $expectedUrl, array $query): void { $shortCode = 'abc123'; - $shortUrl = new ShortUrl('http://domain.com/foo/bar?some=thing'); + $shortUrl = ShortUrl::withLongUrl('http://domain.com/foo/bar?some=thing'); $shortCodeToUrl = $this->urlResolver->resolveEnabledShortUrl( new ShortUrlIdentifier($shortCode, ''), )->willReturn($shortUrl); @@ -104,7 +104,7 @@ class RedirectActionTest extends TestCase public function trackingIsDisabledWhenRequestIsForwardedFromHead(): void { $shortCode = 'abc123'; - $shortUrl = new ShortUrl('http://domain.com/foo/bar?some=thing'); + $shortUrl = ShortUrl::withLongUrl('http://domain.com/foo/bar?some=thing'); $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, ''))->willReturn($shortUrl); $track = $this->visitTracker->track(Argument::cetera())->will(function (): void { }); diff --git a/module/Core/test/Entity/ShortUrlTest.php b/module/Core/test/Entity/ShortUrlTest.php index 9f28c41b..1f652274 100644 --- a/module/Core/test/Entity/ShortUrlTest.php +++ b/module/Core/test/Entity/ShortUrlTest.php @@ -37,11 +37,11 @@ class ShortUrlTest extends TestCase public function provideInvalidShortUrls(): iterable { yield 'with custom slug' => [ - new ShortUrl('', ShortUrlMeta::fromRawData(['customSlug' => 'custom-slug'])), + ShortUrl::fromMeta(ShortUrlMeta::fromRawData(['customSlug' => 'custom-slug', 'longUrl' => ''])), 'The short code cannot be regenerated on ShortUrls where a custom slug was provided.', ]; yield 'already persisted' => [ - (new ShortUrl(''))->setId('1'), + ShortUrl::createEmpty()->setId('1'), 'The short code can be regenerated only on new ShortUrls which have not been persisted yet.', ]; } @@ -62,7 +62,7 @@ class ShortUrlTest extends TestCase public function provideValidShortUrls(): iterable { - yield 'no custom slug' => [new ShortUrl('')]; + yield 'no custom slug' => [ShortUrl::createEmpty()]; yield 'imported with custom slug' => [ ShortUrl::fromImport(new ImportedShlinkUrl('', '', [], Chronos::now(), null, 'custom-slug'), true), ]; @@ -74,8 +74,8 @@ class ShortUrlTest extends TestCase */ public function shortCodesHaveExpectedLength(?int $length, int $expectedLength): void { - $shortUrl = new ShortUrl('', ShortUrlMeta::fromRawData( - [ShortUrlMetaInputFilter::SHORT_CODE_LENGTH => $length], + $shortUrl = ShortUrl::fromMeta(ShortUrlMeta::fromRawData( + [ShortUrlMetaInputFilter::SHORT_CODE_LENGTH => $length, 'longUrl' => ''], )); self::assertEquals($expectedLength, strlen($shortUrl->getShortCode())); diff --git a/module/Core/test/Entity/VisitTest.php b/module/Core/test/Entity/VisitTest.php index 9d75f793..d583c799 100644 --- a/module/Core/test/Entity/VisitTest.php +++ b/module/Core/test/Entity/VisitTest.php @@ -19,7 +19,7 @@ class VisitTest extends TestCase */ public function isProperlyJsonSerialized(?Chronos $date): void { - $visit = new Visit(new ShortUrl(''), new Visitor('Chrome', 'some site', '1.2.3.4'), true, $date); + $visit = new Visit(ShortUrl::createEmpty(), new Visitor('Chrome', 'some site', '1.2.3.4'), true, $date); self::assertEquals([ 'referer' => 'some site', @@ -41,7 +41,7 @@ class VisitTest extends TestCase */ public function addressIsAnonymizedWhenRequested(bool $anonymize, ?string $address, ?string $expectedAddress): void { - $visit = new Visit(new ShortUrl(''), new Visitor('Chrome', 'some site', $address), $anonymize); + $visit = new Visit(ShortUrl::createEmpty(), new Visitor('Chrome', 'some site', $address), $anonymize); self::assertEquals($expectedAddress, $visit->getRemoteAddr()); } diff --git a/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php b/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php index 8c9119a5..fdb5bfec 100644 --- a/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php +++ b/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php @@ -78,7 +78,7 @@ class LocateShortUrlVisitTest extends TestCase { $event = new ShortUrlVisited('123'); $findVisit = $this->em->find(Visit::class, '123')->willReturn( - new Visit(new ShortUrl(''), new Visitor('', '', '1.2.3.4')), + new Visit(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4')), ); $resolveLocation = $this->ipLocationResolver->resolveIpLocation(Argument::cetera())->willThrow( WrongIpException::class, @@ -125,7 +125,7 @@ class LocateShortUrlVisitTest extends TestCase public function provideNonLocatableVisits(): iterable { - $shortUrl = new ShortUrl(''); + $shortUrl = ShortUrl::createEmpty(); yield 'null IP' => [new Visit($shortUrl, new Visitor('', '', null))]; yield 'empty IP' => [new Visit($shortUrl, new Visitor('', '', ''))]; @@ -139,7 +139,7 @@ class LocateShortUrlVisitTest extends TestCase public function locatableVisitsResolveToLocation(string $anonymizedIpAddress, ?string $originalIpAddress): void { $ipAddr = $originalIpAddress ?? $anonymizedIpAddress; - $visit = new Visit(new ShortUrl(''), new Visitor('', '', $ipAddr)); + $visit = new Visit(ShortUrl::createEmpty(), new Visitor('', '', $ipAddr)); $location = new Location('', '', '', '', 0.0, 0.0, ''); $event = new ShortUrlVisited('123', $originalIpAddress); @@ -171,7 +171,7 @@ class LocateShortUrlVisitTest extends TestCase { $e = GeolocationDbUpdateFailedException::create(true); $ipAddr = '1.2.3.0'; - $visit = new Visit(new ShortUrl(''), new Visitor('', '', $ipAddr)); + $visit = new Visit(ShortUrl::createEmpty(), new Visitor('', '', $ipAddr)); $location = new Location('', '', '', '', 0.0, 0.0, ''); $event = new ShortUrlVisited('123'); @@ -202,7 +202,7 @@ class LocateShortUrlVisitTest extends TestCase { $e = GeolocationDbUpdateFailedException::create(false); $ipAddr = '1.2.3.0'; - $visit = new Visit(new ShortUrl(''), new Visitor('', '', $ipAddr)); + $visit = new Visit(ShortUrl::createEmpty(), new Visitor('', '', $ipAddr)); $location = new Location('', '', '', '', 0.0, 0.0, ''); $event = new ShortUrlVisited('123'); diff --git a/module/Core/test/EventDispatcher/NotifyVisitToMercureTest.php b/module/Core/test/EventDispatcher/NotifyVisitToMercureTest.php index b8e71297..f5b525ea 100644 --- a/module/Core/test/EventDispatcher/NotifyVisitToMercureTest.php +++ b/module/Core/test/EventDispatcher/NotifyVisitToMercureTest.php @@ -77,7 +77,7 @@ class NotifyVisitToMercureTest extends TestCase public function notificationsAreSentWhenVisitIsFound(): void { $visitId = '123'; - $visit = new Visit(new ShortUrl(''), Visitor::emptyInstance()); + $visit = new Visit(ShortUrl::createEmpty(), Visitor::emptyInstance()); $update = new Update('', ''); $findVisit = $this->em->find(Visit::class, $visitId)->willReturn($visit); @@ -101,7 +101,7 @@ class NotifyVisitToMercureTest extends TestCase public function debugIsLoggedWhenExceptionIsThrown(): void { $visitId = '123'; - $visit = new Visit(new ShortUrl(''), Visitor::emptyInstance()); + $visit = new Visit(ShortUrl::createEmpty(), Visitor::emptyInstance()); $update = new Update('', ''); $e = new RuntimeException('Error'); diff --git a/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php b/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php index e7021e18..ff382f13 100644 --- a/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php +++ b/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php @@ -79,7 +79,9 @@ class NotifyVisitToWebHooksTest extends TestCase $webhooks = ['foo', 'invalid', 'bar', 'baz']; $invalidWebhooks = ['invalid', 'baz']; - $find = $this->em->find(Visit::class, '1')->willReturn(new Visit(new ShortUrl(''), Visitor::emptyInstance())); + $find = $this->em->find(Visit::class, '1')->willReturn( + new Visit(ShortUrl::createEmpty(), Visitor::emptyInstance()), + ); $requestAsync = $this->httpClient->requestAsync( RequestMethodInterface::METHOD_POST, Argument::type('string'), diff --git a/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php b/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php index aef2a489..b7382f84 100644 --- a/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php +++ b/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php @@ -28,7 +28,7 @@ class MercureUpdatesGeneratorTest extends TestCase */ public function visitIsProperlySerializedIntoUpdate(string $method, string $expectedTopic): void { - $shortUrl = new ShortUrl('', ShortUrlMeta::fromRawData(['customSlug' => 'foo'])); + $shortUrl = ShortUrl::fromMeta(ShortUrlMeta::fromRawData(['customSlug' => 'foo', 'longUrl' => ''])); $visit = new Visit($shortUrl, Visitor::emptyInstance()); $update = $this->generator->{$method}($visit); diff --git a/module/Core/test/Model/ShortUrlMetaTest.php b/module/Core/test/Model/ShortUrlMetaTest.php index 3c45dad9..848bd90b 100644 --- a/module/Core/test/Model/ShortUrlMetaTest.php +++ b/module/Core/test/Model/ShortUrlMetaTest.php @@ -56,6 +56,9 @@ class ShortUrlMetaTest extends TestCase yield [[ ShortUrlMetaInputFilter::CUSTOM_SLUG => ' ', ]]; + yield [[ + ShortUrlMetaInputFilter::LONG_URL => [], + ]]; } /** @@ -64,9 +67,11 @@ class ShortUrlMetaTest extends TestCase */ public function properlyCreatedInstanceReturnsValues(string $customSlug, string $expectedSlug): void { - $meta = ShortUrlMeta::fromRawData( - ['validSince' => Chronos::parse('2015-01-01')->toAtomString(), 'customSlug' => $customSlug], - ); + $meta = ShortUrlMeta::fromRawData([ + 'validSince' => Chronos::parse('2015-01-01')->toAtomString(), + 'customSlug' => $customSlug, + 'longUrl' => '', + ]); self::assertTrue($meta->hasValidSince()); self::assertEquals(Chronos::parse('2015-01-01'), $meta->getValidSince()); diff --git a/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php b/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php index 449220b4..9e2ca4ec 100644 --- a/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php +++ b/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php @@ -33,8 +33,8 @@ class DeleteShortUrlServiceTest extends TestCase public function setUp(): void { - $shortUrl = (new ShortUrl(''))->setVisits(new ArrayCollection( - map(range(0, 10), fn () => new Visit(new ShortUrl(''), Visitor::emptyInstance())), + $shortUrl = ShortUrl::createEmpty()->setVisits(new ArrayCollection( + map(range(0, 10), fn () => new Visit(ShortUrl::createEmpty(), Visitor::emptyInstance())), )); $this->shortCode = $shortUrl->getShortCode(); diff --git a/module/Core/test/Service/ShortUrl/ShortUrlResolverTest.php b/module/Core/test/Service/ShortUrl/ShortUrlResolverTest.php index e7cc0041..038fe457 100644 --- a/module/Core/test/Service/ShortUrl/ShortUrlResolverTest.php +++ b/module/Core/test/Service/ShortUrl/ShortUrlResolverTest.php @@ -44,7 +44,7 @@ class ShortUrlResolverTest extends TestCase */ public function shortCodeIsProperlyParsed(?ApiKey $apiKey): void { - $shortUrl = new ShortUrl('expected_url'); + $shortUrl = ShortUrl::withLongUrl('expected_url'); $shortCode = $shortUrl->getShortCode(); $repo = $this->prophesize(ShortUrlRepositoryInterface::class); @@ -80,7 +80,7 @@ class ShortUrlResolverTest extends TestCase /** @test */ public function shortCodeToEnabledShortUrlProperlyParsesShortCode(): void { - $shortUrl = new ShortUrl('expected_url'); + $shortUrl = ShortUrl::withLongUrl('expected_url'); $shortCode = $shortUrl->getShortCode(); $repo = $this->prophesize(ShortUrlRepositoryInterface::class); @@ -118,7 +118,7 @@ class ShortUrlResolverTest extends TestCase $now = Chronos::now(); yield 'maxVisits reached' => [(function () { - $shortUrl = new ShortUrl('', ShortUrlMeta::fromRawData(['maxVisits' => 3])); + $shortUrl = ShortUrl::fromMeta(ShortUrlMeta::fromRawData(['maxVisits' => 3, 'longUrl' => ''])); $shortUrl->setVisits(new ArrayCollection(map( range(0, 4), fn () => new Visit($shortUrl, Visitor::emptyInstance()), @@ -126,16 +126,17 @@ class ShortUrlResolverTest extends TestCase 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 'future validSince' => [ShortUrl::fromMeta(ShortUrlMeta::fromRawData( + ['validSince' => $now->addMonth()->toAtomString(), 'longUrl' => ''], + ))]; + yield 'past validUntil' => [ShortUrl::fromMeta(ShortUrlMeta::fromRawData( + ['validUntil' => $now->subMonth()->toAtomString(), 'longUrl' => ''], + ))]; yield 'mixed' => [(function () use ($now) { - $shortUrl = new ShortUrl('', ShortUrlMeta::fromRawData([ + $shortUrl = ShortUrl::fromMeta(ShortUrlMeta::fromRawData([ 'maxVisits' => 3, 'validUntil' => $now->subMonth()->toAtomString(), + 'longUrl' => '', ])); $shortUrl->setVisits(new ArrayCollection(map( range(0, 4), diff --git a/module/Core/test/Service/ShortUrlServiceTest.php b/module/Core/test/Service/ShortUrlServiceTest.php index 5ced9b1a..d056b91b 100644 --- a/module/Core/test/Service/ShortUrlServiceTest.php +++ b/module/Core/test/Service/ShortUrlServiceTest.php @@ -58,10 +58,10 @@ class ShortUrlServiceTest extends TestCase public function listedUrlsAreReturnedFromEntityManager(?ApiKey $apiKey): void { $list = [ - new ShortUrl(''), - new ShortUrl(''), - new ShortUrl(''), - new ShortUrl(''), + ShortUrl::createEmpty(), + ShortUrl::createEmpty(), + ShortUrl::createEmpty(), + ShortUrl::createEmpty(), ]; $repo = $this->prophesize(ShortUrlRepository::class); @@ -106,7 +106,7 @@ class ShortUrlServiceTest extends TestCase ?ApiKey $apiKey ): void { $originalLongUrl = 'originalLongUrl'; - $shortUrl = new ShortUrl($originalLongUrl); + $shortUrl = ShortUrl::withLongUrl($originalLongUrl); $findShortUrl = $this->urlResolver->resolveShortUrl( new ShortUrlIdentifier('abc123'), diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index 9d8c5273..fb4affc7 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -69,9 +69,8 @@ class UrlShortenerTest extends TestCase public function urlIsProperlyShortened(): void { $shortUrl = $this->urlShortener->shorten( - 'http://foobar.com/12345/hello?foo=bar', [], - ShortUrlMeta::createEmpty(), + ShortUrlMeta::fromRawData(['longUrl' => 'http://foobar.com/12345/hello?foo=bar']), ); self::assertEquals('http://foobar.com/12345/hello?foo=bar', $shortUrl->getLongUrl()); @@ -85,28 +84,22 @@ class UrlShortenerTest extends TestCase $ensureUniqueness->shouldBeCalledOnce(); $this->expectException(NonUniqueSlugException::class); - $this->urlShortener->shorten( - 'http://foobar.com/12345/hello?foo=bar', - [], - ShortUrlMeta::fromRawData(['customSlug' => 'custom-slug']), - ); + $this->urlShortener->shorten([], ShortUrlMeta::fromRawData( + ['customSlug' => 'custom-slug', 'longUrl' => 'http://foobar.com/12345/hello?foo=bar'], + )); } /** * @test * @dataProvider provideExistingShortUrls */ - public function existingShortUrlIsReturnedWhenRequested( - string $url, - array $tags, - ShortUrlMeta $meta, - ShortUrl $expected - ): void { + public function existingShortUrlIsReturnedWhenRequested(array $tags, ShortUrlMeta $meta, ShortUrl $expected): void + { $repo = $this->prophesize(ShortUrlRepository::class); $findExisting = $repo->findOneMatching(Argument::cetera())->willReturn($expected); $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); - $result = $this->urlShortener->shorten($url, $tags, $meta); + $result = $this->urlShortener->shorten($tags, $meta); $findExisting->shouldHaveBeenCalledOnce(); $getRepo->shouldHaveBeenCalledOnce(); @@ -119,52 +112,58 @@ class UrlShortenerTest extends TestCase { $url = 'http://foo.com'; - yield [$url, [], ShortUrlMeta::fromRawData(['findIfExists' => true]), new ShortUrl($url)]; - yield [$url, [], ShortUrlMeta::fromRawData( - ['findIfExists' => true, 'customSlug' => 'foo'], - ), new ShortUrl($url)]; - yield [ + yield [[], ShortUrlMeta::fromRawData(['findIfExists' => true, 'longUrl' => $url]), ShortUrl::withLongUrl( $url, + )]; + yield [[], ShortUrlMeta::fromRawData( + ['findIfExists' => true, 'customSlug' => 'foo', 'longUrl' => $url], + ), ShortUrl::withLongUrl($url)]; + yield [ ['foo', 'bar'], - ShortUrlMeta::fromRawData(['findIfExists' => true]), - (new ShortUrl($url))->setTags(new ArrayCollection([new Tag('bar'), new Tag('foo')])), + ShortUrlMeta::fromRawData(['findIfExists' => true, 'longUrl' => $url]), + ShortUrl::withLongUrl($url)->setTags(new ArrayCollection([new Tag('bar'), new Tag('foo')])), ]; yield [ - $url, [], - ShortUrlMeta::fromRawData(['findIfExists' => true, 'maxVisits' => 3]), - new ShortUrl($url, ShortUrlMeta::fromRawData(['maxVisits' => 3])), + ShortUrlMeta::fromRawData(['findIfExists' => true, 'maxVisits' => 3, 'longUrl' => $url]), + ShortUrl::fromMeta(ShortUrlMeta::fromRawData(['maxVisits' => 3, 'longUrl' => $url])), ]; yield [ - $url, [], - ShortUrlMeta::fromRawData(['findIfExists' => true, 'validSince' => Chronos::parse('2017-01-01')]), - new ShortUrl($url, ShortUrlMeta::fromRawData(['validSince' => Chronos::parse('2017-01-01')])), + ShortUrlMeta::fromRawData( + ['findIfExists' => true, 'validSince' => Chronos::parse('2017-01-01'), 'longUrl' => $url], + ), + ShortUrl::fromMeta( + ShortUrlMeta::fromRawData(['validSince' => Chronos::parse('2017-01-01'), 'longUrl' => $url]), + ), ]; yield [ - $url, [], - ShortUrlMeta::fromRawData(['findIfExists' => true, 'validUntil' => Chronos::parse('2017-01-01')]), - new ShortUrl($url, ShortUrlMeta::fromRawData(['validUntil' => Chronos::parse('2017-01-01')])), + ShortUrlMeta::fromRawData( + ['findIfExists' => true, 'validUntil' => Chronos::parse('2017-01-01'), 'longUrl' => $url], + ), + ShortUrl::fromMeta( + ShortUrlMeta::fromRawData(['validUntil' => Chronos::parse('2017-01-01'), 'longUrl' => $url]), + ), ]; yield [ - $url, [], - ShortUrlMeta::fromRawData(['findIfExists' => true, 'domain' => 'example.com']), - new ShortUrl($url, ShortUrlMeta::fromRawData(['domain' => 'example.com'])), + ShortUrlMeta::fromRawData(['findIfExists' => true, 'domain' => 'example.com', 'longUrl' => $url]), + ShortUrl::fromMeta(ShortUrlMeta::fromRawData(['domain' => 'example.com', 'longUrl' => $url])), ]; yield [ - $url, ['baz', 'foo', 'bar'], ShortUrlMeta::fromRawData([ 'findIfExists' => true, 'validUntil' => Chronos::parse('2017-01-01'), 'maxVisits' => 4, + 'longUrl' => $url, ]), - (new ShortUrl($url, ShortUrlMeta::fromRawData([ + ShortUrl::fromMeta(ShortUrlMeta::fromRawData([ 'validUntil' => Chronos::parse('2017-01-01'), 'maxVisits' => 4, - ])))->setTags(new ArrayCollection([new Tag('foo'), new Tag('bar'), new Tag('baz')])), + 'longUrl' => $url, + ]))->setTags(new ArrayCollection([new Tag('foo'), new Tag('bar'), new Tag('baz')])), ]; } } diff --git a/module/Core/test/Service/VisitsTrackerTest.php b/module/Core/test/Service/VisitsTrackerTest.php index 1efe61df..9b627a17 100644 --- a/module/Core/test/Service/VisitsTrackerTest.php +++ b/module/Core/test/Service/VisitsTrackerTest.php @@ -56,7 +56,7 @@ class VisitsTrackerTest extends TestCase $this->em->persist(Argument::that(fn (Visit $visit) => $visit->setId('1')))->shouldBeCalledOnce(); $this->em->flush()->shouldBeCalledOnce(); - $this->visitsTracker->track(new ShortUrl($shortCode), Visitor::emptyInstance()); + $this->visitsTracker->track(ShortUrl::withLongUrl($shortCode), Visitor::emptyInstance()); $this->eventDispatcher->dispatch(Argument::type(ShortUrlVisited::class))->shouldHaveBeenCalled(); } @@ -73,7 +73,7 @@ class VisitsTrackerTest extends TestCase $count = $repo->shortCodeIsInUse($shortCode, null, $spec)->willReturn(true); $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal())->shouldBeCalledOnce(); - $list = map(range(0, 1), fn () => new Visit(new ShortUrl(''), Visitor::emptyInstance())); + $list = map(range(0, 1), fn () => new Visit(ShortUrl::createEmpty(), Visitor::emptyInstance())); $repo2 = $this->prophesize(VisitRepository::class); $repo2->findVisitsByShortCode($shortCode, null, Argument::type(DateRange::class), 1, 0, $spec)->willReturn( $list, @@ -129,7 +129,7 @@ class VisitsTrackerTest extends TestCase $getRepo = $this->em->getRepository(Tag::class)->willReturn($repo->reveal()); $spec = $apiKey === null ? null : $apiKey->spec(); - $list = map(range(0, 1), fn () => new Visit(new ShortUrl(''), Visitor::emptyInstance())); + $list = map(range(0, 1), fn () => new Visit(ShortUrl::createEmpty(), Visitor::emptyInstance())); $repo2 = $this->prophesize(VisitRepository::class); $repo2->findVisitsByTag($tag, Argument::type(DateRange::class), 1, 0, $spec)->willReturn($list); $repo2->countVisitsByTag($tag, Argument::type(DateRange::class), $spec)->willReturn(1); diff --git a/module/Core/test/Transformer/ShortUrlDataTransformerTest.php b/module/Core/test/Transformer/ShortUrlDataTransformerTest.php index 9abe5f1a..0a621799 100644 --- a/module/Core/test/Transformer/ShortUrlDataTransformerTest.php +++ b/module/Core/test/Transformer/ShortUrlDataTransformerTest.php @@ -37,18 +37,23 @@ class ShortUrlDataTransformerTest extends TestCase $maxVisits = random_int(1, 1000); $now = Chronos::now(); - yield 'no metadata' => [new ShortUrl('', ShortUrlMeta::createEmpty()), [ + yield 'no metadata' => [ShortUrl::createEmpty(), [ 'validSince' => null, 'validUntil' => null, 'maxVisits' => null, ]]; - yield 'max visits only' => [new ShortUrl('', ShortUrlMeta::fromRawData(['maxVisits' => $maxVisits])), [ + yield 'max visits only' => [ShortUrl::fromMeta(ShortUrlMeta::fromRawData([ + 'maxVisits' => $maxVisits, + 'longUrl' => '', + ])), [ 'validSince' => null, 'validUntil' => null, 'maxVisits' => $maxVisits, ]]; yield 'max visits and valid since' => [ - new ShortUrl('', ShortUrlMeta::fromRawData(['validSince' => $now, 'maxVisits' => $maxVisits])), + ShortUrl::fromMeta(ShortUrlMeta::fromRawData( + ['validSince' => $now, 'maxVisits' => $maxVisits, 'longUrl' => ''], + )), [ 'validSince' => $now->toAtomString(), 'validUntil' => null, @@ -56,8 +61,8 @@ class ShortUrlDataTransformerTest extends TestCase ], ]; yield 'both dates' => [ - new ShortUrl('', ShortUrlMeta::fromRawData( - ['validSince' => $now, 'validUntil' => $now->subDays(10)], + ShortUrl::fromMeta(ShortUrlMeta::fromRawData( + ['validSince' => $now, 'validUntil' => $now->subDays(10), 'longUrl' => ''], )), [ 'validSince' => $now->toAtomString(), @@ -66,8 +71,8 @@ class ShortUrlDataTransformerTest extends TestCase ], ]; yield 'everything' => [ - new ShortUrl('', ShortUrlMeta::fromRawData( - ['validSince' => $now, 'validUntil' => $now->subDays(5), 'maxVisits' => $maxVisits], + ShortUrl::fromMeta(ShortUrlMeta::fromRawData( + ['validSince' => $now, 'validUntil' => $now->subDays(5), 'maxVisits' => $maxVisits, 'longUrl' => ''], )), [ 'validSince' => $now->toAtomString(), diff --git a/module/Core/test/Visit/VisitLocatorTest.php b/module/Core/test/Visit/VisitLocatorTest.php index e9f1a2d5..96caf968 100644 --- a/module/Core/test/Visit/VisitLocatorTest.php +++ b/module/Core/test/Visit/VisitLocatorTest.php @@ -57,7 +57,7 @@ class VisitLocatorTest extends TestCase ): void { $unlocatedVisits = map( range(1, 200), - fn (int $i) => new Visit(new ShortUrl(sprintf('short_code_%s', $i)), Visitor::emptyInstance()), + fn (int $i) => new Visit(ShortUrl::withLongUrl(sprintf('short_code_%s', $i)), Visitor::emptyInstance()), ); $findVisits = $this->mockRepoMethod($expectedRepoMethodName)->willReturn($unlocatedVisits); @@ -107,7 +107,7 @@ class VisitLocatorTest extends TestCase bool $isNonLocatableAddress ): void { $unlocatedVisits = [ - new Visit(new ShortUrl('foo'), Visitor::emptyInstance()), + new Visit(ShortUrl::withLongUrl('foo'), Visitor::emptyInstance()), ]; $findVisits = $this->mockRepoMethod($expectedRepoMethodName)->willReturn($unlocatedVisits); diff --git a/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php b/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php index 8d4ea777..2da2044a 100644 --- a/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php @@ -27,11 +27,10 @@ abstract class AbstractCreateShortUrlAction extends AbstractRestAction public function handle(Request $request): Response { $shortUrlData = $this->buildShortUrlData($request); - $longUrl = $shortUrlData->getLongUrl(); $tags = $shortUrlData->getTags(); $shortUrlMeta = $shortUrlData->getMeta(); - $shortUrl = $this->urlShortener->shorten($longUrl, $tags, $shortUrlMeta); + $shortUrl = $this->urlShortener->shorten($tags, $shortUrlMeta); $transformer = new ShortUrlDataTransformer($this->domainConfig); return new JsonResponse($transformer->transform($shortUrl)); diff --git a/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php b/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php index 28941579..834fa541 100644 --- a/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php @@ -22,15 +22,9 @@ class CreateShortUrlAction extends AbstractCreateShortUrlAction protected function buildShortUrlData(Request $request): CreateShortUrlData { $payload = (array) $request->getParsedBody(); - if (! isset($payload['longUrl'])) { - throw ValidationException::fromArray([ - 'longUrl' => 'A URL was not provided', - ]); - } - $payload[ShortUrlMetaInputFilter::API_KEY] = AuthenticationMiddleware::apiKeyFromRequest($request); $meta = ShortUrlMeta::fromRawData($payload); - return new CreateShortUrlData($payload['longUrl'], (array) ($payload['tags'] ?? []), $meta); + return new CreateShortUrlData((array) ($payload['tags'] ?? []), $meta); } } diff --git a/module/Rest/src/Action/ShortUrl/SingleStepCreateShortUrlAction.php b/module/Rest/src/Action/ShortUrl/SingleStepCreateShortUrlAction.php index b8bd86aa..edba3184 100644 --- a/module/Rest/src/Action/ShortUrl/SingleStepCreateShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/SingleStepCreateShortUrlAction.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\ShortUrl; use Psr\Http\Message\ServerRequestInterface as Request; -use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Model\CreateShortUrlData; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; @@ -20,15 +19,10 @@ class SingleStepCreateShortUrlAction extends AbstractCreateShortUrlAction { $query = $request->getQueryParams(); $longUrl = $query['longUrl'] ?? null; - - if ($longUrl === null) { - throw ValidationException::fromArray([ - 'longUrl' => 'A URL was not provided', - ]); - } - $apiKey = AuthenticationMiddleware::apiKeyFromRequest($request); - return new CreateShortUrlData($longUrl, [], ShortUrlMeta::fromRawData([ + + return new CreateShortUrlData([], ShortUrlMeta::fromRawData([ + ShortUrlMetaInputFilter::LONG_URL => $longUrl, ShortUrlMetaInputFilter::API_KEY => $apiKey, // This will usually be null, unless this API key enforces one specific domain ShortUrlMetaInputFilter::DOMAIN => $request->getAttribute(ShortUrlMetaInputFilter::DOMAIN), diff --git a/module/Rest/test-api/Action/CreateShortUrlTest.php b/module/Rest/test-api/Action/CreateShortUrlTest.php index 60cffddd..54ea0218 100644 --- a/module/Rest/test-api/Action/CreateShortUrlTest.php +++ b/module/Rest/test-api/Action/CreateShortUrlTest.php @@ -212,10 +212,12 @@ class CreateShortUrlTest extends ApiTestCase yield ['http://téstb.shlink.io']; // Redirects to http://tést.shlink.io } - /** @test */ - public function failsToCreateShortUrlWithInvalidLongUrl(): void + /** + * @test + * @dataProvider provideInvalidUrls + */ + public function failsToCreateShortUrlWithInvalidLongUrl(string $url): void { - $url = 'https://this-has-to-be-invalid.com'; $expectedDetail = sprintf('Provided URL %s is invalid. Try with a different one.', $url); [$statusCode, $payload] = $this->createShortUrl(['longUrl' => $url]); @@ -228,6 +230,25 @@ class CreateShortUrlTest extends ApiTestCase self::assertEquals($url, $payload['url']); } + public function provideInvalidUrls(): iterable + { + yield 'empty URL' => ['']; + yield 'non-reachable URL' => ['https://this-has-to-be-invalid.com']; + } + + /** @test */ + public function failsToCreateShortUrlWithoutLongUrl(): void + { + $resp = $this->callApiWithKey(self::METHOD_POST, '/short-urls', [RequestOptions::JSON => []]); + $payload = $this->getJsonResponsePayload($resp); + + self::assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); + self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); + self::assertEquals('INVALID_ARGUMENT', $payload['type']); + self::assertEquals('Provided data is not valid', $payload['detail']); + self::assertEquals('Invalid data', $payload['title']); + } + /** @test */ public function defaultDomainIsDroppedIfProvided(): void { diff --git a/module/Rest/test-api/Fixtures/ShortUrlsFixture.php b/module/Rest/test-api/Fixtures/ShortUrlsFixture.php index 954d2059..390a2144 100644 --- a/module/Rest/test-api/Fixtures/ShortUrlsFixture.php +++ b/module/Rest/test-api/Fixtures/ShortUrlsFixture.php @@ -27,44 +27,46 @@ class ShortUrlsFixture extends AbstractFixture implements DependentFixtureInterf $authorApiKey = $this->getReference('author_api_key'); $abcShortUrl = $this->setShortUrlDate( - new ShortUrl('https://shlink.io', ShortUrlMeta::fromRawData( - ['customSlug' => 'abc123', 'apiKey' => $authorApiKey], + ShortUrl::fromMeta(ShortUrlMeta::fromRawData( + ['customSlug' => 'abc123', 'apiKey' => $authorApiKey, 'longUrl' => 'https://shlink.io'], )), '2018-05-01', ); $manager->persist($abcShortUrl); - $defShortUrl = $this->setShortUrlDate(new ShortUrl( - 'https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/', - ShortUrlMeta::fromRawData( - ['validSince' => Chronos::parse('2020-05-01'), 'customSlug' => 'def456', 'apiKey' => $authorApiKey], - ), - ), '2019-01-01 00:00:10'); + $defShortUrl = $this->setShortUrlDate(ShortUrl::fromMeta(ShortUrlMeta::fromRawData([ + 'validSince' => Chronos::parse('2020-05-01'), + 'customSlug' => 'def456', + 'apiKey' => $authorApiKey, + 'longUrl' => + 'https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/', + ])), '2019-01-01 00:00:10'); $manager->persist($defShortUrl); - $customShortUrl = $this->setShortUrlDate(new ShortUrl( - 'https://shlink.io', - ShortUrlMeta::fromRawData(['customSlug' => 'custom', 'maxVisits' => 2, 'apiKey' => $authorApiKey]), - ), '2019-01-01 00:00:20'); + $customShortUrl = $this->setShortUrlDate(ShortUrl::fromMeta(ShortUrlMeta::fromRawData( + ['customSlug' => 'custom', 'maxVisits' => 2, 'apiKey' => $authorApiKey, 'longUrl' => 'https://shlink.io'], + )), '2019-01-01 00:00:20'); $manager->persist($customShortUrl); $ghiShortUrl = $this->setShortUrlDate( - new ShortUrl('https://shlink.io/documentation/', ShortUrlMeta::fromRawData(['customSlug' => 'ghi789'])), + ShortUrl::fromMeta(ShortUrlMeta::fromRawData( + ['customSlug' => 'ghi789', 'longUrl' => 'https://shlink.io/documentation/'], + )), '2018-05-01', ); $manager->persist($ghiShortUrl); - $withDomainDuplicatingShortCode = $this->setShortUrlDate(new ShortUrl( - 'https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/', - ShortUrlMeta::fromRawData(['domain' => 'example.com', 'customSlug' => 'ghi789']), - new PersistenceShortUrlRelationResolver($manager), - ), '2019-01-01 00:00:30'); + $withDomainDuplicatingShortCode = $this->setShortUrlDate(ShortUrl::fromMeta(ShortUrlMeta::fromRawData([ + 'domain' => 'example.com', + 'customSlug' => 'ghi789', + 'longUrl' => 'https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-' + . 'source-software-projects/', + ]), new PersistenceShortUrlRelationResolver($manager)), '2019-01-01 00:00:30'); $manager->persist($withDomainDuplicatingShortCode); - $withDomainAndSlugShortUrl = $this->setShortUrlDate(new ShortUrl( - 'https://google.com', - ShortUrlMeta::fromRawData(['domain' => 'some-domain.com', 'customSlug' => 'custom-with-domain']), - ), '2018-10-20'); + $withDomainAndSlugShortUrl = $this->setShortUrlDate(ShortUrl::fromMeta(ShortUrlMeta::fromRawData( + ['domain' => 'some-domain.com', 'customSlug' => 'custom-with-domain', 'longUrl' => 'https://google.com'], + )), '2018-10-20'); $manager->persist($withDomainAndSlugShortUrl); $manager->flush(); diff --git a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php index 80ccfc17..2388a4c8 100644 --- a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php @@ -39,24 +39,22 @@ class CreateShortUrlActionTest extends TestCase } /** @test */ - public function missingLongUrlParamReturnsError(): void - { - $this->expectException(ValidationException::class); - $this->action->handle(new ServerRequest()); - } - - /** - * @test - * @dataProvider provideRequestBodies - */ - public function properShortcodeConversionReturnsData(array $body, array $expectedMeta): void + public function properShortcodeConversionReturnsData(): void { $apiKey = new ApiKey(); - $shortUrl = new ShortUrl(''); + $shortUrl = ShortUrl::createEmpty(); + $expectedMeta = $body = [ + 'longUrl' => 'http://www.domain.com/foo/bar', + 'validSince' => Chronos::now()->toAtomString(), + 'validUntil' => Chronos::now()->toAtomString(), + 'customSlug' => 'foo-bar-baz', + 'maxVisits' => 50, + 'findIfExists' => true, + 'domain' => 'my-domain.com', + ]; $expectedMeta['apiKey'] = $apiKey; $shorten = $this->urlShortener->shorten( - Argument::type('string'), Argument::type('array'), ShortUrlMeta::fromRawData($expectedMeta), )->willReturn($shortUrl); @@ -70,29 +68,13 @@ class CreateShortUrlActionTest extends TestCase $shorten->shouldHaveBeenCalledOnce(); } - public function provideRequestBodies(): iterable - { - $fullMeta = [ - 'longUrl' => 'http://www.domain.com/foo/bar', - 'validSince' => Chronos::now()->toAtomString(), - 'validUntil' => Chronos::now()->toAtomString(), - 'customSlug' => 'foo-bar-baz', - 'maxVisits' => 50, - 'findIfExists' => true, - 'domain' => 'my-domain.com', - ]; - - yield 'no data' => [['longUrl' => 'http://www.domain.com/foo/bar'], []]; - yield 'all data' => [$fullMeta, $fullMeta]; - } - /** * @test * @dataProvider provideInvalidDomains */ public function anInvalidDomainReturnsError(string $domain): void { - $shortUrl = new ShortUrl(''); + $shortUrl = ShortUrl::createEmpty(); $urlToShortCode = $this->urlShortener->shorten(Argument::cetera())->willReturn($shortUrl); $request = (new ServerRequest())->withParsedBody([ diff --git a/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php index 5e9eadf7..ad482098 100644 --- a/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php @@ -49,7 +49,7 @@ class EditShortUrlActionTest extends TestCase 'maxVisits' => 5, ]); $updateMeta = $this->shortUrlService->updateMetadataByShortCode(Argument::cetera())->willReturn( - new ShortUrl(''), + ShortUrl::createEmpty(), ); $resp = $this->action->handle($request); diff --git a/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php b/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php index 9c72dd91..60d1d093 100644 --- a/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php +++ b/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php @@ -45,7 +45,7 @@ class EditShortUrlTagsActionTest extends TestCase new ShortUrlIdentifier($shortCode), [], Argument::type(ApiKey::class), - )->willReturn(new ShortUrl('')) + )->willReturn(ShortUrl::createEmpty()) ->shouldBeCalledOnce(); $response = $this->action->handle( diff --git a/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php index f4c49a60..2f47089f 100644 --- a/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php @@ -35,7 +35,7 @@ class ResolveShortUrlActionTest extends TestCase $shortCode = 'abc123'; $apiKey = new ApiKey(); $this->urlResolver->resolveShortUrl(new ShortUrlIdentifier($shortCode), $apiKey)->willReturn( - new ShortUrl('http://domain.com/foo/bar'), + ShortUrl::withLongUrl('http://domain.com/foo/bar'), )->shouldBeCalledOnce(); $request = (new ServerRequest())->withAttribute('shortCode', $shortCode)->withAttribute(ApiKey::class, $apiKey); diff --git a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php index 0973a198..41d37f18 100644 --- a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php @@ -5,13 +5,10 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; use Laminas\Diactoros\ServerRequest; -use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; -use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Shlinkio\Shlink\Rest\Action\ShortUrl\SingleStepCreateShortUrlAction; @@ -38,16 +35,6 @@ class SingleStepCreateShortUrlActionTest extends TestCase ); } - /** @test */ - public function errorResponseIsReturnedIfNoUrlIsProvided(): void - { - $request = new ServerRequest(); - - $this->expectException(ValidationException::class); - - $this->action->handle($request); - } - /** @test */ public function properDataIsPassedWhenGeneratingShortCode(): void { @@ -57,13 +44,9 @@ class SingleStepCreateShortUrlActionTest extends TestCase 'longUrl' => 'http://foobar.com', ])->withAttribute(ApiKey::class, $apiKey); $generateShortCode = $this->urlShortener->shorten( - Argument::that(function (string $argument): bool { - Assert::assertEquals('http://foobar.com', $argument); - return true; - }), [], - ShortUrlMeta::fromRawData(['apiKey' => $apiKey]), - )->willReturn(new ShortUrl('')); + ShortUrlMeta::fromRawData(['apiKey' => $apiKey, 'longUrl' => 'http://foobar.com']), + )->willReturn(ShortUrl::createEmpty()); $resp = $this->action->handle($request);