Enforce a schema to be provided when short URLs are created

This commit is contained in:
Alejandro Celaya 2023-03-25 09:52:47 +01:00
parent 11f94b8306
commit b6e1c65c4c
26 changed files with 135 additions and 107 deletions

View file

@ -13,6 +13,7 @@ const DEFAULT_REDIRECT_STATUS_CODE = RedirectStatus::STATUS_302; // Deprecated.
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30; const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory'; const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
const TITLE_TAG_VALUE = '/<title[^>]*>(.*?)<\/title>/i'; // Matches the value inside a html title tag const TITLE_TAG_VALUE = '/<title[^>]*>(.*?)<\/title>/i'; // Matches the value inside a html title tag
const LOOSE_URI_MATCHER = '/(.+)\:\/\/(.+)/i'; // Matches anything starting with a schema.
const DEFAULT_QR_CODE_SIZE = 300; const DEFAULT_QR_CODE_SIZE = 300;
const DEFAULT_QR_CODE_MARGIN = 0; const DEFAULT_QR_CODE_MARGIN = 0;
const DEFAULT_QR_CODE_FORMAT = 'png'; const DEFAULT_QR_CODE_FORMAT = 'png';

View file

@ -49,7 +49,7 @@ class ListShortUrlsCommandTest extends TestCase
// The paginator will return more than one page // The paginator will return more than one page
$data = []; $data = [];
for ($i = 0; $i < 50; $i++) { for ($i = 0; $i < 50; $i++) {
$data[] = ShortUrl::withLongUrl('url_' . $i); $data[] = ShortUrl::withLongUrl('https://url_' . $i);
} }
$this->shortUrlService->expects($this->exactly(3))->method('listShortUrls')->withAnyParameters() $this->shortUrlService->expects($this->exactly(3))->method('listShortUrls')->withAnyParameters()
@ -71,7 +71,7 @@ class ListShortUrlsCommandTest extends TestCase
// The paginator will return more than one page // The paginator will return more than one page
$data = []; $data = [];
for ($i = 0; $i < 30; $i++) { for ($i = 0; $i < 30; $i++) {
$data[] = ShortUrl::withLongUrl('url_' . $i); $data[] = ShortUrl::withLongUrl('https://url_' . $i);
} }
$this->shortUrlService->expects($this->once())->method('listShortUrls')->with( $this->shortUrlService->expects($this->once())->method('listShortUrls')->with(
@ -114,7 +114,7 @@ class ListShortUrlsCommandTest extends TestCase
ShortUrlsParams::emptyInstance(), ShortUrlsParams::emptyInstance(),
)->willReturn(new Paginator(new ArrayAdapter([ )->willReturn(new Paginator(new ArrayAdapter([
ShortUrl::create(ShortUrlCreation::fromRawData([ ShortUrl::create(ShortUrlCreation::fromRawData([
'longUrl' => 'foo.com', 'longUrl' => 'https://foo.com',
'tags' => ['foo', 'bar', 'baz'], 'tags' => ['foo', 'bar', 'baz'],
'apiKey' => $apiKey, 'apiKey' => $apiKey,
])), ])),

View file

@ -33,6 +33,7 @@ use function Shlinkio\Shlink\Core\enumValues;
use function Shlinkio\Shlink\Core\generateRandomShortCode; use function Shlinkio\Shlink\Core\generateRandomShortCode;
use function Shlinkio\Shlink\Core\normalizeDate; use function Shlinkio\Shlink\Core\normalizeDate;
use function Shlinkio\Shlink\Core\normalizeOptionalDate; use function Shlinkio\Shlink\Core\normalizeOptionalDate;
use function str_contains;
class ShortUrl extends AbstractEntity class ShortUrl extends AbstractEntity
{ {
@ -68,7 +69,7 @@ class ShortUrl extends AbstractEntity
*/ */
public static function createFake(): self public static function createFake(): self
{ {
return self::withLongUrl('foo'); return self::withLongUrl('https://foo');
} }
/** /**

View file

@ -12,8 +12,10 @@ use Shlinkio\Shlink\Common\Validation;
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Entity\ApiKey;
use function preg_match;
use function substr; use function substr;
use const Shlinkio\Shlink\LOOSE_URI_MATCHER;
use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH; use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH;
/** /**
@ -59,27 +61,13 @@ class ShortUrlInputFilter extends InputFilter
private function initialize(bool $requireLongUrl, UrlShortenerOptions $options): void private function initialize(bool $requireLongUrl, UrlShortenerOptions $options): void
{ {
$longUrlNotEmptyCommonOptions = [
Validator\NotEmpty::OBJECT,
Validator\NotEmpty::SPACE,
Validator\NotEmpty::EMPTY_ARRAY,
Validator\NotEmpty::BOOLEAN,
Validator\NotEmpty::STRING,
];
$longUrlInput = $this->createInput(self::LONG_URL, $requireLongUrl); $longUrlInput = $this->createInput(self::LONG_URL, $requireLongUrl);
$longUrlInput->getValidatorChain()->attach(new Validator\NotEmpty([ $longUrlInput->getValidatorChain()->merge($this->longUrlValidators());
...$longUrlNotEmptyCommonOptions,
Validator\NotEmpty::NULL,
]));
$this->add($longUrlInput); $this->add($longUrlInput);
$deviceLongUrlsInput = $this->createInput(self::DEVICE_LONG_URLS, false); $deviceLongUrlsInput = $this->createInput(self::DEVICE_LONG_URLS, false);
$deviceLongUrlsInput->getValidatorChain()->attach( $deviceLongUrlsInput->getValidatorChain()->attach(
new DeviceLongUrlsValidator(new Validator\NotEmpty([ new DeviceLongUrlsValidator($this->longUrlValidators(allowNull: ! $requireLongUrl))
...$longUrlNotEmptyCommonOptions,
...($requireLongUrl ? [Validator\NotEmpty::NULL] : []),
])),
); );
$this->add($deviceLongUrlsInput); $this->add($deviceLongUrlsInput);
@ -129,4 +117,24 @@ class ShortUrlInputFilter extends InputFilter
$this->add($this->createBooleanInput(self::CRAWLABLE, false)); $this->add($this->createBooleanInput(self::CRAWLABLE, false));
} }
private function longUrlValidators(bool $allowNull = false): Validator\ValidatorChain
{
$emptyModifiers = [
Validator\NotEmpty::OBJECT,
Validator\NotEmpty::SPACE,
Validator\NotEmpty::EMPTY_ARRAY,
Validator\NotEmpty::BOOLEAN,
Validator\NotEmpty::STRING,
];
if (! $allowNull) {
$emptyModifiers[] = Validator\NotEmpty::NULL;
}
return (new Validator\ValidatorChain())
->attach(new Validator\NotEmpty($emptyModifiers))
->attach(new Validator\Callback(
fn (?string $value) => ($allowNull && $value === null) || preg_match(LOOSE_URI_MATCHER, $value) === 1
));
}
} }

View file

@ -132,7 +132,7 @@ class DomainRepositoryTest extends DatabaseTestCase
{ {
return ShortUrl::create( return ShortUrl::create(
ShortUrlCreation::fromRawData( ShortUrlCreation::fromRawData(
['domain' => $domain->authority, 'apiKey' => $apiKey, 'longUrl' => 'foo'], ['domain' => $domain->authority, 'apiKey' => $apiKey, 'longUrl' => 'https://foo'],
), ),
new class ($domain) implements ShortUrlRelationResolverInterface { new class ($domain) implements ShortUrlRelationResolverInterface {
public function __construct(private Domain $domain) public function __construct(private Domain $domain)

View file

@ -24,7 +24,7 @@ class CrawlableShortCodesQueryTest extends DatabaseTestCase
public function invokingQueryReturnsExpectedResult(): void public function invokingQueryReturnsExpectedResult(): void
{ {
$createShortUrl = fn (bool $crawlable) => ShortUrl::create( $createShortUrl = fn (bool $crawlable) => ShortUrl::create(
ShortUrlCreation::fromRawData(['crawlable' => $crawlable, 'longUrl' => 'foo.com']), ShortUrlCreation::fromRawData(['crawlable' => $crawlable, 'longUrl' => 'https://foo.com']),
); );
$shortUrl1 = $createShortUrl(true); $shortUrl1 = $createShortUrl(true);

View file

@ -43,7 +43,7 @@ class ShortUrlListRepositoryTest extends DatabaseTestCase
{ {
$count = 5; $count = 5;
for ($i = 0; $i < $count; $i++) { for ($i = 0; $i < $count; $i++) {
$this->getEntityManager()->persist(ShortUrl::withLongUrl((string) $i)); $this->getEntityManager()->persist(ShortUrl::withLongUrl('https://' . $i));
} }
$this->getEntityManager()->flush(); $this->getEntityManager()->flush();
@ -54,12 +54,12 @@ class ShortUrlListRepositoryTest extends DatabaseTestCase
public function findListProperlyFiltersResult(): void public function findListProperlyFiltersResult(): void
{ {
$foo = ShortUrl::create( $foo = ShortUrl::create(
ShortUrlCreation::fromRawData(['longUrl' => 'foo', 'tags' => ['bar']]), ShortUrlCreation::fromRawData(['longUrl' => 'foo', 'tags' => ['https://bar']]),
$this->relationResolver, $this->relationResolver,
); );
$this->getEntityManager()->persist($foo); $this->getEntityManager()->persist($foo);
$bar = ShortUrl::withLongUrl('bar'); $bar = ShortUrl::withLongUrl('https://bar');
$visits = map(range(0, 5), function () use ($bar) { $visits = map(range(0, 5), function () use ($bar) {
$visit = Visit::forValidShortUrl($bar, Visitor::botInstance()); $visit = Visit::forValidShortUrl($bar, Visitor::botInstance());
$this->getEntityManager()->persist($visit); $this->getEntityManager()->persist($visit);
@ -69,7 +69,7 @@ class ShortUrlListRepositoryTest extends DatabaseTestCase
$bar->setVisits(new ArrayCollection($visits)); $bar->setVisits(new ArrayCollection($visits));
$this->getEntityManager()->persist($bar); $this->getEntityManager()->persist($bar);
$foo2 = ShortUrl::withLongUrl('foo_2'); $foo2 = ShortUrl::withLongUrl('https://foo_2');
$visits2 = map(range(0, 3), function () use ($foo2) { $visits2 = map(range(0, 3), function () use ($foo2) {
$visit = Visit::forValidShortUrl($foo2, Visitor::emptyInstance()); $visit = Visit::forValidShortUrl($foo2, Visitor::emptyInstance());
$this->getEntityManager()->persist($visit); $this->getEntityManager()->persist($visit);
@ -147,7 +147,7 @@ class ShortUrlListRepositoryTest extends DatabaseTestCase
#[Test] #[Test]
public function findListProperlyMapsFieldNamesToColumnNamesWhenOrdering(): void public function findListProperlyMapsFieldNamesToColumnNamesWhenOrdering(): void
{ {
$urls = ['a', 'z', 'c', 'b']; $urls = ['https://a', 'https://z', 'https://c', 'https://b'];
foreach ($urls as $url) { foreach ($urls as $url) {
$this->getEntityManager()->persist(ShortUrl::withLongUrl($url)); $this->getEntityManager()->persist(ShortUrl::withLongUrl($url));
} }
@ -159,37 +159,37 @@ class ShortUrlListRepositoryTest extends DatabaseTestCase
); );
self::assertCount(count($urls), $result); self::assertCount(count($urls), $result);
self::assertEquals('a', $result[0]->getLongUrl()); self::assertEquals('https://a', $result[0]->getLongUrl());
self::assertEquals('b', $result[1]->getLongUrl()); self::assertEquals('https://b', $result[1]->getLongUrl());
self::assertEquals('c', $result[2]->getLongUrl()); self::assertEquals('https://c', $result[2]->getLongUrl());
self::assertEquals('z', $result[3]->getLongUrl()); self::assertEquals('https://z', $result[3]->getLongUrl());
} }
#[Test] #[Test]
public function findListReturnsOnlyThoseWithMatchingTags(): void public function findListReturnsOnlyThoseWithMatchingTags(): void
{ {
$shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([
'longUrl' => 'foo1', 'longUrl' => 'https://foo1',
'tags' => ['foo', 'bar'], 'tags' => ['foo', 'bar'],
]), $this->relationResolver); ]), $this->relationResolver);
$this->getEntityManager()->persist($shortUrl1); $this->getEntityManager()->persist($shortUrl1);
$shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([
'longUrl' => 'foo2', 'longUrl' => 'https://foo2',
'tags' => ['foo', 'baz'], 'tags' => ['foo', 'baz'],
]), $this->relationResolver); ]), $this->relationResolver);
$this->getEntityManager()->persist($shortUrl2); $this->getEntityManager()->persist($shortUrl2);
$shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([
'longUrl' => 'foo3', 'longUrl' => 'https://foo3',
'tags' => ['foo'], 'tags' => ['foo'],
]), $this->relationResolver); ]), $this->relationResolver);
$this->getEntityManager()->persist($shortUrl3); $this->getEntityManager()->persist($shortUrl3);
$shortUrl4 = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl4 = ShortUrl::create(ShortUrlCreation::fromRawData([
'longUrl' => 'foo4', 'longUrl' => 'https://foo4',
'tags' => ['bar', 'baz'], 'tags' => ['bar', 'baz'],
]), $this->relationResolver); ]), $this->relationResolver);
$this->getEntityManager()->persist($shortUrl4); $this->getEntityManager()->persist($shortUrl4);
$shortUrl5 = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl5 = ShortUrl::create(ShortUrlCreation::fromRawData([
'longUrl' => 'foo5', 'longUrl' => 'https://foo5',
'tags' => ['bar', 'baz'], 'tags' => ['bar', 'baz'],
]), $this->relationResolver); ]), $this->relationResolver);
$this->getEntityManager()->persist($shortUrl5); $this->getEntityManager()->persist($shortUrl5);
@ -278,17 +278,17 @@ class ShortUrlListRepositoryTest extends DatabaseTestCase
public function findListReturnsOnlyThoseWithMatchingDomains(): void public function findListReturnsOnlyThoseWithMatchingDomains(): void
{ {
$shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([
'longUrl' => 'foo1', 'longUrl' => 'https://foo1',
'domain' => null, 'domain' => null,
]), $this->relationResolver); ]), $this->relationResolver);
$this->getEntityManager()->persist($shortUrl1); $this->getEntityManager()->persist($shortUrl1);
$shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([
'longUrl' => 'foo2', 'longUrl' => 'https://foo2',
'domain' => null, 'domain' => null,
]), $this->relationResolver); ]), $this->relationResolver);
$this->getEntityManager()->persist($shortUrl2); $this->getEntityManager()->persist($shortUrl2);
$shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([
'longUrl' => 'foo3', 'longUrl' => 'https://foo3',
'domain' => 'another.com', 'domain' => 'another.com',
]), $this->relationResolver); ]), $this->relationResolver);
$this->getEntityManager()->persist($shortUrl3); $this->getEntityManager()->persist($shortUrl3);
@ -314,22 +314,22 @@ class ShortUrlListRepositoryTest extends DatabaseTestCase
public function findListReturnsOnlyThoseWithoutExcludedUrls(): void public function findListReturnsOnlyThoseWithoutExcludedUrls(): void
{ {
$shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([
'longUrl' => 'foo1', 'longUrl' => 'https://foo1',
'validUntil' => Chronos::now()->addDays(1)->toAtomString(), 'validUntil' => Chronos::now()->addDays(1)->toAtomString(),
'maxVisits' => 100, 'maxVisits' => 100,
]), $this->relationResolver); ]), $this->relationResolver);
$this->getEntityManager()->persist($shortUrl1); $this->getEntityManager()->persist($shortUrl1);
$shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([
'longUrl' => 'foo2', 'longUrl' => 'https://foo2',
'validUntil' => Chronos::now()->subDays(1)->toAtomString(), 'validUntil' => Chronos::now()->subDays(1)->toAtomString(),
]), $this->relationResolver); ]), $this->relationResolver);
$this->getEntityManager()->persist($shortUrl2); $this->getEntityManager()->persist($shortUrl2);
$shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([
'longUrl' => 'foo3', 'longUrl' => 'https://foo3',
]), $this->relationResolver); ]), $this->relationResolver);
$this->getEntityManager()->persist($shortUrl3); $this->getEntityManager()->persist($shortUrl3);
$shortUrl4 = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl4 = ShortUrl::create(ShortUrlCreation::fromRawData([
'longUrl' => 'foo4', 'longUrl' => 'https://foo4',
'maxVisits' => 3, 'maxVisits' => 3,
]), $this->relationResolver); ]), $this->relationResolver);
$this->getEntityManager()->persist($shortUrl4); $this->getEntityManager()->persist($shortUrl4);

View file

@ -34,16 +34,18 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
#[Test] #[Test]
public function findOneWithDomainFallbackReturnsProperData(): void public function findOneWithDomainFallbackReturnsProperData(): void
{ {
$regularOne = ShortUrl::create(ShortUrlCreation::fromRawData(['customSlug' => 'Foo', 'longUrl' => 'foo'])); $regularOne = ShortUrl::create(
ShortUrlCreation::fromRawData(['customSlug' => 'Foo', 'longUrl' => 'https://foo']),
);
$this->getEntityManager()->persist($regularOne); $this->getEntityManager()->persist($regularOne);
$withDomain = ShortUrl::create(ShortUrlCreation::fromRawData( $withDomain = ShortUrl::create(ShortUrlCreation::fromRawData(
['domain' => 'example.com', 'customSlug' => 'domain-short-code', 'longUrl' => 'foo'], ['domain' => 'example.com', 'customSlug' => 'domain-short-code', 'longUrl' => 'https://foo'],
)); ));
$this->getEntityManager()->persist($withDomain); $this->getEntityManager()->persist($withDomain);
$withDomainDuplicatingRegular = ShortUrl::create(ShortUrlCreation::fromRawData( $withDomainDuplicatingRegular = ShortUrl::create(ShortUrlCreation::fromRawData(
['domain' => 's.test', 'customSlug' => 'Foo', 'longUrl' => 'foo_with_domain'], ['domain' => 's.test', 'customSlug' => 'Foo', 'longUrl' => 'https://foo_with_domain'],
)); ));
$this->getEntityManager()->persist($withDomainDuplicatingRegular); $this->getEntityManager()->persist($withDomainDuplicatingRegular);
@ -102,7 +104,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
public function shortCodeIsInUseLooksForShortUrlInProperSetOfTables(): void public function shortCodeIsInUseLooksForShortUrlInProperSetOfTables(): void
{ {
$shortUrlWithoutDomain = ShortUrl::create( $shortUrlWithoutDomain = ShortUrl::create(
ShortUrlCreation::fromRawData(['customSlug' => 'my-cool-slug', 'longUrl' => 'foo']), ShortUrlCreation::fromRawData(['customSlug' => 'my-cool-slug', 'longUrl' => 'https://foo']),
); );
$this->getEntityManager()->persist($shortUrlWithoutDomain); $this->getEntityManager()->persist($shortUrlWithoutDomain);
@ -396,7 +398,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
public function importedShortUrlsAreFoundWhenExpected(): void public function importedShortUrlsAreFoundWhenExpected(): void
{ {
$buildImported = static fn (string $shortCode, ?string $domain = null) => $buildImported = static fn (string $shortCode, ?string $domain = null) =>
new ImportedShlinkUrl(ImportSource::BITLY, 'foo', [], Chronos::now(), $domain, $shortCode, null); new ImportedShlinkUrl(ImportSource::BITLY, 'https://foo', [], Chronos::now(), $domain, $shortCode, null);
$shortUrlWithoutDomain = ShortUrl::fromImport($buildImported('my-cool-slug'), true); $shortUrlWithoutDomain = ShortUrl::fromImport($buildImported('my-cool-slug'), true);
$this->getEntityManager()->persist($shortUrlWithoutDomain); $this->getEntityManager()->persist($shortUrlWithoutDomain);

View file

@ -74,7 +74,7 @@ class TagRepositoryTest extends DatabaseTestCase
[$firstUrlTags] = array_chunk($names, 3); [$firstUrlTags] = array_chunk($names, 3);
$secondUrlTags = [$names[0]]; $secondUrlTags = [$names[0]];
$metaWithTags = static fn (array $tags, ?ApiKey $apiKey) => ShortUrlCreation::fromRawData( $metaWithTags = static fn (array $tags, ?ApiKey $apiKey) => ShortUrlCreation::fromRawData(
['longUrl' => 'longUrl', 'tags' => $tags, 'apiKey' => $apiKey], ['longUrl' => 'https://longUrl', 'tags' => $tags, 'apiKey' => $apiKey],
); );
$shortUrl = ShortUrl::create($metaWithTags($firstUrlTags, $apiKey), $this->relationResolver); $shortUrl = ShortUrl::create($metaWithTags($firstUrlTags, $apiKey), $this->relationResolver);
@ -241,14 +241,14 @@ class TagRepositoryTest extends DatabaseTestCase
[$firstUrlTags, $secondUrlTags] = array_chunk($names, 3); [$firstUrlTags, $secondUrlTags] = array_chunk($names, 3);
$shortUrl = ShortUrl::create( $shortUrl = ShortUrl::create(
ShortUrlCreation::fromRawData(['apiKey' => $authorApiKey, 'longUrl' => 'longUrl', 'tags' => $firstUrlTags]), ShortUrlCreation::fromRawData(['apiKey' => $authorApiKey, 'longUrl' => 'https://longUrl', 'tags' => $firstUrlTags]),
$this->relationResolver, $this->relationResolver,
); );
$this->getEntityManager()->persist($shortUrl); $this->getEntityManager()->persist($shortUrl);
$shortUrl2 = ShortUrl::create( $shortUrl2 = ShortUrl::create(
ShortUrlCreation::fromRawData( ShortUrlCreation::fromRawData(
['domain' => $domain->authority, 'longUrl' => 'longUrl', 'tags' => $secondUrlTags], ['domain' => $domain->authority, 'longUrl' => 'https://longUrl', 'tags' => $secondUrlTags],
), ),
$this->relationResolver, $this->relationResolver,
); );

View file

@ -266,7 +266,7 @@ class VisitRepositoryTest extends DatabaseTestCase
$this->getEntityManager()->persist($apiKey1); $this->getEntityManager()->persist($apiKey1);
$shortUrl = ShortUrl::create( $shortUrl = ShortUrl::create(
ShortUrlCreation::fromRawData( ShortUrlCreation::fromRawData(
['apiKey' => $apiKey1, 'domain' => $domain->authority, 'longUrl' => 'longUrl'], ['apiKey' => $apiKey1, 'domain' => $domain->authority, 'longUrl' => 'https://longUrl'],
), ),
$this->relationResolver, $this->relationResolver,
); );
@ -275,13 +275,15 @@ class VisitRepositoryTest extends DatabaseTestCase
$apiKey2 = ApiKey::fromMeta(ApiKeyMeta::withRoles(RoleDefinition::forAuthoredShortUrls())); $apiKey2 = ApiKey::fromMeta(ApiKeyMeta::withRoles(RoleDefinition::forAuthoredShortUrls()));
$this->getEntityManager()->persist($apiKey2); $this->getEntityManager()->persist($apiKey2);
$shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData(['apiKey' => $apiKey2, 'longUrl' => 'longUrl'])); $shortUrl2 = ShortUrl::create(
ShortUrlCreation::fromRawData(['apiKey' => $apiKey2, 'longUrl' => 'https://longUrl']),
);
$this->getEntityManager()->persist($shortUrl2); $this->getEntityManager()->persist($shortUrl2);
$this->createVisitsForShortUrl($shortUrl2, 5); $this->createVisitsForShortUrl($shortUrl2, 5);
$shortUrl3 = ShortUrl::create( $shortUrl3 = ShortUrl::create(
ShortUrlCreation::fromRawData( ShortUrlCreation::fromRawData(
['apiKey' => $apiKey2, 'domain' => $domain->authority, 'longUrl' => 'longUrl'], ['apiKey' => $apiKey2, 'domain' => $domain->authority, 'longUrl' => 'https://longUrl'],
), ),
$this->relationResolver, $this->relationResolver,
); );
@ -320,7 +322,7 @@ class VisitRepositoryTest extends DatabaseTestCase
#[Test] #[Test]
public function findOrphanVisitsReturnsExpectedResult(): void public function findOrphanVisitsReturnsExpectedResult(): void
{ {
$shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData(['longUrl' => 'longUrl'])); $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData(['longUrl' => 'https://longUrl']));
$this->getEntityManager()->persist($shortUrl); $this->getEntityManager()->persist($shortUrl);
$this->createVisitsForShortUrl($shortUrl, 7); $this->createVisitsForShortUrl($shortUrl, 7);

View file

@ -58,7 +58,7 @@ class NotifyNewShortUrlToMercureTest extends TestCase
#[Test] #[Test]
public function expectedNotificationIsPublished(): void public function expectedNotificationIsPublished(): void
{ {
$shortUrl = ShortUrl::withLongUrl('longUrl'); $shortUrl = ShortUrl::withLongUrl('https://longUrl');
$update = Update::forTopicAndPayload('', []); $update = Update::forTopicAndPayload('', []);
$this->em->expects($this->once())->method('find')->with(ShortUrl::class, '123')->willReturn($shortUrl); $this->em->expects($this->once())->method('find')->with(ShortUrl::class, '123')->willReturn($shortUrl);
@ -75,7 +75,7 @@ class NotifyNewShortUrlToMercureTest extends TestCase
#[Test] #[Test]
public function messageIsPrintedIfPublishingFails(): void public function messageIsPrintedIfPublishingFails(): void
{ {
$shortUrl = ShortUrl::withLongUrl('longUrl'); $shortUrl = ShortUrl::withLongUrl('https://longUrl');
$update = Update::forTopicAndPayload('', []); $update = Update::forTopicAndPayload('', []);
$e = new Exception('Error'); $e = new Exception('Error');

View file

@ -37,7 +37,7 @@ class PublishingUpdatesGeneratorTest extends TestCase
{ {
$shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([
'customSlug' => 'foo', 'customSlug' => 'foo',
'longUrl' => 'longUrl', 'longUrl' => 'https://longUrl',
'title' => $title, 'title' => $title,
])); ]));
$visit = Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance()); $visit = Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance());
@ -50,7 +50,7 @@ class PublishingUpdatesGeneratorTest extends TestCase
'shortUrl' => [ 'shortUrl' => [
'shortCode' => $shortUrl->getShortCode(), 'shortCode' => $shortUrl->getShortCode(),
'shortUrl' => 'http:/' . $shortUrl->getShortCode(), 'shortUrl' => 'http:/' . $shortUrl->getShortCode(),
'longUrl' => 'longUrl', 'longUrl' => 'https://longUrl',
'deviceLongUrls' => $shortUrl->deviceLongUrls(), 'deviceLongUrls' => $shortUrl->deviceLongUrls(),
'dateCreated' => $shortUrl->getDateCreated()->toAtomString(), 'dateCreated' => $shortUrl->getDateCreated()->toAtomString(),
'visitsCount' => 0, 'visitsCount' => 0,
@ -115,7 +115,7 @@ class PublishingUpdatesGeneratorTest extends TestCase
{ {
$shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([
'customSlug' => 'foo', 'customSlug' => 'foo',
'longUrl' => 'longUrl', 'longUrl' => 'https://longUrl',
'title' => 'The title', 'title' => 'The title',
])); ]));
@ -125,7 +125,7 @@ class PublishingUpdatesGeneratorTest extends TestCase
self::assertEquals(['shortUrl' => [ self::assertEquals(['shortUrl' => [
'shortCode' => $shortUrl->getShortCode(), 'shortCode' => $shortUrl->getShortCode(),
'shortUrl' => 'http:/' . $shortUrl->getShortCode(), 'shortUrl' => 'http:/' . $shortUrl->getShortCode(),
'longUrl' => 'longUrl', 'longUrl' => 'https://longUrl',
'deviceLongUrls' => $shortUrl->deviceLongUrls(), 'deviceLongUrls' => $shortUrl->deviceLongUrls(),
'dateCreated' => $shortUrl->getDateCreated()->toAtomString(), 'dateCreated' => $shortUrl->getDateCreated()->toAtomString(),
'visitsCount' => 0, 'visitsCount' => 0,

View file

@ -70,7 +70,7 @@ class NotifyNewShortUrlToRabbitMqTest extends TestCase
$shortUrlId = '123'; $shortUrlId = '123';
$update = Update::forTopicAndPayload(Topic::NEW_SHORT_URL->value, []); $update = Update::forTopicAndPayload(Topic::NEW_SHORT_URL->value, []);
$this->em->expects($this->once())->method('find')->with(ShortUrl::class, $shortUrlId)->willReturn( $this->em->expects($this->once())->method('find')->with(ShortUrl::class, $shortUrlId)->willReturn(
ShortUrl::withLongUrl('longUrl'), ShortUrl::withLongUrl('https://longUrl'),
); );
$this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with( $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with(
$this->isInstanceOf(ShortUrl::class), $this->isInstanceOf(ShortUrl::class),
@ -87,7 +87,7 @@ class NotifyNewShortUrlToRabbitMqTest extends TestCase
$shortUrlId = '123'; $shortUrlId = '123';
$update = Update::forTopicAndPayload(Topic::NEW_SHORT_URL->value, []); $update = Update::forTopicAndPayload(Topic::NEW_SHORT_URL->value, []);
$this->em->expects($this->once())->method('find')->with(ShortUrl::class, $shortUrlId)->willReturn( $this->em->expects($this->once())->method('find')->with(ShortUrl::class, $shortUrlId)->willReturn(
ShortUrl::withLongUrl('longUrl'), ShortUrl::withLongUrl('https://longUrl'),
); );
$this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with( $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with(
$this->isInstanceOf(ShortUrl::class), $this->isInstanceOf(ShortUrl::class),

View file

@ -98,7 +98,7 @@ class NotifyVisitToRabbitMqTest extends TestCase
yield 'non-orphan visit' => [ yield 'non-orphan visit' => [
Visit::forValidShortUrl( Visit::forValidShortUrl(
ShortUrl::create(ShortUrlCreation::fromRawData([ ShortUrl::create(ShortUrlCreation::fromRawData([
'longUrl' => 'foo', 'longUrl' => 'https://foo',
'customSlug' => 'bar', 'customSlug' => 'bar',
])), ])),
$visitor, $visitor,
@ -152,7 +152,7 @@ class NotifyVisitToRabbitMqTest extends TestCase
{ {
yield 'legacy non-orphan visit' => [ yield 'legacy non-orphan visit' => [
true, true,
$visit = Visit::forValidShortUrl(ShortUrl::withLongUrl('longUrl'), Visitor::emptyInstance()), $visit = Visit::forValidShortUrl(ShortUrl::withLongUrl('https://longUrl'), Visitor::emptyInstance()),
noop(...), noop(...),
function (MockObject & PublishingHelperInterface $helper) use ($visit): void { function (MockObject & PublishingHelperInterface $helper) use ($visit): void {
$helper->method('publishUpdate')->with(self::callback(function (Update $update) use ($visit): bool { $helper->method('publishUpdate')->with(self::callback(function (Update $update) use ($visit): bool {
@ -183,7 +183,7 @@ class NotifyVisitToRabbitMqTest extends TestCase
]; ];
yield 'non-legacy non-orphan visit' => [ yield 'non-legacy non-orphan visit' => [
false, false,
Visit::forValidShortUrl(ShortUrl::withLongUrl('longUrl'), Visitor::emptyInstance()), Visit::forValidShortUrl(ShortUrl::withLongUrl('https://longUrl'), Visitor::emptyInstance()),
function (MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator): void { function (MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator): void {
$update = Update::forTopicAndPayload('', []); $update = Update::forTopicAndPayload('', []);
$updatesGenerator->expects(self::never())->method('newOrphanVisitUpdate'); $updatesGenerator->expects(self::never())->method('newOrphanVisitUpdate');

View file

@ -54,7 +54,7 @@ class NotifyNewShortUrlToRedisTest extends TestCase
$shortUrlId = '123'; $shortUrlId = '123';
$update = Update::forTopicAndPayload(Topic::NEW_SHORT_URL->value, []); $update = Update::forTopicAndPayload(Topic::NEW_SHORT_URL->value, []);
$this->em->expects($this->once())->method('find')->with(ShortUrl::class, $shortUrlId)->willReturn( $this->em->expects($this->once())->method('find')->with(ShortUrl::class, $shortUrlId)->willReturn(
ShortUrl::withLongUrl('longUrl'), ShortUrl::withLongUrl('https://longUrl'),
); );
$this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with( $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with(
$this->isInstanceOf(ShortUrl::class), $this->isInstanceOf(ShortUrl::class),

View file

@ -117,11 +117,11 @@ class ImportedLinksProcessorTest extends TestCase
public function alreadyImportedUrlsAreSkipped(): void public function alreadyImportedUrlsAreSkipped(): void
{ {
$urls = [ $urls = [
new ImportedShlinkUrl(ImportSource::BITLY, 'foo', [], Chronos::now(), null, 'foo', null), new ImportedShlinkUrl(ImportSource::BITLY, 'https://foo', [], Chronos::now(), null, 'foo', null),
new ImportedShlinkUrl(ImportSource::BITLY, 'bar', [], Chronos::now(), null, 'bar', null), new ImportedShlinkUrl(ImportSource::BITLY, 'https://bar', [], Chronos::now(), null, 'bar', null),
new ImportedShlinkUrl(ImportSource::BITLY, 'baz', [], Chronos::now(), null, 'baz', null), new ImportedShlinkUrl(ImportSource::BITLY, 'https://baz', [], Chronos::now(), null, 'baz', null),
new ImportedShlinkUrl(ImportSource::BITLY, 'baz2', [], Chronos::now(), null, 'baz2', null), new ImportedShlinkUrl(ImportSource::BITLY, 'https://baz2', [], Chronos::now(), null, 'baz2', null),
new ImportedShlinkUrl(ImportSource::BITLY, 'baz3', [], Chronos::now(), null, 'baz3', null), new ImportedShlinkUrl(ImportSource::BITLY, 'https://baz3', [], Chronos::now(), null, 'baz3', null),
]; ];
$this->em->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); $this->em->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo);

View file

@ -43,7 +43,9 @@ class ShortUrlTest extends TestCase
public static function provideInvalidShortUrls(): iterable public static function provideInvalidShortUrls(): iterable
{ {
yield 'with custom slug' => [ yield 'with custom slug' => [
ShortUrl::create(ShortUrlCreation::fromRawData(['customSlug' => 'custom-slug', 'longUrl' => 'longUrl'])), ShortUrl::create(
ShortUrlCreation::fromRawData(['customSlug' => 'custom-slug', 'longUrl' => 'https://longUrl']),
),
'The short code cannot be regenerated on ShortUrls where a custom slug was provided.', 'The short code cannot be regenerated on ShortUrls where a custom slug was provided.',
]; ];
yield 'already persisted' => [ yield 'already persisted' => [
@ -68,7 +70,7 @@ class ShortUrlTest extends TestCase
{ {
yield 'no custom slug' => [ShortUrl::createFake()]; yield 'no custom slug' => [ShortUrl::createFake()];
yield 'imported with custom slug' => [ShortUrl::fromImport( yield 'imported with custom slug' => [ShortUrl::fromImport(
new ImportedShlinkUrl(ImportSource::BITLY, 'longUrl', [], Chronos::now(), null, 'custom-slug', null), new ImportedShlinkUrl(ImportSource::BITLY, 'https://url', [], Chronos::now(), null, 'custom-slug', null),
true, true,
)]; )];
} }
@ -77,7 +79,7 @@ class ShortUrlTest extends TestCase
public function shortCodesHaveExpectedLength(?int $length, int $expectedLength): void public function shortCodesHaveExpectedLength(?int $length, int $expectedLength): void
{ {
$shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData( $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData(
[ShortUrlInputFilter::SHORT_CODE_LENGTH => $length, 'longUrl' => 'longUrl'], [ShortUrlInputFilter::SHORT_CODE_LENGTH => $length, 'longUrl' => 'https://longUrl'],
)); ));
self::assertEquals($expectedLength, strlen($shortUrl->getShortCode())); self::assertEquals($expectedLength, strlen($shortUrl->getShortCode()));
@ -92,7 +94,7 @@ class ShortUrlTest extends TestCase
#[Test] #[Test]
public function deviceLongUrlsAreUpdated(): void public function deviceLongUrlsAreUpdated(): void
{ {
$shortUrl = ShortUrl::withLongUrl('foo'); $shortUrl = ShortUrl::withLongUrl('https://foo');
$shortUrl->update(ShortUrlEdition::fromRawData([ $shortUrl->update(ShortUrlEdition::fromRawData([
ShortUrlInputFilter::DEVICE_LONG_URLS => [ ShortUrlInputFilter::DEVICE_LONG_URLS => [

View file

@ -29,7 +29,7 @@ class ShortUrlStringifierTest extends TestCase
{ {
$shortUrlWithShortCode = fn (string $shortCode, ?string $domain = null) => ShortUrl::create( $shortUrlWithShortCode = fn (string $shortCode, ?string $domain = null) => ShortUrl::create(
ShortUrlCreation::fromRawData([ ShortUrlCreation::fromRawData([
'longUrl' => 'longUrl', 'longUrl' => 'https://longUrl',
'customSlug' => $shortCode, 'customSlug' => $shortCode,
'domain' => $domain, 'domain' => $domain,
]), ]),

View file

@ -136,7 +136,7 @@ class ExtraPathRedirectMiddlewareTest extends TestCase
$type->method('isInvalidShortUrl')->willReturn(true); $type->method('isInvalidShortUrl')->willReturn(true);
$request = ServerRequestFactory::fromGlobals()->withAttribute(NotFoundType::class, $type) $request = ServerRequestFactory::fromGlobals()->withAttribute(NotFoundType::class, $type)
->withUri(new Uri('https://s.test/shortCode/bar/baz')); ->withUri(new Uri('https://s.test/shortCode/bar/baz'));
$shortUrl = ShortUrl::withLongUrl('longUrl'); $shortUrl = ShortUrl::withLongUrl('https://longUrl');
$currentIteration = 1; $currentIteration = 1;
$this->resolver->expects($this->exactly($expectedResolveCalls))->method('resolveEnabledShortUrl')->with( $this->resolver->expects($this->exactly($expectedResolveCalls))->method('resolveEnabledShortUrl')->with(

View file

@ -115,7 +115,7 @@ class ShortUrlCreationTest extends TestCase
$creation = ShortUrlCreation::fromRawData([ $creation = ShortUrlCreation::fromRawData([
'validSince' => Chronos::parse('2015-01-01')->toAtomString(), 'validSince' => Chronos::parse('2015-01-01')->toAtomString(),
'customSlug' => $customSlug, 'customSlug' => $customSlug,
'longUrl' => 'longUrl', 'longUrl' => 'https://longUrl',
], new UrlShortenerOptions(multiSegmentSlugsEnabled: $multiSegmentEnabled, mode: $shortUrlMode)); ], new UrlShortenerOptions(multiSegmentSlugsEnabled: $multiSegmentEnabled, mode: $shortUrlMode));
self::assertTrue($creation->hasValidSince()); self::assertTrue($creation->hasValidSince());
@ -161,7 +161,7 @@ class ShortUrlCreationTest extends TestCase
{ {
$creation = ShortUrlCreation::fromRawData([ $creation = ShortUrlCreation::fromRawData([
'title' => $title, 'title' => $title,
'longUrl' => 'longUrl', 'longUrl' => 'https://longUrl',
]); ]);
self::assertEquals($expectedTitle, $creation->title); self::assertEquals($expectedTitle, $creation->title);
@ -184,7 +184,7 @@ class ShortUrlCreationTest extends TestCase
{ {
$creation = ShortUrlCreation::fromRawData([ $creation = ShortUrlCreation::fromRawData([
'domain' => $domain, 'domain' => $domain,
'longUrl' => 'longUrl', 'longUrl' => 'https://longUrl',
]); ]);
self::assertSame($expectedDomain, $creation->domain); self::assertSame($expectedDomain, $creation->domain);

View file

@ -31,23 +31,29 @@ class ShortUrlEditionTest extends TestCase
yield 'null' => [null, [], []]; yield 'null' => [null, [], []];
yield 'empty' => [[], [], []]; yield 'empty' => [[], [], []];
yield 'only new urls' => [[ yield 'only new urls' => [[
DeviceType::DESKTOP->value => 'foo', DeviceType::DESKTOP->value => 'https://foo',
DeviceType::IOS->value => 'bar', DeviceType::IOS->value => 'https://bar',
], [ ], [
DeviceType::DESKTOP->value => DeviceLongUrlPair::fromRawTypeAndLongUrl(DeviceType::DESKTOP->value, 'foo'), DeviceType::DESKTOP->value => DeviceLongUrlPair::fromRawTypeAndLongUrl(
DeviceType::IOS->value => DeviceLongUrlPair::fromRawTypeAndLongUrl(DeviceType::IOS->value, 'bar'), DeviceType::DESKTOP->value,
'https://foo',
),
DeviceType::IOS->value => DeviceLongUrlPair::fromRawTypeAndLongUrl(DeviceType::IOS->value, 'https://bar'),
], []]; ], []];
yield 'only urls to remove' => [[ yield 'only urls to remove' => [[
DeviceType::ANDROID->value => null, DeviceType::ANDROID->value => null,
DeviceType::IOS->value => null, DeviceType::IOS->value => null,
], [], [DeviceType::ANDROID, DeviceType::IOS]]; ], [], [DeviceType::ANDROID, DeviceType::IOS]];
yield 'both' => [[ yield 'both' => [[
DeviceType::DESKTOP->value => 'bar', DeviceType::DESKTOP->value => 'https://bar',
DeviceType::IOS->value => 'foo', DeviceType::IOS->value => 'https://foo',
DeviceType::ANDROID->value => null, DeviceType::ANDROID->value => null,
], [ ], [
DeviceType::DESKTOP->value => DeviceLongUrlPair::fromRawTypeAndLongUrl(DeviceType::DESKTOP->value, 'bar'), DeviceType::DESKTOP->value => DeviceLongUrlPair::fromRawTypeAndLongUrl(
DeviceType::IOS->value => DeviceLongUrlPair::fromRawTypeAndLongUrl(DeviceType::IOS->value, 'foo'), DeviceType::DESKTOP->value,
'https://bar',
),
DeviceType::IOS->value => DeviceLongUrlPair::fromRawTypeAndLongUrl(DeviceType::IOS->value, 'https://foo'),
], [DeviceType::ANDROID]]; ], [DeviceType::ANDROID]];
} }
} }

View file

@ -45,7 +45,7 @@ class ShortUrlResolverTest extends TestCase
#[Test, DataProvider('provideAdminApiKeys')] #[Test, DataProvider('provideAdminApiKeys')]
public function shortCodeIsProperlyParsed(?ApiKey $apiKey): void public function shortCodeIsProperlyParsed(?ApiKey $apiKey): void
{ {
$shortUrl = ShortUrl::withLongUrl('expected_url'); $shortUrl = ShortUrl::withLongUrl('https://expected_url');
$shortCode = $shortUrl->getShortCode(); $shortCode = $shortUrl->getShortCode();
$identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode);
@ -76,7 +76,7 @@ class ShortUrlResolverTest extends TestCase
#[Test] #[Test]
public function shortCodeToEnabledShortUrlProperlyParsesShortCode(): void public function shortCodeToEnabledShortUrlProperlyParsesShortCode(): void
{ {
$shortUrl = ShortUrl::withLongUrl('expected_url'); $shortUrl = ShortUrl::withLongUrl('https://expected_url');
$shortCode = $shortUrl->getShortCode(); $shortCode = $shortUrl->getShortCode();
$this->repo->expects($this->once())->method('findOneWithDomainFallback')->with( $this->repo->expects($this->once())->method('findOneWithDomainFallback')->with(
@ -111,7 +111,9 @@ class ShortUrlResolverTest extends TestCase
$now = Chronos::now(); $now = Chronos::now();
yield 'maxVisits reached' => [(function () { yield 'maxVisits reached' => [(function () {
$shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData(['maxVisits' => 3, 'longUrl' => 'longUrl'])); $shortUrl = ShortUrl::create(
ShortUrlCreation::fromRawData(['maxVisits' => 3, 'longUrl' => 'https://longUrl']),
);
$shortUrl->setVisits(new ArrayCollection(map( $shortUrl->setVisits(new ArrayCollection(map(
range(0, 4), range(0, 4),
fn () => Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance()), fn () => Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance()),
@ -120,16 +122,16 @@ class ShortUrlResolverTest extends TestCase
return $shortUrl; return $shortUrl;
})()]; })()];
yield 'future validSince' => [ShortUrl::create(ShortUrlCreation::fromRawData( yield 'future validSince' => [ShortUrl::create(ShortUrlCreation::fromRawData(
['validSince' => $now->addMonth()->toAtomString(), 'longUrl' => 'longUrl'], ['validSince' => $now->addMonth()->toAtomString(), 'longUrl' => 'https://longUrl'],
))]; ))];
yield 'past validUntil' => [ShortUrl::create(ShortUrlCreation::fromRawData( yield 'past validUntil' => [ShortUrl::create(ShortUrlCreation::fromRawData(
['validUntil' => $now->subMonth()->toAtomString(), 'longUrl' => 'longUrl'], ['validUntil' => $now->subMonth()->toAtomString(), 'longUrl' => 'https://longUrl'],
))]; ))];
yield 'mixed' => [(function () use ($now) { yield 'mixed' => [(function () use ($now) {
$shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([ $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([
'maxVisits' => 3, 'maxVisits' => 3,
'validUntil' => $now->subMonth()->toAtomString(), 'validUntil' => $now->subMonth()->toAtomString(),
'longUrl' => 'longUrl', 'longUrl' => 'https://longUrl',
])); ]));
$shortUrl->setVisits(new ArrayCollection(map( $shortUrl->setVisits(new ArrayCollection(map(
range(0, 4), range(0, 4),

View file

@ -57,7 +57,7 @@ class ShortUrlServiceTest extends TestCase
ShortUrlEdition $shortUrlEdit, ShortUrlEdition $shortUrlEdit,
?ApiKey $apiKey, ?ApiKey $apiKey,
): void { ): void {
$originalLongUrl = 'originalLongUrl'; $originalLongUrl = 'https://originalLongUrl';
$shortUrl = ShortUrl::withLongUrl($originalLongUrl); $shortUrl = ShortUrl::withLongUrl($originalLongUrl);
$this->urlResolver->expects($this->once())->method('resolveShortUrl')->with( $this->urlResolver->expects($this->once())->method('resolveShortUrl')->with(
@ -103,16 +103,16 @@ class ShortUrlServiceTest extends TestCase
yield 'long URL and API key' => [new InvokedCount(1), ShortUrlEdition::fromRawData([ yield 'long URL and API key' => [new InvokedCount(1), ShortUrlEdition::fromRawData([
'validSince' => Chronos::parse('2017-01-01 00:00:00')->toAtomString(), 'validSince' => Chronos::parse('2017-01-01 00:00:00')->toAtomString(),
'maxVisits' => 10, 'maxVisits' => 10,
'longUrl' => 'modifiedLongUrl', 'longUrl' => 'https://modifiedLongUrl',
]), ApiKey::create()]; ]), ApiKey::create()];
yield 'long URL with validation' => [new InvokedCount(1), ShortUrlEdition::fromRawData([ yield 'long URL with validation' => [new InvokedCount(1), ShortUrlEdition::fromRawData([
'longUrl' => 'modifiedLongUrl', 'longUrl' => 'https://modifiedLongUrl',
'validateUrl' => true, 'validateUrl' => true,
]), null]; ]), null];
yield 'device redirects' => [new InvokedCount(0), ShortUrlEdition::fromRawData([ yield 'device redirects' => [new InvokedCount(0), ShortUrlEdition::fromRawData([
'deviceLongUrls' => [ 'deviceLongUrls' => [
DeviceType::IOS->value => 'iosLongUrl', DeviceType::IOS->value => 'https://iosLongUrl',
DeviceType::ANDROID->value => 'androidLongUrl', DeviceType::ANDROID->value => 'https://androidLongUrl',
], ],
]), null]; ]), null];
} }

View file

@ -44,7 +44,7 @@ class ShortUrlDataTransformerTest extends TestCase
]]; ]];
yield 'max visits only' => [ShortUrl::create(ShortUrlCreation::fromRawData([ yield 'max visits only' => [ShortUrl::create(ShortUrlCreation::fromRawData([
'maxVisits' => $maxVisits, 'maxVisits' => $maxVisits,
'longUrl' => 'longUrl', 'longUrl' => 'https://longUrl',
])), [ ])), [
'validSince' => null, 'validSince' => null,
'validUntil' => null, 'validUntil' => null,
@ -52,7 +52,7 @@ class ShortUrlDataTransformerTest extends TestCase
]]; ]];
yield 'max visits and valid since' => [ yield 'max visits and valid since' => [
ShortUrl::create(ShortUrlCreation::fromRawData( ShortUrl::create(ShortUrlCreation::fromRawData(
['validSince' => $now, 'maxVisits' => $maxVisits, 'longUrl' => 'longUrl'], ['validSince' => $now, 'maxVisits' => $maxVisits, 'longUrl' => 'https://longUrl'],
)), )),
[ [
'validSince' => $now->toAtomString(), 'validSince' => $now->toAtomString(),
@ -62,7 +62,7 @@ class ShortUrlDataTransformerTest extends TestCase
]; ];
yield 'both dates' => [ yield 'both dates' => [
ShortUrl::create(ShortUrlCreation::fromRawData( ShortUrl::create(ShortUrlCreation::fromRawData(
['validSince' => $now, 'validUntil' => $now->subDays(10), 'longUrl' => 'longUrl'], ['validSince' => $now, 'validUntil' => $now->subDays(10), 'longUrl' => 'https://longUrl'],
)), )),
[ [
'validSince' => $now->toAtomString(), 'validSince' => $now->toAtomString(),
@ -75,7 +75,7 @@ class ShortUrlDataTransformerTest extends TestCase
'validSince' => $now, 'validSince' => $now,
'validUntil' => $now->subDays(5), 'validUntil' => $now->subDays(5),
'maxVisits' => $maxVisits, 'maxVisits' => $maxVisits,
'longUrl' => 'longUrl', 'longUrl' => 'https://longUrl',
])), ])),
[ [
'validSince' => $now->toAtomString(), 'validSince' => $now->toAtomString(),

View file

@ -47,8 +47,10 @@ class VisitLocatorTest extends TestCase
): void { ): void {
$unlocatedVisits = map( $unlocatedVisits = map(
range(1, 200), range(1, 200),
fn (int $i) => fn (int $i) => Visit::forValidShortUrl(
Visit::forValidShortUrl(ShortUrl::withLongUrl(sprintf('short_code_%s', $i)), Visitor::emptyInstance()), ShortUrl::withLongUrl(sprintf('https://short_code_%s', $i)),
Visitor::emptyInstance(),
),
); );
$this->repo->expects($this->once())->method($expectedRepoMethodName)->willReturn($unlocatedVisits); $this->repo->expects($this->once())->method($expectedRepoMethodName)->willReturn($unlocatedVisits);
@ -85,7 +87,7 @@ class VisitLocatorTest extends TestCase
bool $isNonLocatableAddress, bool $isNonLocatableAddress,
): void { ): void {
$unlocatedVisits = [ $unlocatedVisits = [
Visit::forValidShortUrl(ShortUrl::withLongUrl('foo'), Visitor::emptyInstance()), Visit::forValidShortUrl(ShortUrl::withLongUrl('https://foo'), Visitor::emptyInstance()),
]; ];
$this->repo->expects($this->once())->method($expectedRepoMethodName)->willReturn($unlocatedVisits); $this->repo->expects($this->once())->method($expectedRepoMethodName)->willReturn($unlocatedVisits);

View file

@ -268,6 +268,8 @@ class CreateShortUrlTest extends ApiTestCase
yield 'missing long url v3' => [[], '3', 'https://shlink.io/api/error/invalid-data']; yield 'missing long url v3' => [[], '3', 'https://shlink.io/api/error/invalid-data'];
yield 'empty long url v2' => [['longUrl' => null], '2', 'INVALID_ARGUMENT']; yield 'empty long url v2' => [['longUrl' => null], '2', 'INVALID_ARGUMENT'];
yield 'empty long url v3' => [['longUrl' => ' '], '3', 'https://shlink.io/api/error/invalid-data']; yield 'empty long url v3' => [['longUrl' => ' '], '3', 'https://shlink.io/api/error/invalid-data'];
yield 'missing url schema v2' => [['longUrl' => 'foo.com'], '2', 'INVALID_ARGUMENT'];
yield 'missing url schema v3' => [['longUrl' => 'foo.com'], '3', 'https://shlink.io/api/error/invalid-data'];
yield 'empty device long url v2' => [[ yield 'empty device long url v2' => [[
'longUrl' => 'foo', 'longUrl' => 'foo',
'deviceLongUrls' => [ 'deviceLongUrls' => [