From 25fbbee883e2e6f28304c1d2341086af9332a353 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 20 Sep 2020 13:21:21 +0200 Subject: [PATCH 01/73] Added support to order short urls liusts using the : notaiton as string --- module/Core/src/Model/ShortUrlsOrdering.php | 14 +++++++++++--- module/Rest/test-api/Action/ListShortUrlsTest.php | 8 ++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/module/Core/src/Model/ShortUrlsOrdering.php b/module/Core/src/Model/ShortUrlsOrdering.php index 00c30a54..3f1dbd88 100644 --- a/module/Core/src/Model/ShortUrlsOrdering.php +++ b/module/Core/src/Model/ShortUrlsOrdering.php @@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Core\Model; use Shlinkio\Shlink\Core\Exception\ValidationException; +use function explode; use function is_array; use function is_string; use function key; @@ -40,15 +41,22 @@ final class ShortUrlsOrdering return; } + // FIXME Providing the ordering as array is considered deprecated. To be removed in v3.0.0 $isArray = is_array($orderBy); - if (! $isArray && $orderBy !== null && ! is_string($orderBy)) { + if (! $isArray && ! is_string($orderBy)) { throw ValidationException::fromArray([ 'orderBy' => '"Order by" must be an array, string or null', ]); } - $this->orderField = $isArray ? key($orderBy) : $orderBy; - $this->orderDirection = $isArray ? $orderBy[$this->orderField] : self::DEFAULT_ORDER_DIRECTION; + if (! $isArray) { + $parts = explode(':', $orderBy); + $this->orderField = $parts[0]; + $this->orderDirection = $parts[1] ?? self::DEFAULT_ORDER_DIRECTION; + } else { + $this->orderField = key($orderBy); + $this->orderDirection = $orderBy[$this->orderField]; + } } public function orderField(): ?string diff --git a/module/Rest/test-api/Action/ListShortUrlsTest.php b/module/Rest/test-api/Action/ListShortUrlsTest.php index 7d4e51a7..32d87bdf 100644 --- a/module/Rest/test-api/Action/ListShortUrlsTest.php +++ b/module/Rest/test-api/Action/ListShortUrlsTest.php @@ -145,6 +145,14 @@ class ListShortUrlsTest extends ApiTestCase self::SHORT_URL_CUSTOM_SLUG, self::SHORT_URL_SHLINK, ]]; + yield [['orderBy' => 'shortCode:DESC'], [ + self::SHORT_URL_DOCS, + self::SHORT_URL_CUSTOM_DOMAIN, + self::SHORT_URL_META, + self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, + self::SHORT_URL_CUSTOM_SLUG, + self::SHORT_URL_SHLINK, + ]]; yield [['startDate' => Chronos::parse('2018-12-01')->toAtomString()], [ self::SHORT_URL_META, self::SHORT_URL_CUSTOM_SLUG, From be71a6eeb41eef54c4332945f20cece18feaf5f5 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 21 Sep 2020 22:03:43 +0200 Subject: [PATCH 02/73] Replaced colon by hyphen as the ordering field-dir separator as it's a valid URL character --- docs/swagger/paths/v1_short-urls.json | 14 +++++++++----- module/Core/src/Model/ShortUrlsOrdering.php | 2 +- module/Rest/test-api/Action/ListShortUrlsTest.php | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/swagger/paths/v1_short-urls.json b/docs/swagger/paths/v1_short-urls.json index ee8a6060..2467de24 100644 --- a/docs/swagger/paths/v1_short-urls.json +++ b/docs/swagger/paths/v1_short-urls.json @@ -31,7 +31,7 @@ { "name": "tags[]", "in": "query", - "description": "A list of tags used to filter the resultset. Only short URLs tagged with at least one of the provided tags will be returned. (Since v1.3.0)", + "description": "A list of tags used to filter the result set. Only short URLs tagged with at least one of the provided tags will be returned. (Since v1.3.0)", "required": false, "schema": { "type": "array", @@ -48,10 +48,14 @@ "schema": { "type": "string", "enum": [ - "longUrl", - "shortCode", - "dateCreated", - "visits" + "longUrl-ASC", + "longUrl-DESC", + "shortCode-ASC", + "shortCode-DESC", + "dateCreated-ASC", + "dateCreated-DESC", + "visits-ASC", + "visits-DESC" ] } }, diff --git a/module/Core/src/Model/ShortUrlsOrdering.php b/module/Core/src/Model/ShortUrlsOrdering.php index 3f1dbd88..25c7c940 100644 --- a/module/Core/src/Model/ShortUrlsOrdering.php +++ b/module/Core/src/Model/ShortUrlsOrdering.php @@ -50,7 +50,7 @@ final class ShortUrlsOrdering } if (! $isArray) { - $parts = explode(':', $orderBy); + $parts = explode('-', $orderBy); $this->orderField = $parts[0]; $this->orderDirection = $parts[1] ?? self::DEFAULT_ORDER_DIRECTION; } else { diff --git a/module/Rest/test-api/Action/ListShortUrlsTest.php b/module/Rest/test-api/Action/ListShortUrlsTest.php index 32d87bdf..d59c488d 100644 --- a/module/Rest/test-api/Action/ListShortUrlsTest.php +++ b/module/Rest/test-api/Action/ListShortUrlsTest.php @@ -137,7 +137,7 @@ class ListShortUrlsTest extends ApiTestCase self::SHORT_URL_DOCS, self::SHORT_URL_CUSTOM_DOMAIN, ]]; - yield [['orderBy' => ['shortCode' => 'DESC']], [ + yield [['orderBy' => ['shortCode' => 'DESC']], [ // Deprecated self::SHORT_URL_DOCS, self::SHORT_URL_CUSTOM_DOMAIN, self::SHORT_URL_META, @@ -145,7 +145,7 @@ class ListShortUrlsTest extends ApiTestCase self::SHORT_URL_CUSTOM_SLUG, self::SHORT_URL_SHLINK, ]]; - yield [['orderBy' => 'shortCode:DESC'], [ + yield [['orderBy' => 'shortCode-DESC'], [ self::SHORT_URL_DOCS, self::SHORT_URL_CUSTOM_DOMAIN, self::SHORT_URL_META, From ee563978acf8212ab13bae6cccbb74ac09435232 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 21 Sep 2020 22:06:41 +0200 Subject: [PATCH 03/73] Updated changelog --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a50a42ef..d2ae6058 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). +## [Unreleased] + +#### Added + +* *Nothing* + +#### Changed + +* [#836](https://github.com/shlinkio/shlink/issues/836) Added support for the `-` notation while determining how to order the short URLs list, as in `?orderBy=shortCode-DESC`. This effectively deprecates the array notation (`?orderBy[shortCode]=DESC`), that will be removed in Shlink 3.0.0 + +#### Deprecated + +* *Nothing* + +#### Removed + +* *Nothing* + +#### Fixed + +* *Nothing* + ## 2.3.0 - 2020-08-09 #### Added From 55d9f2a4a11a804c57f1b4515674789c7752434e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 21 Sep 2020 22:48:52 +0200 Subject: [PATCH 04/73] Added support to return the QR code in SVG format --- docs/swagger/paths/{shortCode}_qr-code.json | 19 +++++++++++++++++++ module/Core/src/Action/QrCodeAction.php | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/docs/swagger/paths/{shortCode}_qr-code.json b/docs/swagger/paths/{shortCode}_qr-code.json index 300a7429..a3fdaffb 100644 --- a/docs/swagger/paths/{shortCode}_qr-code.json +++ b/docs/swagger/paths/{shortCode}_qr-code.json @@ -27,6 +27,19 @@ "maximum": 1000, "default": 300 } + }, + { + "name": "format", + "in": "query", + "description": "The format for the QR code image, being valid values png and svg. Not providing the param or providing any other value will fall back to png.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "png", + "svg" + ] + } } ], "responses": { @@ -38,6 +51,12 @@ "type": "string", "format": "binary" } + }, + "image/svg+xml": { + "schema": { + "type": "string", + "format": "binary" + } } } } diff --git a/module/Core/src/Action/QrCodeAction.php b/module/Core/src/Action/QrCodeAction.php index 979e34fe..4a8b7db5 100644 --- a/module/Core/src/Action/QrCodeAction.php +++ b/module/Core/src/Action/QrCodeAction.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Action; use Endroid\QrCode\QrCode; +use Endroid\QrCode\Writer\SvgWriter; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\MiddlewareInterface; @@ -51,6 +52,11 @@ class QrCodeAction implements MiddlewareInterface $qrCode->setSize($this->getSizeParam($request)); $qrCode->setMargin(0); + $format = $request->getQueryParams()['format'] ?? 'png'; + if ($format === 'svg') { + $qrCode->setWriter(new SvgWriter()); + } + return new QrCodeResponse($qrCode); } From 4b7184ac85c14f43600b0f1ca90f06fab1ca2f4d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 21 Sep 2020 22:54:05 +0200 Subject: [PATCH 05/73] Added tests for new QR code format --- module/Core/test/Action/QrCodeActionTest.php | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index eb68f0e1..236288dd 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -81,4 +81,30 @@ class QrCodeActionTest extends TestCase $this->assertEquals(200, $resp->getStatusCode()); $delegate->handle(Argument::any())->shouldHaveBeenCalledTimes(0); } + + /** + * @test + * @dataProvider provideQueries + */ + public function imageIsReturnedWithExpectedContentTypeBasedOnProvidedFormat( + array $query, + string $expectedContentType + ): void { + $code = 'abc123'; + $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn(new ShortUrl('')); + $delegate = $this->prophesize(RequestHandlerInterface::class); + $req = (new ServerRequest())->withAttribute('shortCode', $code)->withQueryParams($query); + + $resp = $this->action->process($req, $delegate->reveal()); + + $this->assertEquals($expectedContentType, $resp->getHeaderLine('Content-Type')); + } + + public function provideQueries(): iterable + { + yield 'no format' => [[], 'image/png']; + yield 'png format' => [['format' => 'png'], 'image/png']; + yield 'svg format' => [['format' => 'svg'], 'image/svg+xml']; + yield 'unsupported format' => [['format' => 'jpg'], 'image/png']; + } } From 504d08101a5f5eafdc283fd8c266785067b76b03 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 21 Sep 2020 22:55:18 +0200 Subject: [PATCH 06/73] Updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2ae6058..62ad683b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this #### Added -* *Nothing* +* [#829](https://github.com/shlinkio/shlink/issues/829) Added support for QR codes in SVG format, by passing `?format=svg` to the QR code URL. #### Changed From 460ca032d28302a13eaf810bcf0d3ab92a95e596 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 23 Sep 2020 00:22:29 +0200 Subject: [PATCH 07/73] Drastically improved performance when creating new short URLs with findIfExists by moving logic to DB query --- module/Core/src/Entity/ShortUrl.php | 26 -------- .../src/Repository/ShortUrlRepository.php | 60 +++++++++++++++++++ .../ShortUrlRepositoryInterface.php | 3 + module/Core/src/Service/UrlShortener.php | 24 ++------ .../Repository/ShortUrlRepositoryTest.php | 30 ++++++++++ module/Core/test/Entity/ShortUrlTest.php | 34 ----------- module/Core/test/Service/UrlShortenerTest.php | 31 +--------- 7 files changed, 98 insertions(+), 110 deletions(-) diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 0f2811fb..ba10a44a 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -15,10 +15,7 @@ use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException; use Shlinkio\Shlink\Core\Model\ShortUrlEdit; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; -use function array_reduce; use function count; -use function Functional\contains; -use function Functional\invoke; use function Shlinkio\Shlink\Core\generateRandomShortCode; class ShortUrl extends AbstractEntity @@ -195,27 +192,4 @@ class ShortUrl extends AbstractEntity return $this->domain->getAuthority(); } - - public function matchesCriteria(ShortUrlMeta $meta, array $tags): bool - { - if ($meta->hasMaxVisits() && $meta->getMaxVisits() !== $this->maxVisits) { - return false; - } - if ($meta->hasDomain() && $meta->getDomain() !== $this->resolveDomain()) { - return false; - } - if ($meta->hasValidSince() && ($this->validSince === null || ! $meta->getValidSince()->eq($this->validSince))) { - return false; - } - if ($meta->hasValidUntil() && ($this->validUntil === null || ! $meta->getValidUntil()->eq($this->validUntil))) { - return false; - } - - $shortUrlTags = invoke($this->getTags(), '__toString'); - return count($shortUrlTags) === count($tags) && array_reduce( - $tags, - fn (bool $hasAllTags, string $tag) => $hasAllTags && contains($shortUrlTags, $tag), - true, - ); - } } diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index 31fe1385..4bcbe059 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -5,13 +5,16 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Repository; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Entity\ShortUrl; +use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering; use function array_column; use function array_key_exists; +use function count; use function Functional\contains; class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryInterface @@ -196,4 +199,61 @@ DQL; return $qb; } + + public function findOneMatching(string $url, 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) + ->setMaxResults(1); + + if ($meta->hasCustomSlug()) { + $qb->andWhere($qb->expr()->eq('s.shortCode', ':slug')) + ->setParameter('slug', $meta->getCustomSlug()); + } + if ($meta->hasMaxVisits()) { + $qb->andWhere($qb->expr()->eq('s.maxVisits', ':maxVisits')) + ->setParameter('maxVisits', $meta->getMaxVisits()); + } + if ($meta->hasValidSince()) { + $qb->andWhere($qb->expr()->eq('s.validSince', ':validSince')) + ->setParameter('validSince', $meta->getValidSince()); + } + if ($meta->hasValidUntil()) { + $qb->andWhere($qb->expr()->eq('s.validUntil', ':validUntil')) + ->setParameter('validUntil', $meta->getValidUntil()); + } + + if ($meta->hasDomain()) { + $qb->join('s.domain', 'd') + ->andWhere($qb->expr()->eq('d.authority', ':domain')) + ->setParameter('domain', $meta->getDomain()); + } + + $tagsAmount = count($tags); + if ($tagsAmount === 0) { + return $qb->getQuery()->getOneOrNullResult(); + } + + foreach ($tags as $index => $tag) { + $alias = 't_' . $index; + $qb->join('s.tags', $alias, Join::WITH, $qb->expr()->eq($alias . '.name', ':tag' . $index)) + ->setParameter('tag' . $index, $tag); + } + + // If tags where provided, we need an extra join to see the amount of tags that every short URL has, so that we + // can discard those that also have more tags, making sure only those fully matching are included. + $qb->addSelect('COUNT(t.id) as tagsAmount') + ->join('s.tags', 't') + ->groupBy('s') + ->having($qb->expr()->eq('tagsAmount', ':tagsAmount')) + ->setParameter('tagsAmount', $tagsAmount); + + $result = $qb->getQuery()->getOneOrNullResult() ?? []; + + return $result[0] ?? null; + } } diff --git a/module/Core/src/Repository/ShortUrlRepositoryInterface.php b/module/Core/src/Repository/ShortUrlRepositoryInterface.php index 065198b4..65278a85 100644 --- a/module/Core/src/Repository/ShortUrlRepositoryInterface.php +++ b/module/Core/src/Repository/ShortUrlRepositoryInterface.php @@ -7,6 +7,7 @@ namespace Shlinkio\Shlink\Core\Repository; use Doctrine\Persistence\ObjectRepository; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Entity\ShortUrl; +use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering; interface ShortUrlRepositoryInterface extends ObjectRepository @@ -27,4 +28,6 @@ interface ShortUrlRepositoryInterface extends ObjectRepository public function findOne(string $shortCode, ?string $domain = null): ?ShortUrl; public function shortCodeIsInUse(string $slug, ?string $domain): bool; + + public function findOneMatching(string $url, array $tags, ShortUrlMeta $meta): ?ShortUrl; } diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index 7892f959..cf17d0bd 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -11,12 +11,11 @@ use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; +use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Shlinkio\Shlink\Core\Util\UrlValidatorInterface; use Throwable; -use function array_reduce; - class UrlShortener implements UrlShortenerInterface { use TagManagerTrait; @@ -77,24 +76,9 @@ class UrlShortener implements UrlShortenerInterface return null; } - $criteria = ['longUrl' => $url]; - if ($meta->hasCustomSlug()) { - $criteria['shortCode'] = $meta->getCustomSlug(); - } - /** @var ShortUrl[] $shortUrls */ - $shortUrls = $this->em->getRepository(ShortUrl::class)->findBy($criteria); - if (empty($shortUrls)) { - return null; - } - - // Iterate short URLs until one that matches is found, or return null otherwise - return array_reduce($shortUrls, function (?ShortUrl $found, ShortUrl $shortUrl) use ($tags, $meta) { - if ($found !== null) { - return $found; - } - - return $shortUrl->matchesCriteria($meta, $tags) ? $shortUrl : null; - }); + /** @var ShortUrlRepositoryInterface $repo */ + $repo = $this->em->getRepository(ShortUrl::class); + return $repo->findOneMatching($url, $tags, $meta); } private function verifyShortCodeUniqueness(ShortUrlMeta $meta, ShortUrl $shortUrlToBeCreated): void diff --git a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php index 4829fada..85df47de 100644 --- a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php @@ -210,4 +210,34 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $this->assertNull($this->repo->findOne('another-slug', 'example.com')); $this->assertNotNull($this->repo->findOne('another-slug', 'doma.in')); } + +// public function findOneMatchingAppliesProperConditions(): void +// { +// $matches = $this->provideCriteriaToMatch(); +// } +// +// private function provideCriteriaToMatch(): iterable +// { +// $start = Chronos::parse('2020-03-05 20:18:30'); +// $end = Chronos::parse('2021-03-05 20:18:30'); +// +// yield [new ShortUrl('foo'), ShortUrlMeta::fromRawData(['validSince' => $start]), false]; +// yield [new ShortUrl('foo'), ShortUrlMeta::fromRawData(['validUntil' => $end]), false]; +// yield [new ShortUrl('foo'), ShortUrlMeta::fromRawData(['validSince' => $start, 'validUntil' => $end]), false]; +// yield [ +// new ShortUrl('foo', ShortUrlMeta::fromRawData(['validSince' => $start])), +// ShortUrlMeta::fromRawData(['validSince' => $start]), +// true, +// ]; +// yield [ +// new ShortUrl('foo', ShortUrlMeta::fromRawData(['validUntil' => $end])), +// ShortUrlMeta::fromRawData(['validUntil' => $end]), +// true, +// ]; +// yield [ +// new ShortUrl('foo', ShortUrlMeta::fromRawData(['validUntil' => $end, 'validSince' => $start])), +// ShortUrlMeta::fromRawData(['validUntil' => $end, 'validSince' => $start]), +// true, +// ]; +// } } diff --git a/module/Core/test/Entity/ShortUrlTest.php b/module/Core/test/Entity/ShortUrlTest.php index 07308580..c5933e39 100644 --- a/module/Core/test/Entity/ShortUrlTest.php +++ b/module/Core/test/Entity/ShortUrlTest.php @@ -75,38 +75,4 @@ class ShortUrlTest extends TestCase yield [null, DEFAULT_SHORT_CODES_LENGTH]; yield from map(range(4, 10), fn (int $value) => [$value, $value]); } - - /** - * @test - * @dataProvider provideCriteriaToMatch - */ - public function criteriaIsMatchedWhenDatesMatch(ShortUrl $shortUrl, ShortUrlMeta $meta, bool $expected): void - { - $this->assertEquals($expected, $shortUrl->matchesCriteria($meta, [])); - } - - public function provideCriteriaToMatch(): iterable - { - $start = Chronos::parse('2020-03-05 20:18:30'); - $end = Chronos::parse('2021-03-05 20:18:30'); - - yield [new ShortUrl('foo'), ShortUrlMeta::fromRawData(['validSince' => $start]), false]; - yield [new ShortUrl('foo'), ShortUrlMeta::fromRawData(['validUntil' => $end]), false]; - yield [new ShortUrl('foo'), ShortUrlMeta::fromRawData(['validSince' => $start, 'validUntil' => $end]), false]; - yield [ - new ShortUrl('foo', ShortUrlMeta::fromRawData(['validSince' => $start])), - ShortUrlMeta::fromRawData(['validSince' => $start]), - true, - ]; - yield [ - new ShortUrl('foo', ShortUrlMeta::fromRawData(['validUntil' => $end])), - ShortUrlMeta::fromRawData(['validUntil' => $end]), - true, - ]; - yield [ - new ShortUrl('foo', ShortUrlMeta::fromRawData(['validUntil' => $end, 'validSince' => $start])), - ShortUrlMeta::fromRawData(['validUntil' => $end, 'validSince' => $start]), - true, - ]; - } } diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index f1ef88af..9e33af6b 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -147,7 +147,7 @@ class UrlShortenerTest extends TestCase ShortUrl $expected ): void { $repo = $this->prophesize(ShortUrlRepository::class); - $findExisting = $repo->findBy(Argument::any())->willReturn([$expected]); + $findExisting = $repo->findOneMatching(Argument::cetera())->willReturn($expected); $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); $result = $this->urlShortener->urlToShortCode($url, $tags, $meta); @@ -211,33 +211,4 @@ class UrlShortenerTest extends TestCase ])))->setTags(new ArrayCollection([new Tag('foo'), new Tag('bar'), new Tag('baz')])), ]; } - - /** @test */ - public function properExistingShortUrlIsReturnedWhenMultipleMatch(): void - { - $url = 'http://foo.com'; - $tags = ['baz', 'foo', 'bar']; - $meta = ShortUrlMeta::fromRawData([ - 'findIfExists' => true, - 'validUntil' => Chronos::parse('2017-01-01'), - 'maxVisits' => 4, - ]); - $tagsCollection = new ArrayCollection(array_map(fn (string $tag) => new Tag($tag), $tags)); - $expected = (new ShortUrl($url, $meta))->setTags($tagsCollection); - - $repo = $this->prophesize(ShortUrlRepository::class); - $findExisting = $repo->findBy(Argument::any())->willReturn([ - new ShortUrl($url), - new ShortUrl($url, $meta), - $expected, - (new ShortUrl($url))->setTags($tagsCollection), - ]); - $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); - - $result = $this->urlShortener->urlToShortCode($url, $tags, $meta); - - $this->assertSame($expected, $result); - $findExisting->shouldHaveBeenCalledOnce(); - $getRepo->shouldHaveBeenCalledOnce(); - } } From 4e94f070501550e00fb69ea3342d006b86b493d3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 23 Sep 2020 07:34:36 +0200 Subject: [PATCH 08/73] Added tests for new ShortUrlRepository::findOneMatching method --- .../src/Repository/ShortUrlRepository.php | 12 +- .../Repository/ShortUrlRepositoryTest.php | 138 ++++++++++++++---- 2 files changed, 114 insertions(+), 36 deletions(-) diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index 4bcbe059..6147298e 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -208,7 +208,8 @@ DQL; ->from(ShortUrl::class, 's') ->where($qb->expr()->eq('s.longUrl', ':longUrl')) ->setParameter('longUrl', $url) - ->setMaxResults(1); + ->setMaxResults(1) + ->orderBy('s.id'); if ($meta->hasCustomSlug()) { $qb->andWhere($qb->expr()->eq('s.shortCode', ':slug')) @@ -246,14 +247,11 @@ DQL; // If tags where provided, we need an extra join to see the amount of tags that every short URL has, so that we // can discard those that also have more tags, making sure only those fully matching are included. - $qb->addSelect('COUNT(t.id) as tagsAmount') - ->join('s.tags', 't') + $qb->join('s.tags', 't') ->groupBy('s') - ->having($qb->expr()->eq('tagsAmount', ':tagsAmount')) + ->having($qb->expr()->eq('COUNT(t.id)', ':tagsAmount')) ->setParameter('tagsAmount', $tagsAmount); - $result = $qb->getQuery()->getOneOrNullResult() ?? []; - - return $result[0] ?? null; + return $qb->getQuery()->getOneOrNullResult(); } } diff --git a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php index 85df47de..57a174c7 100644 --- a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php @@ -16,12 +16,15 @@ use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering; use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; +use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase; use function count; class ShortUrlRepositoryTest extends DatabaseTestCase { + use TagManagerTrait; + protected const ENTITIES_TO_EMPTY = [ Tag::class, Visit::class, @@ -211,33 +214,110 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $this->assertNotNull($this->repo->findOne('another-slug', 'doma.in')); } -// public function findOneMatchingAppliesProperConditions(): void -// { -// $matches = $this->provideCriteriaToMatch(); -// } -// -// private function provideCriteriaToMatch(): iterable -// { -// $start = Chronos::parse('2020-03-05 20:18:30'); -// $end = Chronos::parse('2021-03-05 20:18:30'); -// -// yield [new ShortUrl('foo'), ShortUrlMeta::fromRawData(['validSince' => $start]), false]; -// yield [new ShortUrl('foo'), ShortUrlMeta::fromRawData(['validUntil' => $end]), false]; -// yield [new ShortUrl('foo'), ShortUrlMeta::fromRawData(['validSince' => $start, 'validUntil' => $end]), false]; -// yield [ -// new ShortUrl('foo', ShortUrlMeta::fromRawData(['validSince' => $start])), -// ShortUrlMeta::fromRawData(['validSince' => $start]), -// true, -// ]; -// yield [ -// new ShortUrl('foo', ShortUrlMeta::fromRawData(['validUntil' => $end])), -// ShortUrlMeta::fromRawData(['validUntil' => $end]), -// true, -// ]; -// yield [ -// new ShortUrl('foo', ShortUrlMeta::fromRawData(['validUntil' => $end, 'validSince' => $start])), -// ShortUrlMeta::fromRawData(['validUntil' => $end, 'validSince' => $start]), -// true, -// ]; -// } + /** @test */ + public function findOneMatchingReturnsNullForNonExistingShortUrls(): void + { + $this->assertNull($this->repo->findOneMatching('', [], ShortUrlMeta::createEmpty())); + $this->assertNull($this->repo->findOneMatching('foobar', [], ShortUrlMeta::createEmpty())); + $this->assertNull($this->repo->findOneMatching('foobar', ['foo', 'bar'], ShortUrlMeta::createEmpty())); + $this->assertNull($this->repo->findOneMatching('foobar', ['foo', 'bar'], ShortUrlMeta::fromRawData([ + 'validSince' => Chronos::parse('2020-03-05 20:18:30'), + 'customSlug' => 'this_slug_does_not_exist', + ]))); + } + + /** @test */ + public function findOneMatchingAppliesProperConditions(): void + { + $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->setTags($this->tagNamesToEntities($this->getEntityManager(), ['foo', 'bar'])); + $this->getEntityManager()->persist($shortUrl); + + $shortUrl2 = new ShortUrl('bar', ShortUrlMeta::fromRawData(['validUntil' => $end])); + $this->getEntityManager()->persist($shortUrl2); + + $shortUrl3 = new ShortUrl('baz', ShortUrlMeta::fromRawData(['validSince' => $start, 'validUntil' => $end])); + $this->getEntityManager()->persist($shortUrl3); + + $shortUrl4 = new ShortUrl('foo', ShortUrlMeta::fromRawData(['customSlug' => 'custom', 'validUntil' => $end])); + $this->getEntityManager()->persist($shortUrl4); + + $shortUrl5 = new ShortUrl('foo', ShortUrlMeta::fromRawData(['maxVisits' => 3])); + $this->getEntityManager()->persist($shortUrl5); + + $shortUrl6 = new ShortUrl('foo', ShortUrlMeta::fromRawData(['domain' => 'doma.in'])); + $this->getEntityManager()->persist($shortUrl6); + + $this->getEntityManager()->flush(); + + $this->assertSame( + $shortUrl, + $this->repo->findOneMatching('foo', ['foo', 'bar'], ShortUrlMeta::fromRawData(['validSince' => $start])), + ); + $this->assertSame( + $shortUrl2, + $this->repo->findOneMatching('bar', [], ShortUrlMeta::fromRawData(['validUntil' => $end])), + ); + $this->assertSame( + $shortUrl3, + $this->repo->findOneMatching('baz', [], ShortUrlMeta::fromRawData([ + 'validSince' => $start, + 'validUntil' => $end, + ])), + ); + $this->assertSame( + $shortUrl4, + $this->repo->findOneMatching('foo', [], ShortUrlMeta::fromRawData([ + 'customSlug' => 'custom', + 'validUntil' => $end, + ])), + ); + $this->assertSame( + $shortUrl5, + $this->repo->findOneMatching('foo', [], ShortUrlMeta::fromRawData(['maxVisits' => 3])), + ); + $this->assertSame( + $shortUrl6, + $this->repo->findOneMatching('foo', [], ShortUrlMeta::fromRawData(['domain' => 'doma.in'])), + ); + } + + /** @test */ + public function findOneMatchingReturnsOldestOneWhenThereAreMultipleMatches(): void + { + $start = Chronos::parse('2020-03-05 20:18:30'); + $meta = ['validSince' => $start, 'maxVisits' => 50]; + $tags = ['foo', 'bar']; + $tagEntities = $this->tagNamesToEntities($this->getEntityManager(), $tags); + + $shortUrl1 = new ShortUrl('foo', ShortUrlMeta::fromRawData($meta)); + $shortUrl1->setTags($tagEntities); + $this->getEntityManager()->persist($shortUrl1); + + $shortUrl2 = new ShortUrl('foo', ShortUrlMeta::fromRawData($meta)); + $shortUrl2->setTags($tagEntities); + $this->getEntityManager()->persist($shortUrl2); + + $shortUrl3 = new ShortUrl('foo', ShortUrlMeta::fromRawData($meta)); + $shortUrl3->setTags($tagEntities); + $this->getEntityManager()->persist($shortUrl3); + + $this->getEntityManager()->flush(); + + $this->assertSame( + $shortUrl1, + $this->repo->findOneMatching('foo', $tags, ShortUrlMeta::fromRawData($meta)), + ); + $this->assertNotSame( + $shortUrl2, + $this->repo->findOneMatching('foo', $tags, ShortUrlMeta::fromRawData($meta)), + ); + $this->assertNotSame( + $shortUrl3, + $this->repo->findOneMatching('foo', $tags, ShortUrlMeta::fromRawData($meta)), + ); + } } From 641f35ae0516334d0db4a16a48a939ea953ee9b7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 23 Sep 2020 07:46:25 +0200 Subject: [PATCH 09/73] Updated changelog --- CHANGELOG.md | 2 +- module/Core/src/Repository/ShortUrlRepository.php | 2 +- module/Core/test/Entity/ShortUrlTest.php | 1 - module/Core/test/Service/UrlShortenerTest.php | 2 -- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62ad683b..dfd3ef05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this #### Fixed -* *Nothing* +* [#837](https://github.com/shlinkio/shlink/issues/837) Drastically improved performance when creating a new shortUrl and providing `findIfExists = true`. ## 2.3.0 - 2020-08-09 diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index 6147298e..e2cf578f 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -241,7 +241,7 @@ DQL; foreach ($tags as $index => $tag) { $alias = 't_' . $index; - $qb->join('s.tags', $alias, Join::WITH, $qb->expr()->eq($alias . '.name', ':tag' . $index)) + $qb->join('s.tags', $alias, Join::WITH, $alias . '.name = :tag' . $index) ->setParameter('tag' . $index, $tag); } diff --git a/module/Core/test/Entity/ShortUrlTest.php b/module/Core/test/Entity/ShortUrlTest.php index c5933e39..e410dedb 100644 --- a/module/Core/test/Entity/ShortUrlTest.php +++ b/module/Core/test/Entity/ShortUrlTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Entity; -use Cake\Chronos\Chronos; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException; diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index 9e33af6b..40ed6718 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -21,8 +21,6 @@ use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Core\Util\UrlValidatorInterface; -use function array_map; - class UrlShortenerTest extends TestCase { private UrlShortener $urlShortener; From aa0124f4e9940ea3ad7a2543424b17e2f5f6e6c0 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 23 Sep 2020 07:49:59 +0200 Subject: [PATCH 10/73] Moved API tests back to composer ci command --- .travis.yml | 2 +- composer.json | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 29395d33..cd132c43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,7 +56,7 @@ before_script: - export DOCKERFILE_CHANGED=$(git diff ${TRAVIS_COMMIT_RANGE:-origin/main} --name-only | grep Dockerfile) script: - - bin/test/run-api-tests.sh --coverage-php build/coverage-api.cov && composer ci + - composer ci - if [[ ! -z "${DOCKERFILE_CHANGED}" && "${TRAVIS_PHP_VERSION}" == "7.4" ]]; then docker build -t shlink-docker-image:temp . ; fi after_success: diff --git a/composer.json b/composer.json index f1a072fe..c37f378f 100644 --- a/composer.json +++ b/composer.json @@ -112,7 +112,8 @@ ], "test:ci": [ "@test:unit:ci", - "@test:db" + "@test:db", + "@test:api:ci" ], "test:unit": "phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox", "test:unit:ci": "@test:unit --coverage-clover=build/clover.xml --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml", @@ -130,6 +131,7 @@ "test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite", "test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite", "test:api": "bin/test/run-api-tests.sh", + "test:api:ci": "bin/test/run-api-tests.sh --coverage-php build/coverage-api.cov", "test:unit:pretty": "phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage", "infect": "infection --threads=4 --min-msi=80 --log-verbosity=default --only-covered", "infect:ci:base": "@infect --skip-initial-tests", From d5eac3b1c3c2b8da4b6b3fdd4e4f6fb433bc7c41 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 23 Sep 2020 19:19:17 +0200 Subject: [PATCH 11/73] Added validateUrl optional flag for create/edit short URLs --- module/Core/functions/functions.php | 13 +++++++++++ module/Core/src/Model/ShortUrlEdit.php | 17 ++++++++------ module/Core/src/Model/ShortUrlMeta.php | 19 +++++++++------- module/Core/src/Service/ShortUrlService.php | 2 +- module/Core/src/Service/UrlShortener.php | 2 +- module/Core/src/Util/UrlValidator.php | 7 +++--- .../Core/src/Util/UrlValidatorInterface.php | 2 +- .../Validation/ShortUrlMetaInputFilter.php | 3 +++ .../Core/test/Service/ShortUrlServiceTest.php | 11 +++++++++- module/Core/test/Service/UrlShortenerTest.php | 2 +- module/Core/test/Util/UrlValidatorTest.php | 22 ++++++++++++++----- 11 files changed, 71 insertions(+), 29 deletions(-) diff --git a/module/Core/functions/functions.php b/module/Core/functions/functions.php index 5b6657d1..2f7f86e9 100644 --- a/module/Core/functions/functions.php +++ b/module/Core/functions/functions.php @@ -7,6 +7,7 @@ namespace Shlinkio\Shlink\Core; use Cake\Chronos\Chronos; use DateTimeInterface; use Fig\Http\Message\StatusCodeInterface; +use Laminas\InputFilter\InputFilter; use PUGX\Shortid\Factory as ShortIdFactory; use function sprintf; @@ -62,3 +63,15 @@ function determineTableName(string $tableName, array $emConfig = []): string return sprintf('%s.%s', $schema, $tableName); } + +function getOptionalIntFromInputFilter(InputFilter $inputFilter, string $fieldName): ?int +{ + $value = $inputFilter->getValue($fieldName); + return $value !== null ? (int) $value : null; +} + +function getOptionalBoolFromInputFilter(InputFilter $inputFilter, string $fieldName): ?bool +{ + $value = $inputFilter->getValue($fieldName); + return $value !== null ? (bool) $value : null; +} diff --git a/module/Core/src/Model/ShortUrlEdit.php b/module/Core/src/Model/ShortUrlEdit.php index 2f3f6919..67300682 100644 --- a/module/Core/src/Model/ShortUrlEdit.php +++ b/module/Core/src/Model/ShortUrlEdit.php @@ -9,6 +9,8 @@ use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; use function array_key_exists; +use function Shlinkio\Shlink\Core\getOptionalBoolFromInputFilter; +use function Shlinkio\Shlink\Core\getOptionalIntFromInputFilter; use function Shlinkio\Shlink\Core\parseDateField; final class ShortUrlEdit @@ -21,6 +23,7 @@ final class ShortUrlEdit private ?Chronos $validUntil = null; private bool $maxVisitsPropWasProvided = false; private ?int $maxVisits = null; + private ?bool $validateUrl = null; // Enforce named constructors private function __construct() @@ -55,13 +58,8 @@ final class ShortUrlEdit $this->longUrl = $inputFilter->getValue(ShortUrlMetaInputFilter::LONG_URL); $this->validSince = parseDateField($inputFilter->getValue(ShortUrlMetaInputFilter::VALID_SINCE)); $this->validUntil = parseDateField($inputFilter->getValue(ShortUrlMetaInputFilter::VALID_UNTIL)); - $this->maxVisits = $this->getOptionalIntFromInputFilter($inputFilter, ShortUrlMetaInputFilter::MAX_VISITS); - } - - private function getOptionalIntFromInputFilter(ShortUrlMetaInputFilter $inputFilter, string $fieldName): ?int - { - $value = $inputFilter->getValue($fieldName); - return $value !== null ? (int) $value : null; + $this->maxVisits = getOptionalIntFromInputFilter($inputFilter, ShortUrlMetaInputFilter::MAX_VISITS); + $this->validateUrl = getOptionalBoolFromInputFilter($inputFilter, ShortUrlMetaInputFilter::VALIDATE_URL); } public function longUrl(): ?string @@ -103,4 +101,9 @@ final class ShortUrlEdit { return $this->maxVisitsPropWasProvided; } + + public function doValidateUrl(): ?bool + { + return $this->validateUrl; + } } diff --git a/module/Core/src/Model/ShortUrlMeta.php b/module/Core/src/Model/ShortUrlMeta.php index 76f6d80b..23121d71 100644 --- a/module/Core/src/Model/ShortUrlMeta.php +++ b/module/Core/src/Model/ShortUrlMeta.php @@ -8,6 +8,8 @@ use Cake\Chronos\Chronos; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; +use function Shlinkio\Shlink\Core\getOptionalBoolFromInputFilter; +use function Shlinkio\Shlink\Core\getOptionalIntFromInputFilter; use function Shlinkio\Shlink\Core\parseDateField; use const Shlinkio\Shlink\Core\DEFAULT_SHORT_CODES_LENGTH; @@ -21,6 +23,7 @@ final class ShortUrlMeta private ?bool $findIfExists = null; private ?string $domain = null; private int $shortCodeLength = 5; + private ?bool $validateUrl = null; // Enforce named constructors private function __construct() @@ -55,21 +58,16 @@ final class ShortUrlMeta $this->validSince = parseDateField($inputFilter->getValue(ShortUrlMetaInputFilter::VALID_SINCE)); $this->validUntil = parseDateField($inputFilter->getValue(ShortUrlMetaInputFilter::VALID_UNTIL)); $this->customSlug = $inputFilter->getValue(ShortUrlMetaInputFilter::CUSTOM_SLUG); - $this->maxVisits = $this->getOptionalIntFromInputFilter($inputFilter, ShortUrlMetaInputFilter::MAX_VISITS); + $this->maxVisits = getOptionalIntFromInputFilter($inputFilter, ShortUrlMetaInputFilter::MAX_VISITS); $this->findIfExists = $inputFilter->getValue(ShortUrlMetaInputFilter::FIND_IF_EXISTS); + $this->validateUrl = getOptionalBoolFromInputFilter($inputFilter, ShortUrlMetaInputFilter::VALIDATE_URL); $this->domain = $inputFilter->getValue(ShortUrlMetaInputFilter::DOMAIN); - $this->shortCodeLength = $this->getOptionalIntFromInputFilter( + $this->shortCodeLength = getOptionalIntFromInputFilter( $inputFilter, ShortUrlMetaInputFilter::SHORT_CODE_LENGTH, ) ?? DEFAULT_SHORT_CODES_LENGTH; } - private function getOptionalIntFromInputFilter(ShortUrlMetaInputFilter $inputFilter, string $fieldName): ?int - { - $value = $inputFilter->getValue($fieldName); - return $value !== null ? (int) $value : null; - } - public function getValidSince(): ?Chronos { return $this->validSince; @@ -129,4 +127,9 @@ final class ShortUrlMeta { return $this->shortCodeLength; } + + public function doValidateUrl(): ?bool + { + return $this->validateUrl; + } } diff --git a/module/Core/src/Service/ShortUrlService.php b/module/Core/src/Service/ShortUrlService.php index 456c46ad..9159ef63 100644 --- a/module/Core/src/Service/ShortUrlService.php +++ b/module/Core/src/Service/ShortUrlService.php @@ -71,7 +71,7 @@ class ShortUrlService implements ShortUrlServiceInterface public function updateMetadataByShortCode(ShortUrlIdentifier $identifier, ShortUrlEdit $shortUrlEdit): ShortUrl { if ($shortUrlEdit->hasLongUrl()) { - $this->urlValidator->validateUrl($shortUrlEdit->longUrl()); + $this->urlValidator->validateUrl($shortUrlEdit->longUrl(), $shortUrlEdit->doValidateUrl()); } $shortUrl = $this->urlResolver->resolveShortUrl($identifier); diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index cf17d0bd..c6464f41 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -48,7 +48,7 @@ class UrlShortener implements UrlShortenerInterface return $existingShortUrl; } - $this->urlValidator->validateUrl($url); + $this->urlValidator->validateUrl($url, $meta->doValidateUrl()); $this->em->beginTransaction(); $shortUrl = new ShortUrl($url, $meta, $this->domainResolver); $shortUrl->setTags($this->tagNamesToEntities($this->em, $tags)); diff --git a/module/Core/src/Util/UrlValidator.php b/module/Core/src/Util/UrlValidator.php index 01885446..ccf69dd1 100644 --- a/module/Core/src/Util/UrlValidator.php +++ b/module/Core/src/Util/UrlValidator.php @@ -27,10 +27,11 @@ class UrlValidator implements UrlValidatorInterface, RequestMethodInterface /** * @throws InvalidUrlException */ - public function validateUrl(string $url): void + public function validateUrl(string $url, ?bool $doValidate): void { - // If the URL validation is not enabled, skip check - if (! $this->options->isUrlValidationEnabled()) { + // If the URL validation is not enabled or it was explicitly set to not validate, skip check + $doValidate = $doValidate ?? $this->options->isUrlValidationEnabled(); + if (! $doValidate) { return; } diff --git a/module/Core/src/Util/UrlValidatorInterface.php b/module/Core/src/Util/UrlValidatorInterface.php index 05230605..fdf1e781 100644 --- a/module/Core/src/Util/UrlValidatorInterface.php +++ b/module/Core/src/Util/UrlValidatorInterface.php @@ -11,5 +11,5 @@ interface UrlValidatorInterface /** * @throws InvalidUrlException */ - public function validateUrl(string $url): void; + public function validateUrl(string $url, ?bool $doValidate): void; } diff --git a/module/Core/src/Validation/ShortUrlMetaInputFilter.php b/module/Core/src/Validation/ShortUrlMetaInputFilter.php index 6d0cfffe..5c0030f0 100644 --- a/module/Core/src/Validation/ShortUrlMetaInputFilter.php +++ b/module/Core/src/Validation/ShortUrlMetaInputFilter.php @@ -27,6 +27,7 @@ class ShortUrlMetaInputFilter extends InputFilter public const DOMAIN = 'domain'; public const SHORT_CODE_LENGTH = 'shortCodeLength'; public const LONG_URL = 'longUrl'; + public const VALIDATE_URL = 'validateUrl'; public function __construct(array $data) { @@ -64,6 +65,8 @@ class ShortUrlMetaInputFilter extends InputFilter $this->add($this->createBooleanInput(self::FIND_IF_EXISTS, false)); + $this->add($this->createInput(self::VALIDATE_URL, false)); + $domain = $this->createInput(self::DOMAIN, false); $domain->getValidatorChain()->attach(new Validation\HostAndPortValidator()); $this->add($domain); diff --git a/module/Core/test/Service/ShortUrlServiceTest.php b/module/Core/test/Service/ShortUrlServiceTest.php index 9becdf8b..fcb63ec2 100644 --- a/module/Core/test/Service/ShortUrlServiceTest.php +++ b/module/Core/test/Service/ShortUrlServiceTest.php @@ -104,7 +104,10 @@ class ShortUrlServiceTest extends TestCase $this->assertEquals($shortUrlEdit->longUrl() ?? $originalLongUrl, $shortUrl->getLongUrl()); $findShortUrl->shouldHaveBeenCalled(); $flush->shouldHaveBeenCalled(); - $this->urlValidator->validateUrl($shortUrlEdit->longUrl())->shouldHaveBeenCalledTimes($expectedValidateCalls); + $this->urlValidator->validateUrl( + $shortUrlEdit->longUrl(), + $shortUrlEdit->doValidateUrl(), + )->shouldHaveBeenCalledTimes($expectedValidateCalls); } public function provideShortUrlEdits(): iterable @@ -123,5 +126,11 @@ class ShortUrlServiceTest extends TestCase 'longUrl' => 'modifiedLongUrl', ], )]; + yield 'long URL with validation' => [1, ShortUrlEdit::fromRawData( + [ + 'longUrl' => 'modifiedLongUrl', + 'validateUrl' => true, + ], + )]; } } diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index 40ed6718..adeda920 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -30,7 +30,7 @@ class UrlShortenerTest extends TestCase public function setUp(): void { $this->urlValidator = $this->prophesize(UrlValidatorInterface::class); - $this->urlValidator->validateUrl('http://foobar.com/12345/hello?foo=bar')->will( + $this->urlValidator->validateUrl('http://foobar.com/12345/hello?foo=bar', null)->will( function (): void { }, ); diff --git a/module/Core/test/Util/UrlValidatorTest.php b/module/Core/test/Util/UrlValidatorTest.php index a20ed693..41541b3c 100644 --- a/module/Core/test/Util/UrlValidatorTest.php +++ b/module/Core/test/Util/UrlValidatorTest.php @@ -37,7 +37,7 @@ class UrlValidatorTest extends TestCase $request->shouldBeCalledOnce(); $this->expectException(InvalidUrlException::class); - $this->urlValidator->validateUrl('http://foobar.com/12345/hello?foo=bar'); + $this->urlValidator->validateUrl('http://foobar.com/12345/hello?foo=bar', null); } /** @test */ @@ -54,19 +54,29 @@ class UrlValidatorTest extends TestCase ], )->willReturn(new Response()); - $this->urlValidator->validateUrl($expectedUrl); + $this->urlValidator->validateUrl($expectedUrl, null); $request->shouldHaveBeenCalledOnce(); } - /** @test */ - public function noCheckIsPerformedWhenUrlValidationIsDisabled(): void + /** + * @test + * @dataProvider provideDisabledCombinations + */ + public function noCheckIsPerformedWhenUrlValidationIsDisabled(?bool $doValidate, bool $validateUrl): void { $request = $this->httpClient->request(Argument::cetera())->willReturn(new Response()); - $this->options->validateUrl = false; + $this->options->validateUrl = $validateUrl; - $this->urlValidator->validateUrl(''); + $this->urlValidator->validateUrl('', $doValidate); $request->shouldNotHaveBeenCalled(); } + + public function provideDisabledCombinations(): iterable + { + yield 'config is disabled and no runtime option is provided' => [null, false]; + yield 'config is enabled but runtime option is disabled' => [false, true]; + yield 'both config and runtime option are disabled' => [false, false]; + } } From cdd87f596299acf7ee8c2dc2addb8b1aed179b00 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 23 Sep 2020 19:24:15 +0200 Subject: [PATCH 12/73] Documented validateUrl params on create/edit short URL endpoints --- docs/swagger/paths/v1_short-urls.json | 4 ++++ docs/swagger/paths/v1_short-urls_{shortCode}.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/swagger/paths/v1_short-urls.json b/docs/swagger/paths/v1_short-urls.json index 2467de24..a89dd187 100644 --- a/docs/swagger/paths/v1_short-urls.json +++ b/docs/swagger/paths/v1_short-urls.json @@ -251,6 +251,10 @@ "shortCodeLength": { "description": "The length for generated short code. It has to be at least 4 and defaults to 5. It will be ignored when customSlug is provided", "type": "number" + }, + "validateUrl": { + "description": "Tells if the long URL should or should not be validated as a reachable URL. If not provided, it will fall back to app-level config", + "type": "boolean" } } } diff --git a/docs/swagger/paths/v1_short-urls_{shortCode}.json b/docs/swagger/paths/v1_short-urls_{shortCode}.json index 71a6a427..c7e7dc8a 100644 --- a/docs/swagger/paths/v1_short-urls_{shortCode}.json +++ b/docs/swagger/paths/v1_short-urls_{shortCode}.json @@ -127,6 +127,10 @@ "maxVisits": { "description": "The maximum number of allowed visits for this short code", "type": "number" + }, + "validateUrl": { + "description": "Tells if the long URL (if provided) should or should not be validated as a reachable URL. If not provided, it will fall back to app-level config", + "type": "boolean" } } } From 405369824bf5fe5dcdfa9808d809d04d188b3652 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 24 Sep 2020 21:54:03 +0200 Subject: [PATCH 13/73] Added hability to override URL validation from the CLI --- .../ShortUrl/GenerateShortUrlCommand.php | 29 +++++++++++++++++ .../ShortUrl/GenerateShortUrlCommandTest.php | 31 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php index 06cdd274..97907fea 100644 --- a/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php @@ -22,6 +22,7 @@ use function Functional\curry; use function Functional\flatten; use function Functional\unique; use function sprintf; +use function strpos; class GenerateShortUrlCommand extends Command { @@ -94,6 +95,18 @@ class GenerateShortUrlCommand extends Command 'l', InputOption::VALUE_REQUIRED, 'The length for generated short code (it will be ignored if --customSlug was provided).', + ) + ->addOption( + 'validate-url', + null, + InputOption::VALUE_NONE, + 'Forces the long URL to be validated, regardless what is globally configured.', + ) + ->addOption( + 'no-validate-url', + null, + InputOption::VALUE_NONE, + 'Forces the long URL to not be validated, regardless what is globally configured.', ); } @@ -125,6 +138,7 @@ class GenerateShortUrlCommand extends Command $customSlug = $input->getOption('customSlug'); $maxVisits = $input->getOption('maxVisits'); $shortCodeLength = $input->getOption('shortCodeLength') ?? $this->defaultShortCodeLength; + $doValidateUrl = $this->doValidateUrl($input); try { $shortUrl = $this->urlShortener->urlToShortCode($longUrl, $tags, ShortUrlMeta::fromRawData([ @@ -135,6 +149,7 @@ class GenerateShortUrlCommand extends Command ShortUrlMetaInputFilter::FIND_IF_EXISTS => $input->getOption('findIfExists'), ShortUrlMetaInputFilter::DOMAIN => $input->getOption('domain'), ShortUrlMetaInputFilter::SHORT_CODE_LENGTH => $shortCodeLength, + ShortUrlMetaInputFilter::VALIDATE_URL => $doValidateUrl, ])); $io->writeln([ @@ -147,4 +162,18 @@ class GenerateShortUrlCommand extends Command return ExitCodes::EXIT_FAILURE; } } + + private function doValidateUrl(InputInterface $input): ?bool + { + $rawInput = (string) $input; + + if (strpos($rawInput, '--no-validate-url') !== false) { + return false; + } + if (strpos($rawInput, '--validate-url') !== false) { + return true; + } + + return null; + } } diff --git a/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php index 689a5e7c..eee57b81 100644 --- a/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php @@ -13,6 +13,7 @@ use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; +use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Service\UrlShortener; use Symfony\Component\Console\Application; use Symfony\Component\Console\Tester\CommandTester; @@ -105,4 +106,34 @@ class GenerateShortUrlCommandTest extends TestCase $this->assertStringContainsString($shortUrl->toString(self::DOMAIN_CONFIG), $output); $urlToShortCode->shouldHaveBeenCalledOnce(); } + + /** + * @test + * @dataProvider provideFlags + */ + public function urlValidationHasExpectedValueBasedOnProvidedTags(array $options, ?bool $expectedValidateUrl): void + { + $shortUrl = new ShortUrl(''); + $urlToShortCode = $this->urlShortener->urlToShortCode( + Argument::type('string'), + Argument::type('array'), + Argument::that(function (ShortUrlMeta $meta) use ($expectedValidateUrl) { + Assert::assertEquals($expectedValidateUrl, $meta->doValidateUrl()); + return $meta; + }), + )->willReturn($shortUrl); + + $options['longUrl'] = 'http://domain.com/foo/bar'; + $this->commandTester->execute($options); + + $urlToShortCode->shouldHaveBeenCalledOnce(); + } + + public function provideFlags(): iterable + { + yield 'no flags' => [[], null]; + yield 'no-validate-url only' => [['--no-validate-url' => true], false]; + yield 'validate-url' => [['--validate-url' => true], true]; + yield 'both flags' => [['--validate-url' => true, '--no-validate-url' => true], false]; + } } From 25554241240069e5402d889aba392558b9181037 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 24 Sep 2020 22:04:38 +0200 Subject: [PATCH 14/73] Updated changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfd3ef05..1ace111c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this #### Added * [#829](https://github.com/shlinkio/shlink/issues/829) Added support for QR codes in SVG format, by passing `?format=svg` to the QR code URL. +* [#820](https://github.com/shlinkio/shlink/issues/820) Added new option to force enabling or disabling URL validation on a per-URL basis. + + Currently, there's a global config that tells if long URLs should be validated (by ensuring they are publicly accessible and return a 2xx status). However, this is either always applied or never applied. + + Now, it is possible to enforce validation or enforce disabling validation when a new short URL is created or edited: + + * On the `POST /short-url` and `PATCH /short-url/{shortCode}` endpoints, you can now pass `validateUrl: true/false` in order to enforce enabling or disabling validation, ignoring the global config. If the value is not provided, the global config is still normally applied. + * On the `short-url:generate` CLI command, you can pass `--validate-url` or `--no-validate-url` flags, in order to enforce enabling or disabling validation. If none of them is provided, the global config is still normally applied. #### Changed From cfc9a1b77277e94cac4239308c435e82b14300e6 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 24 Sep 2020 22:15:26 +0200 Subject: [PATCH 15/73] Ensure string casting safety --- module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php index 97907fea..36293377 100644 --- a/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php @@ -21,6 +21,7 @@ use function array_map; use function Functional\curry; use function Functional\flatten; use function Functional\unique; +use function method_exists; use function sprintf; use function strpos; @@ -165,7 +166,7 @@ class GenerateShortUrlCommand extends Command private function doValidateUrl(InputInterface $input): ?bool { - $rawInput = (string) $input; + $rawInput = method_exists($input, '__toString') ? $input->__toString() : ''; if (strpos($rawInput, '--no-validate-url') !== false) { return false; From cc57dcd01a1ffbd7e329f49a124ff3cba4cb99f1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 26 Sep 2020 10:43:50 +0200 Subject: [PATCH 16/73] Added code coverage to API tests --- .travis.yml | 4 +-- bin/test/run-api-tests.sh | 3 +- composer.json | 17 +++++---- config/test/bootstrap_api_tests.php | 18 +++++++++- config/test/constants.php | 8 +++++ config/test/test_config.global.php | 54 ++++++++++++++++++++++++++--- data/infra/php.Dockerfile | 21 +++-------- data/infra/php.ini | 2 ++ data/infra/swoole.Dockerfile | 10 +++--- phpstan.neon | 1 + 10 files changed, 99 insertions(+), 39 deletions(-) create mode 100644 config/test/constants.php diff --git a/.travis.yml b/.travis.yml index cd132c43..bf7e8b15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ before_install: - phpenv config-rm xdebug.ini || return 0 - sudo ./data/infra/ci/install-ms-odbc.sh - docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_ms shlink_db shlink_db_postgres shlink_db_maria - - yes | pecl install pdo_sqlsrv swoole-4.5.2 + - yes | pecl install pdo_sqlsrv swoole-4.5.2 pcov install: - composer self-update @@ -62,6 +62,6 @@ script: after_success: - rm -f build/clover.xml - wget https://phar.phpunit.de/phpcov-7.0.2.phar - - phpdbg -qrr phpcov-7.0.2.phar merge build --clover build/clover.xml + - php phpcov-7.0.2.phar merge build --clover build/clover.xml - wget https://scrutinizer-ci.com/ocular.phar - php ocular.phar code-coverage:upload --format=php-clover build/clover.xml diff --git a/bin/test/run-api-tests.sh b/bin/test/run-api-tests.sh index fae0c628..f3236d1b 100755 --- a/bin/test/run-api-tests.sh +++ b/bin/test/run-api-tests.sh @@ -1,6 +1,7 @@ #!/usr/bin/env sh export APP_ENV=test export DB_DRIVER=mysql +export TEST_ENV=api # Try to stop server just in case it hanged in last execution vendor/bin/mezzio-swoole stop @@ -9,7 +10,7 @@ echo 'Starting server...' vendor/bin/mezzio-swoole start -d sleep 2 -phpdbg -qrr vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --colors=always $* +vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --colors=always --log-junit=build/coverage-api/junit.xml $* testsExitCode=$? vendor/bin/mezzio-swoole stop diff --git a/composer.json b/composer.json index c37f378f..a2cb9539 100644 --- a/composer.json +++ b/composer.json @@ -68,6 +68,7 @@ "eaglewu/swoole-ide-helper": "dev-master", "infection/infection": "^0.16.1", "phpstan/phpstan": "^0.12.18", + "phpunit/php-code-coverage": "^8.0", "phpunit/phpunit": "~9.0.1", "roave/security-advisories": "dev-master", "shlinkio/php-coding-standard": "~2.1.0", @@ -93,7 +94,10 @@ "module/Core/test", "module/Core/test-db" ] - } + }, + "files": [ + "config/test/constants.php" + ] }, "scripts": { "ci": [ @@ -113,10 +117,10 @@ "test:ci": [ "@test:unit:ci", "@test:db", - "@test:api:ci" + "@test:api" ], - "test:unit": "phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox", - "test:unit:ci": "@test:unit --coverage-clover=build/clover.xml --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml", + "test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox", + "test:unit:ci": "@test:unit --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml", "test:db": [ "@test:db:sqlite:ci", "@test:db:mysql", @@ -124,15 +128,14 @@ "@test:db:postgres", "@test:db:ms" ], - "test:db:sqlite": "APP_ENV=test phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-db.xml", + "test:db:sqlite": "APP_ENV=test php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-db.xml", "test:db:sqlite:ci": "@test:db:sqlite --coverage-php build/coverage-db.cov --coverage-xml=build/coverage-db/coverage-xml --log-junit=build/coverage-db/junit.xml", "test:db:mysql": "DB_DRIVER=mysql composer test:db:sqlite", "test:db:maria": "DB_DRIVER=maria composer test:db:sqlite", "test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite", "test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite", "test:api": "bin/test/run-api-tests.sh", - "test:api:ci": "bin/test/run-api-tests.sh --coverage-php build/coverage-api.cov", - "test:unit:pretty": "phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage", + "test:unit:pretty": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage-unit-html", "infect": "infection --threads=4 --min-msi=80 --log-verbosity=default --only-covered", "infect:ci:base": "@infect --skip-initial-tests", "infect:ci": [ diff --git a/config/test/bootstrap_api_tests.php b/config/test/bootstrap_api_tests.php index 4cf01807..7bda8c10 100644 --- a/config/test/bootstrap_api_tests.php +++ b/config/test/bootstrap_api_tests.php @@ -7,12 +7,28 @@ namespace Shlinkio\Shlink\TestUtils; use Doctrine\ORM\EntityManager; use Psr\Container\ContainerInterface; +use function register_shutdown_function; +use function sprintf; + +use const ShlinkioTest\Shlink\SWOOLE_TESTING_HOST; +use const ShlinkioTest\Shlink\SWOOLE_TESTING_PORT; + /** @var ContainerInterface $container */ $container = require __DIR__ . '/../container.php'; $testHelper = $container->get(Helper\TestHelper::class); $config = $container->get('config'); $em = $container->get(EntityManager::class); +$httpClient = $container->get('shlink_test_api_client'); + +// Start code coverage collecting on swoole process, and stop it when process shuts down +$httpClient->request('GET', sprintf('http://%s:%s/api-tests/start-coverage', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT)); +register_shutdown_function(function () use ($httpClient): void { + $httpClient->request( + 'GET', + sprintf('http://%s:%s/api-tests/stop-coverage', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT), + ); +}); $testHelper->createTestDb(); -ApiTest\ApiTestCase::setApiClient($container->get('shlink_test_api_client')); +ApiTest\ApiTestCase::setApiClient($httpClient); ApiTest\ApiTestCase::setSeedFixturesCallback(fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? [])); diff --git a/config/test/constants.php b/config/test/constants.php new file mode 100644 index 00000000..a2c880fc --- /dev/null +++ b/config/test/constants.php @@ -0,0 +1,8 @@ +filter()->addDirectoryToWhitelist($item); + } +} $buildDbConnection = function (): array { $driver = env('DB_DRIVER', 'sqlite'); @@ -78,8 +93,8 @@ return [ 'mezzio-swoole' => [ 'enable_coroutine' => false, 'swoole-http-server' => [ - 'host' => $swooleTestingHost, - 'port' => $swooleTestingPort, + 'host' => SWOOLE_TESTING_HOST, + 'port' => SWOOLE_TESTING_PORT, 'process-name' => 'shlink_test', 'options' => [ 'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid', @@ -88,6 +103,35 @@ return [ ], ], + 'routes' => !$isApiTest ? [] : [ + [ + 'name' => 'start_collecting_coverage', + 'path' => '/api-tests/start-coverage', + 'middleware' => middleware(static function () use (&$coverage) { + if ($coverage) { + $coverage->start('api tests'); + } + return new EmptyResponse(); + }), + 'allowed_methods' => ['GET'], + ], + [ + 'name' => 'dump_coverage', + 'path' => '/api-tests/stop-coverage', + 'middleware' => middleware(static function () use (&$coverage) { + if ($coverage) { + $basePath = __DIR__ . '/../../build/coverage-api'; + $coverage->stop(); + (new Clover())->process($coverage, $basePath . '.cov'); + (new Xml(Version::getVersionString()))->process($coverage, $basePath . '/coverage-xml'); + } + + return new EmptyResponse(); + }), + 'allowed_methods' => ['GET'], + ], + ], + 'mercure' => [ 'public_hub_url' => null, 'internal_hub_url' => null, @@ -97,7 +141,7 @@ return [ 'dependencies' => [ 'services' => [ 'shlink_test_api_client' => new Client([ - 'base_uri' => sprintf('http://%s:%s/', $swooleTestingHost, $swooleTestingPort), + 'base_uri' => sprintf('http://%s:%s/', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT), 'http_errors' => false, ]), ], diff --git a/data/infra/php.Dockerfile b/data/infra/php.Dockerfile index 5b0ac28a..94d5e8fe 100644 --- a/data/infra/php.Dockerfile +++ b/data/infra/php.Dockerfile @@ -3,7 +3,6 @@ MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.18 ENV APCU_BC_VERSION 1.0.5 -ENV XDEBUG_VERSION 2.9.0 RUN apk update @@ -55,29 +54,17 @@ RUN rm /tmp/apcu_bc.tar.gz RUN rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini RUN echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini -# Install xdebug -ADD https://pecl.php.net/get/xdebug-$XDEBUG_VERSION /tmp/xdebug.tar.gz -RUN mkdir -p /usr/src/php/ext/xdebug\ - && tar xf /tmp/xdebug.tar.gz -C /usr/src/php/ext/xdebug --strip-components=1 -# configure and install -RUN docker-php-ext-configure xdebug\ - && docker-php-ext-install xdebug -# cleanup -RUN rm /tmp/xdebug.tar.gz - -# Install sqlsrv driver +# Install pcov and sqlsrv driver RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_17.5.1.1-1_amd64.apk && \ apk add --allow-untrusted msodbcsql17_17.5.1.1-1_amd64.apk && \ apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \ - pecl install pdo_sqlsrv && \ - docker-php-ext-enable pdo_sqlsrv && \ + pecl install pdo_sqlsrv pcov && \ + docker-php-ext-enable pdo_sqlsrv pcov && \ apk del .phpize-deps && \ rm msodbcsql17_17.5.1.1-1_amd64.apk # Install composer -RUN php -r "readfile('https://getcomposer.org/installer');" | php -RUN chmod +x composer.phar -RUN mv composer.phar /usr/local/bin/composer +COPY --from=composer:1.10.13 /usr/bin/composer /usr/local/bin/composer # Make home directory writable by anyone RUN chmod 777 /home diff --git a/data/infra/php.ini b/data/infra/php.ini index 5ef7b7ea..64838d11 100644 --- a/data/infra/php.ini +++ b/data/infra/php.ini @@ -4,3 +4,5 @@ memory_limit=-1 log_errors_max_len=0 zend.assertions=1 assert.exception=1 +pcov.enabled=1 +pcov.directory=module diff --git a/data/infra/swoole.Dockerfile b/data/infra/swoole.Dockerfile index 70d52fa1..ecc8ede0 100644 --- a/data/infra/swoole.Dockerfile +++ b/data/infra/swoole.Dockerfile @@ -66,19 +66,17 @@ RUN docker-php-ext-configure inotify\ # cleanup RUN rm /tmp/inotify.tar.gz -# Install swoole and mssql driver +# Install swoole, pcov and mssql driver RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_17.5.1.1-1_amd64.apk && \ apk add --allow-untrusted msodbcsql17_17.5.1.1-1_amd64.apk && \ apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \ - pecl install swoole-${SWOOLE_VERSION} pdo_sqlsrv && \ - docker-php-ext-enable swoole pdo_sqlsrv && \ + pecl install swoole-${SWOOLE_VERSION} pdo_sqlsrv pcov && \ + docker-php-ext-enable swoole pdo_sqlsrv pcov && \ apk del .phpize-deps && \ rm msodbcsql17_17.5.1.1-1_amd64.apk # Install composer -RUN php -r "readfile('https://getcomposer.org/installer');" | php -RUN chmod +x composer.phar -RUN mv composer.phar /usr/local/bin/composer +COPY --from=composer:1.10.13 /usr/bin/composer /usr/local/bin/composer # Make home directory writable by anyone RUN chmod 777 /home diff --git a/phpstan.neon b/phpstan.neon index 35b1beda..0ce0463c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -5,3 +5,4 @@ parameters: - '#AbstractQuery::setParameters\(\)#' - '#mustRun\(\)#' - '#AssociationBuilder::setOrderBy#' + - '#If condition is always false#' From d9d57743e6dfe5713895bb04ac15ab6b6660dcf5 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 26 Sep 2020 10:49:56 +0200 Subject: [PATCH 17/73] Fixed code copverage on API tests being exported as Clover instead of PHP --- config/test/test_config.global.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/test/test_config.global.php b/config/test/test_config.global.php index 7e8d8e93..e79651dd 100644 --- a/config/test/test_config.global.php +++ b/config/test/test_config.global.php @@ -12,7 +12,7 @@ use Laminas\Stdlib\Glob; use PDO; use PHPUnit\Runner\Version; use SebastianBergmann\CodeCoverage\CodeCoverage; -use SebastianBergmann\CodeCoverage\Report\Clover; +use SebastianBergmann\CodeCoverage\Report\PHP; use SebastianBergmann\CodeCoverage\Report\Xml\Facade as Xml; use function Laminas\Stratigility\middleware; @@ -109,7 +109,7 @@ return [ 'path' => '/api-tests/start-coverage', 'middleware' => middleware(static function () use (&$coverage) { if ($coverage) { - $coverage->start('api tests'); + $coverage->start('API tests'); } return new EmptyResponse(); }), @@ -122,7 +122,7 @@ return [ if ($coverage) { $basePath = __DIR__ . '/../../build/coverage-api'; $coverage->stop(); - (new Clover())->process($coverage, $basePath . '.cov'); + (new PHP())->process($coverage, $basePath . '.cov'); (new Xml(Version::getVersionString()))->process($coverage, $basePath . '/coverage-xml'); } From 20cd5cd752b1a344055a9a8da1087741a346253a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 26 Sep 2020 10:54:52 +0200 Subject: [PATCH 18/73] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ace111c..35260abc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this #### Changed * [#836](https://github.com/shlinkio/shlink/issues/836) Added support for the `-` notation while determining how to order the short URLs list, as in `?orderBy=shortCode-DESC`. This effectively deprecates the array notation (`?orderBy[shortCode]=DESC`), that will be removed in Shlink 3.0.0 +* [#782](https://github.com/shlinkio/shlink/issues/782) Added code coverage to API tests. #### Deprecated From 84b291e3105d2478793541b933ca8361b08d5536 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 26 Sep 2020 11:07:02 +0200 Subject: [PATCH 19/73] Added message with exit code in API tests script --- bin/test/run-api-tests.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/test/run-api-tests.sh b/bin/test/run-api-tests.sh index f3236d1b..00b97407 100755 --- a/bin/test/run-api-tests.sh +++ b/bin/test/run-api-tests.sh @@ -15,5 +15,7 @@ testsExitCode=$? vendor/bin/mezzio-swoole stop +echo "The exist code was $testsExitCode" + # Exit this script with the same code as the tests. If tests failed, this script has to fail exit $testsExitCode From 6163e343272b8a962bd73d55e8577d0185fded35 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 26 Sep 2020 11:16:35 +0200 Subject: [PATCH 20/73] Directly run API tests on travis, because they get stuck when run through composer --- .travis.yml | 1 + bin/test/run-api-tests.sh | 2 -- composer.json | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index bf7e8b15..f6e7091b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,6 +57,7 @@ before_script: script: - composer ci + - bin/test/run-api-tests.sh - if [[ ! -z "${DOCKERFILE_CHANGED}" && "${TRAVIS_PHP_VERSION}" == "7.4" ]]; then docker build -t shlink-docker-image:temp . ; fi after_success: diff --git a/bin/test/run-api-tests.sh b/bin/test/run-api-tests.sh index 00b97407..f3236d1b 100755 --- a/bin/test/run-api-tests.sh +++ b/bin/test/run-api-tests.sh @@ -15,7 +15,5 @@ testsExitCode=$? vendor/bin/mezzio-swoole stop -echo "The exist code was $testsExitCode" - # Exit this script with the same code as the tests. If tests failed, this script has to fail exit $testsExitCode diff --git a/composer.json b/composer.json index a2cb9539..c508b0fd 100644 --- a/composer.json +++ b/composer.json @@ -116,8 +116,7 @@ ], "test:ci": [ "@test:unit:ci", - "@test:db", - "@test:api" + "@test:db" ], "test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox", "test:unit:ci": "@test:unit --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml", From 76d6d9a7a9b6467ca3034641c2f4c1e90bcec025 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 27 Sep 2020 09:53:12 +0200 Subject: [PATCH 21/73] Created rest endpoint to list existing domains --- docs/swagger/paths/v2_domains.json | 86 +++++++++++++++++++ docs/swagger/swagger.json | 4 + module/Core/config/dependencies.config.php | 2 + .../Shlinkio.Shlink.Core.Entity.Domain.php | 3 +- module/Core/src/Domain/DomainService.php | 29 +++++++ .../src/Domain/DomainServiceInterface.php | 15 ++++ .../Domain/Repository/DomainRepository.php | 26 ++++++ .../Repository/DomainRepositoryInterface.php | 16 ++++ module/Rest/config/dependencies.config.php | 3 + module/Rest/config/routes.config.php | 5 +- .../src/Action/Domain/ListDomainsAction.php | 52 +++++++++++ 11 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 docs/swagger/paths/v2_domains.json create mode 100644 module/Core/src/Domain/DomainService.php create mode 100644 module/Core/src/Domain/DomainServiceInterface.php create mode 100644 module/Core/src/Domain/Repository/DomainRepository.php create mode 100644 module/Core/src/Domain/Repository/DomainRepositoryInterface.php create mode 100644 module/Rest/src/Action/Domain/ListDomainsAction.php diff --git a/docs/swagger/paths/v2_domains.json b/docs/swagger/paths/v2_domains.json new file mode 100644 index 00000000..d92ae995 --- /dev/null +++ b/docs/swagger/paths/v2_domains.json @@ -0,0 +1,86 @@ +{ + "get": { + "operationId": "listDomains", + "tags": [ + "Domains" + ], + "summary": "List existing domains", + "description": "Returns the list of all domains ever used, with a flag that tells if they are the default domain", + "security": [ + { + "ApiKey": [] + } + ], + "parameters": [ + { + "$ref": "../parameters/version.json" + } + ], + "responses": { + "200": { + "description": "The list of tags", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["domains"], + "properties": { + "domains": { + "type": "object", + "required": ["data"], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": ["domain", "isDefault"], + "properties": { + "domain": { + "type": "string" + }, + "isDefault": { + "type": "boolean" + } + } + } + } + } + } + } + } + } + }, + "examples": { + "application/json": { + "domains": { + "data": [ + { + "domain": "example.com", + "isDefault": true + }, + { + "domain": "aaa.com", + "isDefault": false + }, + { + "domain": "bbb.com", + "isDefault": false + } + ] + } + } + } + }, + "500": { + "description": "Unexpected error.", + "content": { + "application/problem+json": { + "schema": { + "$ref": "../definitions/Error.json" + } + } + } + } + } + } +} diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index 8dc21997..5abe1946 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -88,6 +88,10 @@ "$ref": "paths/v2_tags_{tag}_visits.json" }, + "/rest/v{version}/domains": { + "$ref": "paths/v2_domains.json" + }, + "/rest/v{version}/mercure-info": { "$ref": "paths/v2_mercure-info.json" }, diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 46bf1735..5dcef9a2 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -31,6 +31,7 @@ return [ Tag\TagService::class => ConfigAbstractFactory::class, Service\ShortUrl\DeleteShortUrlService::class => ConfigAbstractFactory::class, Service\ShortUrl\ShortUrlResolver::class => ConfigAbstractFactory::class, + Domain\DomainService::class => ConfigAbstractFactory::class, Util\UrlValidator::class => ConfigAbstractFactory::class, @@ -69,6 +70,7 @@ return [ Service\ShortUrl\ShortUrlResolver::class, ], Service\ShortUrl\ShortUrlResolver::class => ['em'], + Domain\DomainService::class => ['em'], Util\UrlValidator::class => ['httpClient', Options\UrlShortenerOptions::class], diff --git a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Domain.php b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Domain.php index c6349b74..e3d8c3cf 100644 --- a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Domain.php +++ b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Domain.php @@ -11,7 +11,8 @@ use Doctrine\ORM\Mapping\ClassMetadata; return static function (ClassMetadata $metadata, array $emConfig): void { $builder = new ClassMetadataBuilder($metadata); - $builder->setTable(determineTableName('domains', $emConfig)); + $builder->setTable(determineTableName('domains', $emConfig)) + ->setCustomRepositoryClass(Domain\Repository\DomainRepository::class); $builder->createField('id', Types::BIGINT) ->columnName('id') diff --git a/module/Core/src/Domain/DomainService.php b/module/Core/src/Domain/DomainService.php new file mode 100644 index 00000000..d7575361 --- /dev/null +++ b/module/Core/src/Domain/DomainService.php @@ -0,0 +1,29 @@ +em = $em; + } + + /** + * @return Domain[] + */ + public function listDomainsWithout(?string $excludeDomain = null): array + { + /** @var DomainRepositoryInterface $repo */ + $repo = $this->em->getRepository(Domain::class); + return $repo->findDomainsWithout($excludeDomain); + } +} diff --git a/module/Core/src/Domain/DomainServiceInterface.php b/module/Core/src/Domain/DomainServiceInterface.php new file mode 100644 index 00000000..3e56c69c --- /dev/null +++ b/module/Core/src/Domain/DomainServiceInterface.php @@ -0,0 +1,15 @@ +createQueryBuilder('d')->orderBy('d.authority', 'ASC'); + + if ($excludedAuthority !== null) { + $qb->where($qb->expr()->neq('d.authority', ':excludedAuthority')) + ->setParameter('excludedAuthority', $excludedAuthority); + } + + return $qb->getQuery()->getResult(); + } +} diff --git a/module/Core/src/Domain/Repository/DomainRepositoryInterface.php b/module/Core/src/Domain/Repository/DomainRepositoryInterface.php new file mode 100644 index 00000000..56a765ac --- /dev/null +++ b/module/Core/src/Domain/Repository/DomainRepositoryInterface.php @@ -0,0 +1,16 @@ + ConfigAbstractFactory::class, Action\Tag\CreateTagsAction::class => ConfigAbstractFactory::class, Action\Tag\UpdateTagAction::class => ConfigAbstractFactory::class, + Action\Domain\ListDomainsAction::class => ConfigAbstractFactory::class, ImplicitOptionsMiddleware::class => Middleware\EmptyResponseImplicitOptionsMiddlewareFactory::class, Middleware\BodyParserMiddleware::class => InvokableFactory::class, @@ -72,6 +74,7 @@ return [ Action\Tag\DeleteTagsAction::class => [TagService::class], Action\Tag\CreateTagsAction::class => [TagService::class], Action\Tag\UpdateTagAction::class => [TagService::class], + Action\Domain\ListDomainsAction::class => [DomainService::class, 'config.url_shortener.domain.hostname'], Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class => ['config.url_shortener.domain.hostname'], Middleware\ShortUrl\DefaultShortCodesLengthMiddleware::class => [ diff --git a/module/Rest/config/routes.config.php b/module/Rest/config/routes.config.php index 0bde3da0..64333254 100644 --- a/module/Rest/config/routes.config.php +++ b/module/Rest/config/routes.config.php @@ -12,7 +12,7 @@ return [ 'routes' => [ Action\HealthAction::getRouteDef(), - // Short codes + // Short URLs Action\ShortUrl\CreateShortUrlAction::getRouteDef([ $contentNegotiationMiddleware, $dropDomainMiddleware, @@ -36,6 +36,9 @@ return [ Action\Tag\CreateTagsAction::getRouteDef(), Action\Tag\UpdateTagAction::getRouteDef(), + // Domains + Action\Domain\ListDomainsAction::getRouteDef(), + Action\MercureInfoAction::getRouteDef(), ], diff --git a/module/Rest/src/Action/Domain/ListDomainsAction.php b/module/Rest/src/Action/Domain/ListDomainsAction.php new file mode 100644 index 00000000..682286a1 --- /dev/null +++ b/module/Rest/src/Action/Domain/ListDomainsAction.php @@ -0,0 +1,52 @@ +domainService = $domainService; + $this->defaultDomain = $defaultDomain; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $regularDomains = $this->domainService->listDomainsWithout($this->defaultDomain); + + return new JsonResponse([ + 'domains' => [ + 'data' => [ + $this->mapDomain($this->defaultDomain, true), + ...map($regularDomains, fn (Domain $domain) => $this->mapDomain($domain->getAuthority())), + ], + ], + ]); + } + + private function mapDomain(string $domain, bool $isDefault = false): array + { + return [ + 'domain' => $domain, + 'isDefault' => $isDefault, + ]; + } +} From 24aab5cc0e8e8cd2ae45cd049d69b36b2f80bea6 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 27 Sep 2020 10:11:41 +0200 Subject: [PATCH 22/73] Created unit tests for new Domain-related elements --- module/Core/test/Domain/DomainServiceTest.php | 49 ++++++++++++++++ .../Action/Domain/ListDomainsActionTest.php | 58 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 module/Core/test/Domain/DomainServiceTest.php create mode 100644 module/Rest/test/Action/Domain/ListDomainsActionTest.php diff --git a/module/Core/test/Domain/DomainServiceTest.php b/module/Core/test/Domain/DomainServiceTest.php new file mode 100644 index 00000000..e91c3281 --- /dev/null +++ b/module/Core/test/Domain/DomainServiceTest.php @@ -0,0 +1,49 @@ +em = $this->prophesize(EntityManagerInterface::class); + $this->domainService = new DomainService($this->em->reveal()); + } + + /** + * @test + * @dataProvider provideExcludedDomains + */ + public function listDomainsWithoutDelegatesIntoRepository(?string $excludedDomain, array $expectedResult): void + { + $repo = $this->prophesize(DomainRepositoryInterface::class); + $getRepo = $this->em->getRepository(Domain::class)->willReturn($repo->reveal()); + $findDomains = $repo->findDomainsWithout($excludedDomain)->willReturn($expectedResult); + + $result = $this->domainService->listDomainsWithout($excludedDomain); + + self::assertEquals($expectedResult, $result); + $getRepo->shouldHaveBeenCalledOnce(); + $findDomains->shouldHaveBeenCalledOnce(); + } + + public function provideExcludedDomains(): iterable + { + yield 'no excluded domain' => [null, []]; + yield 'foo.com excluded domain' => ['foo.com', []]; + yield 'bar.com excluded domain' => ['bar.com', [new Domain('bar.com')]]; + yield 'baz.com excluded domain' => ['baz.com', [new Domain('foo.com'), new Domain('bar.com')]]; + } +} diff --git a/module/Rest/test/Action/Domain/ListDomainsActionTest.php b/module/Rest/test/Action/Domain/ListDomainsActionTest.php new file mode 100644 index 00000000..fe8ffa07 --- /dev/null +++ b/module/Rest/test/Action/Domain/ListDomainsActionTest.php @@ -0,0 +1,58 @@ +domainService = $this->prophesize(DomainServiceInterface::class); + $this->action = new ListDomainsAction($this->domainService->reveal(), 'foo.com'); + } + + /** @test */ + public function domainsAreProperlyListed(): void + { + $listDomains = $this->domainService->listDomainsWithout('foo.com')->willReturn([ + new Domain('bar.com'), + new Domain('baz.com'), + ]); + + /** @var JsonResponse $resp */ + $resp = $this->action->handle(ServerRequestFactory::fromGlobals()); + $payload = $resp->getPayload(); + + self::assertEquals([ + 'domains' => [ + 'data' => [ + [ + 'domain' => 'foo.com', + 'isDefault' => true, + ], + [ + 'domain' => 'bar.com', + 'isDefault' => false, + ], + [ + 'domain' => 'baz.com', + 'isDefault' => false, + ], + ], + ], + ], $payload); + $listDomains->shouldHaveBeenCalledOnce(); + } +} From 614e1c37f82eb84123a646392ceb457d4192332a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 27 Sep 2020 10:18:49 +0200 Subject: [PATCH 23/73] Added database test for Domainrepository --- .../Repository/DomainRepositoryTest.php | 39 +++++++++++++++++++ phpunit.xml.dist | 2 + 2 files changed, 41 insertions(+) create mode 100644 module/Core/test-db/Domain/Repository/DomainRepositoryTest.php diff --git a/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php b/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php new file mode 100644 index 00000000..b79f15f1 --- /dev/null +++ b/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php @@ -0,0 +1,39 @@ +repo = $this->getEntityManager()->getRepository(Domain::class); + } + + /** @test */ + public function findDomainsReturnsExpectedResult(): void + { + $fooDomain = new Domain('foo.com'); + $barDomain = new Domain('bar.com'); + $bazDomain = new Domain('baz.com'); + + $this->getEntityManager()->persist($fooDomain); + $this->getEntityManager()->persist($barDomain); + $this->getEntityManager()->persist($bazDomain); + $this->getEntityManager()->flush(); + + self::assertEquals([$barDomain, $bazDomain, $fooDomain], $this->repo->findDomainsWithout()); + self::assertEquals([$barDomain, $bazDomain], $this->repo->findDomainsWithout('foo.com')); + self::assertEquals([$bazDomain, $fooDomain], $this->repo->findDomainsWithout('bar.com')); + self::assertEquals([$barDomain, $fooDomain], $this->repo->findDomainsWithout('baz.com')); + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 03f73521..c45743cf 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -23,6 +23,8 @@ ./module/Core/src/Repository + ./module/Core/src/**/Repository + ./module/Core/src/**/**/Repository From 06eda073bf2594c8c99ee8b5e3451a291e03c09c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 27 Sep 2020 10:23:17 +0200 Subject: [PATCH 24/73] Added API test for /domains endpoint --- .../Rest/test-api/Action/ListDomainsTest.php | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 module/Rest/test-api/Action/ListDomainsTest.php diff --git a/module/Rest/test-api/Action/ListDomainsTest.php b/module/Rest/test-api/Action/ListDomainsTest.php new file mode 100644 index 00000000..045197e8 --- /dev/null +++ b/module/Rest/test-api/Action/ListDomainsTest.php @@ -0,0 +1,37 @@ +callApiWithKey(self::METHOD_GET, '/domains'); + $respPayload = $this->getJsonResponsePayload($resp); + + self::assertEquals(self::STATUS_OK, $resp->getStatusCode()); + self::assertEquals([ + 'domains' => [ + 'data' => [ + [ + 'domain' => 'doma.in', + 'isDefault' => true, + ], + [ + 'domain' => 'example.com', + 'isDefault' => false, + ], + [ + 'domain' => 'some-domain.com', + 'isDefault' => false, + ], + ], + ], + ], $respPayload); + } +} From 073e4eeac803fba512806a75aa21a42d4494eb78 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 27 Sep 2020 12:39:02 +0200 Subject: [PATCH 25/73] Created command to list domains --- module/CLI/config/cli.config.php | 2 + module/CLI/config/dependencies.config.php | 5 ++ .../src/Command/Domain/ListDomainsCommand.php | 49 +++++++++++++++++++ .../src/Action/Domain/ListDomainsAction.php | 1 - 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 module/CLI/src/Command/Domain/ListDomainsCommand.php diff --git a/module/CLI/config/cli.config.php b/module/CLI/config/cli.config.php index fa9efc69..6e32428a 100644 --- a/module/CLI/config/cli.config.php +++ b/module/CLI/config/cli.config.php @@ -25,6 +25,8 @@ return [ Command\Tag\RenameTagCommand::NAME => Command\Tag\RenameTagCommand::class, Command\Tag\DeleteTagsCommand::NAME => Command\Tag\DeleteTagsCommand::class, + Command\Domain\ListDomainsCommand::NAME => Command\Domain\ListDomainsCommand::class, + Command\Db\CreateDatabaseCommand::NAME => Command\Db\CreateDatabaseCommand::class, Command\Db\MigrateDatabaseCommand::NAME => Command\Db\MigrateDatabaseCommand::class, ], diff --git a/module/CLI/config/dependencies.config.php b/module/CLI/config/dependencies.config.php index 516bbbd4..199d29ef 100644 --- a/module/CLI/config/dependencies.config.php +++ b/module/CLI/config/dependencies.config.php @@ -10,6 +10,7 @@ use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory; use Laminas\ServiceManager\Factory\InvokableFactory; use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdater; use Shlinkio\Shlink\Common\Doctrine\NoDbNameConnectionFactory; +use Shlinkio\Shlink\Core\Domain\DomainService; use Shlinkio\Shlink\Core\Service; use Shlinkio\Shlink\Core\Tag\TagService; use Shlinkio\Shlink\Core\Visit; @@ -52,6 +53,8 @@ return [ Command\Db\CreateDatabaseCommand::class => ConfigAbstractFactory::class, Command\Db\MigrateDatabaseCommand::class => ConfigAbstractFactory::class, + + Command\Domain\ListDomainsCommand::class => ConfigAbstractFactory::class, ], ], @@ -84,6 +87,8 @@ return [ Command\Tag\RenameTagCommand::class => [TagService::class], Command\Tag\DeleteTagsCommand::class => [TagService::class], + Command\Domain\ListDomainsCommand::class => [DomainService::class, 'config.url_shortener.domain.hostname'], + Command\Db\CreateDatabaseCommand::class => [ LockFactory::class, SymfonyCli\Helper\ProcessHelper::class, diff --git a/module/CLI/src/Command/Domain/ListDomainsCommand.php b/module/CLI/src/Command/Domain/ListDomainsCommand.php new file mode 100644 index 00000000..0368f1dd --- /dev/null +++ b/module/CLI/src/Command/Domain/ListDomainsCommand.php @@ -0,0 +1,49 @@ +domainService = $domainService; + $this->defaultDomain = $defaultDomain; + } + + protected function configure(): void + { + $this + ->setName(self::NAME) + ->setDescription('List all domains that have been ever used for some short URL'); + } + + protected function execute(InputInterface $input, OutputInterface $output): ?int + { + $regularDomains = $this->domainService->listDomainsWithout($this->defaultDomain); + + ShlinkTable::fromOutput($output)->render(['Domain', 'Is default'], [ + [$this->defaultDomain, 'Yes'], + ...map($regularDomains, fn (Domain $domain) => [$domain->getAuthority(), 'No']), + ]); + + return ExitCodes::EXIT_SUCCESS; + } +} diff --git a/module/Rest/src/Action/Domain/ListDomainsAction.php b/module/Rest/src/Action/Domain/ListDomainsAction.php index 682286a1..7362123a 100644 --- a/module/Rest/src/Action/Domain/ListDomainsAction.php +++ b/module/Rest/src/Action/Domain/ListDomainsAction.php @@ -11,7 +11,6 @@ use Shlinkio\Shlink\Core\Domain\DomainServiceInterface; use Shlinkio\Shlink\Core\Entity\Domain; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; -use function Functional\compose; use function Functional\map; class ListDomainsAction extends AbstractRestAction From 63a24342e31e70b8843b474557b9009ae22fd9bb Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 27 Sep 2020 12:48:24 +0200 Subject: [PATCH 26/73] Created unit test for ListDomainsCommand --- .../Command/Domain/ListDomainsCommandTest.php | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 module/CLI/test/Command/Domain/ListDomainsCommandTest.php diff --git a/module/CLI/test/Command/Domain/ListDomainsCommandTest.php b/module/CLI/test/Command/Domain/ListDomainsCommandTest.php new file mode 100644 index 00000000..ded8572f --- /dev/null +++ b/module/CLI/test/Command/Domain/ListDomainsCommandTest.php @@ -0,0 +1,56 @@ +domainService = $this->prophesize(DomainServiceInterface::class); + + $command = new ListDomainsCommand($this->domainService->reveal(), 'foo.com'); + $app = new Application(); + $app->add($command); + + $this->commandTester = new CommandTester($command); + } + + /** @test */ + public function allDomainsAreProperlyPrinted(): void + { + $expectedOutput = <<domainService->listDomainsWithout('foo.com')->willReturn([ + new Domain('bar.com'), + new Domain('baz.com'), + ]); + + $this->commandTester->execute([]); + + self::assertEquals($expectedOutput, $this->commandTester->getDisplay()); + self::assertEquals(ExitCodes::EXIT_SUCCESS, $this->commandTester->getStatusCode()); + $listDomains->shouldHaveBeenCalledOnce(); + } +} From 34c10c0bc97dda4046ff7faad7b668e73ff77463 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 27 Sep 2020 12:50:03 +0200 Subject: [PATCH 27/73] Updated changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35260abc..ba16ae5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * On the `POST /short-url` and `PATCH /short-url/{shortCode}` endpoints, you can now pass `validateUrl: true/false` in order to enforce enabling or disabling validation, ignoring the global config. If the value is not provided, the global config is still normally applied. * On the `short-url:generate` CLI command, you can pass `--validate-url` or `--no-validate-url` flags, in order to enforce enabling or disabling validation. If none of them is provided, the global config is still normally applied. +* [#838](https://github.com/shlinkio/shlink/issues/838) Added new endpoint and CLI command to list existing domains. + + It returns both default domain and specific domains that were used for some short URLs. + + * REST endpoint: `GET /rest/v2/domains` + * CLI Command: `domain:list` + #### Changed * [#836](https://github.com/shlinkio/shlink/issues/836) Added support for the `-` notation while determining how to order the short URLs list, as in `?orderBy=shortCode-DESC`. This effectively deprecates the array notation (`?orderBy[shortCode]=DESC`), that will be removed in Shlink 3.0.0 From 00a96e621583d695aa114e543f3fbfde5f2a190e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 3 Oct 2020 11:49:25 +0200 Subject: [PATCH 28/73] Allowed to change swoole port in docker image by using the PORT env var --- Dockerfile | 6 +++--- docker/config/shlink_in_docker.local.php | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1cd764e0..0c5411e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM php:7.4.9-alpine3.12 as base -ARG SHLINK_VERSION=2.2.2 +ARG SHLINK_VERSION=2.3.0 ENV SHLINK_VERSION ${SHLINK_VERSION} ENV SWOOLE_VERSION 4.5.2 ENV LC_ALL "C" @@ -44,7 +44,7 @@ RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS && \ # Install shlink FROM base as builder COPY . . -COPY --from=composer:1.10.1 /usr/bin/composer ./composer.phar +COPY --from=composer:1.10.13 /usr/bin/composer ./composer.phar RUN apk add --no-cache git && \ php composer.phar install --no-dev --optimize-autoloader --prefer-dist --no-progress --no-interaction && \ php composer.phar clear-cache && \ @@ -59,7 +59,7 @@ LABEL maintainer="Alejandro Celaya " COPY --from=builder /etc/shlink . RUN ln -s /etc/shlink/bin/cli /usr/local/bin/shlink -# Expose swoole port +# Expose default swoole port EXPOSE 8080 # Expose params config dir, since the user is expected to provide custom config from there diff --git a/docker/config/shlink_in_docker.local.php b/docker/config/shlink_in_docker.local.php index 8d3c55fa..8efee8a4 100644 --- a/docker/config/shlink_in_docker.local.php +++ b/docker/config/shlink_in_docker.local.php @@ -159,6 +159,7 @@ return [ 'mezzio-swoole' => [ 'swoole-http-server' => [ + 'port' => (int) env('PORT', 8080), 'options' => [ 'worker_num' => (int) env('WEB_WORKER_NUM', 16), 'task_worker_num' => (int) env('TASK_WORKER_NUM', 16), From c8d7413dd46118106bf8fc8cffddac0c61711c70 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 3 Oct 2020 11:52:27 +0200 Subject: [PATCH 29/73] Documented support for PORT env var in Docker image --- docker/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docker/README.md b/docker/README.md index 89c9565b..2cc0b5b9 100644 --- a/docker/README.md +++ b/docker/README.md @@ -176,15 +176,17 @@ This is the complete list of supported env vars: * `ANONYMIZE_REMOTE_ADDR`: Tells if IP addresses from visitors should be obfuscated before storing them in the database. Default value is `true`. **Careful!** Setting this to `false` will make your Shlink instance no longer be in compliance with the GDPR and other similar data protection regulations. * `REDIRECT_STATUS_CODE`: Either **301** or **302**. Used to determine if redirects from short to long URLs should be done with a 301 or 302 status. Defaults to 302. * `REDIRECT_CACHE_LIFETIME`: Allows to set the amount of seconds that redirects should be cached when redirect status is 301. Default values is 30. +* `PORT`: Can be used to set the port in which shlink listens. Defaults to 8080 (Some cloud providers, like Google cloud or Heroku, expect to be able to customize exposed port by providing this env var). An example using all env vars could look like this: ```bash docker run \ --name shlink \ - -p 8080:8080 \ + -p 8080:8888 \ -e SHORT_DOMAIN_HOST=doma.in \ -e SHORT_DOMAIN_SCHEMA=https \ + -e PORT=8888 \ -e DB_DRIVER=mysql \ -e DB_NAME=shlink \ -e DB_USER=root \ @@ -257,7 +259,8 @@ The whole configuration should have this format, but it can be split into multip "mercure_jwt_secret": "super_secret_key", "anonymize_remote_addr": false, "redirect_status_code": 301, - "redirect_cache_lifetime": 90 + "redirect_cache_lifetime": 90, + "port": 8888 } ``` From 450eea64aa5338fad4b4545270d800e5b529fff1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 3 Oct 2020 11:54:31 +0200 Subject: [PATCH 30/73] Added support for port option in SimplifiedConfigParser --- module/Core/src/Config/SimplifiedConfigParser.php | 1 + module/Core/test/Config/SimplifiedConfigParserTest.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/module/Core/src/Config/SimplifiedConfigParser.php b/module/Core/src/Config/SimplifiedConfigParser.php index 38fbdea3..aebeb2c3 100644 --- a/module/Core/src/Config/SimplifiedConfigParser.php +++ b/module/Core/src/Config/SimplifiedConfigParser.php @@ -40,6 +40,7 @@ class SimplifiedConfigParser 'anonymize_remote_addr' => ['url_shortener', 'anonymize_remote_addr'], 'redirect_status_code' => ['url_shortener', 'redirect_status_code'], 'redirect_cache_lifetime' => ['url_shortener', 'redirect_cache_lifetime'], + 'port' => ['mezzio-swoole', 'swoole-http-server', 'port'], ]; private const SIMPLIFIED_CONFIG_SIDE_EFFECTS = [ 'delete_short_url_threshold' => [ diff --git a/module/Core/test/Config/SimplifiedConfigParserTest.php b/module/Core/test/Config/SimplifiedConfigParserTest.php index 9d4bca69..dc85196a 100644 --- a/module/Core/test/Config/SimplifiedConfigParserTest.php +++ b/module/Core/test/Config/SimplifiedConfigParserTest.php @@ -67,6 +67,7 @@ class SimplifiedConfigParserTest extends TestCase 'anonymize_remote_addr' => false, 'redirect_status_code' => 301, 'redirect_cache_lifetime' => 90, + 'port' => 8888, ]; $expected = [ 'app_options' => [ @@ -132,6 +133,7 @@ class SimplifiedConfigParserTest extends TestCase 'mezzio-swoole' => [ 'swoole-http-server' => [ + 'port' => 8888, 'options' => [ 'task_worker_num' => 50, ], From c6c78f383f335e823d4fdb957e3ffd13f217f91a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 3 Oct 2020 11:56:09 +0200 Subject: [PATCH 31/73] Updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba16ae5e..df10baf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * REST endpoint: `GET /rest/v2/domains` * CLI Command: `domain:list` +* [#832](https://github.com/shlinkio/shlink/issues/832) Added support to customize the port in which the docker image listens by using the `PORT` env var or the `port` config option. + #### Changed * [#836](https://github.com/shlinkio/shlink/issues/836) Added support for the `-` notation while determining how to order the short URLs list, as in `?orderBy=shortCode-DESC`. This effectively deprecates the array notation (`?orderBy[shortCode]=DESC`), that will be removed in Shlink 3.0.0 From 065d314608eeac5924de90ab1426f91afb1e191f Mon Sep 17 00:00:00 2001 From: Daniel Londero Date: Sun, 4 Oct 2020 00:35:14 +0200 Subject: [PATCH 32/73] Invoke PHPUnit's assertions statically --- .../Command/Api/DisableKeyCommandTest.php | 4 +- .../Command/Api/GenerateKeyCommandTest.php | 2 +- .../test/Command/Api/ListKeysCommandTest.php | 20 ++-- .../Command/Db/CreateDatabaseCommandTest.php | 6 +- .../Command/Db/MigrateDatabaseCommandTest.php | 4 +- .../ShortUrl/DeleteShortUrlCommandTest.php | 12 +- .../ShortUrl/GenerateShortUrlCommandTest.php | 16 +-- .../Command/ShortUrl/GetVisitsCommandTest.php | 8 +- .../ShortUrl/ListShortUrlsCommandTest.php | 22 ++-- .../ShortUrl/ResolveUrlCommandTest.php | 4 +- .../test/Command/Tag/CreateTagCommandTest.php | 4 +- .../Command/Tag/DeleteTagsCommandTest.php | 4 +- .../test/Command/Tag/ListTagsCommandTest.php | 14 +-- .../test/Command/Tag/RenameTagCommandTest.php | 4 +- .../Command/Visit/LocateVisitsCommandTest.php | 22 ++-- module/CLI/test/ConfigProviderTest.php | 4 +- ...GeolocationDbUpdateFailedExceptionTest.php | 8 +- .../test/Factory/ApplicationFactoryTest.php | 6 +- .../test/Util/GeolocationDbUpdaterTest.php | 18 +-- module/CLI/test/Util/ShlinkTableTest.php | 2 +- .../Repository/ShortUrlRepositoryTest.php | 106 +++++++++--------- .../test-db/Repository/TagRepositoryTest.php | 14 +-- .../Repository/VisitRepositoryTest.php | 52 ++++----- module/Core/test/Action/PixelActionTest.php | 6 +- module/Core/test/Action/QrCodeActionTest.php | 6 +- .../Core/test/Action/RedirectActionTest.php | 16 +-- .../Core/test/Config/BasePathPrefixerTest.php | 6 +- .../Config/DeprecatedConfigParserTest.php | 10 +- .../Config/SimplifiedConfigParserTest.php | 2 +- module/Core/test/ConfigProviderTest.php | 8 +- .../PersistenceDomainResolverTest.php | 8 +- .../Resolver/SimpleDomainResolverTest.php | 6 +- module/Core/test/Entity/ShortUrlTest.php | 4 +- module/Core/test/Entity/TagTest.php | 2 +- module/Core/test/Entity/VisitLocationTest.php | 2 +- module/Core/test/Entity/VisitTest.php | 4 +- .../NotFoundRedirectHandlerTest.php | 6 +- .../NotFoundTemplateHandlerTest.php | 2 +- ...DbConnectionEventListenerDelegatorTest.php | 2 +- .../CloseDbConnectionEventListenerTest.php | 2 +- .../LocateShortUrlVisitTest.php | 8 +- .../Exception/DeleteShortUrlExceptionTest.php | 14 +-- .../Exception/InvalidUrlExceptionTest.php | 16 +-- .../IpCannotBeLocatedExceptionTest.php | 24 ++-- .../Exception/NonUniqueSlugExceptionTest.php | 12 +- .../ShortUrlNotFoundExceptionTest.php | 12 +- .../Exception/TagConflictExceptionTest.php | 12 +- .../Exception/TagNotFoundExceptionTest.php | 12 +- .../Exception/ValidationExceptionTest.php | 12 +- .../Mercure/MercureUpdatesGeneratorTest.php | 4 +- module/Core/test/Model/ShortUrlMetaTest.php | 16 +-- module/Core/test/Model/VisitorTest.php | 6 +- .../Service/ShortUrl/ShortUrlResolverTest.php | 4 +- .../Core/test/Service/ShortUrlServiceTest.php | 12 +- .../Core/test/Service/Tag/TagServiceTest.php | 10 +- module/Core/test/Service/UrlShortenerTest.php | 6 +- .../Core/test/Service/VisitsTrackerTest.php | 4 +- .../ShortUrlDataTransformerTest.php | 2 +- .../Core/test/Visit/VisitsStatsHelperTest.php | 2 +- .../Action/CreateShortUrlActionTest.php | 82 +++++++------- .../Action/DeleteShortUrlActionTest.php | 36 +++--- .../Action/EditShortUrlActionTest.php | 42 +++---- .../Action/EditShortUrlTagsActionTest.php | 30 ++--- .../Action/GlobalVisitsActionTest.php | 6 +- .../test-api/Action/ListShortUrlsTest.php | 4 +- .../test-api/Action/ListTagsActionTest.php | 2 +- .../Action/ResolveShortUrlActionTest.php | 20 ++-- .../Action/ShortUrlVisitsActionTest.php | 18 +-- .../test-api/Action/TagVisitsActionTest.php | 16 +-- .../test-api/Action/UpdateTagActionTest.php | 32 +++--- .../Middleware/AuthenticationTest.php | 20 ++-- .../Middleware/ImplicitOptionsTest.php | 6 +- module/Rest/test/Action/HealthActionTest.php | 30 ++--- .../test/Action/MercureInfoActionTest.php | 10 +- .../ShortUrl/CreateShortUrlActionTest.php | 4 +- .../ShortUrl/DeleteShortUrlActionTest.php | 2 +- .../ShortUrl/EditShortUrlActionTest.php | 2 +- .../ShortUrl/EditShortUrlTagsActionTest.php | 2 +- .../ShortUrl/ListShortUrlsActionTest.php | 8 +- .../ShortUrl/ResolveShortUrlActionTest.php | 4 +- .../SingleStepCreateShortUrlActionTest.php | 2 +- .../test/Action/Tag/CreateTagsActionTest.php | 2 +- .../test/Action/Tag/DeleteTagsActionTest.php | 2 +- .../test/Action/Tag/ListTagsActionTest.php | 4 +- .../test/Action/Tag/UpdateTagActionTest.php | 2 +- .../Action/Visit/GlobalVisitsActionTest.php | 2 +- .../Action/Visit/ShortUrlVisitsActionTest.php | 4 +- .../test/Action/Visit/TagVisitsActionTest.php | 2 +- ...AuthenticationPluginManagerFactoryTest.php | 2 +- .../Plugin/ApiKeyHeaderPluginTest.php | 2 +- module/Rest/test/ConfigProviderTest.php | 6 +- .../MissingAuthenticationExceptionTest.php | 12 +- .../VerifyAuthenticationExceptionTest.php | 2 +- .../AuthenticationMiddlewareTest.php | 2 +- .../Middleware/BodyParserMiddlewareTest.php | 4 +- .../Middleware/CrossDomainMiddlewareTest.php | 46 ++++---- ...seImplicitOptionsMiddlewareFactoryTest.php | 4 +- ...ortUrlContentNegotiationMiddlewareTest.php | 6 +- .../Rest/test/Service/ApiKeyServiceTest.php | 16 +-- 99 files changed, 567 insertions(+), 567 deletions(-) diff --git a/module/CLI/test/Command/Api/DisableKeyCommandTest.php b/module/CLI/test/Command/Api/DisableKeyCommandTest.php index 8b5ef6c4..2138fff6 100644 --- a/module/CLI/test/Command/Api/DisableKeyCommandTest.php +++ b/module/CLI/test/Command/Api/DisableKeyCommandTest.php @@ -37,7 +37,7 @@ class DisableKeyCommandTest extends TestCase ]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('API key "abcd1234" properly disabled', $output); + self::assertStringContainsString('API key "abcd1234" properly disabled', $output); } /** @test */ @@ -52,7 +52,7 @@ class DisableKeyCommandTest extends TestCase ]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString($expectedMessage, $output); + self::assertStringContainsString($expectedMessage, $output); $disable->shouldHaveBeenCalledOnce(); } } diff --git a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php index 8ddd9f0b..bb467337 100644 --- a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php +++ b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php @@ -36,7 +36,7 @@ class GenerateKeyCommandTest extends TestCase $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('Generated API key: ', $output); + self::assertStringContainsString('Generated API key: ', $output); $create->shouldHaveBeenCalledOnce(); } diff --git a/module/CLI/test/Command/Api/ListKeysCommandTest.php b/module/CLI/test/Command/Api/ListKeysCommandTest.php index 9e30605a..fc03a180 100644 --- a/module/CLI/test/Command/Api/ListKeysCommandTest.php +++ b/module/CLI/test/Command/Api/ListKeysCommandTest.php @@ -38,11 +38,11 @@ class ListKeysCommandTest extends TestCase $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('Key', $output); - $this->assertStringContainsString('Is enabled', $output); - $this->assertStringContainsString(' +++ ', $output); - $this->assertStringNotContainsString(' --- ', $output); - $this->assertStringContainsString('Expiration date', $output); + self::assertStringContainsString('Key', $output); + self::assertStringContainsString('Is enabled', $output); + self::assertStringContainsString(' +++ ', $output); + self::assertStringNotContainsString(' --- ', $output); + self::assertStringContainsString('Expiration date', $output); } /** @test */ @@ -58,10 +58,10 @@ class ListKeysCommandTest extends TestCase ]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('Key', $output); - $this->assertStringNotContainsString('Is enabled', $output); - $this->assertStringNotContainsString(' +++ ', $output); - $this->assertStringNotContainsString(' --- ', $output); - $this->assertStringContainsString('Expiration date', $output); + self::assertStringContainsString('Key', $output); + self::assertStringNotContainsString('Is enabled', $output); + self::assertStringNotContainsString(' +++ ', $output); + self::assertStringNotContainsString(' --- ', $output); + self::assertStringContainsString('Expiration date', $output); } } diff --git a/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php b/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php index d890f264..b34ebb56 100644 --- a/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php +++ b/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php @@ -77,7 +77,7 @@ class CreateDatabaseCommandTest extends TestCase $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('Database already exists. Run "db:migrate" command', $output); + self::assertStringContainsString('Database already exists. Run "db:migrate" command', $output); $getDatabase->shouldHaveBeenCalledOnce(); $listDatabases->shouldHaveBeenCalledOnce(); $createDatabase->shouldNotHaveBeenCalled(); @@ -121,8 +121,8 @@ class CreateDatabaseCommandTest extends TestCase $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('Creating database tables...', $output); - $this->assertStringContainsString('Database properly created!', $output); + self::assertStringContainsString('Creating database tables...', $output); + self::assertStringContainsString('Database properly created!', $output); $getDatabase->shouldHaveBeenCalledOnce(); $listDatabases->shouldHaveBeenCalledOnce(); $createDatabase->shouldNotHaveBeenCalled(); diff --git a/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php b/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php index 71587eea..985a7e74 100644 --- a/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php +++ b/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php @@ -60,8 +60,8 @@ class MigrateDatabaseCommandTest extends TestCase $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('Migrating database...', $output); - $this->assertStringContainsString('Database properly migrated!', $output); + self::assertStringContainsString('Migrating database...', $output); + self::assertStringContainsString('Database properly migrated!', $output); $runCommand->shouldHaveBeenCalledOnce(); } } diff --git a/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php index 2c3526f5..b72a27a1 100644 --- a/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php @@ -47,7 +47,7 @@ class DeleteShortUrlCommandTest extends TestCase $this->commandTester->execute(['shortCode' => $shortCode]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString( + self::assertStringContainsString( sprintf('Short URL with short code "%s" successfully deleted.', $shortCode), $output, ); @@ -66,7 +66,7 @@ class DeleteShortUrlCommandTest extends TestCase $this->commandTester->execute(['shortCode' => $shortCode]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString(sprintf('No URL found with short code "%s"', $shortCode), $output); + self::assertStringContainsString(sprintf('No URL found with short code "%s"', $shortCode), $output); $deleteByShortCode->shouldHaveBeenCalledOnce(); } @@ -95,11 +95,11 @@ class DeleteShortUrlCommandTest extends TestCase $this->commandTester->execute(['shortCode' => $shortCode]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString(sprintf( + self::assertStringContainsString(sprintf( 'Impossible to delete short URL with short code "%s" since it has more than "10" visits.', $shortCode, ), $output); - $this->assertStringContainsString($expectedMessage, $output); + self::assertStringContainsString($expectedMessage, $output); $deleteByShortCode->shouldHaveBeenCalledTimes($expectedDeleteCalls); } @@ -122,11 +122,11 @@ class DeleteShortUrlCommandTest extends TestCase $this->commandTester->execute(['shortCode' => $shortCode]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString(sprintf( + self::assertStringContainsString(sprintf( 'Impossible to delete short URL with short code "%s" since it has more than "10" visits.', $shortCode, ), $output); - $this->assertStringContainsString('Short URL was not deleted.', $output); + self::assertStringContainsString('Short URL was not deleted.', $output); $deleteByShortCode->shouldHaveBeenCalledOnce(); } } diff --git a/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php index eee57b81..fd63fc2a 100644 --- a/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php @@ -49,8 +49,8 @@ class GenerateShortUrlCommandTest extends TestCase ]); $output = $this->commandTester->getDisplay(); - $this->assertEquals(ExitCodes::EXIT_SUCCESS, $this->commandTester->getStatusCode()); - $this->assertStringContainsString($shortUrl->toString(self::DOMAIN_CONFIG), $output); + self::assertEquals(ExitCodes::EXIT_SUCCESS, $this->commandTester->getStatusCode()); + self::assertStringContainsString($shortUrl->toString(self::DOMAIN_CONFIG), $output); $urlToShortCode->shouldHaveBeenCalledOnce(); } @@ -64,8 +64,8 @@ class GenerateShortUrlCommandTest extends TestCase $this->commandTester->execute(['longUrl' => $url]); $output = $this->commandTester->getDisplay(); - $this->assertEquals(ExitCodes::EXIT_FAILURE, $this->commandTester->getStatusCode()); - $this->assertStringContainsString('Provided URL http://domain.com/invalid is invalid.', $output); + self::assertEquals(ExitCodes::EXIT_FAILURE, $this->commandTester->getStatusCode()); + self::assertStringContainsString('Provided URL http://domain.com/invalid is invalid.', $output); } /** @test */ @@ -78,8 +78,8 @@ class GenerateShortUrlCommandTest extends TestCase $this->commandTester->execute(['longUrl' => 'http://domain.com/invalid', '--customSlug' => 'my-slug']); $output = $this->commandTester->getDisplay(); - $this->assertEquals(ExitCodes::EXIT_FAILURE, $this->commandTester->getStatusCode()); - $this->assertStringContainsString('Provided slug "my-slug" is already in use', $output); + self::assertEquals(ExitCodes::EXIT_FAILURE, $this->commandTester->getStatusCode()); + self::assertStringContainsString('Provided slug "my-slug" is already in use', $output); $urlToShortCode->shouldHaveBeenCalledOnce(); } @@ -102,8 +102,8 @@ class GenerateShortUrlCommandTest extends TestCase ]); $output = $this->commandTester->getDisplay(); - $this->assertEquals(ExitCodes::EXIT_SUCCESS, $this->commandTester->getStatusCode()); - $this->assertStringContainsString($shortUrl->toString(self::DOMAIN_CONFIG), $output); + self::assertEquals(ExitCodes::EXIT_SUCCESS, $this->commandTester->getStatusCode()); + self::assertStringContainsString($shortUrl->toString(self::DOMAIN_CONFIG), $output); $urlToShortCode->shouldHaveBeenCalledOnce(); } diff --git a/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php b/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php index a725240e..d57deb2e 100644 --- a/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php @@ -88,7 +88,7 @@ class GetVisitsCommandTest extends TestCase $output = $this->commandTester->getDisplay(); $info->shouldHaveBeenCalledOnce(); - $this->assertStringContainsString( + self::assertStringContainsString( sprintf('Ignored provided "startDate" since its value "%s" is not a valid date', $startDate), $output, ); @@ -108,8 +108,8 @@ class GetVisitsCommandTest extends TestCase $this->commandTester->execute(['shortCode' => $shortCode]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('foo', $output); - $this->assertStringContainsString('Spain', $output); - $this->assertStringContainsString('bar', $output); + self::assertStringContainsString('foo', $output); + self::assertStringContainsString('Spain', $output); + self::assertStringContainsString('bar', $output); } } diff --git a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php index d8bc0f60..4fbeec40 100644 --- a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php @@ -50,9 +50,9 @@ class ListShortUrlsCommandTest extends TestCase $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('Continue with page 2?', $output); - $this->assertStringContainsString('Continue with page 3?', $output); - $this->assertStringContainsString('Continue with page 4?', $output); + self::assertStringContainsString('Continue with page 2?', $output); + self::assertStringContainsString('Continue with page 3?', $output); + self::assertStringContainsString('Continue with page 4?', $output); } /** @test */ @@ -72,13 +72,13 @@ class ListShortUrlsCommandTest extends TestCase $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('url_1', $output); - $this->assertStringContainsString('url_9', $output); - $this->assertStringNotContainsString('url_10', $output); - $this->assertStringNotContainsString('url_20', $output); - $this->assertStringNotContainsString('url_30', $output); - $this->assertStringContainsString('Continue with page 2?', $output); - $this->assertStringNotContainsString('Continue with page 3?', $output); + self::assertStringContainsString('url_1', $output); + self::assertStringContainsString('url_9', $output); + self::assertStringNotContainsString('url_10', $output); + self::assertStringNotContainsString('url_20', $output); + self::assertStringNotContainsString('url_30', $output); + self::assertStringContainsString('Continue with page 2?', $output); + self::assertStringNotContainsString('Continue with page 3?', $output); } /** @test */ @@ -103,7 +103,7 @@ class ListShortUrlsCommandTest extends TestCase $this->commandTester->setInputs(['y']); $this->commandTester->execute(['--showTags' => true]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('Tags', $output); + self::assertStringContainsString('Tags', $output); } /** diff --git a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php index 7c307252..8beeecd1 100644 --- a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php @@ -44,7 +44,7 @@ class ResolveUrlCommandTest extends TestCase $this->commandTester->execute(['shortCode' => $shortCode]); $output = $this->commandTester->getDisplay(); - $this->assertEquals('Long URL: ' . $expectedUrl . PHP_EOL, $output); + self::assertEquals('Long URL: ' . $expectedUrl . PHP_EOL, $output); } /** @test */ @@ -59,6 +59,6 @@ class ResolveUrlCommandTest extends TestCase $this->commandTester->execute(['shortCode' => $shortCode]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString(sprintf('No URL found with short code "%s"', $shortCode), $output); + self::assertStringContainsString(sprintf('No URL found with short code "%s"', $shortCode), $output); } } diff --git a/module/CLI/test/Command/Tag/CreateTagCommandTest.php b/module/CLI/test/Command/Tag/CreateTagCommandTest.php index e156cf28..c0a25371 100644 --- a/module/CLI/test/Command/Tag/CreateTagCommandTest.php +++ b/module/CLI/test/Command/Tag/CreateTagCommandTest.php @@ -34,7 +34,7 @@ class CreateTagCommandTest extends TestCase $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('You have to provide at least one tag name', $output); + self::assertStringContainsString('You have to provide at least one tag name', $output); } /** @test */ @@ -48,7 +48,7 @@ class CreateTagCommandTest extends TestCase ]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('Tags properly created', $output); + self::assertStringContainsString('Tags properly created', $output); $createTags->shouldHaveBeenCalled(); } } diff --git a/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php b/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php index 27a95de8..d76f1c7d 100644 --- a/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php +++ b/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php @@ -33,7 +33,7 @@ class DeleteTagsCommandTest extends TestCase $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('You have to provide at least one tag name', $output); + self::assertStringContainsString('You have to provide at least one tag name', $output); } /** @test */ @@ -48,7 +48,7 @@ class DeleteTagsCommandTest extends TestCase ]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('Tags properly deleted', $output); + self::assertStringContainsString('Tags properly deleted', $output); $deleteTags->shouldHaveBeenCalled(); } } diff --git a/module/CLI/test/Command/Tag/ListTagsCommandTest.php b/module/CLI/test/Command/Tag/ListTagsCommandTest.php index b6087307..d9b28fb0 100644 --- a/module/CLI/test/Command/Tag/ListTagsCommandTest.php +++ b/module/CLI/test/Command/Tag/ListTagsCommandTest.php @@ -37,7 +37,7 @@ class ListTagsCommandTest extends TestCase $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('No tags found', $output); + self::assertStringContainsString('No tags found', $output); $tagsInfo->shouldHaveBeenCalled(); } @@ -52,12 +52,12 @@ class ListTagsCommandTest extends TestCase $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('| foo', $output); - $this->assertStringContainsString('| bar', $output); - $this->assertStringContainsString('| 10 ', $output); - $this->assertStringContainsString('| 2 ', $output); - $this->assertStringContainsString('| 7 ', $output); - $this->assertStringContainsString('| 32 ', $output); + self::assertStringContainsString('| foo', $output); + self::assertStringContainsString('| bar', $output); + self::assertStringContainsString('| 10 ', $output); + self::assertStringContainsString('| 2 ', $output); + self::assertStringContainsString('| 7 ', $output); + self::assertStringContainsString('| 32 ', $output); $tagsInfo->shouldHaveBeenCalled(); } } diff --git a/module/CLI/test/Command/Tag/RenameTagCommandTest.php b/module/CLI/test/Command/Tag/RenameTagCommandTest.php index ee499c48..b9685668 100644 --- a/module/CLI/test/Command/Tag/RenameTagCommandTest.php +++ b/module/CLI/test/Command/Tag/RenameTagCommandTest.php @@ -42,7 +42,7 @@ class RenameTagCommandTest extends TestCase ]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('Tag with name "foo" could not be found', $output); + self::assertStringContainsString('Tag with name "foo" could not be found', $output); $renameTag->shouldHaveBeenCalled(); } @@ -59,7 +59,7 @@ class RenameTagCommandTest extends TestCase ]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('Tag properly renamed', $output); + self::assertStringContainsString('Tag properly renamed', $output); $renameTag->shouldHaveBeenCalled(); } } diff --git a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php index 803ae472..3eb89f57 100644 --- a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php @@ -92,11 +92,11 @@ class LocateVisitsCommandTest extends TestCase $this->commandTester->execute($args); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('Processing IP 1.2.3.0', $output); + self::assertStringContainsString('Processing IP 1.2.3.0', $output); if ($expectWarningPrint) { - $this->assertStringContainsString('Continue at your own risk', $output); + self::assertStringContainsString('Continue at your own risk', $output); } else { - $this->assertStringNotContainsString('Continue at your own risk', $output); + self::assertStringNotContainsString('Continue at your own risk', $output); } $locateVisits->shouldHaveBeenCalledTimes($expectedUnlocatedCalls); $locateEmptyVisits->shouldHaveBeenCalledTimes($expectedEmptyCalls); @@ -132,11 +132,11 @@ class LocateVisitsCommandTest extends TestCase $this->commandTester->execute([], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString($message, $output); + self::assertStringContainsString($message, $output); if (empty($address)) { - $this->assertStringNotContainsString('Processing IP', $output); + self::assertStringNotContainsString('Processing IP', $output); } else { - $this->assertStringContainsString('Processing IP', $output); + self::assertStringContainsString('Processing IP', $output); } $locateVisits->shouldHaveBeenCalledOnce(); $resolveIpLocation->shouldNotHaveBeenCalled(); @@ -164,7 +164,7 @@ class LocateVisitsCommandTest extends TestCase $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('An error occurred while locating IP. Skipped', $output); + self::assertStringContainsString('An error occurred while locating IP. Skipped', $output); $locateVisits->shouldHaveBeenCalledOnce(); $resolveIpLocation->shouldHaveBeenCalledOnce(); } @@ -192,7 +192,7 @@ class LocateVisitsCommandTest extends TestCase $this->commandTester->execute([], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString( + self::assertStringContainsString( sprintf('Command "%s" is already in progress. Skipping.', LocateVisitsCommand::NAME), $output, ); @@ -222,11 +222,11 @@ class LocateVisitsCommandTest extends TestCase $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString( + self::assertStringContainsString( sprintf('%s GeoLite2 database...', $olderDbExists ? 'Updating' : 'Downloading'), $output, ); - $this->assertStringContainsString($expectedMessage, $output); + self::assertStringContainsString($expectedMessage, $output); $locateVisits->shouldHaveBeenCalledTimes((int) $olderDbExists); $checkDbUpdate->shouldHaveBeenCalledOnce(); } @@ -243,7 +243,7 @@ class LocateVisitsCommandTest extends TestCase $this->commandTester->execute(['--all' => true]); $output = $this->commandTester->getDisplay(); - $this->assertStringContainsString('The --all flag has no effect on its own', $output); + self::assertStringContainsString('The --all flag has no effect on its own', $output); } /** diff --git a/module/CLI/test/ConfigProviderTest.php b/module/CLI/test/ConfigProviderTest.php index baa4f311..7d1e5059 100644 --- a/module/CLI/test/ConfigProviderTest.php +++ b/module/CLI/test/ConfigProviderTest.php @@ -21,7 +21,7 @@ class ConfigProviderTest extends TestCase { $config = ($this->configProvider)(); - $this->assertArrayHasKey('cli', $config); - $this->assertArrayHasKey('dependencies', $config); + self::assertArrayHasKey('cli', $config); + self::assertArrayHasKey('dependencies', $config); } } diff --git a/module/CLI/test/Exception/GeolocationDbUpdateFailedExceptionTest.php b/module/CLI/test/Exception/GeolocationDbUpdateFailedExceptionTest.php index 21a1e006..33d7d76e 100644 --- a/module/CLI/test/Exception/GeolocationDbUpdateFailedExceptionTest.php +++ b/module/CLI/test/Exception/GeolocationDbUpdateFailedExceptionTest.php @@ -20,13 +20,13 @@ class GeolocationDbUpdateFailedExceptionTest extends TestCase { $e = GeolocationDbUpdateFailedException::create($olderDbExists, $prev); - $this->assertEquals($olderDbExists, $e->olderDbExists()); - $this->assertEquals( + self::assertEquals($olderDbExists, $e->olderDbExists()); + self::assertEquals( 'An error occurred while updating geolocation database, and an older version could not be found', $e->getMessage(), ); - $this->assertEquals(0, $e->getCode()); - $this->assertEquals($prev, $e->getPrevious()); + self::assertEquals(0, $e->getCode()); + self::assertEquals($prev, $e->getPrevious()); } public function provideCreateArgs(): iterable diff --git a/module/CLI/test/Factory/ApplicationFactoryTest.php b/module/CLI/test/Factory/ApplicationFactoryTest.php index 043349ab..b05e291e 100644 --- a/module/CLI/test/Factory/ApplicationFactoryTest.php +++ b/module/CLI/test/Factory/ApplicationFactoryTest.php @@ -37,9 +37,9 @@ class ApplicationFactoryTest extends TestCase $instance = ($this->factory)($sm); - $this->assertTrue($instance->has('foo')); - $this->assertTrue($instance->has('bar')); - $this->assertFalse($instance->has('baz')); + self::assertTrue($instance->has('foo')); + self::assertTrue($instance->has('bar')); + self::assertFalse($instance->has('baz')); } private function createServiceManager(array $config = []): ServiceManager diff --git a/module/CLI/test/Util/GeolocationDbUpdaterTest.php b/module/CLI/test/Util/GeolocationDbUpdaterTest.php index b5346629..91bf5627 100644 --- a/module/CLI/test/Util/GeolocationDbUpdaterTest.php +++ b/module/CLI/test/Util/GeolocationDbUpdaterTest.php @@ -50,7 +50,7 @@ class GeolocationDbUpdaterTest extends TestCase /** @test */ public function exceptionIsThrownWhenOlderDbDoesNotExistAndDownloadFails(): void { - $mustBeUpdated = fn () => $this->assertTrue(true); + $mustBeUpdated = fn () => self::assertTrue(true); $prev = new RuntimeException(''); $fileExists = $this->dbUpdater->databaseFileExists()->willReturn(false); @@ -59,12 +59,12 @@ class GeolocationDbUpdaterTest extends TestCase try { $this->geolocationDbUpdater->checkDbUpdate($mustBeUpdated); - $this->assertTrue(false); // If this is reached, the test will fail + self::assertTrue(false); // If this is reached, the test will fail } catch (Throwable $e) { /** @var GeolocationDbUpdateFailedException $e */ - $this->assertInstanceOf(GeolocationDbUpdateFailedException::class, $e); - $this->assertSame($prev, $e->getPrevious()); - $this->assertFalse($e->olderDbExists()); + self::assertInstanceOf(GeolocationDbUpdateFailedException::class, $e); + self::assertSame($prev, $e->getPrevious()); + self::assertFalse($e->olderDbExists()); } $fileExists->shouldHaveBeenCalledOnce(); @@ -95,12 +95,12 @@ class GeolocationDbUpdaterTest extends TestCase try { $this->geolocationDbUpdater->checkDbUpdate(); - $this->assertTrue(false); // If this is reached, the test will fail + self::assertTrue(false); // If this is reached, the test will fail } catch (Throwable $e) { /** @var GeolocationDbUpdateFailedException $e */ - $this->assertInstanceOf(GeolocationDbUpdateFailedException::class, $e); - $this->assertSame($prev, $e->getPrevious()); - $this->assertTrue($e->olderDbExists()); + self::assertInstanceOf(GeolocationDbUpdateFailedException::class, $e); + self::assertSame($prev, $e->getPrevious()); + self::assertTrue($e->olderDbExists()); } $fileExists->shouldHaveBeenCalledOnce(); diff --git a/module/CLI/test/Util/ShlinkTableTest.php b/module/CLI/test/Util/ShlinkTableTest.php index 23c4eb32..4d270854 100644 --- a/module/CLI/test/Util/ShlinkTableTest.php +++ b/module/CLI/test/Util/ShlinkTableTest.php @@ -60,6 +60,6 @@ class ShlinkTableTest extends TestCase $baseTable = $ref->getProperty('baseTable'); $baseTable->setAccessible(true); - $this->assertInstanceOf(Table::class, $baseTable->getValue($instance)); + self::assertInstanceOf(Table::class, $baseTable->getValue($instance)); } } diff --git a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php index 57a174c7..ad99a9a3 100644 --- a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php @@ -57,25 +57,25 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $this->getEntityManager()->flush(); - $this->assertSame($regularOne, $this->repo->findOneWithDomainFallback($regularOne->getShortCode())); - $this->assertSame($regularOne, $this->repo->findOneWithDomainFallback( + self::assertSame($regularOne, $this->repo->findOneWithDomainFallback($regularOne->getShortCode())); + self::assertSame($regularOne, $this->repo->findOneWithDomainFallback( $withDomainDuplicatingRegular->getShortCode(), )); - $this->assertSame($withDomain, $this->repo->findOneWithDomainFallback( + self::assertSame($withDomain, $this->repo->findOneWithDomainFallback( $withDomain->getShortCode(), 'example.com', )); - $this->assertSame( + self::assertSame( $withDomainDuplicatingRegular, $this->repo->findOneWithDomainFallback($withDomainDuplicatingRegular->getShortCode(), 'doma.in'), ); - $this->assertSame( + self::assertSame( $regularOne, $this->repo->findOneWithDomainFallback($withDomainDuplicatingRegular->getShortCode(), 'other-domain.com'), ); - $this->assertNull($this->repo->findOneWithDomainFallback('invalid')); - $this->assertNull($this->repo->findOneWithDomainFallback($withDomain->getShortCode())); - $this->assertNull($this->repo->findOneWithDomainFallback($withDomain->getShortCode(), 'other-domain.com')); + self::assertNull($this->repo->findOneWithDomainFallback('invalid')); + self::assertNull($this->repo->findOneWithDomainFallback($withDomain->getShortCode())); + self::assertNull($this->repo->findOneWithDomainFallback($withDomain->getShortCode(), 'other-domain.com')); } /** @test */ @@ -87,7 +87,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase } $this->getEntityManager()->flush(); - $this->assertEquals($count, $this->repo->countList()); + self::assertEquals($count, $this->repo->countList()); } /** @test */ @@ -116,37 +116,37 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $this->getEntityManager()->flush(); $result = $this->repo->findList(null, null, 'foo', ['bar']); - $this->assertCount(1, $result); - $this->assertEquals(1, $this->repo->countList('foo', ['bar'])); - $this->assertSame($foo, $result[0]); + self::assertCount(1, $result); + self::assertEquals(1, $this->repo->countList('foo', ['bar'])); + self::assertSame($foo, $result[0]); $result = $this->repo->findList(); - $this->assertCount(3, $result); + self::assertCount(3, $result); $result = $this->repo->findList(2); - $this->assertCount(2, $result); + self::assertCount(2, $result); $result = $this->repo->findList(2, 1); - $this->assertCount(2, $result); + self::assertCount(2, $result); - $this->assertCount(1, $this->repo->findList(2, 2)); + self::assertCount(1, $this->repo->findList(2, 2)); $result = $this->repo->findList(null, null, null, [], ShortUrlsOrdering::fromRawData([ 'orderBy' => ['visits' => 'DESC'], ])); - $this->assertCount(3, $result); - $this->assertSame($bar, $result[0]); + self::assertCount(3, $result); + self::assertSame($bar, $result[0]); $result = $this->repo->findList(null, null, null, [], null, new DateRange(null, Chronos::now()->subDays(2))); - $this->assertCount(1, $result); - $this->assertEquals(1, $this->repo->countList(null, [], new DateRange(null, Chronos::now()->subDays(2)))); - $this->assertSame($foo2, $result[0]); + self::assertCount(1, $result); + self::assertEquals(1, $this->repo->countList(null, [], new DateRange(null, Chronos::now()->subDays(2)))); + self::assertSame($foo2, $result[0]); - $this->assertCount( + self::assertCount( 2, $this->repo->findList(null, null, null, [], null, new DateRange(Chronos::now()->subDays(2))), ); - $this->assertEquals(2, $this->repo->countList(null, [], new DateRange(Chronos::now()->subDays(2)))); + self::assertEquals(2, $this->repo->countList(null, [], new DateRange(Chronos::now()->subDays(2)))); } /** @test */ @@ -163,11 +163,11 @@ class ShortUrlRepositoryTest extends DatabaseTestCase 'orderBy' => ['longUrl' => 'ASC'], ])); - $this->assertCount(count($urls), $result); - $this->assertEquals('a', $result[0]->getLongUrl()); - $this->assertEquals('b', $result[1]->getLongUrl()); - $this->assertEquals('c', $result[2]->getLongUrl()); - $this->assertEquals('z', $result[3]->getLongUrl()); + self::assertCount(count($urls), $result); + self::assertEquals('a', $result[0]->getLongUrl()); + self::assertEquals('b', $result[1]->getLongUrl()); + self::assertEquals('c', $result[2]->getLongUrl()); + self::assertEquals('z', $result[3]->getLongUrl()); } /** @test */ @@ -184,12 +184,12 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $this->getEntityManager()->flush(); - $this->assertTrue($this->repo->shortCodeIsInUse('my-cool-slug')); - $this->assertFalse($this->repo->shortCodeIsInUse('my-cool-slug', 'doma.in')); - $this->assertFalse($this->repo->shortCodeIsInUse('slug-not-in-use')); - $this->assertFalse($this->repo->shortCodeIsInUse('another-slug')); - $this->assertFalse($this->repo->shortCodeIsInUse('another-slug', 'example.com')); - $this->assertTrue($this->repo->shortCodeIsInUse('another-slug', 'doma.in')); + self::assertTrue($this->repo->shortCodeIsInUse('my-cool-slug')); + self::assertFalse($this->repo->shortCodeIsInUse('my-cool-slug', 'doma.in')); + self::assertFalse($this->repo->shortCodeIsInUse('slug-not-in-use')); + self::assertFalse($this->repo->shortCodeIsInUse('another-slug')); + self::assertFalse($this->repo->shortCodeIsInUse('another-slug', 'example.com')); + self::assertTrue($this->repo->shortCodeIsInUse('another-slug', 'doma.in')); } /** @test */ @@ -206,21 +206,21 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $this->getEntityManager()->flush(); - $this->assertNotNull($this->repo->findOne('my-cool-slug')); - $this->assertNull($this->repo->findOne('my-cool-slug', 'doma.in')); - $this->assertNull($this->repo->findOne('slug-not-in-use')); - $this->assertNull($this->repo->findOne('another-slug')); - $this->assertNull($this->repo->findOne('another-slug', 'example.com')); - $this->assertNotNull($this->repo->findOne('another-slug', 'doma.in')); + self::assertNotNull($this->repo->findOne('my-cool-slug')); + self::assertNull($this->repo->findOne('my-cool-slug', 'doma.in')); + self::assertNull($this->repo->findOne('slug-not-in-use')); + self::assertNull($this->repo->findOne('another-slug')); + self::assertNull($this->repo->findOne('another-slug', 'example.com')); + self::assertNotNull($this->repo->findOne('another-slug', 'doma.in')); } /** @test */ public function findOneMatchingReturnsNullForNonExistingShortUrls(): void { - $this->assertNull($this->repo->findOneMatching('', [], ShortUrlMeta::createEmpty())); - $this->assertNull($this->repo->findOneMatching('foobar', [], ShortUrlMeta::createEmpty())); - $this->assertNull($this->repo->findOneMatching('foobar', ['foo', 'bar'], ShortUrlMeta::createEmpty())); - $this->assertNull($this->repo->findOneMatching('foobar', ['foo', 'bar'], ShortUrlMeta::fromRawData([ + 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([ 'validSince' => Chronos::parse('2020-03-05 20:18:30'), 'customSlug' => 'this_slug_does_not_exist', ]))); @@ -253,33 +253,33 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $this->getEntityManager()->flush(); - $this->assertSame( + self::assertSame( $shortUrl, $this->repo->findOneMatching('foo', ['foo', 'bar'], ShortUrlMeta::fromRawData(['validSince' => $start])), ); - $this->assertSame( + self::assertSame( $shortUrl2, $this->repo->findOneMatching('bar', [], ShortUrlMeta::fromRawData(['validUntil' => $end])), ); - $this->assertSame( + self::assertSame( $shortUrl3, $this->repo->findOneMatching('baz', [], ShortUrlMeta::fromRawData([ 'validSince' => $start, 'validUntil' => $end, ])), ); - $this->assertSame( + self::assertSame( $shortUrl4, $this->repo->findOneMatching('foo', [], ShortUrlMeta::fromRawData([ 'customSlug' => 'custom', 'validUntil' => $end, ])), ); - $this->assertSame( + self::assertSame( $shortUrl5, $this->repo->findOneMatching('foo', [], ShortUrlMeta::fromRawData(['maxVisits' => 3])), ); - $this->assertSame( + self::assertSame( $shortUrl6, $this->repo->findOneMatching('foo', [], ShortUrlMeta::fromRawData(['domain' => 'doma.in'])), ); @@ -307,15 +307,15 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $this->getEntityManager()->flush(); - $this->assertSame( + self::assertSame( $shortUrl1, $this->repo->findOneMatching('foo', $tags, ShortUrlMeta::fromRawData($meta)), ); - $this->assertNotSame( + self::assertNotSame( $shortUrl2, $this->repo->findOneMatching('foo', $tags, ShortUrlMeta::fromRawData($meta)), ); - $this->assertNotSame( + self::assertNotSame( $shortUrl3, $this->repo->findOneMatching('foo', $tags, ShortUrlMeta::fromRawData($meta)), ); diff --git a/module/Core/test-db/Repository/TagRepositoryTest.php b/module/Core/test-db/Repository/TagRepositoryTest.php index 8e1a11ef..9f8b9893 100644 --- a/module/Core/test-db/Repository/TagRepositoryTest.php +++ b/module/Core/test-db/Repository/TagRepositoryTest.php @@ -32,7 +32,7 @@ class TagRepositoryTest extends DatabaseTestCase /** @test */ public function deleteByNameDoesNothingWhenEmptyListIsProvided(): void { - $this->assertEquals(0, $this->repo->deleteByName([])); + self::assertEquals(0, $this->repo->deleteByName([])); } /** @test */ @@ -46,7 +46,7 @@ class TagRepositoryTest extends DatabaseTestCase } $this->getEntityManager()->flush(); - $this->assertEquals(2, $this->repo->deleteByName($toDelete)); + self::assertEquals(2, $this->repo->deleteByName($toDelete)); } /** @test */ @@ -79,20 +79,20 @@ class TagRepositoryTest extends DatabaseTestCase $result = $this->repo->findTagsWithInfo(); - $this->assertCount(4, $result); - $this->assertEquals( + self::assertCount(4, $result); + self::assertEquals( ['tag' => $tags[3], 'shortUrlsCount' => 0, 'visitsCount' => 0], $result[0]->jsonSerialize(), ); - $this->assertEquals( + self::assertEquals( ['tag' => $tags[1], 'shortUrlsCount' => 1, 'visitsCount' => 3], $result[1]->jsonSerialize(), ); - $this->assertEquals( + self::assertEquals( ['tag' => $tags[2], 'shortUrlsCount' => 1, 'visitsCount' => 3], $result[2]->jsonSerialize(), ); - $this->assertEquals( + self::assertEquals( ['tag' => $tags[0], 'shortUrlsCount' => 2, 'visitsCount' => 4], $result[3]->jsonSerialize(), ); diff --git a/module/Core/test-db/Repository/VisitRepositoryTest.php b/module/Core/test-db/Repository/VisitRepositoryTest.php index 529a5ae0..f6df4b9b 100644 --- a/module/Core/test-db/Repository/VisitRepositoryTest.php +++ b/module/Core/test-db/Repository/VisitRepositoryTest.php @@ -75,9 +75,9 @@ class VisitRepositoryTest extends DatabaseTestCase // Important! assertCount will not work here, as this iterable object loads data dynamically and the count // is 0 if not iterated - $this->assertEquals(2, $countIterable($unlocated)); - $this->assertEquals(4, $countIterable($withEmptyLocation)); - $this->assertEquals(6, $countIterable($all)); + self::assertEquals(2, $countIterable($unlocated)); + self::assertEquals(4, $countIterable($withEmptyLocation)); + self::assertEquals(6, $countIterable($all)); } public function provideBlockSize(): iterable @@ -90,22 +90,22 @@ class VisitRepositoryTest extends DatabaseTestCase { [$shortCode, $domain] = $this->createShortUrlsAndVisits(); - $this->assertCount(0, $this->repo->findVisitsByShortCode('invalid')); - $this->assertCount(6, $this->repo->findVisitsByShortCode($shortCode)); - $this->assertCount(3, $this->repo->findVisitsByShortCode($shortCode, $domain)); - $this->assertCount(2, $this->repo->findVisitsByShortCode($shortCode, null, new DateRange( + self::assertCount(0, $this->repo->findVisitsByShortCode('invalid')); + self::assertCount(6, $this->repo->findVisitsByShortCode($shortCode)); + self::assertCount(3, $this->repo->findVisitsByShortCode($shortCode, $domain)); + self::assertCount(2, $this->repo->findVisitsByShortCode($shortCode, null, new DateRange( Chronos::parse('2016-01-02'), Chronos::parse('2016-01-03'), ))); - $this->assertCount(4, $this->repo->findVisitsByShortCode($shortCode, null, new DateRange( + self::assertCount(4, $this->repo->findVisitsByShortCode($shortCode, null, new DateRange( Chronos::parse('2016-01-03'), ))); - $this->assertCount(1, $this->repo->findVisitsByShortCode($shortCode, $domain, new DateRange( + self::assertCount(1, $this->repo->findVisitsByShortCode($shortCode, $domain, new DateRange( Chronos::parse('2016-01-03'), ))); - $this->assertCount(3, $this->repo->findVisitsByShortCode($shortCode, null, null, 3, 2)); - $this->assertCount(2, $this->repo->findVisitsByShortCode($shortCode, null, null, 5, 4)); - $this->assertCount(1, $this->repo->findVisitsByShortCode($shortCode, $domain, null, 3, 2)); + self::assertCount(3, $this->repo->findVisitsByShortCode($shortCode, null, null, 3, 2)); + self::assertCount(2, $this->repo->findVisitsByShortCode($shortCode, null, null, 5, 4)); + self::assertCount(1, $this->repo->findVisitsByShortCode($shortCode, $domain, null, 3, 2)); } /** @test */ @@ -113,17 +113,17 @@ class VisitRepositoryTest extends DatabaseTestCase { [$shortCode, $domain] = $this->createShortUrlsAndVisits(); - $this->assertEquals(0, $this->repo->countVisitsByShortCode('invalid')); - $this->assertEquals(6, $this->repo->countVisitsByShortCode($shortCode)); - $this->assertEquals(3, $this->repo->countVisitsByShortCode($shortCode, $domain)); - $this->assertEquals(2, $this->repo->countVisitsByShortCode($shortCode, null, new DateRange( + self::assertEquals(0, $this->repo->countVisitsByShortCode('invalid')); + self::assertEquals(6, $this->repo->countVisitsByShortCode($shortCode)); + self::assertEquals(3, $this->repo->countVisitsByShortCode($shortCode, $domain)); + self::assertEquals(2, $this->repo->countVisitsByShortCode($shortCode, null, new DateRange( Chronos::parse('2016-01-02'), Chronos::parse('2016-01-03'), ))); - $this->assertEquals(4, $this->repo->countVisitsByShortCode($shortCode, null, new DateRange( + self::assertEquals(4, $this->repo->countVisitsByShortCode($shortCode, null, new DateRange( Chronos::parse('2016-01-03'), ))); - $this->assertEquals(1, $this->repo->countVisitsByShortCode($shortCode, $domain, new DateRange( + self::assertEquals(1, $this->repo->countVisitsByShortCode($shortCode, $domain, new DateRange( Chronos::parse('2016-01-03'), ))); } @@ -147,13 +147,13 @@ class VisitRepositoryTest extends DatabaseTestCase $this->getEntityManager()->flush(); - $this->assertCount(0, $this->repo->findVisitsByTag('invalid')); - $this->assertCount(18, $this->repo->findVisitsByTag((string) $foo)); - $this->assertCount(6, $this->repo->findVisitsByTag((string) $foo, new DateRange( + self::assertCount(0, $this->repo->findVisitsByTag('invalid')); + self::assertCount(18, $this->repo->findVisitsByTag((string) $foo)); + self::assertCount(6, $this->repo->findVisitsByTag((string) $foo, new DateRange( Chronos::parse('2016-01-02'), Chronos::parse('2016-01-03'), ))); - $this->assertCount(12, $this->repo->findVisitsByTag((string) $foo, new DateRange( + self::assertCount(12, $this->repo->findVisitsByTag((string) $foo, new DateRange( Chronos::parse('2016-01-03'), ))); } @@ -174,13 +174,13 @@ class VisitRepositoryTest extends DatabaseTestCase $this->getEntityManager()->flush(); - $this->assertEquals(0, $this->repo->countVisitsByTag('invalid')); - $this->assertEquals(12, $this->repo->countVisitsByTag((string) $foo)); - $this->assertEquals(4, $this->repo->countVisitsByTag((string) $foo, new DateRange( + self::assertEquals(0, $this->repo->countVisitsByTag('invalid')); + self::assertEquals(12, $this->repo->countVisitsByTag((string) $foo)); + self::assertEquals(4, $this->repo->countVisitsByTag((string) $foo, new DateRange( Chronos::parse('2016-01-02'), Chronos::parse('2016-01-03'), ))); - $this->assertEquals(8, $this->repo->countVisitsByTag((string) $foo, new DateRange( + self::assertEquals(8, $this->repo->countVisitsByTag((string) $foo, new DateRange( Chronos::parse('2016-01-03'), ))); } diff --git a/module/Core/test/Action/PixelActionTest.php b/module/Core/test/Action/PixelActionTest.php index f4aed872..c678fa3a 100644 --- a/module/Core/test/Action/PixelActionTest.php +++ b/module/Core/test/Action/PixelActionTest.php @@ -47,8 +47,8 @@ class PixelActionTest extends TestCase $request = (new ServerRequest())->withAttribute('shortCode', $shortCode); $response = $this->action->process($request, $this->prophesize(RequestHandlerInterface::class)->reveal()); - $this->assertInstanceOf(PixelResponse::class, $response); - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals('image/gif', $response->getHeaderLine('content-type')); + self::assertInstanceOf(PixelResponse::class, $response); + self::assertEquals(200, $response->getStatusCode()); + self::assertEquals('image/gif', $response->getHeaderLine('content-type')); } } diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index 236288dd..06e602e2 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -77,8 +77,8 @@ class QrCodeActionTest extends TestCase $delegate->reveal(), ); - $this->assertInstanceOf(QrCodeResponse::class, $resp); - $this->assertEquals(200, $resp->getStatusCode()); + self::assertInstanceOf(QrCodeResponse::class, $resp); + self::assertEquals(200, $resp->getStatusCode()); $delegate->handle(Argument::any())->shouldHaveBeenCalledTimes(0); } @@ -97,7 +97,7 @@ class QrCodeActionTest extends TestCase $resp = $this->action->process($req, $delegate->reveal()); - $this->assertEquals($expectedContentType, $resp->getHeaderLine('Content-Type')); + self::assertEquals($expectedContentType, $resp->getHeaderLine('Content-Type')); } public function provideQueries(): iterable diff --git a/module/Core/test/Action/RedirectActionTest.php b/module/Core/test/Action/RedirectActionTest.php index 1a6bb617..495162bb 100644 --- a/module/Core/test/Action/RedirectActionTest.php +++ b/module/Core/test/Action/RedirectActionTest.php @@ -60,10 +60,10 @@ class RedirectActionTest extends TestCase $request = (new ServerRequest())->withAttribute('shortCode', $shortCode)->withQueryParams($query); $response = $this->action->process($request, $this->prophesize(RequestHandlerInterface::class)->reveal()); - $this->assertInstanceOf(Response\RedirectResponse::class, $response); - $this->assertEquals(302, $response->getStatusCode()); - $this->assertTrue($response->hasHeader('Location')); - $this->assertEquals($expectedUrl, $response->getHeaderLine('Location')); + self::assertInstanceOf(Response\RedirectResponse::class, $response); + self::assertEquals(302, $response->getStatusCode()); + self::assertTrue($response->hasHeader('Location')); + self::assertEquals($expectedUrl, $response->getHeaderLine('Location')); $shortCodeToUrl->shouldHaveBeenCalledOnce(); $track->shouldHaveBeenCalledTimes(array_key_exists('foobar', $query) ? 0 : 1); } @@ -135,10 +135,10 @@ class RedirectActionTest extends TestCase $request = (new ServerRequest())->withAttribute('shortCode', $shortCode); $response = $this->action->process($request, $this->prophesize(RequestHandlerInterface::class)->reveal()); - $this->assertInstanceOf(Response\RedirectResponse::class, $response); - $this->assertEquals($expectedStatus, $response->getStatusCode()); - $this->assertEquals($response->hasHeader('Cache-Control'), $expectedCacheControl !== null); - $this->assertEquals($response->getHeaderLine('Cache-Control'), $expectedCacheControl ?? ''); + self::assertInstanceOf(Response\RedirectResponse::class, $response); + self::assertEquals($expectedStatus, $response->getStatusCode()); + self::assertEquals($response->hasHeader('Cache-Control'), $expectedCacheControl !== null); + self::assertEquals($response->getHeaderLine('Cache-Control'), $expectedCacheControl ?? ''); } public function provideRedirectConfigs(): iterable diff --git a/module/Core/test/Config/BasePathPrefixerTest.php b/module/Core/test/Config/BasePathPrefixerTest.php index 0e08fa89..e0949514 100644 --- a/module/Core/test/Config/BasePathPrefixerTest.php +++ b/module/Core/test/Config/BasePathPrefixerTest.php @@ -32,9 +32,9 @@ class BasePathPrefixerTest extends TestCase 'url_shortener' => $urlShortener, ] = ($this->prefixer)($originalConfig); - $this->assertEquals($expectedRoutes, $routes); - $this->assertEquals($expectedMiddlewares, $middlewares); - $this->assertEquals([ + self::assertEquals($expectedRoutes, $routes); + self::assertEquals($expectedMiddlewares, $middlewares); + self::assertEquals([ 'domain' => [ 'hostname' => $expectedHostname, ], diff --git a/module/Core/test/Config/DeprecatedConfigParserTest.php b/module/Core/test/Config/DeprecatedConfigParserTest.php index 0da1d314..c58d9050 100644 --- a/module/Core/test/Config/DeprecatedConfigParserTest.php +++ b/module/Core/test/Config/DeprecatedConfigParserTest.php @@ -29,7 +29,7 @@ class DeprecatedConfigParserTest extends TestCase $result = ($this->postProcessor)($config); - $this->assertEquals($config, $result); + self::assertEquals($config, $result); } /** @test */ @@ -46,7 +46,7 @@ class DeprecatedConfigParserTest extends TestCase $result = ($this->postProcessor)($config); - $this->assertEquals($config, $result); + self::assertEquals($config, $result); } /** @test */ @@ -68,7 +68,7 @@ class DeprecatedConfigParserTest extends TestCase $result = ($this->postProcessor)($config); - $this->assertEquals($expected, $result); + self::assertEquals($expected, $result); } /** @test */ @@ -89,7 +89,7 @@ class DeprecatedConfigParserTest extends TestCase $result = ($this->postProcessor)($config); - $this->assertEquals($expected, $result); + self::assertEquals($expected, $result); } /** @test */ @@ -106,6 +106,6 @@ class DeprecatedConfigParserTest extends TestCase $result = ($this->postProcessor)($config); - $this->assertEquals($expected, $result); + self::assertEquals($expected, $result); } } diff --git a/module/Core/test/Config/SimplifiedConfigParserTest.php b/module/Core/test/Config/SimplifiedConfigParserTest.php index dc85196a..6f040bb6 100644 --- a/module/Core/test/Config/SimplifiedConfigParserTest.php +++ b/module/Core/test/Config/SimplifiedConfigParserTest.php @@ -153,6 +153,6 @@ class SimplifiedConfigParserTest extends TestCase $result = ($this->postProcessor)(array_merge($config, $simplified)); - $this->assertEquals(array_merge($expected, $simplified), $result); + self::assertEquals(array_merge($expected, $simplified), $result); } } diff --git a/module/Core/test/ConfigProviderTest.php b/module/Core/test/ConfigProviderTest.php index 71cd90d1..2660803b 100644 --- a/module/Core/test/ConfigProviderTest.php +++ b/module/Core/test/ConfigProviderTest.php @@ -21,9 +21,9 @@ class ConfigProviderTest extends TestCase { $config = $this->configProvider->__invoke(); - $this->assertArrayHasKey('routes', $config); - $this->assertArrayHasKey('dependencies', $config); - $this->assertArrayHasKey('templates', $config); - $this->assertArrayHasKey('mezzio', $config); + self::assertArrayHasKey('routes', $config); + self::assertArrayHasKey('dependencies', $config); + self::assertArrayHasKey('templates', $config); + self::assertArrayHasKey('mezzio', $config); } } diff --git a/module/Core/test/Domain/Resolver/PersistenceDomainResolverTest.php b/module/Core/test/Domain/Resolver/PersistenceDomainResolverTest.php index d3769af9..c9f71851 100644 --- a/module/Core/test/Domain/Resolver/PersistenceDomainResolverTest.php +++ b/module/Core/test/Domain/Resolver/PersistenceDomainResolverTest.php @@ -27,7 +27,7 @@ class PersistenceDomainResolverTest extends TestCase { $getRepository = $this->em->getRepository(Domain::class); - $this->assertNull($this->domainResolver->resolveDomain(null)); + self::assertNull($this->domainResolver->resolveDomain(null)); $getRepository->shouldNotHaveBeenCalled(); } @@ -44,10 +44,10 @@ class PersistenceDomainResolverTest extends TestCase $result = $this->domainResolver->resolveDomain($authority); if ($foundDomain !== null) { - $this->assertSame($result, $foundDomain); + self::assertSame($result, $foundDomain); } - $this->assertInstanceOf(Domain::class, $result); - $this->assertEquals($authority, $result->getAuthority()); + self::assertInstanceOf(Domain::class, $result); + self::assertEquals($authority, $result->getAuthority()); $findDomain->shouldHaveBeenCalledOnce(); $getRepository->shouldHaveBeenCalledOnce(); } diff --git a/module/Core/test/Domain/Resolver/SimpleDomainResolverTest.php b/module/Core/test/Domain/Resolver/SimpleDomainResolverTest.php index a0fa4bf1..ff5b6b90 100644 --- a/module/Core/test/Domain/Resolver/SimpleDomainResolverTest.php +++ b/module/Core/test/Domain/Resolver/SimpleDomainResolverTest.php @@ -26,10 +26,10 @@ class SimpleDomainResolverTest extends TestCase $result = $this->domainResolver->resolveDomain($domain); if ($domain === null) { - $this->assertNull($result); + self::assertNull($result); } else { - $this->assertInstanceOf(Domain::class, $result); - $this->assertEquals($domain, $result->getAuthority()); + self::assertInstanceOf(Domain::class, $result); + self::assertEquals($domain, $result->getAuthority()); } } diff --git a/module/Core/test/Entity/ShortUrlTest.php b/module/Core/test/Entity/ShortUrlTest.php index e410dedb..054182ff 100644 --- a/module/Core/test/Entity/ShortUrlTest.php +++ b/module/Core/test/Entity/ShortUrlTest.php @@ -53,7 +53,7 @@ class ShortUrlTest extends TestCase $shortUrl->regenerateShortCode(); $secondShortCode = $shortUrl->getShortCode(); - $this->assertNotEquals($firstShortCode, $secondShortCode); + self::assertNotEquals($firstShortCode, $secondShortCode); } /** @@ -66,7 +66,7 @@ class ShortUrlTest extends TestCase [ShortUrlMetaInputFilter::SHORT_CODE_LENGTH => $length], )); - $this->assertEquals($expectedLength, strlen($shortUrl->getShortCode())); + self::assertEquals($expectedLength, strlen($shortUrl->getShortCode())); } public function provideLengths(): iterable diff --git a/module/Core/test/Entity/TagTest.php b/module/Core/test/Entity/TagTest.php index 01b2f6ea..dcc21e71 100644 --- a/module/Core/test/Entity/TagTest.php +++ b/module/Core/test/Entity/TagTest.php @@ -13,6 +13,6 @@ class TagTest extends TestCase public function jsonSerializationOfTagsReturnsItsStringRepresentation(): void { $tag = new Tag('This is my name'); - $this->assertEquals((string) $tag, $tag->jsonSerialize()); + self::assertEquals((string) $tag, $tag->jsonSerialize()); } } diff --git a/module/Core/test/Entity/VisitLocationTest.php b/module/Core/test/Entity/VisitLocationTest.php index f662645d..057a1920 100644 --- a/module/Core/test/Entity/VisitLocationTest.php +++ b/module/Core/test/Entity/VisitLocationTest.php @@ -19,7 +19,7 @@ class VisitLocationTest extends TestCase $payload = new Location(...$args); $location = new VisitLocation($payload); - $this->assertEquals($isEmpty, $location->isEmpty()); + self::assertEquals($isEmpty, $location->isEmpty()); } public function provideArgs(): iterable diff --git a/module/Core/test/Entity/VisitTest.php b/module/Core/test/Entity/VisitTest.php index 73e41f12..9d75f793 100644 --- a/module/Core/test/Entity/VisitTest.php +++ b/module/Core/test/Entity/VisitTest.php @@ -21,7 +21,7 @@ class VisitTest extends TestCase { $visit = new Visit(new ShortUrl(''), new Visitor('Chrome', 'some site', '1.2.3.4'), true, $date); - $this->assertEquals([ + self::assertEquals([ 'referer' => 'some site', 'date' => ($date ?? $visit->getDate())->toAtomString(), 'userAgent' => 'Chrome', @@ -43,7 +43,7 @@ class VisitTest extends TestCase { $visit = new Visit(new ShortUrl(''), new Visitor('Chrome', 'some site', $address), $anonymize); - $this->assertEquals($expectedAddress, $visit->getRemoteAddr()); + self::assertEquals($expectedAddress, $visit->getRemoteAddr()); } public function provideAddresses(): iterable diff --git a/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php b/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php index 61288e02..2dc5f16d 100644 --- a/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php +++ b/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php @@ -45,8 +45,8 @@ class NotFoundRedirectHandlerTest extends TestCase $resp = $this->middleware->process($request, $next->reveal()); - $this->assertInstanceOf(Response\RedirectResponse::class, $resp); - $this->assertEquals($expectedRedirectTo, $resp->getHeaderLine('Location')); + self::assertInstanceOf(Response\RedirectResponse::class, $resp); + self::assertEquals($expectedRedirectTo, $resp->getHeaderLine('Location')); $handle->shouldNotHaveBeenCalled(); } @@ -93,7 +93,7 @@ class NotFoundRedirectHandlerTest extends TestCase $result = $this->middleware->process($req, $next->reveal()); - $this->assertSame($resp, $result); + self::assertSame($resp, $result); $handle->shouldHaveBeenCalledOnce(); } } diff --git a/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php b/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php index e43b3aab..a967c3f7 100644 --- a/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php +++ b/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php @@ -37,7 +37,7 @@ class NotFoundTemplateHandlerTest extends TestCase $resp = $this->handler->handle($request); - $this->assertInstanceOf(Response\HtmlResponse::class, $resp); + self::assertInstanceOf(Response\HtmlResponse::class, $resp); $render->shouldHaveBeenCalledOnce(); } diff --git a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php index 60113fc7..bb396983 100644 --- a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php +++ b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php @@ -37,7 +37,7 @@ class CloseDbConnectionEventListenerDelegatorTest extends TestCase ($this->delegator)($this->container->reveal(), '', $callback); - $this->assertTrue($callbackInvoked); + self::assertTrue($callbackInvoked); $getEm->shouldHaveBeenCalledOnce(); } } diff --git a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php index acc1784f..b75ec8dc 100644 --- a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php +++ b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php @@ -45,7 +45,7 @@ class CloseDbConnectionEventListenerTest extends TestCase // Ignore exceptions } - $this->assertTrue($wrappedWasCalled); + self::assertTrue($wrappedWasCalled); $close->shouldHaveBeenCalledOnce(); $getConn->shouldHaveBeenCalledOnce(); $clear->shouldHaveBeenCalledOnce(); diff --git a/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php b/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php index 087c0e0b..fc87e98c 100644 --- a/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php +++ b/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php @@ -112,7 +112,7 @@ class LocateShortUrlVisitTest extends TestCase ($this->locateVisit)($event); - $this->assertEquals($visit->getVisitLocation(), new VisitLocation(Location::emptyInstance())); + self::assertEquals($visit->getVisitLocation(), new VisitLocation(Location::emptyInstance())); $findVisit->shouldHaveBeenCalledOnce(); $flush->shouldHaveBeenCalledOnce(); $resolveIp->shouldNotHaveBeenCalled(); @@ -149,7 +149,7 @@ class LocateShortUrlVisitTest extends TestCase ($this->locateVisit)($event); - $this->assertEquals($visit->getVisitLocation(), new VisitLocation($location)); + self::assertEquals($visit->getVisitLocation(), new VisitLocation($location)); $findVisit->shouldHaveBeenCalledOnce(); $flush->shouldHaveBeenCalledOnce(); $resolveIp->shouldHaveBeenCalledOnce(); @@ -182,7 +182,7 @@ class LocateShortUrlVisitTest extends TestCase ($this->locateVisit)($event); - $this->assertEquals($visit->getVisitLocation(), new VisitLocation($location)); + self::assertEquals($visit->getVisitLocation(), new VisitLocation($location)); $findVisit->shouldHaveBeenCalledOnce(); $flush->shouldHaveBeenCalledOnce(); $resolveIp->shouldHaveBeenCalledOnce(); @@ -217,7 +217,7 @@ class LocateShortUrlVisitTest extends TestCase ($this->locateVisit)($event); - $this->assertNull($visit->getVisitLocation()); + self::assertNull($visit->getVisitLocation()); $findVisit->shouldHaveBeenCalledOnce(); $flush->shouldNotHaveBeenCalled(); $resolveIp->shouldNotHaveBeenCalled(); diff --git a/module/Core/test/Exception/DeleteShortUrlExceptionTest.php b/module/Core/test/Exception/DeleteShortUrlExceptionTest.php index 6e45521f..a7028a02 100644 --- a/module/Core/test/Exception/DeleteShortUrlExceptionTest.php +++ b/module/Core/test/Exception/DeleteShortUrlExceptionTest.php @@ -25,16 +25,16 @@ class DeleteShortUrlExceptionTest extends TestCase ): void { $e = DeleteShortUrlException::fromVisitsThreshold($threshold, $shortCode); - $this->assertEquals($threshold, $e->getVisitsThreshold()); - $this->assertEquals($expectedMessage, $e->getMessage()); - $this->assertEquals($expectedMessage, $e->getDetail()); - $this->assertEquals([ + self::assertEquals($threshold, $e->getVisitsThreshold()); + self::assertEquals($expectedMessage, $e->getMessage()); + self::assertEquals($expectedMessage, $e->getDetail()); + self::assertEquals([ 'shortCode' => $shortCode, 'threshold' => $threshold, ], $e->getAdditionalData()); - $this->assertEquals('Cannot delete short URL', $e->getTitle()); - $this->assertEquals('INVALID_SHORTCODE_DELETION', $e->getType()); - $this->assertEquals(422, $e->getStatus()); + self::assertEquals('Cannot delete short URL', $e->getTitle()); + self::assertEquals('INVALID_SHORTCODE_DELETION', $e->getType()); + self::assertEquals(422, $e->getStatus()); } public function provideThresholds(): array diff --git a/module/Core/test/Exception/InvalidUrlExceptionTest.php b/module/Core/test/Exception/InvalidUrlExceptionTest.php index cb0a08bc..5351c1b3 100644 --- a/module/Core/test/Exception/InvalidUrlExceptionTest.php +++ b/module/Core/test/Exception/InvalidUrlExceptionTest.php @@ -24,14 +24,14 @@ class InvalidUrlExceptionTest extends TestCase $expectedMessage = sprintf('Provided URL %s is invalid. Try with a different one.', $url); $e = InvalidUrlException::fromUrl($url, $prev); - $this->assertEquals($expectedMessage, $e->getMessage()); - $this->assertEquals($expectedMessage, $e->getDetail()); - $this->assertEquals('Invalid URL', $e->getTitle()); - $this->assertEquals('INVALID_URL', $e->getType()); - $this->assertEquals(['url' => $url], $e->getAdditionalData()); - $this->assertEquals(StatusCodeInterface::STATUS_BAD_REQUEST, $e->getCode()); - $this->assertEquals(StatusCodeInterface::STATUS_BAD_REQUEST, $e->getStatus()); - $this->assertEquals($prev, $e->getPrevious()); + self::assertEquals($expectedMessage, $e->getMessage()); + self::assertEquals($expectedMessage, $e->getDetail()); + self::assertEquals('Invalid URL', $e->getTitle()); + self::assertEquals('INVALID_URL', $e->getType()); + self::assertEquals(['url' => $url], $e->getAdditionalData()); + self::assertEquals(StatusCodeInterface::STATUS_BAD_REQUEST, $e->getCode()); + self::assertEquals(StatusCodeInterface::STATUS_BAD_REQUEST, $e->getStatus()); + self::assertEquals($prev, $e->getPrevious()); } public function providePrevious(): iterable diff --git a/module/Core/test/Exception/IpCannotBeLocatedExceptionTest.php b/module/Core/test/Exception/IpCannotBeLocatedExceptionTest.php index 84ee433c..b1487b69 100644 --- a/module/Core/test/Exception/IpCannotBeLocatedExceptionTest.php +++ b/module/Core/test/Exception/IpCannotBeLocatedExceptionTest.php @@ -18,10 +18,10 @@ class IpCannotBeLocatedExceptionTest extends TestCase { $e = IpCannotBeLocatedException::forEmptyAddress(); - $this->assertTrue($e->isNonLocatableAddress()); - $this->assertEquals('Ignored visit with no IP address', $e->getMessage()); - $this->assertEquals(0, $e->getCode()); - $this->assertNull($e->getPrevious()); + self::assertTrue($e->isNonLocatableAddress()); + self::assertEquals('Ignored visit with no IP address', $e->getMessage()); + self::assertEquals(0, $e->getCode()); + self::assertNull($e->getPrevious()); } /** @test */ @@ -29,10 +29,10 @@ class IpCannotBeLocatedExceptionTest extends TestCase { $e = IpCannotBeLocatedException::forLocalhost(); - $this->assertTrue($e->isNonLocatableAddress()); - $this->assertEquals('Ignored localhost address', $e->getMessage()); - $this->assertEquals(0, $e->getCode()); - $this->assertNull($e->getPrevious()); + self::assertTrue($e->isNonLocatableAddress()); + self::assertEquals('Ignored localhost address', $e->getMessage()); + self::assertEquals(0, $e->getCode()); + self::assertNull($e->getPrevious()); } /** @@ -43,10 +43,10 @@ class IpCannotBeLocatedExceptionTest extends TestCase { $e = IpCannotBeLocatedException::forError($prev); - $this->assertFalse($e->isNonLocatableAddress()); - $this->assertEquals('An error occurred while locating IP', $e->getMessage()); - $this->assertEquals($prev->getCode(), $e->getCode()); - $this->assertSame($prev, $e->getPrevious()); + self::assertFalse($e->isNonLocatableAddress()); + self::assertEquals('An error occurred while locating IP', $e->getMessage()); + self::assertEquals($prev->getCode(), $e->getCode()); + self::assertSame($prev, $e->getPrevious()); } public function provideErrors(): iterable diff --git a/module/Core/test/Exception/NonUniqueSlugExceptionTest.php b/module/Core/test/Exception/NonUniqueSlugExceptionTest.php index 00efa3cf..6720f0f3 100644 --- a/module/Core/test/Exception/NonUniqueSlugExceptionTest.php +++ b/module/Core/test/Exception/NonUniqueSlugExceptionTest.php @@ -22,12 +22,12 @@ class NonUniqueSlugExceptionTest extends TestCase $e = NonUniqueSlugException::fromSlug($slug, $domain); - $this->assertEquals($expectedMessage, $e->getMessage()); - $this->assertEquals($expectedMessage, $e->getDetail()); - $this->assertEquals('Invalid custom slug', $e->getTitle()); - $this->assertEquals('INVALID_SLUG', $e->getType()); - $this->assertEquals(400, $e->getStatus()); - $this->assertEquals($expectedAdditional, $e->getAdditionalData()); + self::assertEquals($expectedMessage, $e->getMessage()); + self::assertEquals($expectedMessage, $e->getDetail()); + self::assertEquals('Invalid custom slug', $e->getTitle()); + self::assertEquals('INVALID_SLUG', $e->getType()); + self::assertEquals(400, $e->getStatus()); + self::assertEquals($expectedAdditional, $e->getAdditionalData()); } public function provideMessages(): iterable diff --git a/module/Core/test/Exception/ShortUrlNotFoundExceptionTest.php b/module/Core/test/Exception/ShortUrlNotFoundExceptionTest.php index d0d77fb8..e6a48914 100644 --- a/module/Core/test/Exception/ShortUrlNotFoundExceptionTest.php +++ b/module/Core/test/Exception/ShortUrlNotFoundExceptionTest.php @@ -26,12 +26,12 @@ class ShortUrlNotFoundExceptionTest extends TestCase $e = ShortUrlNotFoundException::fromNotFound(new ShortUrlIdentifier($shortCode, $domain)); - $this->assertEquals($expectedMessage, $e->getMessage()); - $this->assertEquals($expectedMessage, $e->getDetail()); - $this->assertEquals('Short URL not found', $e->getTitle()); - $this->assertEquals('INVALID_SHORTCODE', $e->getType()); - $this->assertEquals(404, $e->getStatus()); - $this->assertEquals($expectedAdditional, $e->getAdditionalData()); + self::assertEquals($expectedMessage, $e->getMessage()); + self::assertEquals($expectedMessage, $e->getDetail()); + self::assertEquals('Short URL not found', $e->getTitle()); + self::assertEquals('INVALID_SHORTCODE', $e->getType()); + self::assertEquals(404, $e->getStatus()); + self::assertEquals($expectedAdditional, $e->getAdditionalData()); } public function provideMessages(): iterable diff --git a/module/Core/test/Exception/TagConflictExceptionTest.php b/module/Core/test/Exception/TagConflictExceptionTest.php index f09e3a32..156fd500 100644 --- a/module/Core/test/Exception/TagConflictExceptionTest.php +++ b/module/Core/test/Exception/TagConflictExceptionTest.php @@ -19,11 +19,11 @@ class TagConflictExceptionTest extends TestCase $expectedMessage = sprintf('You cannot rename tag %s to %s, because it already exists', $oldName, $newName); $e = TagConflictException::fromExistingTag($oldName, $newName); - $this->assertEquals($expectedMessage, $e->getMessage()); - $this->assertEquals($expectedMessage, $e->getDetail()); - $this->assertEquals('Tag conflict', $e->getTitle()); - $this->assertEquals('TAG_CONFLICT', $e->getType()); - $this->assertEquals(['oldName' => $oldName, 'newName' => $newName], $e->getAdditionalData()); - $this->assertEquals(409, $e->getStatus()); + self::assertEquals($expectedMessage, $e->getMessage()); + self::assertEquals($expectedMessage, $e->getDetail()); + self::assertEquals('Tag conflict', $e->getTitle()); + self::assertEquals('TAG_CONFLICT', $e->getType()); + self::assertEquals(['oldName' => $oldName, 'newName' => $newName], $e->getAdditionalData()); + self::assertEquals(409, $e->getStatus()); } } diff --git a/module/Core/test/Exception/TagNotFoundExceptionTest.php b/module/Core/test/Exception/TagNotFoundExceptionTest.php index ccee7b38..c6e8bf1d 100644 --- a/module/Core/test/Exception/TagNotFoundExceptionTest.php +++ b/module/Core/test/Exception/TagNotFoundExceptionTest.php @@ -18,11 +18,11 @@ class TagNotFoundExceptionTest extends TestCase $expectedMessage = sprintf('Tag with name "%s" could not be found', $tag); $e = TagNotFoundException::fromTag($tag); - $this->assertEquals($expectedMessage, $e->getMessage()); - $this->assertEquals($expectedMessage, $e->getDetail()); - $this->assertEquals('Tag not found', $e->getTitle()); - $this->assertEquals('TAG_NOT_FOUND', $e->getType()); - $this->assertEquals(['tag' => $tag], $e->getAdditionalData()); - $this->assertEquals(404, $e->getStatus()); + self::assertEquals($expectedMessage, $e->getMessage()); + self::assertEquals($expectedMessage, $e->getDetail()); + self::assertEquals('Tag not found', $e->getTitle()); + self::assertEquals('TAG_NOT_FOUND', $e->getType()); + self::assertEquals(['tag' => $tag], $e->getAdditionalData()); + self::assertEquals(404, $e->getStatus()); } } diff --git a/module/Core/test/Exception/ValidationExceptionTest.php b/module/Core/test/Exception/ValidationExceptionTest.php index 780b25e3..db689a11 100644 --- a/module/Core/test/Exception/ValidationExceptionTest.php +++ b/module/Core/test/Exception/ValidationExceptionTest.php @@ -38,12 +38,12 @@ EOT; $e = ValidationException::fromInputFilter($inputFilter->reveal()); - $this->assertEquals($invalidData, $e->getInvalidElements()); - $this->assertEquals(['invalidElements' => array_keys($invalidData)], $e->getAdditionalData()); - $this->assertEquals('Provided data is not valid', $e->getMessage()); - $this->assertEquals(StatusCodeInterface::STATUS_BAD_REQUEST, $e->getCode()); - $this->assertEquals($prev, $e->getPrevious()); - $this->assertStringContainsString($expectedStringRepresentation, (string) $e); + self::assertEquals($invalidData, $e->getInvalidElements()); + self::assertEquals(['invalidElements' => array_keys($invalidData)], $e->getAdditionalData()); + self::assertEquals('Provided data is not valid', $e->getMessage()); + self::assertEquals(StatusCodeInterface::STATUS_BAD_REQUEST, $e->getCode()); + self::assertEquals($prev, $e->getPrevious()); + self::assertStringContainsString($expectedStringRepresentation, (string) $e); $getMessages->shouldHaveBeenCalledOnce(); } diff --git a/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php b/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php index 992e25d6..aef2a489 100644 --- a/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php +++ b/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php @@ -33,8 +33,8 @@ class MercureUpdatesGeneratorTest extends TestCase $update = $this->generator->{$method}($visit); - $this->assertEquals([$expectedTopic], $update->getTopics()); - $this->assertEquals([ + self::assertEquals([$expectedTopic], $update->getTopics()); + self::assertEquals([ 'shortUrl' => [ 'shortCode' => $shortUrl->getShortCode(), 'shortUrl' => 'http:/' . $shortUrl->getShortCode(), diff --git a/module/Core/test/Model/ShortUrlMetaTest.php b/module/Core/test/Model/ShortUrlMetaTest.php index fe3d42fc..c8cc3ff6 100644 --- a/module/Core/test/Model/ShortUrlMetaTest.php +++ b/module/Core/test/Model/ShortUrlMetaTest.php @@ -68,17 +68,17 @@ class ShortUrlMetaTest extends TestCase ['validSince' => Chronos::parse('2015-01-01')->toAtomString(), 'customSlug' => $customSlug], ); - $this->assertTrue($meta->hasValidSince()); - $this->assertEquals(Chronos::parse('2015-01-01'), $meta->getValidSince()); + self::assertTrue($meta->hasValidSince()); + self::assertEquals(Chronos::parse('2015-01-01'), $meta->getValidSince()); - $this->assertFalse($meta->hasValidUntil()); - $this->assertNull($meta->getValidUntil()); + self::assertFalse($meta->hasValidUntil()); + self::assertNull($meta->getValidUntil()); - $this->assertTrue($meta->hasCustomSlug()); - $this->assertEquals($expectedSlug, $meta->getCustomSlug()); + self::assertTrue($meta->hasCustomSlug()); + self::assertEquals($expectedSlug, $meta->getCustomSlug()); - $this->assertFalse($meta->hasMaxVisits()); - $this->assertNull($meta->getMaxVisits()); + self::assertFalse($meta->hasMaxVisits()); + self::assertNull($meta->getMaxVisits()); } public function provideCustomSlugs(): iterable diff --git a/module/Core/test/Model/VisitorTest.php b/module/Core/test/Model/VisitorTest.php index 0a0c1828..d52a6389 100644 --- a/module/Core/test/Model/VisitorTest.php +++ b/module/Core/test/Model/VisitorTest.php @@ -23,9 +23,9 @@ class VisitorTest extends TestCase $visitor = new Visitor(...$params); ['userAgent' => $userAgent, 'referer' => $referer, 'remoteAddress' => $remoteAddress] = $expected; - $this->assertEquals($userAgent, $visitor->getUserAgent()); - $this->assertEquals($referer, $visitor->getReferer()); - $this->assertEquals($remoteAddress, $visitor->getRemoteAddress()); + self::assertEquals($userAgent, $visitor->getUserAgent()); + self::assertEquals($referer, $visitor->getReferer()); + self::assertEquals($remoteAddress, $visitor->getRemoteAddress()); } public function provideParams(): iterable diff --git a/module/Core/test/Service/ShortUrl/ShortUrlResolverTest.php b/module/Core/test/Service/ShortUrl/ShortUrlResolverTest.php index 5b3e3e19..ed6569f2 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 $result = $this->urlResolver->resolveShortUrl(new ShortUrlIdentifier($shortCode)); - $this->assertSame($shortUrl, $result); + self::assertSame($shortUrl, $result); $findOne->shouldHaveBeenCalledOnce(); $getRepo->shouldHaveBeenCalledOnce(); } @@ -77,7 +77,7 @@ class ShortUrlResolverTest extends TestCase $result = $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode)); - $this->assertSame($shortUrl, $result); + self::assertSame($shortUrl, $result); $findOneByShortCode->shouldHaveBeenCalledOnce(); $getRepo->shouldHaveBeenCalledOnce(); } diff --git a/module/Core/test/Service/ShortUrlServiceTest.php b/module/Core/test/Service/ShortUrlServiceTest.php index fcb63ec2..0b6c82b0 100644 --- a/module/Core/test/Service/ShortUrlServiceTest.php +++ b/module/Core/test/Service/ShortUrlServiceTest.php @@ -61,7 +61,7 @@ class ShortUrlServiceTest extends TestCase $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); $list = $this->service->listShortUrls(ShortUrlsParams::emptyInstance()); - $this->assertEquals(4, $list->getCurrentItemCount()); + self::assertEquals(4, $list->getCurrentItemCount()); } /** @test */ @@ -97,11 +97,11 @@ class ShortUrlServiceTest extends TestCase $result = $this->service->updateMetadataByShortCode(new ShortUrlIdentifier('abc123'), $shortUrlEdit); - $this->assertSame($shortUrl, $result); - $this->assertEquals($shortUrlEdit->validSince(), $shortUrl->getValidSince()); - $this->assertEquals($shortUrlEdit->validUntil(), $shortUrl->getValidUntil()); - $this->assertEquals($shortUrlEdit->maxVisits(), $shortUrl->getMaxVisits()); - $this->assertEquals($shortUrlEdit->longUrl() ?? $originalLongUrl, $shortUrl->getLongUrl()); + self::assertSame($shortUrl, $result); + self::assertEquals($shortUrlEdit->validSince(), $shortUrl->getValidSince()); + self::assertEquals($shortUrlEdit->validUntil(), $shortUrl->getValidUntil()); + self::assertEquals($shortUrlEdit->maxVisits(), $shortUrl->getMaxVisits()); + self::assertEquals($shortUrlEdit->longUrl() ?? $originalLongUrl, $shortUrl->getLongUrl()); $findShortUrl->shouldHaveBeenCalled(); $flush->shouldHaveBeenCalled(); $this->urlValidator->validateUrl( diff --git a/module/Core/test/Service/Tag/TagServiceTest.php b/module/Core/test/Service/Tag/TagServiceTest.php index c031e51f..4f17da32 100644 --- a/module/Core/test/Service/Tag/TagServiceTest.php +++ b/module/Core/test/Service/Tag/TagServiceTest.php @@ -39,7 +39,7 @@ class TagServiceTest extends TestCase $result = $this->service->listTags(); - $this->assertEquals($expected, $result); + self::assertEquals($expected, $result); $find->shouldHaveBeenCalled(); } @@ -52,7 +52,7 @@ class TagServiceTest extends TestCase $result = $this->service->tagsInfo(); - $this->assertEquals($expected, $result); + self::assertEquals($expected, $result); $find->shouldHaveBeenCalled(); } @@ -75,7 +75,7 @@ class TagServiceTest extends TestCase $result = $this->service->createTags(['foo', 'bar']); - $this->assertCount(2, $result); + self::assertCount(2, $result); $find->shouldHaveBeenCalled(); $persist->shouldHaveBeenCalledTimes(2); $flush->shouldHaveBeenCalled(); @@ -106,8 +106,8 @@ class TagServiceTest extends TestCase $tag = $this->service->renameTag($oldName, $newName); - $this->assertSame($expected, $tag); - $this->assertEquals($newName, (string) $tag); + self::assertSame($expected, $tag); + self::assertEquals($newName, (string) $tag); $find->shouldHaveBeenCalled(); $flush->shouldHaveBeenCalled(); $countTags->shouldHaveBeenCalledTimes($count > 0 ? 0 : 1); diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index adeda920..ba7185bf 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -67,7 +67,7 @@ class UrlShortenerTest extends TestCase ShortUrlMeta::createEmpty(), ); - $this->assertEquals('http://foobar.com/12345/hello?foo=bar', $shortUrl->getLongUrl()); + self::assertEquals('http://foobar.com/12345/hello?foo=bar', $shortUrl->getLongUrl()); } /** @test */ @@ -91,7 +91,7 @@ class UrlShortenerTest extends TestCase ShortUrlMeta::createEmpty(), ); - $this->assertEquals('http://foobar.com/12345/hello?foo=bar', $shortUrl->getLongUrl()); + self::assertEquals('http://foobar.com/12345/hello?foo=bar', $shortUrl->getLongUrl()); $getRepo->shouldBeCalledTimes($expectedCalls); $shortCodeIsInUse->shouldBeCalledTimes($expectedCalls); } @@ -154,7 +154,7 @@ class UrlShortenerTest extends TestCase $getRepo->shouldHaveBeenCalledOnce(); $this->em->persist(Argument::cetera())->shouldNotHaveBeenCalled(); $this->urlValidator->validateUrl(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->assertSame($expected, $result); + self::assertSame($expected, $result); } public function provideExistingShortUrls(): iterable diff --git a/module/Core/test/Service/VisitsTrackerTest.php b/module/Core/test/Service/VisitsTrackerTest.php index 5893b952..b1609282 100644 --- a/module/Core/test/Service/VisitsTrackerTest.php +++ b/module/Core/test/Service/VisitsTrackerTest.php @@ -71,7 +71,7 @@ class VisitsTrackerTest extends TestCase $paginator = $this->visitsTracker->info(new ShortUrlIdentifier($shortCode), new VisitsParams()); - $this->assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentItems())); + self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentItems())); $count->shouldHaveBeenCalledOnce(); } @@ -120,7 +120,7 @@ class VisitsTrackerTest extends TestCase $paginator = $this->visitsTracker->visitsForTag($tag, new VisitsParams()); - $this->assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentItems())); + self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentItems())); $count->shouldHaveBeenCalledOnce(); $getRepo->shouldHaveBeenCalledOnce(); } diff --git a/module/Core/test/Transformer/ShortUrlDataTransformerTest.php b/module/Core/test/Transformer/ShortUrlDataTransformerTest.php index a65b9506..9abe5f1a 100644 --- a/module/Core/test/Transformer/ShortUrlDataTransformerTest.php +++ b/module/Core/test/Transformer/ShortUrlDataTransformerTest.php @@ -29,7 +29,7 @@ class ShortUrlDataTransformerTest extends TestCase { ['meta' => $meta] = $this->transformer->transform($shortUrl); - $this->assertEquals($expectedMeta, $meta); + self::assertEquals($expectedMeta, $meta); } public function provideShortUrls(): iterable diff --git a/module/Core/test/Visit/VisitsStatsHelperTest.php b/module/Core/test/Visit/VisitsStatsHelperTest.php index a4b692d5..01569f04 100644 --- a/module/Core/test/Visit/VisitsStatsHelperTest.php +++ b/module/Core/test/Visit/VisitsStatsHelperTest.php @@ -38,7 +38,7 @@ class VisitsStatsHelperTest extends TestCase $stats = $this->helper->getVisitsStats(); - $this->assertEquals(new VisitsStats($expectedCount), $stats); + self::assertEquals(new VisitsStats($expectedCount), $stats); $count->shouldHaveBeenCalledOnce(); $getRepo->shouldHaveBeenCalledOnce(); } diff --git a/module/Rest/test-api/Action/CreateShortUrlActionTest.php b/module/Rest/test-api/Action/CreateShortUrlActionTest.php index 79b7ba1e..c9bf6fe5 100644 --- a/module/Rest/test-api/Action/CreateShortUrlActionTest.php +++ b/module/Rest/test-api/Action/CreateShortUrlActionTest.php @@ -20,9 +20,9 @@ class CreateShortUrlActionTest extends ApiTestCase $expectedKeys = ['shortCode', 'shortUrl', 'longUrl', 'dateCreated', 'visitsCount', 'tags']; [$statusCode, $payload] = $this->createShortUrl(); - $this->assertEquals(self::STATUS_OK, $statusCode); + self::assertEquals(self::STATUS_OK, $statusCode); foreach ($expectedKeys as $key) { - $this->assertArrayHasKey($key, $payload); + self::assertArrayHasKey($key, $payload); } } @@ -31,8 +31,8 @@ class CreateShortUrlActionTest extends ApiTestCase { [$statusCode, $payload] = $this->createShortUrl(['customSlug' => 'my cool slug']); - $this->assertEquals(self::STATUS_OK, $statusCode); - $this->assertEquals('my-cool-slug', $payload['shortCode']); + self::assertEquals(self::STATUS_OK, $statusCode); + self::assertEquals('my-cool-slug', $payload['shortCode']); } /** @@ -46,17 +46,17 @@ class CreateShortUrlActionTest extends ApiTestCase [$statusCode, $payload] = $this->createShortUrl(['customSlug' => $slug, 'domain' => $domain]); - $this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode); - $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); - $this->assertEquals($detail, $payload['detail']); - $this->assertEquals('INVALID_SLUG', $payload['type']); - $this->assertEquals('Invalid custom slug', $payload['title']); - $this->assertEquals($slug, $payload['customSlug']); + self::assertEquals(self::STATUS_BAD_REQUEST, $statusCode); + self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); + self::assertEquals($detail, $payload['detail']); + self::assertEquals('INVALID_SLUG', $payload['type']); + self::assertEquals('Invalid custom slug', $payload['title']); + self::assertEquals($slug, $payload['customSlug']); if ($domain !== null) { - $this->assertEquals($domain, $payload['domain']); + self::assertEquals($domain, $payload['domain']); } else { - $this->assertArrayNotHasKey('domain', $payload); + self::assertArrayNotHasKey('domain', $payload); } } @@ -65,8 +65,8 @@ class CreateShortUrlActionTest extends ApiTestCase { [$statusCode, ['tags' => $tags]] = $this->createShortUrl(['tags' => ['foo', 'bar', 'baz']]); - $this->assertEquals(self::STATUS_OK, $statusCode); - $this->assertEquals(['foo', 'bar', 'baz'], $tags); + self::assertEquals(self::STATUS_OK, $statusCode); + self::assertEquals(['foo', 'bar', 'baz'], $tags); } /** @@ -77,14 +77,14 @@ class CreateShortUrlActionTest extends ApiTestCase { [$statusCode, ['shortCode' => $shortCode]] = $this->createShortUrl(['maxVisits' => $maxVisits]); - $this->assertEquals(self::STATUS_OK, $statusCode); + self::assertEquals(self::STATUS_OK, $statusCode); // Last request to the short URL will return a 404, and the rest, a 302 for ($i = 0; $i < $maxVisits; $i++) { - $this->assertEquals(self::STATUS_FOUND, $this->callShortUrl($shortCode)->getStatusCode()); + self::assertEquals(self::STATUS_FOUND, $this->callShortUrl($shortCode)->getStatusCode()); } $lastResp = $this->callShortUrl($shortCode); - $this->assertEquals(self::STATUS_NOT_FOUND, $lastResp->getStatusCode()); + self::assertEquals(self::STATUS_NOT_FOUND, $lastResp->getStatusCode()); } public function provideMaxVisits(): array @@ -99,11 +99,11 @@ class CreateShortUrlActionTest extends ApiTestCase 'validSince' => Chronos::now()->addDay()->toAtomString(), ]); - $this->assertEquals(self::STATUS_OK, $statusCode); + self::assertEquals(self::STATUS_OK, $statusCode); // Request to the short URL will return a 404 since it's not valid yet $lastResp = $this->callShortUrl($shortCode); - $this->assertEquals(self::STATUS_NOT_FOUND, $lastResp->getStatusCode()); + self::assertEquals(self::STATUS_NOT_FOUND, $lastResp->getStatusCode()); } /** @test */ @@ -113,11 +113,11 @@ class CreateShortUrlActionTest extends ApiTestCase 'validUntil' => Chronos::now()->subDay()->toAtomString(), ]); - $this->assertEquals(self::STATUS_OK, $statusCode); + self::assertEquals(self::STATUS_OK, $statusCode); // Request to the short URL will return a 404 since it's no longer valid $lastResp = $this->callShortUrl($shortCode); - $this->assertEquals(self::STATUS_NOT_FOUND, $lastResp->getStatusCode()); + self::assertEquals(self::STATUS_NOT_FOUND, $lastResp->getStatusCode()); } /** @@ -131,9 +131,9 @@ class CreateShortUrlActionTest extends ApiTestCase $body['findIfExists'] = true; [$secondStatusCode, ['shortCode' => $secondShortCode]] = $this->createShortUrl($body); - $this->assertEquals(self::STATUS_OK, $firstStatusCode); - $this->assertEquals(self::STATUS_OK, $secondStatusCode); - $this->assertEquals($firstShortCode, $secondShortCode); + self::assertEquals(self::STATUS_OK, $firstStatusCode); + self::assertEquals(self::STATUS_OK, $secondStatusCode); + self::assertEquals($firstShortCode, $secondShortCode); } public function provideMatchingBodies(): iterable @@ -167,8 +167,8 @@ class CreateShortUrlActionTest extends ApiTestCase 'domain' => $domain, ]); - $this->assertEquals(self::STATUS_OK, $firstStatusCode); - $this->assertEquals(self::STATUS_BAD_REQUEST, $secondStatusCode); + self::assertEquals(self::STATUS_OK, $firstStatusCode); + self::assertEquals(self::STATUS_BAD_REQUEST, $secondStatusCode); } public function provideConflictingSlugs(): iterable @@ -188,9 +188,9 @@ class CreateShortUrlActionTest extends ApiTestCase 'findIfExists' => true, ]); - $this->assertEquals(self::STATUS_OK, $firstStatusCode); - $this->assertEquals(self::STATUS_OK, $secondStatusCode); - $this->assertNotEquals($firstShortCode, $secondShortCode); + self::assertEquals(self::STATUS_OK, $firstStatusCode); + self::assertEquals(self::STATUS_OK, $secondStatusCode); + self::assertNotEquals($firstShortCode, $secondShortCode); } /** @@ -201,8 +201,8 @@ class CreateShortUrlActionTest extends ApiTestCase { [$statusCode, $payload] = $this->createShortUrl(['longUrl' => $longUrl]); - $this->assertEquals(self::STATUS_OK, $statusCode); - $this->assertEquals($payload['longUrl'], $longUrl); + self::assertEquals(self::STATUS_OK, $statusCode); + self::assertEquals($payload['longUrl'], $longUrl); } public function provideIdn(): iterable @@ -220,12 +220,12 @@ class CreateShortUrlActionTest extends ApiTestCase [$statusCode, $payload] = $this->createShortUrl(['longUrl' => $url]); - $this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode); - $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); - $this->assertEquals('INVALID_URL', $payload['type']); - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals('Invalid URL', $payload['title']); - $this->assertEquals($url, $payload['url']); + self::assertEquals(self::STATUS_BAD_REQUEST, $statusCode); + self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); + self::assertEquals('INVALID_URL', $payload['type']); + self::assertEquals($expectedDetail, $payload['detail']); + self::assertEquals('Invalid URL', $payload['title']); + self::assertEquals($url, $payload['url']); } /** @test */ @@ -238,10 +238,10 @@ class CreateShortUrlActionTest extends ApiTestCase $getResp = $this->callApiWithKey(self::METHOD_GET, '/short-urls/' . $shortCode); $payload = $this->getJsonResponsePayload($getResp); - $this->assertEquals(self::STATUS_OK, $createStatusCode); - $this->assertEquals(self::STATUS_OK, $getResp->getStatusCode()); - $this->assertArrayHasKey('domain', $payload); - $this->assertNull($payload['domain']); + self::assertEquals(self::STATUS_OK, $createStatusCode); + self::assertEquals(self::STATUS_OK, $getResp->getStatusCode()); + self::assertArrayHasKey('domain', $payload); + self::assertNull($payload['domain']); } /** diff --git a/module/Rest/test-api/Action/DeleteShortUrlActionTest.php b/module/Rest/test-api/Action/DeleteShortUrlActionTest.php index ef32190b..7c66ff0b 100644 --- a/module/Rest/test-api/Action/DeleteShortUrlActionTest.php +++ b/module/Rest/test-api/Action/DeleteShortUrlActionTest.php @@ -23,13 +23,13 @@ class DeleteShortUrlActionTest extends ApiTestCase $resp = $this->callApiWithKey(self::METHOD_DELETE, $this->buildShortUrlPath($shortCode, $domain)); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); - $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals('Short URL not found', $payload['title']); - $this->assertEquals($shortCode, $payload['shortCode']); - $this->assertEquals($domain, $payload['domain'] ?? null); + self::assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); + self::assertEquals(self::STATUS_NOT_FOUND, $payload['status']); + self::assertEquals('INVALID_SHORTCODE', $payload['type']); + self::assertEquals($expectedDetail, $payload['detail']); + self::assertEquals('Short URL not found', $payload['title']); + self::assertEquals($shortCode, $payload['shortCode']); + self::assertEquals($domain, $payload['domain'] ?? null); } /** @test */ @@ -37,18 +37,18 @@ class DeleteShortUrlActionTest extends ApiTestCase { // Generate visits first for ($i = 0; $i < 20; $i++) { - $this->assertEquals(self::STATUS_FOUND, $this->callShortUrl('abc123')->getStatusCode()); + self::assertEquals(self::STATUS_FOUND, $this->callShortUrl('abc123')->getStatusCode()); } $expectedDetail = 'Impossible to delete short URL with short code "abc123" since it has more than "15" visits.'; $resp = $this->callApiWithKey(self::METHOD_DELETE, '/short-urls/abc123'); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $payload['status']); - $this->assertEquals('INVALID_SHORTCODE_DELETION', $payload['type']); - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals('Cannot delete short URL', $payload['title']); + self::assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $resp->getStatusCode()); + self::assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $payload['status']); + self::assertEquals('INVALID_SHORTCODE_DELETION', $payload['type']); + self::assertEquals($expectedDetail, $payload['detail']); + self::assertEquals('Cannot delete short URL', $payload['title']); } /** @test */ @@ -60,10 +60,10 @@ class DeleteShortUrlActionTest extends ApiTestCase $fetchWithDomainAfter = $this->callApiWithKey(self::METHOD_GET, '/short-urls/ghi789?domain=example.com'); $fetchWithoutDomainAfter = $this->callApiWithKey(self::METHOD_GET, '/short-urls/ghi789'); - $this->assertEquals(self::STATUS_OK, $fetchWithDomainBefore->getStatusCode()); - $this->assertEquals(self::STATUS_OK, $fetchWithoutDomainBefore->getStatusCode()); - $this->assertEquals(self::STATUS_NO_CONTENT, $deleteResp->getStatusCode()); - $this->assertEquals(self::STATUS_NOT_FOUND, $fetchWithDomainAfter->getStatusCode()); - $this->assertEquals(self::STATUS_OK, $fetchWithoutDomainAfter->getStatusCode()); + self::assertEquals(self::STATUS_OK, $fetchWithDomainBefore->getStatusCode()); + self::assertEquals(self::STATUS_OK, $fetchWithoutDomainBefore->getStatusCode()); + self::assertEquals(self::STATUS_NO_CONTENT, $deleteResp->getStatusCode()); + self::assertEquals(self::STATUS_NOT_FOUND, $fetchWithDomainAfter->getStatusCode()); + self::assertEquals(self::STATUS_OK, $fetchWithoutDomainAfter->getStatusCode()); } } diff --git a/module/Rest/test-api/Action/EditShortUrlActionTest.php b/module/Rest/test-api/Action/EditShortUrlActionTest.php index b5cd4fd4..e6b37eba 100644 --- a/module/Rest/test-api/Action/EditShortUrlActionTest.php +++ b/module/Rest/test-api/Action/EditShortUrlActionTest.php @@ -41,9 +41,9 @@ class EditShortUrlActionTest extends ApiTestCase ]); $metaAfterResetting = $this->findShortUrlMetaByShortCode($shortCode); - $this->assertEquals(self::STATUS_NO_CONTENT, $editWithProvidedMeta->getStatusCode()); - $this->assertEquals(self::STATUS_NO_CONTENT, $editWithResetMeta->getStatusCode()); - $this->assertEquals($resetMeta, $metaAfterResetting); + self::assertEquals(self::STATUS_NO_CONTENT, $editWithProvidedMeta->getStatusCode()); + self::assertEquals(self::STATUS_NO_CONTENT, $editWithResetMeta->getStatusCode()); + self::assertEquals($resetMeta, $metaAfterResetting); self::assertArraySubset($meta, $metaAfterEditing); } @@ -84,10 +84,10 @@ class EditShortUrlActionTest extends ApiTestCase 'longUrl' => $longUrl, ]]); - $this->assertEquals($expectedStatus, $resp->getStatusCode()); + self::assertEquals($expectedStatus, $resp->getStatusCode()); if ($expectedError !== null) { $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals($expectedError, $payload['type']); + self::assertEquals($expectedError, $payload['type']); } } @@ -110,13 +110,13 @@ class EditShortUrlActionTest extends ApiTestCase $resp = $this->callApiWithKey(self::METHOD_PATCH, $url, [RequestOptions::JSON => []]); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); - $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals('Short URL not found', $payload['title']); - $this->assertEquals($shortCode, $payload['shortCode']); - $this->assertEquals($domain, $payload['domain'] ?? null); + self::assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); + self::assertEquals(self::STATUS_NOT_FOUND, $payload['status']); + self::assertEquals('INVALID_SHORTCODE', $payload['type']); + self::assertEquals($expectedDetail, $payload['detail']); + self::assertEquals('Short URL not found', $payload['title']); + self::assertEquals($shortCode, $payload['shortCode']); + self::assertEquals($domain, $payload['domain'] ?? null); } /** @test */ @@ -129,11 +129,11 @@ class EditShortUrlActionTest extends ApiTestCase ]]); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); - $this->assertEquals('INVALID_ARGUMENT', $payload['type']); - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals('Invalid data', $payload['title']); + self::assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); + self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); + self::assertEquals('INVALID_ARGUMENT', $payload['type']); + self::assertEquals($expectedDetail, $payload['detail']); + self::assertEquals('Invalid data', $payload['title']); } /** @@ -154,10 +154,10 @@ class EditShortUrlActionTest extends ApiTestCase ]]); $editedShortUrl = $this->getJsonResponsePayload($this->callApiWithKey(self::METHOD_GET, (string) $url)); - $this->assertEquals(self::STATUS_NO_CONTENT, $editResp->getStatusCode()); - $this->assertEquals($domain, $editedShortUrl['domain']); - $this->assertEquals($expectedUrl, $editedShortUrl['longUrl']); - $this->assertEquals(100, $editedShortUrl['meta']['maxVisits'] ?? null); + self::assertEquals(self::STATUS_NO_CONTENT, $editResp->getStatusCode()); + self::assertEquals($domain, $editedShortUrl['domain']); + self::assertEquals($expectedUrl, $editedShortUrl['longUrl']); + self::assertEquals(100, $editedShortUrl['meta']['maxVisits'] ?? null); } public function provideDomains(): iterable diff --git a/module/Rest/test-api/Action/EditShortUrlTagsActionTest.php b/module/Rest/test-api/Action/EditShortUrlTagsActionTest.php index 0433a388..84d2af80 100644 --- a/module/Rest/test-api/Action/EditShortUrlTagsActionTest.php +++ b/module/Rest/test-api/Action/EditShortUrlTagsActionTest.php @@ -20,11 +20,11 @@ class EditShortUrlTagsActionTest extends ApiTestCase $resp = $this->callApiWithKey(self::METHOD_PUT, '/short-urls/abc123/tags', [RequestOptions::JSON => []]); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); - $this->assertEquals('INVALID_ARGUMENT', $payload['type']); - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals('Invalid data', $payload['title']); + self::assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); + self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); + self::assertEquals('INVALID_ARGUMENT', $payload['type']); + self::assertEquals($expectedDetail, $payload['detail']); + self::assertEquals('Invalid data', $payload['title']); } /** @@ -42,13 +42,13 @@ class EditShortUrlTagsActionTest extends ApiTestCase ]]); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); - $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals('Short URL not found', $payload['title']); - $this->assertEquals($shortCode, $payload['shortCode']); - $this->assertEquals($domain, $payload['domain'] ?? null); + self::assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); + self::assertEquals(self::STATUS_NOT_FOUND, $payload['status']); + self::assertEquals('INVALID_SHORTCODE', $payload['type']); + self::assertEquals($expectedDetail, $payload['detail']); + self::assertEquals('Short URL not found', $payload['title']); + self::assertEquals($shortCode, $payload['shortCode']); + self::assertEquals($domain, $payload['domain'] ?? null); } /** @test */ @@ -67,8 +67,8 @@ class EditShortUrlTagsActionTest extends ApiTestCase $this->callApiWithKey(self::METHOD_GET, '/short-urls/ghi789?domain=example.com'), ); - $this->assertEquals(self::STATUS_OK, $setTagsWithDomain->getStatusCode()); - $this->assertEquals([], $fetchWithoutDomain['tags']); - $this->assertEquals(['bar', 'foo'], $fetchWithDomain['tags']); + self::assertEquals(self::STATUS_OK, $setTagsWithDomain->getStatusCode()); + self::assertEquals([], $fetchWithoutDomain['tags']); + self::assertEquals(['bar', 'foo'], $fetchWithDomain['tags']); } } diff --git a/module/Rest/test-api/Action/GlobalVisitsActionTest.php b/module/Rest/test-api/Action/GlobalVisitsActionTest.php index 8e4f5e11..b6767c0f 100644 --- a/module/Rest/test-api/Action/GlobalVisitsActionTest.php +++ b/module/Rest/test-api/Action/GlobalVisitsActionTest.php @@ -14,8 +14,8 @@ class GlobalVisitsActionTest extends ApiTestCase $resp = $this->callApiWithKey(self::METHOD_GET, '/visits'); $payload = $this->getJsonResponsePayload($resp); - $this->assertArrayHasKey('visits', $payload); - $this->assertArrayHasKey('visitsCount', $payload['visits']); - $this->assertEquals(7, $payload['visits']['visitsCount']); + self::assertArrayHasKey('visits', $payload); + self::assertArrayHasKey('visitsCount', $payload['visits']); + self::assertEquals(7, $payload['visits']['visitsCount']); } } diff --git a/module/Rest/test-api/Action/ListShortUrlsTest.php b/module/Rest/test-api/Action/ListShortUrlsTest.php index d59c488d..2f1cf484 100644 --- a/module/Rest/test-api/Action/ListShortUrlsTest.php +++ b/module/Rest/test-api/Action/ListShortUrlsTest.php @@ -110,8 +110,8 @@ class ListShortUrlsTest extends ApiTestCase $resp = $this->callApiWithKey(self::METHOD_GET, '/short-urls', [RequestOptions::QUERY => $query]); $respPayload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_OK, $resp->getStatusCode()); - $this->assertEquals([ + self::assertEquals(self::STATUS_OK, $resp->getStatusCode()); + self::assertEquals([ 'shortUrls' => [ 'data' => $expectedShortUrls, 'pagination' => $this->buildPagination(count($expectedShortUrls)), diff --git a/module/Rest/test-api/Action/ListTagsActionTest.php b/module/Rest/test-api/Action/ListTagsActionTest.php index 0690d4f2..9191b4e0 100644 --- a/module/Rest/test-api/Action/ListTagsActionTest.php +++ b/module/Rest/test-api/Action/ListTagsActionTest.php @@ -18,7 +18,7 @@ class ListTagsActionTest extends ApiTestCase $resp = $this->callApiWithKey(self::METHOD_GET, '/tags', [RequestOptions::QUERY => $query]); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(['tags' => $expectedTags], $payload); + self::assertEquals(['tags' => $expectedTags], $payload); } public function provideQueries(): iterable diff --git a/module/Rest/test-api/Action/ResolveShortUrlActionTest.php b/module/Rest/test-api/Action/ResolveShortUrlActionTest.php index d76d7946..cf1a7212 100644 --- a/module/Rest/test-api/Action/ResolveShortUrlActionTest.php +++ b/module/Rest/test-api/Action/ResolveShortUrlActionTest.php @@ -29,9 +29,9 @@ class ResolveShortUrlActionTest extends ApiTestCase $visitResp = $this->callShortUrl($shortCode); $fetchResp = $this->callApiWithKey(self::METHOD_GET, $url); - $this->assertEquals(self::STATUS_NO_CONTENT, $editResp->getStatusCode()); - $this->assertEquals(self::STATUS_NOT_FOUND, $visitResp->getStatusCode()); - $this->assertEquals(self::STATUS_OK, $fetchResp->getStatusCode()); + self::assertEquals(self::STATUS_NO_CONTENT, $editResp->getStatusCode()); + self::assertEquals(self::STATUS_NOT_FOUND, $visitResp->getStatusCode()); + self::assertEquals(self::STATUS_OK, $fetchResp->getStatusCode()); } public function provideDisabledMeta(): iterable @@ -55,12 +55,12 @@ class ResolveShortUrlActionTest extends ApiTestCase $resp = $this->callApiWithKey(self::METHOD_GET, $this->buildShortUrlPath($shortCode, $domain)); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); - $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals('Short URL not found', $payload['title']); - $this->assertEquals($shortCode, $payload['shortCode']); - $this->assertEquals($domain, $payload['domain'] ?? null); + self::assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); + self::assertEquals(self::STATUS_NOT_FOUND, $payload['status']); + self::assertEquals('INVALID_SHORTCODE', $payload['type']); + self::assertEquals($expectedDetail, $payload['detail']); + self::assertEquals('Short URL not found', $payload['title']); + self::assertEquals($shortCode, $payload['shortCode']); + self::assertEquals($domain, $payload['domain'] ?? null); } } diff --git a/module/Rest/test-api/Action/ShortUrlVisitsActionTest.php b/module/Rest/test-api/Action/ShortUrlVisitsActionTest.php index ea39a267..6e2463a2 100644 --- a/module/Rest/test-api/Action/ShortUrlVisitsActionTest.php +++ b/module/Rest/test-api/Action/ShortUrlVisitsActionTest.php @@ -27,13 +27,13 @@ class ShortUrlVisitsActionTest extends ApiTestCase $resp = $this->callApiWithKey(self::METHOD_GET, $this->buildShortUrlPath($shortCode, $domain, '/visits')); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); - $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals('Short URL not found', $payload['title']); - $this->assertEquals($shortCode, $payload['shortCode']); - $this->assertEquals($domain, $payload['domain'] ?? null); + self::assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); + self::assertEquals(self::STATUS_NOT_FOUND, $payload['status']); + self::assertEquals('INVALID_SHORTCODE', $payload['type']); + self::assertEquals($expectedDetail, $payload['detail']); + self::assertEquals('Short URL not found', $payload['title']); + self::assertEquals($shortCode, $payload['shortCode']); + self::assertEquals($domain, $payload['domain'] ?? null); } /** @@ -52,8 +52,8 @@ class ShortUrlVisitsActionTest extends ApiTestCase $resp = $this->callApiWithKey(self::METHOD_GET, (string) $url); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals($expectedAmountOfVisits, $payload['visits']['pagination']['totalItems'] ?? -1); - $this->assertCount($expectedAmountOfVisits, $payload['visits']['data'] ?? []); + self::assertEquals($expectedAmountOfVisits, $payload['visits']['pagination']['totalItems'] ?? -1); + self::assertCount($expectedAmountOfVisits, $payload['visits']['data'] ?? []); } public function provideDomains(): iterable diff --git a/module/Rest/test-api/Action/TagVisitsActionTest.php b/module/Rest/test-api/Action/TagVisitsActionTest.php index 94e592f6..d0f9838b 100644 --- a/module/Rest/test-api/Action/TagVisitsActionTest.php +++ b/module/Rest/test-api/Action/TagVisitsActionTest.php @@ -19,9 +19,9 @@ class TagVisitsActionTest extends ApiTestCase $resp = $this->callApiWithKey(self::METHOD_GET, sprintf('/tags/%s/visits', $tag)); $payload = $this->getJsonResponsePayload($resp); - $this->assertArrayHasKey('visits', $payload); - $this->assertArrayHasKey('data', $payload['visits']); - $this->assertCount($expectedVisitsAmount, $payload['visits']['data']); + self::assertArrayHasKey('visits', $payload); + self::assertArrayHasKey('data', $payload['visits']); + self::assertCount($expectedVisitsAmount, $payload['visits']['data']); } public function provideTags(): iterable @@ -37,10 +37,10 @@ class TagVisitsActionTest extends ApiTestCase $resp = $this->callApiWithKey(self::METHOD_GET, '/tags/invalid_tag/visits'); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); - $this->assertEquals('TAG_NOT_FOUND', $payload['type']); - $this->assertEquals('Tag with name "invalid_tag" could not be found', $payload['detail']); - $this->assertEquals('Tag not found', $payload['title']); + self::assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); + self::assertEquals(self::STATUS_NOT_FOUND, $payload['status']); + self::assertEquals('TAG_NOT_FOUND', $payload['type']); + self::assertEquals('Tag with name "invalid_tag" could not be found', $payload['detail']); + self::assertEquals('Tag not found', $payload['title']); } } diff --git a/module/Rest/test-api/Action/UpdateTagActionTest.php b/module/Rest/test-api/Action/UpdateTagActionTest.php index eb70685a..de0b1594 100644 --- a/module/Rest/test-api/Action/UpdateTagActionTest.php +++ b/module/Rest/test-api/Action/UpdateTagActionTest.php @@ -20,11 +20,11 @@ class UpdateTagActionTest extends ApiTestCase $resp = $this->callApiWithKey(self::METHOD_PUT, '/tags', [RequestOptions::JSON => $body]); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); - $this->assertEquals('INVALID_ARGUMENT', $payload['type']); - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals('Invalid data', $payload['title']); + self::assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); + self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); + self::assertEquals('INVALID_ARGUMENT', $payload['type']); + self::assertEquals($expectedDetail, $payload['detail']); + self::assertEquals('Invalid data', $payload['title']); } public function provideInvalidBody(): iterable @@ -45,11 +45,11 @@ class UpdateTagActionTest extends ApiTestCase ]]); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); - $this->assertEquals('TAG_NOT_FOUND', $payload['type']); - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals('Tag not found', $payload['title']); + self::assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); + self::assertEquals(self::STATUS_NOT_FOUND, $payload['status']); + self::assertEquals('TAG_NOT_FOUND', $payload['type']); + self::assertEquals($expectedDetail, $payload['detail']); + self::assertEquals('Tag not found', $payload['title']); } /** @test */ @@ -63,11 +63,11 @@ class UpdateTagActionTest extends ApiTestCase ]]); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_CONFLICT, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_CONFLICT, $payload['status']); - $this->assertEquals('TAG_CONFLICT', $payload['type']); - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals('Tag conflict', $payload['title']); + self::assertEquals(self::STATUS_CONFLICT, $resp->getStatusCode()); + self::assertEquals(self::STATUS_CONFLICT, $payload['status']); + self::assertEquals('TAG_CONFLICT', $payload['type']); + self::assertEquals($expectedDetail, $payload['detail']); + self::assertEquals('Tag conflict', $payload['title']); } /** @test */ @@ -78,6 +78,6 @@ class UpdateTagActionTest extends ApiTestCase 'newName' => 'foo', ]]); - $this->assertEquals(self::STATUS_NO_CONTENT, $resp->getStatusCode()); + self::assertEquals(self::STATUS_NO_CONTENT, $resp->getStatusCode()); } } diff --git a/module/Rest/test-api/Middleware/AuthenticationTest.php b/module/Rest/test-api/Middleware/AuthenticationTest.php index aa90451f..f71ddfd1 100644 --- a/module/Rest/test-api/Middleware/AuthenticationTest.php +++ b/module/Rest/test-api/Middleware/AuthenticationTest.php @@ -24,11 +24,11 @@ class AuthenticationTest extends ApiTestCase $resp = $this->callApi(self::METHOD_GET, '/short-urls'); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']); - $this->assertEquals('INVALID_AUTHORIZATION', $payload['type']); - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals('Invalid authorization', $payload['title']); + self::assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode()); + self::assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']); + self::assertEquals('INVALID_AUTHORIZATION', $payload['type']); + self::assertEquals($expectedDetail, $payload['detail']); + self::assertEquals('Invalid authorization', $payload['title']); } /** @@ -46,11 +46,11 @@ class AuthenticationTest extends ApiTestCase ]); $payload = $this->getJsonResponsePayload($resp); - $this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']); - $this->assertEquals('INVALID_API_KEY', $payload['type']); - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals('Invalid API key', $payload['title']); + self::assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode()); + self::assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']); + self::assertEquals('INVALID_API_KEY', $payload['type']); + self::assertEquals($expectedDetail, $payload['detail']); + self::assertEquals('Invalid API key', $payload['title']); } public function provideInvalidApiKeys(): iterable diff --git a/module/Rest/test-api/Middleware/ImplicitOptionsTest.php b/module/Rest/test-api/Middleware/ImplicitOptionsTest.php index ddfaa62d..f9f140c2 100644 --- a/module/Rest/test-api/Middleware/ImplicitOptionsTest.php +++ b/module/Rest/test-api/Middleware/ImplicitOptionsTest.php @@ -15,8 +15,8 @@ class ImplicitOptionsTest extends ApiTestCase { $resp = $this->callApi(self::METHOD_OPTIONS, '/short-urls'); - $this->assertEquals(self::STATUS_NO_CONTENT, $resp->getStatusCode()); - $this->assertEmpty((string) $resp->getBody()); + self::assertEquals(self::STATUS_NO_CONTENT, $resp->getStatusCode()); + self::assertEmpty((string) $resp->getBody()); } /** @test */ @@ -25,7 +25,7 @@ class ImplicitOptionsTest extends ApiTestCase $resp = $this->callApi(self::METHOD_OPTIONS, '/short-urls'); $allowedMethods = $resp->getHeaderLine('Allow'); - $this->assertEquals([ + self::assertEquals([ self::METHOD_GET, self::METHOD_POST, ], explode(',', $allowedMethods)); diff --git a/module/Rest/test/Action/HealthActionTest.php b/module/Rest/test/Action/HealthActionTest.php index 2ec68d25..57c0dd58 100644 --- a/module/Rest/test/Action/HealthActionTest.php +++ b/module/Rest/test/Action/HealthActionTest.php @@ -37,14 +37,14 @@ class HealthActionTest extends TestCase $resp = $this->action->handle(new ServerRequest()); $payload = $resp->getPayload(); - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals('pass', $payload['status']); - $this->assertEquals('1.2.3', $payload['version']); - $this->assertEquals([ + self::assertEquals(200, $resp->getStatusCode()); + self::assertEquals('pass', $payload['status']); + self::assertEquals('1.2.3', $payload['version']); + self::assertEquals([ 'about' => 'https://shlink.io', 'project' => 'https://github.com/shlinkio/shlink', ], $payload['links']); - $this->assertEquals('application/health+json', $resp->getHeaderLine('Content-type')); + self::assertEquals('application/health+json', $resp->getHeaderLine('Content-type')); $ping->shouldHaveBeenCalledOnce(); } @@ -57,14 +57,14 @@ class HealthActionTest extends TestCase $resp = $this->action->handle(new ServerRequest()); $payload = $resp->getPayload(); - $this->assertEquals(503, $resp->getStatusCode()); - $this->assertEquals('fail', $payload['status']); - $this->assertEquals('1.2.3', $payload['version']); - $this->assertEquals([ + self::assertEquals(503, $resp->getStatusCode()); + self::assertEquals('fail', $payload['status']); + self::assertEquals('1.2.3', $payload['version']); + self::assertEquals([ 'about' => 'https://shlink.io', 'project' => 'https://github.com/shlinkio/shlink', ], $payload['links']); - $this->assertEquals('application/health+json', $resp->getHeaderLine('Content-type')); + self::assertEquals('application/health+json', $resp->getHeaderLine('Content-type')); $ping->shouldHaveBeenCalledOnce(); } @@ -77,14 +77,14 @@ class HealthActionTest extends TestCase $resp = $this->action->handle(new ServerRequest()); $payload = $resp->getPayload(); - $this->assertEquals(503, $resp->getStatusCode()); - $this->assertEquals('fail', $payload['status']); - $this->assertEquals('1.2.3', $payload['version']); - $this->assertEquals([ + self::assertEquals(503, $resp->getStatusCode()); + self::assertEquals('fail', $payload['status']); + self::assertEquals('1.2.3', $payload['version']); + self::assertEquals([ 'about' => 'https://shlink.io', 'project' => 'https://github.com/shlinkio/shlink', ], $payload['links']); - $this->assertEquals('application/health+json', $resp->getHeaderLine('Content-type')); + self::assertEquals('application/health+json', $resp->getHeaderLine('Content-type')); $ping->shouldHaveBeenCalledOnce(); } } diff --git a/module/Rest/test/Action/MercureInfoActionTest.php b/module/Rest/test/Action/MercureInfoActionTest.php index d40b3f70..aaf9e089 100644 --- a/module/Rest/test/Action/MercureInfoActionTest.php +++ b/module/Rest/test/Action/MercureInfoActionTest.php @@ -87,11 +87,11 @@ class MercureInfoActionTest extends TestCase $resp = $action->handle(ServerRequestFactory::fromGlobals()); $payload = $resp->getPayload(); - $this->assertArrayHasKey('mercureHubUrl', $payload); - $this->assertEquals('http://foobar.com/.well-known/mercure', $payload['mercureHubUrl']); - $this->assertArrayHasKey('token', $payload); - $this->assertArrayHasKey('jwtExpiration', $payload); - $this->assertEquals( + self::assertArrayHasKey('mercureHubUrl', $payload); + self::assertEquals('http://foobar.com/.well-known/mercure', $payload['mercureHubUrl']); + self::assertArrayHasKey('token', $payload); + self::assertArrayHasKey('jwtExpiration', $payload); + self::assertEquals( Chronos::now()->addDays($days ?? 1)->startOfDay(), Chronos::parse($payload['jwtExpiration'])->startOfDay(), ); diff --git a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php index 66f1eaaa..78b28385 100644 --- a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php @@ -57,8 +57,8 @@ class CreateShortUrlActionTest extends TestCase $request = ServerRequestFactory::fromGlobals()->withParsedBody($body); $response = $this->action->handle($request); - $this->assertEquals(200, $response->getStatusCode()); - $this->assertTrue(strpos($response->getBody()->getContents(), $shortUrl->toString(self::DOMAIN_CONFIG)) > 0); + self::assertEquals(200, $response->getStatusCode()); + self::assertTrue(strpos($response->getBody()->getContents(), $shortUrl->toString(self::DOMAIN_CONFIG)) > 0); $shorten->shouldHaveBeenCalledOnce(); } diff --git a/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php index cbd8e2bc..5c448561 100644 --- a/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php @@ -30,7 +30,7 @@ class DeleteShortUrlActionTest extends TestCase $resp = $this->action->handle(new ServerRequest()); - $this->assertEquals(204, $resp->getStatusCode()); + self::assertEquals(204, $resp->getStatusCode()); $deleteByShortCode->shouldHaveBeenCalledOnce(); } } diff --git a/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php index d6247d9f..57d63cde 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 $resp = $this->action->handle($request); - $this->assertEquals(204, $resp->getStatusCode()); + self::assertEquals(204, $resp->getStatusCode()); $updateMeta->shouldHaveBeenCalled(); } } diff --git a/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php b/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php index d7a86844..527ffb71 100644 --- a/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php +++ b/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php @@ -42,6 +42,6 @@ class EditShortUrlTagsActionTest extends TestCase (new ServerRequest())->withAttribute('shortCode', 'abc123') ->withParsedBody(['tags' => []]), ); - $this->assertEquals(200, $response->getStatusCode()); + self::assertEquals(200, $response->getStatusCode()); } } diff --git a/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php b/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php index 3d98c2fe..423cd497 100644 --- a/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php +++ b/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php @@ -59,10 +59,10 @@ class ListShortUrlsActionTest extends TestCase $response = $this->action->handle((new ServerRequest())->withQueryParams($query)); $payload = $response->getPayload(); - $this->assertArrayHasKey('shortUrls', $payload); - $this->assertArrayHasKey('data', $payload['shortUrls']); - $this->assertEquals([], $payload['shortUrls']['data']); - $this->assertEquals(200, $response->getStatusCode()); + self::assertArrayHasKey('shortUrls', $payload); + self::assertArrayHasKey('data', $payload['shortUrls']); + self::assertEquals([], $payload['shortUrls']['data']); + self::assertEquals(200, $response->getStatusCode()); $listShortUrls->shouldHaveBeenCalledOnce(); } diff --git a/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php index a62b1f95..5e6316a0 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 $request = (new ServerRequest())->withAttribute('shortCode', $shortCode); $response = $this->action->handle($request); - $this->assertEquals(200, $response->getStatusCode()); - $this->assertTrue(strpos($response->getBody()->getContents(), 'http://domain.com/foo/bar') > 0); + self::assertEquals(200, $response->getStatusCode()); + self::assertTrue(strpos($response->getBody()->getContents(), 'http://domain.com/foo/bar') > 0); } } diff --git a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php index d63a83b9..cb931570 100644 --- a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php @@ -80,7 +80,7 @@ class SingleStepCreateShortUrlActionTest extends TestCase $resp = $this->action->handle($request); - $this->assertEquals(200, $resp->getStatusCode()); + self::assertEquals(200, $resp->getStatusCode()); $findApiKey->shouldHaveBeenCalled(); $generateShortCode->shouldHaveBeenCalled(); } diff --git a/module/Rest/test/Action/Tag/CreateTagsActionTest.php b/module/Rest/test/Action/Tag/CreateTagsActionTest.php index 33aa0ba7..ac74740e 100644 --- a/module/Rest/test/Action/Tag/CreateTagsActionTest.php +++ b/module/Rest/test/Action/Tag/CreateTagsActionTest.php @@ -33,7 +33,7 @@ class CreateTagsActionTest extends TestCase $response = $this->action->handle($request); - $this->assertEquals(200, $response->getStatusCode()); + self::assertEquals(200, $response->getStatusCode()); $deleteTags->shouldHaveBeenCalled(); } diff --git a/module/Rest/test/Action/Tag/DeleteTagsActionTest.php b/module/Rest/test/Action/Tag/DeleteTagsActionTest.php index 819a608a..de145bf0 100644 --- a/module/Rest/test/Action/Tag/DeleteTagsActionTest.php +++ b/module/Rest/test/Action/Tag/DeleteTagsActionTest.php @@ -32,7 +32,7 @@ class DeleteTagsActionTest extends TestCase $response = $this->action->handle($request); - $this->assertEquals(204, $response->getStatusCode()); + self::assertEquals(204, $response->getStatusCode()); $deleteTags->shouldHaveBeenCalled(); } diff --git a/module/Rest/test/Action/Tag/ListTagsActionTest.php b/module/Rest/test/Action/Tag/ListTagsActionTest.php index 461ddd3f..f881c75d 100644 --- a/module/Rest/test/Action/Tag/ListTagsActionTest.php +++ b/module/Rest/test/Action/Tag/ListTagsActionTest.php @@ -37,7 +37,7 @@ class ListTagsActionTest extends TestCase $resp = $this->action->handle(ServerRequestFactory::fromGlobals()->withQueryParams($query)); $payload = $resp->getPayload(); - $this->assertEquals([ + self::assertEquals([ 'tags' => [ 'data' => $tags, ], @@ -65,7 +65,7 @@ class ListTagsActionTest extends TestCase $resp = $this->action->handle(ServerRequestFactory::fromGlobals()->withQueryParams(['withStats' => 'true'])); $payload = $resp->getPayload(); - $this->assertEquals([ + self::assertEquals([ 'tags' => [ 'data' => ['foo', 'bar'], 'stats' => $stats, diff --git a/module/Rest/test/Action/Tag/UpdateTagActionTest.php b/module/Rest/test/Action/Tag/UpdateTagActionTest.php index 11b2c1c4..2caf20c3 100644 --- a/module/Rest/test/Action/Tag/UpdateTagActionTest.php +++ b/module/Rest/test/Action/Tag/UpdateTagActionTest.php @@ -54,7 +54,7 @@ class UpdateTagActionTest extends TestCase $resp = $this->action->handle($request); - $this->assertEquals(204, $resp->getStatusCode()); + self::assertEquals(204, $resp->getStatusCode()); $rename->shouldHaveBeenCalled(); } } diff --git a/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php b/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php index 7e1dec06..5c28451e 100644 --- a/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php @@ -33,7 +33,7 @@ class GlobalVisitsActionTest extends TestCase $resp = $this->action->handle(ServerRequestFactory::fromGlobals()); $payload = $resp->getPayload(); - $this->assertEquals($payload, ['visits' => $stats]); + self::assertEquals($payload, ['visits' => $stats]); $getStats->shouldHaveBeenCalledOnce(); } } diff --git a/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php b/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php index 07508acf..c05cc84d 100644 --- a/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php @@ -37,7 +37,7 @@ class ShortUrlVisitsActionTest extends TestCase )->shouldBeCalledOnce(); $response = $this->action->handle((new ServerRequest())->withAttribute('shortCode', $shortCode)); - $this->assertEquals(200, $response->getStatusCode()); + self::assertEquals(200, $response->getStatusCode()); } /** @test */ @@ -60,6 +60,6 @@ class ShortUrlVisitsActionTest extends TestCase 'itemsPerPage' => '10', ]), ); - $this->assertEquals(200, $response->getStatusCode()); + self::assertEquals(200, $response->getStatusCode()); } } diff --git a/module/Rest/test/Action/Visit/TagVisitsActionTest.php b/module/Rest/test/Action/Visit/TagVisitsActionTest.php index 863bc725..7906f530 100644 --- a/module/Rest/test/Action/Visit/TagVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/TagVisitsActionTest.php @@ -35,7 +35,7 @@ class TagVisitsActionTest extends TestCase $response = $this->action->handle((new ServerRequest())->withAttribute('tag', $tag)); - $this->assertEquals(200, $response->getStatusCode()); + self::assertEquals(200, $response->getStatusCode()); $getVisits->shouldHaveBeenCalledOnce(); } } diff --git a/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php b/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php index 326054d7..03ffc8dd 100644 --- a/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php +++ b/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php @@ -29,7 +29,7 @@ class AuthenticationPluginManagerFactoryTest extends TestCase 'config' => $config, ]])); - $this->assertEquals($expectedPlugins, $this->getPlugins($instance)); + self::assertEquals($expectedPlugins, $this->getPlugins($instance)); } private function getPlugins(AuthenticationPluginManager $pluginManager): array diff --git a/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php b/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php index e6a9c23d..146957e4 100644 --- a/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php +++ b/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php @@ -56,7 +56,7 @@ class ApiKeyHeaderPluginTest extends TestCase $returnedResponse = $this->plugin->update($this->createRequest($apiKey), $response); - $this->assertSame($response, $returnedResponse); + self::assertSame($response, $returnedResponse); } private function createRequest(string $apiKey): ServerRequestInterface diff --git a/module/Rest/test/ConfigProviderTest.php b/module/Rest/test/ConfigProviderTest.php index 8032a854..69f745ff 100644 --- a/module/Rest/test/ConfigProviderTest.php +++ b/module/Rest/test/ConfigProviderTest.php @@ -21,8 +21,8 @@ class ConfigProviderTest extends TestCase { $config = ($this->configProvider)(); - $this->assertArrayHasKey('routes', $config); - $this->assertArrayHasKey('dependencies', $config); + self::assertArrayHasKey('routes', $config); + self::assertArrayHasKey('dependencies', $config); } /** @@ -35,7 +35,7 @@ class ConfigProviderTest extends TestCase $config = $configProvider(); - $this->assertEquals($expected, $config['routes']); + self::assertEquals($expected, $config['routes']); } public function provideRoutesConfig(): iterable diff --git a/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php b/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php index eee6058e..afe2a54e 100644 --- a/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php +++ b/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php @@ -25,12 +25,12 @@ class MissingAuthenticationExceptionTest extends TestCase $e = MissingAuthenticationException::fromExpectedTypes($expectedTypes); - $this->assertEquals($expectedMessage, $e->getMessage()); - $this->assertEquals($expectedMessage, $e->getDetail()); - $this->assertEquals('Invalid authorization', $e->getTitle()); - $this->assertEquals('INVALID_AUTHORIZATION', $e->getType()); - $this->assertEquals(401, $e->getStatus()); - $this->assertEquals(['expectedTypes' => $expectedTypes], $e->getAdditionalData()); + self::assertEquals($expectedMessage, $e->getMessage()); + self::assertEquals($expectedMessage, $e->getDetail()); + self::assertEquals('Invalid authorization', $e->getTitle()); + self::assertEquals('INVALID_AUTHORIZATION', $e->getType()); + self::assertEquals(401, $e->getStatus()); + self::assertEquals(['expectedTypes' => $expectedTypes], $e->getAdditionalData()); } public function provideExpectedTypes(): iterable diff --git a/module/Rest/test/Exception/VerifyAuthenticationExceptionTest.php b/module/Rest/test/Exception/VerifyAuthenticationExceptionTest.php index 28563c5f..3221041d 100644 --- a/module/Rest/test/Exception/VerifyAuthenticationExceptionTest.php +++ b/module/Rest/test/Exception/VerifyAuthenticationExceptionTest.php @@ -14,6 +14,6 @@ class VerifyAuthenticationExceptionTest extends TestCase { $e = VerifyAuthenticationException::forInvalidApiKey(); - $this->assertEquals('Provided API key does not exist or is invalid.', $e->getMessage()); + self::assertEquals('Provided API key does not exist or is invalid.', $e->getMessage()); } } diff --git a/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php b/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php index cd002f60..e1b8deca 100644 --- a/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php +++ b/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php @@ -100,7 +100,7 @@ class AuthenticationMiddlewareTest extends TestCase $handle = $handler->handle($request)->willReturn(new Response()); $response = $this->middleware->process($request, $handler->reveal()); - $this->assertSame($response, $newResponse); + self::assertSame($response, $newResponse); $verify->shouldHaveBeenCalledOnce(); $update->shouldHaveBeenCalledOnce(); $handle->shouldHaveBeenCalledOnce(); diff --git a/module/Rest/test/Middleware/BodyParserMiddlewareTest.php b/module/Rest/test/Middleware/BodyParserMiddlewareTest.php index 5adae27d..8c047bb1 100644 --- a/module/Rest/test/Middleware/BodyParserMiddlewareTest.php +++ b/module/Rest/test/Middleware/BodyParserMiddlewareTest.php @@ -35,7 +35,7 @@ class BodyParserMiddlewareTest extends TestCase $request->getMethod()->willReturn($method); $request->getParsedBody()->willReturn([]); - $this->assertHandlingRequestJustFallsBackToNext($request); + self::assertHandlingRequestJustFallsBackToNext($request); } public function provideIgnoredRequestMethods(): iterable @@ -52,7 +52,7 @@ class BodyParserMiddlewareTest extends TestCase $request->getMethod()->willReturn('POST'); $request->getParsedBody()->willReturn(['foo' => 'bar']); - $this->assertHandlingRequestJustFallsBackToNext($request); + self::assertHandlingRequestJustFallsBackToNext($request); } private function assertHandlingRequestJustFallsBackToNext(ProphecyInterface $requestMock): void diff --git a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php index 5cc99fb3..5e9398ea 100644 --- a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php +++ b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php @@ -37,13 +37,13 @@ class CrossDomainMiddlewareTest extends TestCase $response = $this->middleware->process(new ServerRequest(), $this->handler->reveal()); $headers = $response->getHeaders(); - $this->assertSame($originalResponse, $response); - $this->assertEquals(404, $response->getStatusCode()); - $this->assertArrayNotHasKey('Access-Control-Allow-Origin', $headers); - $this->assertArrayNotHasKey('Access-Control-Expose-Headers', $headers); - $this->assertArrayNotHasKey('Access-Control-Allow-Methods', $headers); - $this->assertArrayNotHasKey('Access-Control-Max-Age', $headers); - $this->assertArrayNotHasKey('Access-Control-Allow-Headers', $headers); + self::assertSame($originalResponse, $response); + self::assertEquals(404, $response->getStatusCode()); + self::assertArrayNotHasKey('Access-Control-Allow-Origin', $headers); + self::assertArrayNotHasKey('Access-Control-Expose-Headers', $headers); + self::assertArrayNotHasKey('Access-Control-Allow-Methods', $headers); + self::assertArrayNotHasKey('Access-Control-Max-Age', $headers); + self::assertArrayNotHasKey('Access-Control-Allow-Headers', $headers); } /** @test */ @@ -56,18 +56,18 @@ class CrossDomainMiddlewareTest extends TestCase (new ServerRequest())->withHeader('Origin', 'local'), $this->handler->reveal(), ); - $this->assertNotSame($originalResponse, $response); + self::assertNotSame($originalResponse, $response); $headers = $response->getHeaders(); - $this->assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin')); - $this->assertEquals( + self::assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin')); + self::assertEquals( Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME, $response->getHeaderLine('Access-Control-Expose-Headers'), ); - $this->assertArrayNotHasKey('Access-Control-Allow-Methods', $headers); - $this->assertArrayNotHasKey('Access-Control-Max-Age', $headers); - $this->assertArrayNotHasKey('Access-Control-Allow-Headers', $headers); + self::assertArrayNotHasKey('Access-Control-Allow-Methods', $headers); + self::assertArrayNotHasKey('Access-Control-Max-Age', $headers); + self::assertArrayNotHasKey('Access-Control-Allow-Headers', $headers); } /** @test */ @@ -81,19 +81,19 @@ class CrossDomainMiddlewareTest extends TestCase $this->handler->handle(Argument::any())->willReturn($originalResponse)->shouldBeCalledOnce(); $response = $this->middleware->process($request, $this->handler->reveal()); - $this->assertNotSame($originalResponse, $response); + self::assertNotSame($originalResponse, $response); $headers = $response->getHeaders(); - $this->assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin')); - $this->assertEquals( + self::assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin')); + self::assertEquals( Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME, $response->getHeaderLine('Access-Control-Expose-Headers'), ); - $this->assertArrayHasKey('Access-Control-Allow-Methods', $headers); - $this->assertEquals('1000', $response->getHeaderLine('Access-Control-Max-Age')); - $this->assertEquals('foo, bar, baz', $response->getHeaderLine('Access-Control-Allow-Headers')); - $this->assertEquals(204, $response->getStatusCode()); + self::assertArrayHasKey('Access-Control-Allow-Methods', $headers); + self::assertEquals('1000', $response->getHeaderLine('Access-Control-Max-Age')); + self::assertEquals('foo, bar, baz', $response->getHeaderLine('Access-Control-Allow-Headers')); + self::assertEquals(204, $response->getStatusCode()); } /** @@ -112,8 +112,8 @@ class CrossDomainMiddlewareTest extends TestCase $response = $this->middleware->process($request, $this->handler->reveal()); - $this->assertEquals($response->getHeaderLine('Access-Control-Allow-Methods'), $expectedAllowedMethods); - $this->assertEquals(204, $response->getStatusCode()); + self::assertEquals($response->getHeaderLine('Access-Control-Allow-Methods'), $expectedAllowedMethods); + self::assertEquals(204, $response->getStatusCode()); } public function provideRouteResults(): iterable @@ -145,7 +145,7 @@ class CrossDomainMiddlewareTest extends TestCase $response = $this->middleware->process($request, $this->handler->reveal()); - $this->assertEquals($expectedStatus, $response->getStatusCode()); + self::assertEquals($expectedStatus, $response->getStatusCode()); } public function provideMethods(): iterable diff --git a/module/Rest/test/Middleware/EmptyResponseImplicitOptionsMiddlewareFactoryTest.php b/module/Rest/test/Middleware/EmptyResponseImplicitOptionsMiddlewareFactoryTest.php index 73fdd07e..9d216913 100644 --- a/module/Rest/test/Middleware/EmptyResponseImplicitOptionsMiddlewareFactoryTest.php +++ b/module/Rest/test/Middleware/EmptyResponseImplicitOptionsMiddlewareFactoryTest.php @@ -23,7 +23,7 @@ class EmptyResponseImplicitOptionsMiddlewareFactoryTest extends TestCase public function serviceIsCreated(): void { $instance = ($this->factory)(); - $this->assertInstanceOf(ImplicitOptionsMiddleware::class, $instance); + self::assertInstanceOf(ImplicitOptionsMiddleware::class, $instance); } /** @test */ @@ -34,6 +34,6 @@ class EmptyResponseImplicitOptionsMiddlewareFactoryTest extends TestCase $ref = new ReflectionObject($instance); $prop = $ref->getProperty('responseFactory'); $prop->setAccessible(true); - $this->assertInstanceOf(EmptyResponse::class, $prop->getValue($instance)()); + self::assertInstanceOf(EmptyResponse::class, $prop->getValue($instance)()); } } diff --git a/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php index f70a4d4a..3950282c 100644 --- a/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php +++ b/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php @@ -33,7 +33,7 @@ class CreateShortUrlContentNegotiationMiddlewareTest extends TestCase $resp = $this->middleware->process(new ServerRequest(), $this->requestHandler->reveal()); - $this->assertSame($expectedResp, $resp); + self::assertSame($expectedResp, $resp); } /** @@ -54,7 +54,7 @@ class CreateShortUrlContentNegotiationMiddlewareTest extends TestCase $response = $this->middleware->process($request, $this->requestHandler->reveal()); - $this->assertEquals($expectedContentType, $response->getHeaderLine('Content-type')); + self::assertEquals($expectedContentType, $response->getHeaderLine('Content-type')); $handle->shouldHaveBeenCalled(); } @@ -85,7 +85,7 @@ class CreateShortUrlContentNegotiationMiddlewareTest extends TestCase $response = $this->middleware->process($request, $this->requestHandler->reveal()); - $this->assertEquals($expectedBody, (string) $response->getBody()); + self::assertEquals($expectedBody, (string) $response->getBody()); $handle->shouldHaveBeenCalled(); } diff --git a/module/Rest/test/Service/ApiKeyServiceTest.php b/module/Rest/test/Service/ApiKeyServiceTest.php index c371beab..d4d40e4b 100644 --- a/module/Rest/test/Service/ApiKeyServiceTest.php +++ b/module/Rest/test/Service/ApiKeyServiceTest.php @@ -36,7 +36,7 @@ class ApiKeyServiceTest extends TestCase $key = $this->service->create($date); - $this->assertEquals($date, $key->getExpirationDate()); + self::assertEquals($date, $key->getExpirationDate()); } public function provideCreationDate(): iterable @@ -56,7 +56,7 @@ class ApiKeyServiceTest extends TestCase ->shouldBeCalledOnce(); $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); - $this->assertFalse($this->service->check('12345')); + self::assertFalse($this->service->check('12345')); } public function provideInvalidApiKeys(): iterable @@ -74,7 +74,7 @@ class ApiKeyServiceTest extends TestCase ->shouldBeCalledOnce(); $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); - $this->assertTrue($this->service->check('12345')); + self::assertTrue($this->service->check('12345')); } /** @test */ @@ -101,10 +101,10 @@ class ApiKeyServiceTest extends TestCase $this->em->flush()->shouldBeCalledOnce(); - $this->assertTrue($key->isEnabled()); + self::assertTrue($key->isEnabled()); $returnedKey = $this->service->disable('12345'); - $this->assertFalse($key->isEnabled()); - $this->assertSame($key, $returnedKey); + self::assertFalse($key->isEnabled()); + self::assertSame($key, $returnedKey); } /** @test */ @@ -119,7 +119,7 @@ class ApiKeyServiceTest extends TestCase $result = $this->service->listKeys(); - $this->assertEquals($expectedApiKeys, $result); + self::assertEquals($expectedApiKeys, $result); } /** @test */ @@ -134,6 +134,6 @@ class ApiKeyServiceTest extends TestCase $result = $this->service->listKeys(true); - $this->assertEquals($expectedApiKeys, $result); + self::assertEquals($expectedApiKeys, $result); } } From a8b0c4614255bc834139a02d3920201cebb75793 Mon Sep 17 00:00:00 2001 From: Daniel Londero Date: Sun, 4 Oct 2020 00:35:29 +0200 Subject: [PATCH 33/73] Fix typo --- module/CLI/test/ConfigProviderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/CLI/test/ConfigProviderTest.php b/module/CLI/test/ConfigProviderTest.php index 7d1e5059..42a8f504 100644 --- a/module/CLI/test/ConfigProviderTest.php +++ b/module/CLI/test/ConfigProviderTest.php @@ -17,7 +17,7 @@ class ConfigProviderTest extends TestCase } /** @test */ - public function confiIsProperlyReturned(): void + public function configIsProperlyReturned(): void { $config = ($this->configProvider)(); From cbc9f1257dc0f88d896106e78ad3e4f3f9b9bccf Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 16 Oct 2020 19:21:40 +0200 Subject: [PATCH 34/73] Enabled Diactoros as module --- config/config.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/config.php b/config/config.php index 5ab429f0..d7fd6a3b 100644 --- a/config/config.php +++ b/config/config.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink; use Laminas\ConfigAggregator; +use Laminas\Diactoros; use Mezzio; use Mezzio\ProblemDetails; @@ -17,6 +18,7 @@ return (new ConfigAggregator\ConfigAggregator([ Mezzio\Plates\ConfigProvider::class, Mezzio\Swoole\ConfigProvider::class, ProblemDetails\ConfigProvider::class, + Diactoros\ConfigProvider::class, Common\ConfigProvider::class, Config\ConfigProvider::class, IpGeolocation\ConfigProvider::class, From e7bccb088db2e9ef791f6d6ea173e26045efdf8c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 16 Oct 2020 19:28:57 +0200 Subject: [PATCH 35/73] Updated to latest swoole and pdo_sqlsrv versions which are compatible with PHP8 --- .travis.yml | 2 +- Dockerfile | 4 ++-- data/infra/php.Dockerfile | 2 +- data/infra/swoole.Dockerfile | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index f6e7091b..c9945281 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ before_install: - phpenv config-rm xdebug.ini || return 0 - sudo ./data/infra/ci/install-ms-odbc.sh - docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_ms shlink_db shlink_db_postgres shlink_db_maria - - yes | pecl install pdo_sqlsrv swoole-4.5.2 pcov + - yes | pecl install pdo_sqlsrv-5.9.0preview1 swoole-4.5.5 pcov install: - composer self-update diff --git a/Dockerfile b/Dockerfile index 0c5411e2..b8bdda10 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM php:7.4.9-alpine3.12 as base +FROM php:7.4.11-alpine3.12 as base ARG SHLINK_VERSION=2.3.0 ENV SHLINK_VERSION ${SHLINK_VERSION} -ENV SWOOLE_VERSION 4.5.2 +ENV SWOOLE_VERSION 4.5.5 ENV LC_ALL "C" WORKDIR /etc/shlink diff --git a/data/infra/php.Dockerfile b/data/infra/php.Dockerfile index 94d5e8fe..0e10911e 100644 --- a/data/infra/php.Dockerfile +++ b/data/infra/php.Dockerfile @@ -1,4 +1,4 @@ -FROM php:7.4.9-alpine3.12 +FROM php:7.4.11-alpine3.12 MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.18 diff --git a/data/infra/swoole.Dockerfile b/data/infra/swoole.Dockerfile index ecc8ede0..6dafa0eb 100644 --- a/data/infra/swoole.Dockerfile +++ b/data/infra/swoole.Dockerfile @@ -1,10 +1,10 @@ -FROM php:7.4.9-alpine3.12 +FROM php:7.4.11-alpine3.12 MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.18 ENV APCU_BC_VERSION 1.0.5 ENV INOTIFY_VERSION 2.0.0 -ENV SWOOLE_VERSION 4.5.2 +ENV SWOOLE_VERSION 4.5.5 RUN apk update From 4ec90e02c9f099af7eceb3277681024bf8bb624b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 16 Oct 2020 19:53:05 +0200 Subject: [PATCH 36/73] Updated to latest infection --- composer.json | 6 +++--- phpstan.neon | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index c508b0fd..99844320 100644 --- a/composer.json +++ b/composer.json @@ -66,14 +66,14 @@ "devster/ubench": "^2.0", "dms/phpunit-arraysubset-asserts": "^0.2.0", "eaglewu/swoole-ide-helper": "dev-master", - "infection/infection": "^0.16.1", - "phpstan/phpstan": "^0.12.18", + "infection/infection": "^0.17.7", + "phpstan/phpstan": "^0.12.50", "phpunit/php-code-coverage": "^8.0", "phpunit/phpunit": "~9.0.1", "roave/security-advisories": "dev-master", "shlinkio/php-coding-standard": "~2.1.0", "shlinkio/shlink-test-utils": "^1.5", - "symfony/var-dumper": "^5.0" + "symfony/var-dumper": "^5.1" }, "autoload": { "psr-4": { diff --git a/phpstan.neon b/phpstan.neon index 0ce0463c..969b00b4 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,7 +2,5 @@ parameters: checkMissingIterableValueType: false checkGenericClassInNonGenericObjectType: false ignoreErrors: - - '#AbstractQuery::setParameters\(\)#' - '#mustRun\(\)#' - - '#AssociationBuilder::setOrderBy#' - '#If condition is always false#' From ce3d267572697209a532dc4a0fe04a4e6f43aeda Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 16 Oct 2020 19:54:09 +0200 Subject: [PATCH 37/73] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index df10baf7..ff32f795 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#836](https://github.com/shlinkio/shlink/issues/836) Added support for the `-` notation while determining how to order the short URLs list, as in `?orderBy=shortCode-DESC`. This effectively deprecates the array notation (`?orderBy[shortCode]=DESC`), that will be removed in Shlink 3.0.0 * [#782](https://github.com/shlinkio/shlink/issues/782) Added code coverage to API tests. +* [#858](https://github.com/shlinkio/shlink/issues/858) Updated to latest infection version. Updated docker images to PHP 7.4.11 and swoole 4.5.5 #### Deprecated From 33d3837795a08f31a5882efc4e9decda228d4e21 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 22 Oct 2020 18:12:22 +0200 Subject: [PATCH 38/73] Added dependency on shlinkio/shlink-importer --- CHANGELOG.md | 1 + composer.json | 1 + config/autoload/dependencies.global.php | 6 ++ config/config.php | 1 + module/Core/config/dependencies.config.php | 9 +++ module/Core/src/Entity/ShortUrl.php | 24 ++++++++ .../src/Importer/ImportedLinksProcessor.php | 55 +++++++++++++++++++ 7 files changed, 97 insertions(+) create mode 100644 module/Core/src/Importer/ImportedLinksProcessor.php diff --git a/CHANGELOG.md b/CHANGELOG.md index ff32f795..7c044b9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#837](https://github.com/shlinkio/shlink/issues/837) Drastically improved performance when creating a new shortUrl and providing `findIfExists = true`. + ## 2.3.0 - 2020-08-09 #### Added diff --git a/composer.json b/composer.json index 99844320..597cc4b1 100644 --- a/composer.json +++ b/composer.json @@ -53,6 +53,7 @@ "shlinkio/shlink-common": "^3.2.0", "shlinkio/shlink-config": "^1.0", "shlinkio/shlink-event-dispatcher": "^1.4", + "shlinkio/shlink-importer": "^1.0", "shlinkio/shlink-installer": "^5.1.0", "shlinkio/shlink-ip-geolocation": "^1.5", "symfony/console": "^5.1", diff --git a/config/autoload/dependencies.global.php b/config/autoload/dependencies.global.php index 023b3c4e..dbc553f1 100644 --- a/config/autoload/dependencies.global.php +++ b/config/autoload/dependencies.global.php @@ -2,7 +2,9 @@ declare(strict_types=1); +use GuzzleHttp\Client; use Mezzio\Container; +use Psr\Http\Client\ClientInterface; return [ @@ -13,6 +15,10 @@ return [ ], ], + 'aliases' => [ + ClientInterface::class => Client::class, + ], + 'lazy_services' => [ 'proxies_target_dir' => 'data/proxies', 'proxies_namespace' => 'ShlinkProxy', diff --git a/config/config.php b/config/config.php index d7fd6a3b..ba0657fc 100644 --- a/config/config.php +++ b/config/config.php @@ -21,6 +21,7 @@ return (new ConfigAggregator\ConfigAggregator([ Diactoros\ConfigProvider::class, Common\ConfigProvider::class, Config\ConfigProvider::class, + Importer\ConfigProvider::class, IpGeolocation\ConfigProvider::class, EventDispatcher\ConfigProvider::class, Core\ConfigProvider::class, diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 5dcef9a2..09c4d96e 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -10,6 +10,7 @@ use Psr\EventDispatcher\EventDispatcherInterface; use Shlinkio\Shlink\Core\Domain\Resolver; use Shlinkio\Shlink\Core\ErrorHandler; use Shlinkio\Shlink\Core\Options\NotFoundRedirectOptions; +use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface; return [ @@ -42,6 +43,12 @@ return [ Resolver\PersistenceDomainResolver::class => ConfigAbstractFactory::class, Mercure\MercureUpdatesGenerator::class => ConfigAbstractFactory::class, + + Importer\ImportedLinksProcessor::class => ConfigAbstractFactory::class, + ], + + 'aliases' => [ + ImportedLinksProcessorInterface::class => Importer\ImportedLinksProcessor::class, ], ], @@ -96,6 +103,8 @@ return [ Resolver\PersistenceDomainResolver::class => ['em'], Mercure\MercureUpdatesGenerator::class => ['config.url_shortener.domain'], + + Importer\ImportedLinksProcessor::class => ['em', Resolver\PersistenceDomainResolver::class], ], ]; diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index ba10a44a..6da6562a 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -14,6 +14,8 @@ use Shlinkio\Shlink\Core\Domain\Resolver\SimpleDomainResolver; use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException; use Shlinkio\Shlink\Core\Model\ShortUrlEdit; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; +use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; +use Shlinkio\Shlink\Importer\Model\ShlinkUrl; use function count; use function Shlinkio\Shlink\Core\generateRandomShortCode; @@ -33,6 +35,7 @@ class ShortUrl extends AbstractEntity private ?Domain $domain = null; private bool $customSlugWasProvided; private int $shortCodeLength; + private ?string $source = null; public function __construct( string $longUrl, @@ -54,6 +57,27 @@ class ShortUrl extends AbstractEntity $this->domain = ($domainResolver ?? new SimpleDomainResolver())->resolveDomain($meta->getDomain()); } + public static function fromImport( + ShlinkUrl $url, + string $source, + bool $importShortCode, + ?DomainResolverInterface $domainResolver = null + ): self { + $meta = [ + ShortUrlMetaInputFilter::DOMAIN => $url->domain(), + ShortUrlMetaInputFilter::VALIDATE_URL => false, + ]; + if ($importShortCode) { + $meta[ShortUrlMetaInputFilter::CUSTOM_SLUG] = $url->shortCode(); + } + + $instance = new self($url->longUrl(), ShortUrlMeta::fromRawData($meta), $domainResolver); + $instance->source = $source; + $instance->dateCreated = Chronos::instance($url->createdAt()); + + return $instance; + } + public function getLongUrl(): string { return $this->longUrl; diff --git a/module/Core/src/Importer/ImportedLinksProcessor.php b/module/Core/src/Importer/ImportedLinksProcessor.php new file mode 100644 index 00000000..cb3ca57b --- /dev/null +++ b/module/Core/src/Importer/ImportedLinksProcessor.php @@ -0,0 +1,55 @@ +em = $em; + $this->domainResolver = $domainResolver; + } + + /** + * @param ShlinkUrl[] $shlinkUrls + */ + public function process(iterable $shlinkUrls, string $source, array $params): void + { + $importShortCodes = $params['import_short_codes']; + $count = 0; + $persistBlock = 100; + + foreach ($shlinkUrls as $url) { + $count++; + + $shortUrl = ShortUrl::fromImport($url, $source, $importShortCodes, $this->domainResolver); + $shortUrl->setTags($this->tagNamesToEntities($this->em, $url->tags())); + + // TODO Handle errors while creating short URLs, to avoid making the whole process fail + $this->em->persist($shortUrl); + + // Flush and clear after X iterations + if ($count % $persistBlock === 0) { + $this->em->flush(); + $this->em->clear(); + } + } + + $this->em->flush(); + $this->em->clear(); + } +} From 554d9b092fd80b912f34c4f846f24a4588817c53 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 23 Oct 2020 12:59:39 +0200 Subject: [PATCH 39/73] Added import_source column in ShortUrls --- data/migrations/Version20201023090929.php | 33 +++++++++++++++++++ .../Shlinkio.Shlink.Core.Entity.ShortUrl.php | 5 +++ module/Core/src/Entity/ShortUrl.php | 4 +-- 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 data/migrations/Version20201023090929.php diff --git a/data/migrations/Version20201023090929.php b/data/migrations/Version20201023090929.php new file mode 100644 index 00000000..49a401ba --- /dev/null +++ b/data/migrations/Version20201023090929.php @@ -0,0 +1,33 @@ +getTable('short_urls'); + $this->skipIf($shortUrls->hasColumn(self::IMPORT_SOURCE_COLUMN)); + + $shortUrls->addColumn(self::IMPORT_SOURCE_COLUMN, Types::STRING, [ + 'length' => 255, + 'notnull' => false, + ]); + } + + public function down(Schema $schema): void + { + $shortUrls = $schema->getTable('short_urls'); + $this->skipIf(! $shortUrls->hasColumn(self::IMPORT_SOURCE_COLUMN)); + + $shortUrls->dropColumn(self::IMPORT_SOURCE_COLUMN); + } +} diff --git a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php index 871ac113..55fa230e 100644 --- a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php +++ b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php @@ -51,6 +51,11 @@ return static function (ClassMetadata $metadata, array $emConfig): void { ->nullable() ->build(); + $builder->createField('importSource', Types::STRING) + ->columnName('import_source') + ->nullable() + ->build(); + $builder->createOneToMany('visits', Entity\Visit::class) ->mappedBy('shortUrl') ->fetchExtraLazy() diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 6da6562a..34883127 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -35,7 +35,7 @@ class ShortUrl extends AbstractEntity private ?Domain $domain = null; private bool $customSlugWasProvided; private int $shortCodeLength; - private ?string $source = null; + private ?string $importSource = null; public function __construct( string $longUrl, @@ -72,7 +72,7 @@ class ShortUrl extends AbstractEntity } $instance = new self($url->longUrl(), ShortUrlMeta::fromRawData($meta), $domainResolver); - $instance->source = $source; + $instance->importSource = $source; $instance->dateCreated = Chronos::instance($url->createdAt()); return $instance; From ec3e7212b2a078a97a603c5666f59084a392aff9 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 24 Oct 2020 13:55:54 +0200 Subject: [PATCH 40/73] =?UTF-8?q?Basic=20short-=C3=BArl=20import=20impleme?= =?UTF-8?q?ntation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 2 +- module/Core/src/Entity/ShortUrl.php | 4 +- .../src/Importer/ImportedLinksProcessor.php | 29 ++++---- .../src/Repository/ShortUrlRepository.php | 13 ++++ .../ShortUrlRepositoryInterface.php | 3 + .../Core/src/Util/DoctrineBatchIterator.php | 67 +++++++++++++++++++ 6 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 module/Core/src/Util/DoctrineBatchIterator.php diff --git a/composer.json b/composer.json index 597cc4b1..922057d8 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ "shlinkio/shlink-common": "^3.2.0", "shlinkio/shlink-config": "^1.0", "shlinkio/shlink-event-dispatcher": "^1.4", - "shlinkio/shlink-importer": "^1.0", + "shlinkio/shlink-importer": "^1.0.1", "shlinkio/shlink-installer": "^5.1.0", "shlinkio/shlink-ip-geolocation": "^1.5", "symfony/console": "^5.1", diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 34883127..ec752ba9 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -15,7 +15,7 @@ use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException; use Shlinkio\Shlink\Core\Model\ShortUrlEdit; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; -use Shlinkio\Shlink\Importer\Model\ShlinkUrl; +use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use function count; use function Shlinkio\Shlink\Core\generateRandomShortCode; @@ -58,7 +58,7 @@ class ShortUrl extends AbstractEntity } public static function fromImport( - ShlinkUrl $url, + ImportedShlinkUrl $url, string $source, bool $importShortCode, ?DomainResolverInterface $domainResolver = null diff --git a/module/Core/src/Importer/ImportedLinksProcessor.php b/module/Core/src/Importer/ImportedLinksProcessor.php index cb3ca57b..cb10ad15 100644 --- a/module/Core/src/Importer/ImportedLinksProcessor.php +++ b/module/Core/src/Importer/ImportedLinksProcessor.php @@ -7,9 +7,11 @@ namespace Shlinkio\Shlink\Core\Importer; use Doctrine\ORM\EntityManagerInterface; use Shlinkio\Shlink\Core\Domain\Resolver\DomainResolverInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; +use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; +use Shlinkio\Shlink\Core\Util\DoctrineBatchIterator; use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface; -use Shlinkio\Shlink\Importer\Model\ShlinkUrl; +use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; class ImportedLinksProcessor implements ImportedLinksProcessorInterface { @@ -25,31 +27,28 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface } /** - * @param ShlinkUrl[] $shlinkUrls + * @param iterable|ImportedShlinkUrl[] $shlinkUrls */ public function process(iterable $shlinkUrls, string $source, array $params): void { + /** @var ShortUrlRepositoryInterface $shortUrlRepo */ + $shortUrlRepo = $this->em->getRepository(ShortUrl::class); $importShortCodes = $params['import_short_codes']; - $count = 0; - $persistBlock = 100; + $iterable = new DoctrineBatchIterator($shlinkUrls, $this->em, 100); - foreach ($shlinkUrls as $url) { - $count++; + /** @var ImportedShlinkUrl $url */ + foreach ($iterable as $url) { + // Skip already imported URLs + if ($shortUrlRepo->importedUrlExists($url, $source, $importShortCodes)) { + continue; + } $shortUrl = ShortUrl::fromImport($url, $source, $importShortCodes, $this->domainResolver); $shortUrl->setTags($this->tagNamesToEntities($this->em, $url->tags())); // TODO Handle errors while creating short URLs, to avoid making the whole process fail + // * Duplicated short code $this->em->persist($shortUrl); - - // Flush and clear after X iterations - if ($count % $persistBlock === 0) { - $this->em->flush(); - $this->em->clear(); - } } - - $this->em->flush(); - $this->em->clear(); } } diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index e2cf578f..5cf7d997 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -11,6 +11,7 @@ use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering; +use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use function array_column; use function array_key_exists; @@ -254,4 +255,16 @@ DQL; return $qb->getQuery()->getOneOrNullResult(); } + + public function importedUrlExists(ImportedShlinkUrl $url, string $source, bool $importShortCodes): bool + { + $findConditions = ['importSource' => $source]; + if ($importShortCodes) { + $findConditions['shortCode'] = $url->shortCode(); + } else { + $findConditions['longUrl'] = $url->longUrl(); + } + + return $this->count($findConditions) > 0; + } } diff --git a/module/Core/src/Repository/ShortUrlRepositoryInterface.php b/module/Core/src/Repository/ShortUrlRepositoryInterface.php index 65278a85..fac50980 100644 --- a/module/Core/src/Repository/ShortUrlRepositoryInterface.php +++ b/module/Core/src/Repository/ShortUrlRepositoryInterface.php @@ -9,6 +9,7 @@ use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering; +use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; interface ShortUrlRepositoryInterface extends ObjectRepository { @@ -30,4 +31,6 @@ interface ShortUrlRepositoryInterface extends ObjectRepository public function shortCodeIsInUse(string $slug, ?string $domain): bool; public function findOneMatching(string $url, array $tags, ShortUrlMeta $meta): ?ShortUrl; + + public function importedUrlExists(ImportedShlinkUrl $url, string $source, bool $importShortCodes): bool; } diff --git a/module/Core/src/Util/DoctrineBatchIterator.php b/module/Core/src/Util/DoctrineBatchIterator.php new file mode 100644 index 00000000..05311b09 --- /dev/null +++ b/module/Core/src/Util/DoctrineBatchIterator.php @@ -0,0 +1,67 @@ +resultSet = $resultSet; + $this->em = $em; + $this->batchSize = $batchSize; + } + + /** + * @throws Throwable + */ + public function getIterator(): iterable + { + $iteration = 0; + $resultSet = $this->resultSet; + + $this->em->beginTransaction(); + + try { + foreach ($resultSet as $key => $value) { + $iteration++; + yield $key => $value; + $this->flushAndClearBatch($iteration); + } + } catch (Throwable $e) { + $this->em->rollback(); + + throw $e; + } + + $this->flushAndClearEntityManager(); + $this->em->commit(); + } + + private function flushAndClearBatch(int $iteration): void + { + if ($iteration % $this->batchSize) { + return; + } + + $this->flushAndClearEntityManager(); + } + + private function flushAndClearEntityManager(): void + { + $this->em->flush(); + $this->em->clear(); + } +} From 2256f6a9e77a6af02188d544ec9aeae501deb0d9 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 24 Oct 2020 15:08:34 +0200 Subject: [PATCH 41/73] Added feedback to ImportedLinksProcessor --- composer.json | 2 +- module/Core/src/Entity/ShortUrl.php | 3 +-- module/Core/src/Importer/ImportedLinksProcessor.php | 12 +++++++++--- module/Core/src/Repository/ShortUrlRepository.php | 4 ++-- .../src/Repository/ShortUrlRepositoryInterface.php | 2 +- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 922057d8..c56d584a 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ "shlinkio/shlink-common": "^3.2.0", "shlinkio/shlink-config": "^1.0", "shlinkio/shlink-event-dispatcher": "^1.4", - "shlinkio/shlink-importer": "^1.0.1", + "shlinkio/shlink-importer": "^2.0", "shlinkio/shlink-installer": "^5.1.0", "shlinkio/shlink-ip-geolocation": "^1.5", "symfony/console": "^5.1", diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index ec752ba9..aba0235f 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -59,7 +59,6 @@ class ShortUrl extends AbstractEntity public static function fromImport( ImportedShlinkUrl $url, - string $source, bool $importShortCode, ?DomainResolverInterface $domainResolver = null ): self { @@ -72,7 +71,7 @@ class ShortUrl extends AbstractEntity } $instance = new self($url->longUrl(), ShortUrlMeta::fromRawData($meta), $domainResolver); - $instance->importSource = $source; + $instance->importSource = $url->source(); $instance->dateCreated = Chronos::instance($url->createdAt()); return $instance; diff --git a/module/Core/src/Importer/ImportedLinksProcessor.php b/module/Core/src/Importer/ImportedLinksProcessor.php index cb10ad15..81b8e923 100644 --- a/module/Core/src/Importer/ImportedLinksProcessor.php +++ b/module/Core/src/Importer/ImportedLinksProcessor.php @@ -12,6 +12,8 @@ use Shlinkio\Shlink\Core\Util\DoctrineBatchIterator; use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; +use Symfony\Component\Console\Style\StyleInterface; +use function sprintf; class ImportedLinksProcessor implements ImportedLinksProcessorInterface { @@ -29,7 +31,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface /** * @param iterable|ImportedShlinkUrl[] $shlinkUrls */ - public function process(iterable $shlinkUrls, string $source, array $params): void + public function process(StyleInterface $io, iterable $shlinkUrls, array $params): void { /** @var ShortUrlRepositoryInterface $shortUrlRepo */ $shortUrlRepo = $this->em->getRepository(ShortUrl::class); @@ -39,16 +41,20 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface /** @var ImportedShlinkUrl $url */ foreach ($iterable as $url) { // Skip already imported URLs - if ($shortUrlRepo->importedUrlExists($url, $source, $importShortCodes)) { + if ($shortUrlRepo->importedUrlExists($url, $importShortCodes)) { + $io->text(sprintf('%s: Skipped', $url->longUrl())); continue; } - $shortUrl = ShortUrl::fromImport($url, $source, $importShortCodes, $this->domainResolver); + $shortUrl = ShortUrl::fromImport($url, $importShortCodes, $this->domainResolver); $shortUrl->setTags($this->tagNamesToEntities($this->em, $url->tags())); + // TODO Handle errors while creating short URLs, to avoid making the whole process fail // * Duplicated short code $this->em->persist($shortUrl); + + $io->text(sprintf('%s: Imported', $url->longUrl())); } } } diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index 5cf7d997..39e15d79 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -256,9 +256,9 @@ DQL; return $qb->getQuery()->getOneOrNullResult(); } - public function importedUrlExists(ImportedShlinkUrl $url, string $source, bool $importShortCodes): bool + public function importedUrlExists(ImportedShlinkUrl $url, bool $importShortCodes): bool { - $findConditions = ['importSource' => $source]; + $findConditions = ['importSource' => $url->source()]; if ($importShortCodes) { $findConditions['shortCode'] = $url->shortCode(); } else { diff --git a/module/Core/src/Repository/ShortUrlRepositoryInterface.php b/module/Core/src/Repository/ShortUrlRepositoryInterface.php index fac50980..b6790543 100644 --- a/module/Core/src/Repository/ShortUrlRepositoryInterface.php +++ b/module/Core/src/Repository/ShortUrlRepositoryInterface.php @@ -32,5 +32,5 @@ interface ShortUrlRepositoryInterface extends ObjectRepository public function findOneMatching(string $url, array $tags, ShortUrlMeta $meta): ?ShortUrl; - public function importedUrlExists(ImportedShlinkUrl $url, string $source, bool $importShortCodes): bool; + public function importedUrlExists(ImportedShlinkUrl $url, bool $importShortCodes): bool; } From b1a073b1abd7c6232b9aecaba44968caf48e348d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 25 Oct 2020 10:26:11 +0100 Subject: [PATCH 42/73] Ensured uniqueness on imported short URLs short code --- module/Core/src/Entity/ShortUrl.php | 4 +- .../src/Importer/ImportedLinksProcessor.php | 62 +++++++++++++++++-- module/Core/test/Entity/ShortUrlTest.php | 18 +++++- 3 files changed, 74 insertions(+), 10 deletions(-) diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index aba0235f..ea56fc82 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -135,8 +135,8 @@ class ShortUrl extends AbstractEntity */ public function regenerateShortCode(): self { - // In ShortUrls where a custom slug was provided, do nothing - if ($this->customSlugWasProvided) { + // In ShortUrls where a custom slug was provided, throw error, unless it is an imported one + if ($this->customSlugWasProvided && $this->importSource === null) { throw ShortCodeCannotBeRegeneratedException::forShortUrlWithCustomSlug(); } diff --git a/module/Core/src/Importer/ImportedLinksProcessor.php b/module/Core/src/Importer/ImportedLinksProcessor.php index 81b8e923..4dc8d1a4 100644 --- a/module/Core/src/Importer/ImportedLinksProcessor.php +++ b/module/Core/src/Importer/ImportedLinksProcessor.php @@ -7,12 +7,14 @@ namespace Shlinkio\Shlink\Core\Importer; use Doctrine\ORM\EntityManagerInterface; use Shlinkio\Shlink\Core\Domain\Resolver\DomainResolverInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; +use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Util\DoctrineBatchIterator; use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Symfony\Component\Console\Style\StyleInterface; + use function sprintf; class ImportedLinksProcessor implements ImportedLinksProcessorInterface @@ -40,21 +42,71 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface /** @var ImportedShlinkUrl $url */ foreach ($iterable as $url) { + $longUrl = $url->longUrl(); + // Skip already imported URLs if ($shortUrlRepo->importedUrlExists($url, $importShortCodes)) { - $io->text(sprintf('%s: Skipped', $url->longUrl())); + $io->text(sprintf('%s: Skipped', $longUrl)); continue; } $shortUrl = ShortUrl::fromImport($url, $importShortCodes, $this->domainResolver); $shortUrl->setTags($this->tagNamesToEntities($this->em, $url->tags())); + if (! $this->handleShortcodeUniqueness($url, $shortUrl, $io, $importShortCodes)) { + continue; + } - // TODO Handle errors while creating short URLs, to avoid making the whole process fail - // * Duplicated short code $this->em->persist($shortUrl); - - $io->text(sprintf('%s: Imported', $url->longUrl())); + $io->text(sprintf('%s: Imported', $longUrl)); } } + + private function handleShortcodeUniqueness( + ImportedShlinkUrl $url, + ShortUrl $shortUrl, + StyleInterface $io, + bool $importShortCodes + ): bool { + if ($this->ensureShortCodeUniqueness($shortUrl, $importShortCodes)) { + return true; + } + + $longUrl = $url->longUrl(); + $action = $io->choice(sprintf( + 'Failed to import URL "%s" because its short-code "%s" is already in use. Do you want to generate a new ' + . 'one or skip it?', + $longUrl, + $url->shortCode(), + ), ['Generate new short-code', 'Skip'], 1); + + if ($action === 'Skip') { + $io->text(sprintf('%s: Skipped', $longUrl)); + return false; + } + + return $this->handleShortcodeUniqueness($url, $shortUrl, $io, false); + } + + private function ensureShortCodeUniqueness(ShortUrl $shortUrlToBeCreated, bool $hasCustomSlug): bool + { + $shortCode = $shortUrlToBeCreated->getShortCode(); + $domain = $shortUrlToBeCreated->getDomain(); + $domainAuthority = $domain !== null ? $domain->getAuthority() : null; + + /** @var ShortUrlRepository $repo */ + $repo = $this->em->getRepository(ShortUrl::class); + $otherShortUrlsExist = $repo->shortCodeIsInUse($shortCode, $domainAuthority); + + if (! $otherShortUrlsExist) { + return true; + } + + if ($hasCustomSlug) { + return false; + } + + $shortUrlToBeCreated->regenerateShortCode(); + return $this->ensureShortCodeUniqueness($shortUrlToBeCreated, $hasCustomSlug); + } } diff --git a/module/Core/test/Entity/ShortUrlTest.php b/module/Core/test/Entity/ShortUrlTest.php index 054182ff..c143ae76 100644 --- a/module/Core/test/Entity/ShortUrlTest.php +++ b/module/Core/test/Entity/ShortUrlTest.php @@ -4,12 +4,14 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Entity; +use Cake\Chronos\Chronos; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; +use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use function Functional\map; use function range; use function strlen; @@ -44,10 +46,12 @@ class ShortUrlTest extends TestCase ]; } - /** @test */ - public function regenerateShortCodeProperlyChangesTheValueOnValidShortUrls(): void + /** + * @test + * @dataProvider provideValidShortUrls + */ + public function regenerateShortCodeProperlyChangesTheValueOnValidShortUrls(ShortUrl $shortUrl): void { - $shortUrl = new ShortUrl(''); $firstShortCode = $shortUrl->getShortCode(); $shortUrl->regenerateShortCode(); @@ -56,6 +60,14 @@ class ShortUrlTest extends TestCase self::assertNotEquals($firstShortCode, $secondShortCode); } + public function provideValidShortUrls(): iterable + { + yield 'no custom slug' => [new ShortUrl('')]; + yield 'imported with custom slug' => [ + ShortUrl::fromImport(new ImportedShlinkUrl('', '', [], Chronos::now(), null, 'custom-slug'), true), + ]; + } + /** * @test * @dataProvider provideLengths From 786e4f642b3ea913b690322bc851c8d38d309f62 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 25 Oct 2020 11:16:42 +0100 Subject: [PATCH 43/73] Moved short code uniqueness checks to external helper class that is used in UrlShortener and ImportedLinksProcessor --- module/Core/config/dependencies.config.php | 15 +++- module/Core/src/Entity/ShortUrl.php | 3 +- .../src/Importer/ImportedLinksProcessor.php | 35 +++------ .../src/Service/ShortUrl/ShortCodeHelper.php | 41 ++++++++++ .../ShortUrl/ShortCodeHelperInterface.php | 12 +++ module/Core/src/Service/UrlShortener.php | 27 ++++--- module/Core/test/Entity/ShortUrlTest.php | 2 +- .../Service/ShortUrl/ShortCodeHelperTest.php | 77 +++++++++++++++++++ module/Core/test/Service/UrlShortenerTest.php | 50 ++++-------- 9 files changed, 180 insertions(+), 82 deletions(-) create mode 100644 module/Core/src/Service/ShortUrl/ShortCodeHelper.php create mode 100644 module/Core/src/Service/ShortUrl/ShortCodeHelperInterface.php create mode 100644 module/Core/test/Service/ShortUrl/ShortCodeHelperTest.php diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 09c4d96e..2f0a6aa8 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -32,6 +32,7 @@ return [ Tag\TagService::class => ConfigAbstractFactory::class, Service\ShortUrl\DeleteShortUrlService::class => ConfigAbstractFactory::class, Service\ShortUrl\ShortUrlResolver::class => ConfigAbstractFactory::class, + Service\ShortUrl\ShortCodeHelper::class => ConfigAbstractFactory::class, Domain\DomainService::class => ConfigAbstractFactory::class, Util\UrlValidator::class => ConfigAbstractFactory::class, @@ -61,7 +62,12 @@ return [ Options\NotFoundRedirectOptions::class => ['config.not_found_redirects'], Options\UrlShortenerOptions::class => ['config.url_shortener'], - Service\UrlShortener::class => [Util\UrlValidator::class, 'em', Resolver\PersistenceDomainResolver::class], + Service\UrlShortener::class => [ + Util\UrlValidator::class, + 'em', + Resolver\PersistenceDomainResolver::class, + Service\ShortUrl\ShortCodeHelper::class, + ], Service\VisitsTracker::class => [ 'em', EventDispatcherInterface::class, @@ -77,6 +83,7 @@ return [ Service\ShortUrl\ShortUrlResolver::class, ], Service\ShortUrl\ShortUrlResolver::class => ['em'], + Service\ShortUrl\ShortCodeHelper::class => ['em'], Domain\DomainService::class => ['em'], Util\UrlValidator::class => ['httpClient', Options\UrlShortenerOptions::class], @@ -104,7 +111,11 @@ return [ Mercure\MercureUpdatesGenerator::class => ['config.url_shortener.domain'], - Importer\ImportedLinksProcessor::class => ['em', Resolver\PersistenceDomainResolver::class], + Importer\ImportedLinksProcessor::class => [ + 'em', + Resolver\PersistenceDomainResolver::class, + Service\ShortUrl\ShortCodeHelper::class, + ], ], ]; diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index ea56fc82..8afa8206 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -133,7 +133,7 @@ class ShortUrl extends AbstractEntity /** * @throws ShortCodeCannotBeRegeneratedException */ - public function regenerateShortCode(): self + public function regenerateShortCode(): void { // In ShortUrls where a custom slug was provided, throw error, unless it is an imported one if ($this->customSlugWasProvided && $this->importSource === null) { @@ -146,7 +146,6 @@ class ShortUrl extends AbstractEntity } $this->shortCode = generateRandomShortCode($this->shortCodeLength); - return $this; } public function getValidSince(): ?Chronos diff --git a/module/Core/src/Importer/ImportedLinksProcessor.php b/module/Core/src/Importer/ImportedLinksProcessor.php index 4dc8d1a4..90866057 100644 --- a/module/Core/src/Importer/ImportedLinksProcessor.php +++ b/module/Core/src/Importer/ImportedLinksProcessor.php @@ -7,8 +7,8 @@ namespace Shlinkio\Shlink\Core\Importer; use Doctrine\ORM\EntityManagerInterface; use Shlinkio\Shlink\Core\Domain\Resolver\DomainResolverInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; -use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; +use Shlinkio\Shlink\Core\Service\ShortUrl\ShortCodeHelperInterface; use Shlinkio\Shlink\Core\Util\DoctrineBatchIterator; use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface; @@ -23,11 +23,16 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface private EntityManagerInterface $em; private DomainResolverInterface $domainResolver; + private ShortCodeHelperInterface $shortCodeHelper; - public function __construct(EntityManagerInterface $em, DomainResolverInterface $domainResolver) - { + public function __construct( + EntityManagerInterface $em, + DomainResolverInterface $domainResolver, + ShortCodeHelperInterface $shortCodeHelper + ) { $this->em = $em; $this->domainResolver = $domainResolver; + $this->shortCodeHelper = $shortCodeHelper; } /** @@ -68,7 +73,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface StyleInterface $io, bool $importShortCodes ): bool { - if ($this->ensureShortCodeUniqueness($shortUrl, $importShortCodes)) { + if ($this->shortCodeHelper->ensureShortCodeUniqueness($shortUrl, $importShortCodes)) { return true; } @@ -87,26 +92,4 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface return $this->handleShortcodeUniqueness($url, $shortUrl, $io, false); } - - private function ensureShortCodeUniqueness(ShortUrl $shortUrlToBeCreated, bool $hasCustomSlug): bool - { - $shortCode = $shortUrlToBeCreated->getShortCode(); - $domain = $shortUrlToBeCreated->getDomain(); - $domainAuthority = $domain !== null ? $domain->getAuthority() : null; - - /** @var ShortUrlRepository $repo */ - $repo = $this->em->getRepository(ShortUrl::class); - $otherShortUrlsExist = $repo->shortCodeIsInUse($shortCode, $domainAuthority); - - if (! $otherShortUrlsExist) { - return true; - } - - if ($hasCustomSlug) { - return false; - } - - $shortUrlToBeCreated->regenerateShortCode(); - return $this->ensureShortCodeUniqueness($shortUrlToBeCreated, $hasCustomSlug); - } } diff --git a/module/Core/src/Service/ShortUrl/ShortCodeHelper.php b/module/Core/src/Service/ShortUrl/ShortCodeHelper.php new file mode 100644 index 00000000..6e4e57ac --- /dev/null +++ b/module/Core/src/Service/ShortUrl/ShortCodeHelper.php @@ -0,0 +1,41 @@ +em = $em; + } + + public function ensureShortCodeUniqueness(ShortUrl $shortUrlToBeCreated, bool $hasCustomSlug): bool + { + $shortCode = $shortUrlToBeCreated->getShortCode(); + $domain = $shortUrlToBeCreated->getDomain(); + $domainAuthority = $domain !== null ? $domain->getAuthority() : null; + + /** @var ShortUrlRepository $repo */ + $repo = $this->em->getRepository(ShortUrl::class); + $otherShortUrlsExist = $repo->shortCodeIsInUse($shortCode, $domainAuthority); + + if (! $otherShortUrlsExist) { + return true; + } + + if ($hasCustomSlug) { + return false; + } + + $shortUrlToBeCreated->regenerateShortCode(); + return $this->ensureShortCodeUniqueness($shortUrlToBeCreated, $hasCustomSlug); + } +} diff --git a/module/Core/src/Service/ShortUrl/ShortCodeHelperInterface.php b/module/Core/src/Service/ShortUrl/ShortCodeHelperInterface.php new file mode 100644 index 00000000..af3f2aa5 --- /dev/null +++ b/module/Core/src/Service/ShortUrl/ShortCodeHelperInterface.php @@ -0,0 +1,12 @@ +urlValidator = $urlValidator; $this->em = $em; $this->domainResolver = $domainResolver; + $this->shortCodeHelper = $shortCodeHelper; } /** @@ -83,20 +86,16 @@ class UrlShortener implements UrlShortenerInterface private function verifyShortCodeUniqueness(ShortUrlMeta $meta, ShortUrl $shortUrlToBeCreated): void { - $shortCode = $shortUrlToBeCreated->getShortCode(); - $domain = $meta->getDomain(); + $couldBeMadeUnique = $this->shortCodeHelper->ensureShortCodeUniqueness( + $shortUrlToBeCreated, + $meta->hasCustomSlug(), + ); - /** @var ShortUrlRepository $repo */ - $repo = $this->em->getRepository(ShortUrl::class); - $otherShortUrlsExist = $repo->shortCodeIsInUse($shortCode, $domain); + if (! $couldBeMadeUnique) { + $domain = $shortUrlToBeCreated->getDomain(); + $domainAuthority = $domain !== null ? $domain->getAuthority() : null; - if ($otherShortUrlsExist && $meta->hasCustomSlug()) { - throw NonUniqueSlugException::fromSlug($shortCode, $domain); - } - - if ($otherShortUrlsExist) { - $shortUrlToBeCreated->regenerateShortCode(); - $this->verifyShortCodeUniqueness($meta, $shortUrlToBeCreated); + throw NonUniqueSlugException::fromSlug($shortUrlToBeCreated->getShortCode(), $domainAuthority); } } } diff --git a/module/Core/test/Entity/ShortUrlTest.php b/module/Core/test/Entity/ShortUrlTest.php index c143ae76..9f28c41b 100644 --- a/module/Core/test/Entity/ShortUrlTest.php +++ b/module/Core/test/Entity/ShortUrlTest.php @@ -10,8 +10,8 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; - use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; + use function Functional\map; use function range; use function strlen; diff --git a/module/Core/test/Service/ShortUrl/ShortCodeHelperTest.php b/module/Core/test/Service/ShortUrl/ShortCodeHelperTest.php new file mode 100644 index 00000000..d77aecd5 --- /dev/null +++ b/module/Core/test/Service/ShortUrl/ShortCodeHelperTest.php @@ -0,0 +1,77 @@ +em = $this->prophesize(EntityManagerInterface::class); + $this->helper = new ShortCodeHelper($this->em->reveal()); + + $this->shortUrl = $this->prophesize(ShortUrl::class); + $this->shortUrl->getShortCode()->willReturn('abc123'); + } + + /** + * @test + * @dataProvider provideDomains + */ + public function shortCodeIsRegeneratedIfAlreadyInUse(?Domain $domain, ?string $expectedAuthority): void + { + $callIndex = 0; + $expectedCalls = 3; + $repo = $this->prophesize(ShortUrlRepository::class); + $shortCodeIsInUse = $repo->shortCodeIsInUse('abc123', $expectedAuthority)->will( + function () use (&$callIndex, $expectedCalls) { + $callIndex++; + return $callIndex < $expectedCalls; + }, + ); + $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + $this->shortUrl->getDomain()->willReturn($domain); + + $result = $this->helper->ensureShortCodeUniqueness($this->shortUrl->reveal(), false); + + self::assertTrue($result); + $this->shortUrl->regenerateShortCode()->shouldHaveBeenCalledTimes($expectedCalls - 1); + $getRepo->shouldBeCalledTimes($expectedCalls); + $shortCodeIsInUse->shouldBeCalledTimes($expectedCalls); + } + + public function provideDomains(): iterable + { + yield 'no domain' => [null, null]; + yield 'domain' => [new Domain($authority = 'doma.in'), $authority]; + } + + /** @test */ + public function inUseSlugReturnsError(): void + { + $repo = $this->prophesize(ShortUrlRepository::class); + $shortCodeIsInUse = $repo->shortCodeIsInUse('abc123', null)->willReturn(true); + $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + $this->shortUrl->getDomain()->willReturn(null); + + $result = $this->helper->ensureShortCodeUniqueness($this->shortUrl->reveal(), true); + + self::assertFalse($result); + $this->shortUrl->regenerateShortCode()->shouldNotHaveBeenCalled(); + $getRepo->shouldBeCalledOnce(); + $shortCodeIsInUse->shouldBeCalledOnce(); + } +} diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index ba7185bf..7945c32b 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -18,6 +18,7 @@ use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; +use Shlinkio\Shlink\Core\Service\ShortUrl\ShortCodeHelperInterface; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Core\Util\UrlValidatorInterface; @@ -26,6 +27,7 @@ class UrlShortenerTest extends TestCase private UrlShortener $urlShortener; private ObjectProphecy $em; private ObjectProphecy $urlValidator; + private ObjectProphecy $shortCodeHelper; public function setUp(): void { @@ -51,10 +53,14 @@ class UrlShortenerTest extends TestCase $repo->shortCodeIsInUse(Argument::cetera())->willReturn(false); $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + $this->shortCodeHelper = $this->prophesize(ShortCodeHelperInterface::class); + $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true); + $this->urlShortener = new UrlShortener( $this->urlValidator->reveal(), $this->em->reveal(), new SimpleDomainResolver(), + $this->shortCodeHelper->reveal(), ); } @@ -71,29 +77,18 @@ class UrlShortenerTest extends TestCase } /** @test */ - public function shortCodeIsRegeneratedIfAlreadyInUse(): void + public function exceptionIsThrownWhenNonUniqueSlugIsProvided(): void { - $callIndex = 0; - $expectedCalls = 3; - $repo = $this->prophesize(ShortUrlRepository::class); - $shortCodeIsInUse = $repo->shortCodeIsInUse(Argument::cetera())->will( - function () use (&$callIndex, $expectedCalls) { - $callIndex++; - return $callIndex < $expectedCalls; - }, - ); - $repo->findBy(Argument::cetera())->willReturn([]); - $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + $ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(false); - $shortUrl = $this->urlShortener->urlToShortCode( + $ensureUniqueness->shouldBeCalledOnce(); + $this->expectException(NonUniqueSlugException::class); + + $this->urlShortener->urlToShortCode( 'http://foobar.com/12345/hello?foo=bar', [], - ShortUrlMeta::createEmpty(), + ShortUrlMeta::fromRawData(['customSlug' => 'custom-slug']), ); - - self::assertEquals('http://foobar.com/12345/hello?foo=bar', $shortUrl->getLongUrl()); - $getRepo->shouldBeCalledTimes($expectedCalls); - $shortCodeIsInUse->shouldBeCalledTimes($expectedCalls); } /** @test */ @@ -115,25 +110,6 @@ class UrlShortenerTest extends TestCase ); } - /** @test */ - public function exceptionIsThrownWhenNonUniqueSlugIsProvided(): void - { - $repo = $this->prophesize(ShortUrlRepository::class); - $shortCodeIsInUse = $repo->shortCodeIsInUse('custom-slug', null)->willReturn(true); - $repo->findBy(Argument::cetera())->willReturn([]); - $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); - - $shortCodeIsInUse->shouldBeCalledOnce(); - $getRepo->shouldBeCalled(); - $this->expectException(NonUniqueSlugException::class); - - $this->urlShortener->urlToShortCode( - 'http://foobar.com/12345/hello?foo=bar', - [], - ShortUrlMeta::fromRawData(['customSlug' => 'custom-slug']), - ); - } - /** * @test * @dataProvider provideExistingShortUrls From 7c343f42c1282bba04f2df8cdca81b4524e70f07 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 25 Oct 2020 11:57:26 +0100 Subject: [PATCH 44/73] Improved how existing imported short URLs are checked by tracking its original short code --- composer.json | 2 +- data/migrations/Version20201023090929.php | 11 +++++ .../Shlinkio.Shlink.Core.Entity.ShortUrl.php | 5 +++ module/Core/src/Entity/ShortUrl.php | 2 + .../src/Importer/ImportedLinksProcessor.php | 8 ++-- .../src/Repository/ShortUrlRepository.php | 40 ++++++++++++------- .../ShortUrlRepositoryInterface.php | 2 +- 7 files changed, 49 insertions(+), 21 deletions(-) diff --git a/composer.json b/composer.json index c56d584a..c346e661 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ "shlinkio/shlink-common": "^3.2.0", "shlinkio/shlink-config": "^1.0", "shlinkio/shlink-event-dispatcher": "^1.4", - "shlinkio/shlink-importer": "^2.0", + "shlinkio/shlink-importer": "^2.0.1", "shlinkio/shlink-installer": "^5.1.0", "shlinkio/shlink-ip-geolocation": "^1.5", "symfony/console": "^5.1", diff --git a/data/migrations/Version20201023090929.php b/data/migrations/Version20201023090929.php index 49a401ba..05d16c22 100644 --- a/data/migrations/Version20201023090929.php +++ b/data/migrations/Version20201023090929.php @@ -21,6 +21,15 @@ final class Version20201023090929 extends AbstractMigration 'length' => 255, 'notnull' => false, ]); + $shortUrls->addColumn('import_original_short_code', Types::STRING, [ + 'length' => 255, + 'notnull' => false, + ]); + + $shortUrls->addUniqueIndex( + [self::IMPORT_SOURCE_COLUMN, 'import_original_short_code', 'domain_id'], + 'unique_imports', + ); } public function down(Schema $schema): void @@ -29,5 +38,7 @@ final class Version20201023090929 extends AbstractMigration $this->skipIf(! $shortUrls->hasColumn(self::IMPORT_SOURCE_COLUMN)); $shortUrls->dropColumn(self::IMPORT_SOURCE_COLUMN); + $shortUrls->dropColumn('import_original_short_code'); + $shortUrls->dropIndex('unique_imports'); } } diff --git a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php index 55fa230e..4f9b3747 100644 --- a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php +++ b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php @@ -56,6 +56,11 @@ return static function (ClassMetadata $metadata, array $emConfig): void { ->nullable() ->build(); + $builder->createField('importOriginalShortCode', Types::STRING) + ->columnName('import_original_short_code') + ->nullable() + ->build(); + $builder->createOneToMany('visits', Entity\Visit::class) ->mappedBy('shortUrl') ->fetchExtraLazy() diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 8afa8206..2d5cb6de 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -36,6 +36,7 @@ class ShortUrl extends AbstractEntity private bool $customSlugWasProvided; private int $shortCodeLength; private ?string $importSource = null; + private ?string $importOriginalShortCode = null; public function __construct( string $longUrl, @@ -72,6 +73,7 @@ class ShortUrl extends AbstractEntity $instance = new self($url->longUrl(), ShortUrlMeta::fromRawData($meta), $domainResolver); $instance->importSource = $url->source(); + $instance->importOriginalShortCode = $url->shortCode(); $instance->dateCreated = Chronos::instance($url->createdAt()); return $instance; diff --git a/module/Core/src/Importer/ImportedLinksProcessor.php b/module/Core/src/Importer/ImportedLinksProcessor.php index 90866057..b76ada9a 100644 --- a/module/Core/src/Importer/ImportedLinksProcessor.php +++ b/module/Core/src/Importer/ImportedLinksProcessor.php @@ -50,7 +50,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface $longUrl = $url->longUrl(); // Skip already imported URLs - if ($shortUrlRepo->importedUrlExists($url, $importShortCodes)) { + if ($shortUrlRepo->importedUrlExists($url)) { $io->text(sprintf('%s: Skipped', $longUrl)); continue; } @@ -58,7 +58,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface $shortUrl = ShortUrl::fromImport($url, $importShortCodes, $this->domainResolver); $shortUrl->setTags($this->tagNamesToEntities($this->em, $url->tags())); - if (! $this->handleShortcodeUniqueness($url, $shortUrl, $io, $importShortCodes)) { + if (! $this->handleShortCodeUniqueness($url, $shortUrl, $io, $importShortCodes)) { continue; } @@ -67,7 +67,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface } } - private function handleShortcodeUniqueness( + private function handleShortCodeUniqueness( ImportedShlinkUrl $url, ShortUrl $shortUrl, StyleInterface $io, @@ -90,6 +90,6 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface return false; } - return $this->handleShortcodeUniqueness($url, $shortUrl, $io, false); + return $this->handleShortCodeUniqueness($url, $shortUrl, $io, false); } } diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index 39e15d79..27dac54b 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -190,13 +190,7 @@ DQL; ->setParameter('slug', $slug) ->setMaxResults(1); - if ($domain !== null) { - $qb->join('s.domain', 'd') - ->andWhere($qb->expr()->eq('d.authority', ':authority')) - ->setParameter('authority', $domain); - } else { - $qb->andWhere($qb->expr()->isNull('s.domain')); - } + $this->whereDomainIs($qb, $domain); return $qb; } @@ -256,15 +250,31 @@ DQL; return $qb->getQuery()->getOneOrNullResult(); } - public function importedUrlExists(ImportedShlinkUrl $url, bool $importShortCodes): bool + public function importedUrlExists(ImportedShlinkUrl $url): bool { - $findConditions = ['importSource' => $url->source()]; - if ($importShortCodes) { - $findConditions['shortCode'] = $url->shortCode(); - } else { - $findConditions['longUrl'] = $url->longUrl(); - } + $qb = $this->getEntityManager()->createQueryBuilder(); + $qb->select('COUNT(DISTINCT s.id)') + ->from(ShortUrl::class, 's') + ->andWhere($qb->expr()->eq('s.importOriginalShortCode', ':shortCode')) + ->setParameter('shortCode', $url->shortCode()) + ->andWhere($qb->expr()->eq('s.importSource', ':importSource')) + ->setParameter('importSource', $url->source()) + ->setMaxResults(1); - return $this->count($findConditions) > 0; + $this->whereDomainIs($qb, $url->domain()); + + $result = (int) $qb->getQuery()->getSingleScalarResult(); + return $result > 0; + } + + private function whereDomainIs(QueryBuilder $qb, ?string $domain): void + { + if ($domain !== null) { + $qb->join('s.domain', 'd') + ->andWhere($qb->expr()->eq('d.authority', ':authority')) + ->setParameter('authority', $domain); + } else { + $qb->andWhere($qb->expr()->isNull('s.domain')); + } } } diff --git a/module/Core/src/Repository/ShortUrlRepositoryInterface.php b/module/Core/src/Repository/ShortUrlRepositoryInterface.php index b6790543..1d6f38a8 100644 --- a/module/Core/src/Repository/ShortUrlRepositoryInterface.php +++ b/module/Core/src/Repository/ShortUrlRepositoryInterface.php @@ -32,5 +32,5 @@ interface ShortUrlRepositoryInterface extends ObjectRepository public function findOneMatching(string $url, array $tags, ShortUrlMeta $meta): ?ShortUrl; - public function importedUrlExists(ImportedShlinkUrl $url, bool $importShortCodes): bool; + public function importedUrlExists(ImportedShlinkUrl $url): bool; } From fdcf88de670524b864af8cefa8f198310a7e72c2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 25 Oct 2020 12:06:48 +0100 Subject: [PATCH 45/73] Added database tests for ShortUrlRepository::importedUrlExists --- .../Repository/ShortUrlRepositoryTest.php | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php index ad99a9a3..86eb2aa3 100644 --- a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php @@ -17,6 +17,7 @@ use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering; use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Util\TagManagerTrait; +use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase; use function count; @@ -320,4 +321,26 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $this->repo->findOneMatching('foo', $tags, ShortUrlMeta::fromRawData($meta)), ); } + + /** @test */ + public function importedShortUrlsAreSearchedAsExpected(): void + { + $buildImported = static fn (string $shortCode, ?String $domain = null) => + new ImportedShlinkUrl('', 'foo', [], Chronos::now(), $domain, $shortCode); + + $shortUrlWithoutDomain = ShortUrl::fromImport($buildImported('my-cool-slug'), true); + $this->getEntityManager()->persist($shortUrlWithoutDomain); + + $shortUrlWithDomain = ShortUrl::fromImport($buildImported('another-slug', 'doma.in'), true); + $this->getEntityManager()->persist($shortUrlWithDomain); + + $this->getEntityManager()->flush(); + + self::assertTrue($this->repo->importedUrlExists($buildImported('my-cool-slug'))); + self::assertTrue($this->repo->importedUrlExists($buildImported('another-slug', 'doma.in'))); + self::assertFalse($this->repo->importedUrlExists($buildImported('non-existing-slug'))); + self::assertFalse($this->repo->importedUrlExists($buildImported('non-existing-slug', 'doma.in'))); + self::assertFalse($this->repo->importedUrlExists($buildImported('my-cool-slug', 'doma.in'))); + self::assertFalse($this->repo->importedUrlExists($buildImported('another-slug'))); + } } From 03a9697298c1b4ac101a09e38e846c6bfe71f695 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 25 Oct 2020 13:20:34 +0100 Subject: [PATCH 46/73] Created ImportedLinksProcessorTest --- module/Core/config/dependencies.config.php | 3 + .../src/Importer/ImportedLinksProcessor.php | 11 +- ...chIterator.php => DoctrineBatchHelper.php} | 18 +-- .../src/Util/DoctrineBatchHelperInterface.php | 10 ++ .../Importer/ImportedLinksProcessorTest.php | 145 ++++++++++++++++++ 5 files changed, 171 insertions(+), 16 deletions(-) rename module/Core/src/Util/{DoctrineBatchIterator.php => DoctrineBatchHelper.php} (63%) create mode 100644 module/Core/src/Util/DoctrineBatchHelperInterface.php create mode 100644 module/Core/test/Importer/ImportedLinksProcessorTest.php diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 2f0a6aa8..4d68101b 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -36,6 +36,7 @@ return [ Domain\DomainService::class => ConfigAbstractFactory::class, Util\UrlValidator::class => ConfigAbstractFactory::class, + Util\DoctrineBatchHelper::class => ConfigAbstractFactory::class, Action\RedirectAction::class => ConfigAbstractFactory::class, Action\PixelAction::class => ConfigAbstractFactory::class, @@ -87,6 +88,7 @@ return [ Domain\DomainService::class => ['em'], Util\UrlValidator::class => ['httpClient', Options\UrlShortenerOptions::class], + Util\DoctrineBatchHelper::class => ['em'], Action\RedirectAction::class => [ Service\ShortUrl\ShortUrlResolver::class, @@ -115,6 +117,7 @@ return [ 'em', Resolver\PersistenceDomainResolver::class, Service\ShortUrl\ShortCodeHelper::class, + Util\DoctrineBatchHelper::class, ], ], diff --git a/module/Core/src/Importer/ImportedLinksProcessor.php b/module/Core/src/Importer/ImportedLinksProcessor.php index b76ada9a..e072fbb8 100644 --- a/module/Core/src/Importer/ImportedLinksProcessor.php +++ b/module/Core/src/Importer/ImportedLinksProcessor.php @@ -9,7 +9,7 @@ use Shlinkio\Shlink\Core\Domain\Resolver\DomainResolverInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Service\ShortUrl\ShortCodeHelperInterface; -use Shlinkio\Shlink\Core\Util\DoctrineBatchIterator; +use Shlinkio\Shlink\Core\Util\DoctrineBatchHelperInterface; use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; @@ -24,15 +24,18 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface private EntityManagerInterface $em; private DomainResolverInterface $domainResolver; private ShortCodeHelperInterface $shortCodeHelper; + private DoctrineBatchHelperInterface $batchHelper; public function __construct( EntityManagerInterface $em, DomainResolverInterface $domainResolver, - ShortCodeHelperInterface $shortCodeHelper + ShortCodeHelperInterface $shortCodeHelper, + DoctrineBatchHelperInterface $batchHelper ) { $this->em = $em; $this->domainResolver = $domainResolver; $this->shortCodeHelper = $shortCodeHelper; + $this->batchHelper = $batchHelper; } /** @@ -43,7 +46,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface /** @var ShortUrlRepositoryInterface $shortUrlRepo */ $shortUrlRepo = $this->em->getRepository(ShortUrl::class); $importShortCodes = $params['import_short_codes']; - $iterable = new DoctrineBatchIterator($shlinkUrls, $this->em, 100); + $iterable = $this->batchHelper->wrapIterable($shlinkUrls, 100); /** @var ImportedShlinkUrl $url */ foreach ($iterable as $url) { @@ -90,6 +93,6 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface return false; } - return $this->handleShortCodeUniqueness($url, $shortUrl, $io, false); + return $this->shortCodeHelper->ensureShortCodeUniqueness($shortUrl, false); } } diff --git a/module/Core/src/Util/DoctrineBatchIterator.php b/module/Core/src/Util/DoctrineBatchHelper.php similarity index 63% rename from module/Core/src/Util/DoctrineBatchIterator.php rename to module/Core/src/Util/DoctrineBatchHelper.php index 05311b09..207d2093 100644 --- a/module/Core/src/Util/DoctrineBatchIterator.php +++ b/module/Core/src/Util/DoctrineBatchHelper.php @@ -5,32 +5,26 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Util; use Doctrine\ORM\EntityManagerInterface; -use IteratorAggregate; use Throwable; /** * Inspired by ocramius/doctrine-batch-utils https://github.com/Ocramius/DoctrineBatchUtils */ -class DoctrineBatchIterator implements IteratorAggregate +class DoctrineBatchHelper implements DoctrineBatchHelperInterface { - private iterable $resultSet; private EntityManagerInterface $em; - private int $batchSize; - public function __construct(iterable $resultSet, EntityManagerInterface $em, int $batchSize) + public function __construct(EntityManagerInterface $em) { - $this->resultSet = $resultSet; $this->em = $em; - $this->batchSize = $batchSize; } /** * @throws Throwable */ - public function getIterator(): iterable + public function wrapIterable(iterable $resultSet, int $batchSize): iterable { $iteration = 0; - $resultSet = $this->resultSet; $this->em->beginTransaction(); @@ -38,7 +32,7 @@ class DoctrineBatchIterator implements IteratorAggregate foreach ($resultSet as $key => $value) { $iteration++; yield $key => $value; - $this->flushAndClearBatch($iteration); + $this->flushAndClearBatch($iteration, $batchSize); } } catch (Throwable $e) { $this->em->rollback(); @@ -50,9 +44,9 @@ class DoctrineBatchIterator implements IteratorAggregate $this->em->commit(); } - private function flushAndClearBatch(int $iteration): void + private function flushAndClearBatch(int $iteration, int $batchSize): void { - if ($iteration % $this->batchSize) { + if ($iteration % $batchSize) { return; } diff --git a/module/Core/src/Util/DoctrineBatchHelperInterface.php b/module/Core/src/Util/DoctrineBatchHelperInterface.php new file mode 100644 index 00000000..941561ed --- /dev/null +++ b/module/Core/src/Util/DoctrineBatchHelperInterface.php @@ -0,0 +1,10 @@ +em = $this->prophesize(EntityManagerInterface::class); + $this->repo = $this->prophesize(ShortUrlRepositoryInterface::class); + $this->em->getRepository(ShortUrl::class)->willReturn($this->repo->reveal()); + + $this->shortCodeHelper = $this->prophesize(ShortCodeHelperInterface::class); + $batchHelper = $this->prophesize(DoctrineBatchHelperInterface::class); + $batchHelper->wrapIterable(Argument::cetera())->willReturnArgument(0); + + $this->processor = new ImportedLinksProcessor( + $this->em->reveal(), + new SimpleDomainResolver(), + $this->shortCodeHelper->reveal(), + $batchHelper->reveal(), + ); + + $this->io = $this->prophesize(StyleInterface::class); + } + + /** @test */ + public function newUrlsWithNoErrorsAreAllPersisted(): void + { + $urls = [ + new ImportedShlinkUrl('', 'foo', [], Chronos::now(), null, 'foo'), + new ImportedShlinkUrl('', 'bar', [], Chronos::now(), null, 'bar'), + new ImportedShlinkUrl('', 'baz', [], Chronos::now(), null, 'baz'), + ]; + $expectedCalls = count($urls); + + $importedUrlExists = $this->repo->importedUrlExists(Argument::cetera())->willReturn(false); + $ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true); + $persist = $this->em->persist(Argument::type(ShortUrl::class)); + + $this->processor->process($this->io->reveal(), $urls, ['import_short_codes' => true]); + + $importedUrlExists->shouldHaveBeenCalledTimes($expectedCalls); + $ensureUniqueness->shouldHaveBeenCalledTimes($expectedCalls); + $persist->shouldHaveBeenCalledTimes($expectedCalls); + $this->io->text(Argument::type('string'))->shouldHaveBeenCalledTimes($expectedCalls); + } + + /** @test */ + public function alreadyImportedUrlsAreSkipped(): void + { + $urls = [ + new ImportedShlinkUrl('', 'foo', [], Chronos::now(), null, 'foo'), + new ImportedShlinkUrl('', 'bar', [], Chronos::now(), null, 'bar'), + new ImportedShlinkUrl('', 'baz', [], Chronos::now(), null, 'baz'), + new ImportedShlinkUrl('', 'baz2', [], Chronos::now(), null, 'baz2'), + new ImportedShlinkUrl('', 'baz3', [], Chronos::now(), null, 'baz3'), + ]; + $contains = fn (string $needle) => fn (string $text) => str_contains($text, $needle); + + $importedUrlExists = $this->repo->importedUrlExists(Argument::cetera())->will(function (array $args): bool { + /** @var ImportedShlinkUrl $url */ + [$url] = $args; + + return contains(['foo', 'baz2', 'baz3'], $url->longUrl()); + }); + $ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true); + $persist = $this->em->persist(Argument::type(ShortUrl::class)); + + $this->processor->process($this->io->reveal(), $urls, ['import_short_codes' => true]); + + $importedUrlExists->shouldHaveBeenCalledTimes(count($urls)); + $ensureUniqueness->shouldHaveBeenCalledTimes(2); + $persist->shouldHaveBeenCalledTimes(2); + $this->io->text(Argument::that($contains('Skipped')))->shouldHaveBeenCalledTimes(3); + $this->io->text(Argument::that($contains('Imported')))->shouldHaveBeenCalledTimes(2); + } + + /** @test */ + public function nonUniqueShortCodesAreAskedToUser(): void + { + $urls = [ + new ImportedShlinkUrl('', 'foo', [], Chronos::now(), null, 'foo'), + new ImportedShlinkUrl('', 'bar', [], Chronos::now(), null, 'bar'), + new ImportedShlinkUrl('', 'baz', [], Chronos::now(), null, 'baz'), + new ImportedShlinkUrl('', 'baz2', [], Chronos::now(), null, 'baz2'), + new ImportedShlinkUrl('', 'baz3', [], Chronos::now(), null, 'baz3'), + ]; + $contains = fn (string $needle) => fn (string $text) => str_contains($text, $needle); + + $importedUrlExists = $this->repo->importedUrlExists(Argument::cetera())->willReturn(false); + $failingEnsureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness( + Argument::any(), + true, + )->willReturn(false); + $successEnsureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness( + Argument::any(), + false, + )->willReturn(true); + $choice = $this->io->choice(Argument::cetera())->will(function (array $args) { + /** @var ImportedShlinkUrl $url */ + [$question] = $args; + + return some(['foo', 'baz2', 'baz3'], fn (string $item) => str_contains($question, $item)) ? 'Skip' : ''; + }); + $persist = $this->em->persist(Argument::type(ShortUrl::class)); + + $this->processor->process($this->io->reveal(), $urls, ['import_short_codes' => true]); + + $importedUrlExists->shouldHaveBeenCalledTimes(count($urls)); + $failingEnsureUniqueness->shouldHaveBeenCalledTimes(5); + $successEnsureUniqueness->shouldHaveBeenCalledTimes(2); + $choice->shouldHaveBeenCalledTimes(5); + $persist->shouldHaveBeenCalledTimes(2); + $this->io->text(Argument::that($contains('Skipped')))->shouldHaveBeenCalledTimes(3); + $this->io->text(Argument::that($contains('Imported')))->shouldHaveBeenCalledTimes(2); + } +} From de7096010e9dc896b9a2667ef6df86f298b564d3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 25 Oct 2020 13:30:18 +0100 Subject: [PATCH 47/73] Created DoctrineBatchHelperTest --- .../test/Util/DoctrineBatchHelperTest.php | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 module/Core/test/Util/DoctrineBatchHelperTest.php diff --git a/module/Core/test/Util/DoctrineBatchHelperTest.php b/module/Core/test/Util/DoctrineBatchHelperTest.php new file mode 100644 index 00000000..a89a653f --- /dev/null +++ b/module/Core/test/Util/DoctrineBatchHelperTest.php @@ -0,0 +1,70 @@ +em = $this->prophesize(EntityManagerInterface::class); + $this->helper = new DoctrineBatchHelper($this->em->reveal()); + } + + /** + * @test + * @dataProvider provideIterables + */ + public function entityManagerIsFlushedAndClearedTheExpectedAmountOfTimes( + array $iterable, + int $batchSize, + int $expectedCalls + ): void { + $wrappedIterable = $this->helper->wrapIterable($iterable, $batchSize); + + foreach ($wrappedIterable as $item) { + // Iterable needs to be iterated for the logic to be invoked + } + + $this->em->beginTransaction()->shouldHaveBeenCalledOnce(); + $this->em->commit()->shouldHaveBeenCalledOnce(); + $this->em->rollback()->shouldNotHaveBeenCalled(); + $this->em->flush()->shouldHaveBeenCalledTimes($expectedCalls); + $this->em->clear()->shouldHaveBeenCalledTimes($expectedCalls); + } + + public function provideIterables(): iterable + { + yield [[], 100, 1]; + yield [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 3, 4]; + yield [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 11, 1]; + } + + /** @test */ + public function transactionIsRolledBackWhenAnErrorOccurs(): void + { + $flush = $this->em->flush()->willThrow(RuntimeException::class); + + $wrappedIterable = $this->helper->wrapIterable([1, 2, 3], 1); + + self::expectException(RuntimeException::class); + $flush->shouldBeCalledOnce(); + $this->em->beginTransaction()->shouldBeCalledOnce(); + $this->em->commit()->shouldNotBeCalled(); + $this->em->rollback()->shouldBeCalledOnce(); + + foreach ($wrappedIterable as $item) { + // Iterable needs to be iterated for the logic to be invoked + } + } +} From 90b4bc9b1a60408af8d11ec3e6468d13ae2a9927 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 25 Oct 2020 13:36:21 +0100 Subject: [PATCH 48/73] Updated changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c044b9b..b2afc50f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#832](https://github.com/shlinkio/shlink/issues/832) Added support to customize the port in which the docker image listens by using the `PORT` env var or the `port` config option. +* [#860](https://github.com/shlinkio/shlink/issues/860) Added support to import links from bit.ly. + + Run the command `short-urls:import bitly` and introduce requested information in order to import all your links. + + Other sources will be supported in future releases. + #### Changed * [#836](https://github.com/shlinkio/shlink/issues/836) Added support for the `-` notation while determining how to order the short URLs list, as in `?orderBy=shortCode-DESC`. This effectively deprecates the array notation (`?orderBy[shortCode]=DESC`), that will be removed in Shlink 3.0.0 From b091bd4e2a324a6d57af4881e2b1583808e1b7da Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 25 Oct 2020 13:46:39 +0100 Subject: [PATCH 49/73] Ensured composer 1 for now --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c9945281..d45f1d49 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,7 @@ before_install: - yes | pecl install pdo_sqlsrv-5.9.0preview1 swoole-4.5.5 pcov install: - - composer self-update + - composer self-update --1 - composer install --no-interaction --prefer-dist $COMPOSER_FLAGS before_script: From c1529b7d6c71b5cb8e3c66d672753e3693628082 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 25 Oct 2020 17:59:37 +0100 Subject: [PATCH 50/73] Updated to composer 2 --- .travis.yml | 2 +- Dockerfile | 2 +- composer.json | 1 - data/infra/php.Dockerfile | 2 +- data/infra/swoole.Dockerfile | 2 +- module/CLI/test/Command/Visit/LocateVisitsCommandTest.php | 4 ++-- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index d45f1d49..c9945281 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,7 @@ before_install: - yes | pecl install pdo_sqlsrv-5.9.0preview1 swoole-4.5.5 pcov install: - - composer self-update --1 + - composer self-update - composer install --no-interaction --prefer-dist $COMPOSER_FLAGS before_script: diff --git a/Dockerfile b/Dockerfile index b8bdda10..41fce03a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,7 +44,7 @@ RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS && \ # Install shlink FROM base as builder COPY . . -COPY --from=composer:1.10.13 /usr/bin/composer ./composer.phar +COPY --from=composer:2 /usr/bin/composer ./composer.phar RUN apk add --no-cache git && \ php composer.phar install --no-dev --optimize-autoloader --prefer-dist --no-progress --no-interaction && \ php composer.phar clear-cache && \ diff --git a/composer.json b/composer.json index c346e661..716cfe31 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,6 @@ "guzzlehttp/guzzle": "^7.0", "laminas/laminas-config": "^3.3", "laminas/laminas-config-aggregator": "^1.1", - "laminas/laminas-dependency-plugin": "^1.0", "laminas/laminas-diactoros": "^2.1.3", "laminas/laminas-inputfilter": "^2.10", "laminas/laminas-paginator": "^2.8", diff --git a/data/infra/php.Dockerfile b/data/infra/php.Dockerfile index 0e10911e..218db4c5 100644 --- a/data/infra/php.Dockerfile +++ b/data/infra/php.Dockerfile @@ -64,7 +64,7 @@ RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8 rm msodbcsql17_17.5.1.1-1_amd64.apk # Install composer -COPY --from=composer:1.10.13 /usr/bin/composer /usr/local/bin/composer +COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer # Make home directory writable by anyone RUN chmod 777 /home diff --git a/data/infra/swoole.Dockerfile b/data/infra/swoole.Dockerfile index 6dafa0eb..5d8485ab 100644 --- a/data/infra/swoole.Dockerfile +++ b/data/infra/swoole.Dockerfile @@ -76,7 +76,7 @@ RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8 rm msodbcsql17_17.5.1.1-1_amd64.apk # Install composer -COPY --from=composer:1.10.13 /usr/bin/composer /usr/local/bin/composer +COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer # Make home directory writable by anyone RUN chmod 777 /home diff --git a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php index 3eb89f57..966a702c 100644 --- a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php @@ -94,9 +94,9 @@ class LocateVisitsCommandTest extends TestCase self::assertStringContainsString('Processing IP 1.2.3.0', $output); if ($expectWarningPrint) { - self::assertStringContainsString('Continue at your own risk', $output); + self::assertStringContainsString('Continue at your own', $output); } else { - self::assertStringNotContainsString('Continue at your own risk', $output); + self::assertStringNotContainsString('Continue at your own', $output); } $locateVisits->shouldHaveBeenCalledTimes($expectedUnlocatedCalls); $locateEmptyVisits->shouldHaveBeenCalledTimes($expectedEmptyCalls); From c85eb84b4c6a01cf1c30ecdc515699af8fd29a67 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 29 Oct 2020 17:24:12 +0100 Subject: [PATCH 51/73] Disabled platform checks in composer --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 716cfe31..1c558a25 100644 --- a/composer.json +++ b/composer.json @@ -170,6 +170,7 @@ "clean:dev": "Deletes artifacts which are gitignored and could affect dev env" }, "config": { - "sort-packages": true + "sort-packages": true, + "platform-check": false } } From 33190c07c72aa81e1e76c623c882091bc2bfbdcf Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 31 Oct 2020 08:25:03 +0100 Subject: [PATCH 52/73] Updated references from travis-ci.org to travis-ci.com --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9d51e389..1b03c048 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Shlink](https://raw.githubusercontent.com/shlinkio/shlink.io/main/public/images/shlink-hero.png) -[![Build Status](https://img.shields.io/travis/shlinkio/shlink.svg?style=flat-square)](https://travis-ci.org/shlinkio/shlink) +[![Build Status](https://img.shields.io/travis/com/shlinkio/shlink.svg?style=flat-square)](https://travis-ci.com/shlinkio/shlink) [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/shlinkio/shlink.svg?style=flat-square)](https://scrutinizer-ci.com/g/shlinkio/shlink/) [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/shlinkio/shlink.svg?style=flat-square)](https://scrutinizer-ci.com/g/shlinkio/shlink/) [![Latest Stable Version](https://img.shields.io/github/release/shlinkio/shlink.svg?style=flat-square)](https://packagist.org/packages/shlinkio/shlink) @@ -64,7 +64,7 @@ In order to run Shlink, you will need a built version of the project. There are After that, you will have a `shlink_x.x.x_dist.zip` dist file inside the `build` directory, that you need to decompress in the location fo your choice. - > This is the process used when releasing new shlink versions. After tagging the new version with git, the Github release is automatically created by [travis](https://travis-ci.org/shlinkio/shlink), attaching the generated dist file to it. + > This is the process used when releasing new shlink versions. After tagging the new version with git, the Github release is automatically created by [travis](https://travis-ci.com/shlinkio/shlink), attaching the generated dist file to it. ### Configure From 65651e4bbde8968b4cb9c7453547604ca8e99a0a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 1 Nov 2020 11:22:29 +0100 Subject: [PATCH 53/73] Updated changelog to more strictly endorse to keepachangelog spec --- CHANGELOG.md | 1114 +++++++++++++++++--------------------------------- 1 file changed, 371 insertions(+), 743 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2afc50f..6d4ad7e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). ## [Unreleased] - -#### Added - +### Added * [#829](https://github.com/shlinkio/shlink/issues/829) Added support for QR codes in SVG format, by passing `?format=svg` to the QR code URL. * [#820](https://github.com/shlinkio/shlink/issues/820) Added new option to force enabling or disabling URL validation on a per-URL basis. @@ -33,29 +31,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this Other sources will be supported in future releases. -#### Changed - +### Changed * [#836](https://github.com/shlinkio/shlink/issues/836) Added support for the `-` notation while determining how to order the short URLs list, as in `?orderBy=shortCode-DESC`. This effectively deprecates the array notation (`?orderBy[shortCode]=DESC`), that will be removed in Shlink 3.0.0 * [#782](https://github.com/shlinkio/shlink/issues/782) Added code coverage to API tests. * [#858](https://github.com/shlinkio/shlink/issues/858) Updated to latest infection version. Updated docker images to PHP 7.4.11 and swoole 4.5.5 -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#837](https://github.com/shlinkio/shlink/issues/837) Drastically improved performance when creating a new shortUrl and providing `findIfExists = true`. -## 2.3.0 - 2020-08-09 - -#### Added - +## [2.3.0] - 2020-08-09 +### Added * [#746](https://github.com/shlinkio/shlink/issues/746) Allowed to configure the kind of redirect you want to use for your short URLs. You can either set: * `302` redirects: Default behavior. Visitors always hit the server. @@ -70,77 +62,59 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this It has one limitation, though. Because of the way the CLI tooling works, all rows in the table must be loaded in memory. If the amount of URLs is too high, the command may fail due to too much memory usage. -#### Changed - +### Changed * [#508](https://github.com/shlinkio/shlink/issues/508) Added mutation checks to database tests. * [#790](https://github.com/shlinkio/shlink/issues/790) Updated to doctrine/migrations v3. * [#798](https://github.com/shlinkio/shlink/issues/798) Updated to guzzlehttp/guzzle v7. * [#822](https://github.com/shlinkio/shlink/issues/822) Updated docker image to use PHP 7.4.9 with Alpine 3.12 and swoole 4.5.2. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * *Nothing* -## 2.2.2 - 2020-06-08 - -#### Added - +## [2.2.2] - 2020-06-08 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#769](https://github.com/shlinkio/shlink/issues/769) Fixed custom slugs not allowing valid URL characters, like `.`, `_` or `~`. * [#781](https://github.com/shlinkio/shlink/issues/781) Fixed memory leak when loading visits for a tag which is used for big amounts of short URLs. -## 2.2.1 - 2020-05-11 - -#### Added - +## [2.2.1] - 2020-05-11 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#764](https://github.com/shlinkio/shlink/issues/764) Fixed error when trying to match an existing short URL which does not have `validSince` and/or `validUntil`, but you are providing either one of them for the new one. -## 2.2.0 - 2020-05-09 - -#### Added - +## [2.2.0] - 2020-05-09 +### Added * [#712](https://github.com/shlinkio/shlink/issues/712) Added support to integrate Shlink with a [mercure hub](https://mercure.rocks/) server. Thanks to that, Shlink will be able to publish events that can be consumed in real time. @@ -167,71 +141,55 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#640](https://github.com/shlinkio/shlink/issues/640) Allowed to optionally disable visitors' IP address anonymization. This will make Shlink no longer be GDPR-compliant, but it's OK if you only plan to share your URLs in countries without this regulation. -#### Changed - +### Changed * [#692](https://github.com/shlinkio/shlink/issues/692) Drastically improved performance when loading visits. Specially noticeable when loading big result sets. * [#657](https://github.com/shlinkio/shlink/issues/657) Updated how DB tests are run in travis by using docker containers which allow all engines to be covered. * [#751](https://github.com/shlinkio/shlink/issues/751) Updated PHP and swoole versions used in docker image, and removed mssql-tools, as they are not needed. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#729](https://github.com/shlinkio/shlink/issues/729) Fixed weird error when fetching multiple visits result sets concurrently using mariadb or mysql. * [#735](https://github.com/shlinkio/shlink/issues/735) Fixed error when cleaning metadata cache during installation when APCu is enabled. * [#677](https://github.com/shlinkio/shlink/issues/677) Fixed `/health` endpoint returning `503` fail responses when the database connection has expired. * [#732](https://github.com/shlinkio/shlink/issues/732) Fixed wrong client IP in access logs when serving app with swoole behind load balancer. -## 2.1.4 - 2020-04-30 - -#### Added - +## [2.1.4] - 2020-04-30 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#742](https://github.com/shlinkio/shlink/issues/742) Allowed a custom GeoLite2 license key to be provided, in order to avoid download limits. -## 2.1.3 - 2020-04-09 - -#### Added - +## [2.1.3] - 2020-04-09 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#712](https://github.com/shlinkio/shlink/issues/712) Fixed app set-up not clearing entities metadata cache. * [#711](https://github.com/shlinkio/shlink/issues/711) Fixed `HEAD` requests returning a duplicated `Content-Length` header. * [#716](https://github.com/shlinkio/shlink/issues/716) Fixed Twitter not properly displaying preview for final long URL. @@ -239,230 +197,177 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#705](https://github.com/shlinkio/shlink/issues/705) Fixed how the short URL domain is inferred when generating QR codes, making sure the configured domain is respected even if the request is performed using a different one, and only when a custom domain is used, then that one is used instead. -## 2.1.2 - 2020-03-29 - -#### Added - +## [2.1.2] - 2020-03-29 +### Added * *Nothing* -#### Changed - +### Changed * [#696](https://github.com/shlinkio/shlink/issues/696) Updated to infection v0.16. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#700](https://github.com/shlinkio/shlink/issues/700) Fixed migration not working with postgres. * [#690](https://github.com/shlinkio/shlink/issues/690) Fixed tags being incorrectly sluggified when filtering short URL lists, making results not be the expected. -## 2.1.1 - 2020-03-28 - -#### Added - +## [2.1.1] - 2020-03-28 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#697](https://github.com/shlinkio/shlink/issues/697) Recovered `.htaccess` file that was unintentionally removed in v2.1.0, making Shlink unusable with Apache. -## 2.1.0 - 2020-03-28 - -#### Added - +## [2.1.0] - 2020-03-28 +### Added * [#626](https://github.com/shlinkio/shlink/issues/626) Added support for Microsoft SQL Server. * [#556](https://github.com/shlinkio/shlink/issues/556) Short code lengths can now be customized, both globally and on a per-short URL basis. * [#541](https://github.com/shlinkio/shlink/issues/541) Added a request ID that is returned on `X-Request-Id` header, can be provided from outside and is set in log entries. * [#642](https://github.com/shlinkio/shlink/issues/642) IP geolocation is now performed over the non-anonymized IP address when using swoole. * [#521](https://github.com/shlinkio/shlink/issues/521) The long URL for any existing short URL can now be edited using the `PATCH /short-urls/{shortCode}` endpoint. -#### Changed - +### Changed * [#656](https://github.com/shlinkio/shlink/issues/656) Updated to PHPUnit 9. * [#641](https://github.com/shlinkio/shlink/issues/641) Added two new flags to the `visit:locate` command, `--retry` and `--all`. * When `--retry` is provided, it will try to re-locate visits which IP address was originally considered not found, in case it was a temporal issue. * When `--all` is provided together with `--retry`, it will try to re-locate all existing visits. A warning and confirmation are displayed, as this can have side effects. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#665](https://github.com/shlinkio/shlink/issues/665) Fixed `base_url_redirect_to` simplified config option not being properly parsed. * [#663](https://github.com/shlinkio/shlink/issues/663) Fixed Shlink allowing short URLs to be created with an empty custom slug. * [#678](https://github.com/shlinkio/shlink/issues/678) Fixed `db` commands not running in a non-interactive way. -## 2.0.5 - 2020-02-09 - -#### Added - +## [2.0.5] - 2020-02-09 +### Added * [#651](https://github.com/shlinkio/shlink/issues/651) Documented how Shlink behaves when using multiple domains. -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#648](https://github.com/shlinkio/shlink/issues/648) Ensured any user can write in log files, in case shlink is run by several system users. * [#650](https://github.com/shlinkio/shlink/issues/650) Ensured default domain is ignored when trying to create a short URL. -## 2.0.4 - 2020-02-02 - -#### Added - +## [2.0.4] - 2020-02-02 +### Added * *Nothing* -#### Changed - +### Changed * [#577](https://github.com/shlinkio/shlink/issues/577) Wrapped params used to customize short URL lists into a DTO with implicit validation. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#620](https://github.com/shlinkio/shlink/issues/620) Ensured "controlled" errors (like validation errors and such) won't be logged with error level, preventing logs to be polluted. * [#637](https://github.com/shlinkio/shlink/issues/637) Fixed several work flows in which short URLs with domain are handled form the API. * [#644](https://github.com/shlinkio/shlink/issues/644) Fixed visits to short URL on non-default domain being linked to the URL on default domain with the same short code. * [#643](https://github.com/shlinkio/shlink/issues/643) Fixed searching on short URL lists not taking into consideration the domain name. -## 2.0.3 - 2020-01-27 - -#### Added - +## [2.0.3] - 2020-01-27 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#624](https://github.com/shlinkio/shlink/issues/624) Fixed order in which headers for remote IP detection are inspected. * [#623](https://github.com/shlinkio/shlink/issues/623) Fixed short URLs metadata being impossible to reset. * [#628](https://github.com/shlinkio/shlink/issues/628) Fixed `GET /short-urls/{shortCode}` REST endpoint returning a 404 for short URLs which are not enabled. * [#621](https://github.com/shlinkio/shlink/issues/621) Fixed permission denied error when updating same GeoLite file version more than once. -## 2.0.2 - 2020-01-12 - -#### Added - +## [2.0.2] - 2020-01-12 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#614](https://github.com/shlinkio/shlink/issues/614) Fixed `OPTIONS` requests including the `Origin` header not always returning an empty body with status 2xx. * [#615](https://github.com/shlinkio/shlink/issues/615) Fixed query args with no value being lost from the long URL when users are redirected. -## 2.0.1 - 2020-01-10 - -#### Added - +## [2.0.1] - 2020-01-10 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#607](https://github.com/shlinkio/shlink/issues/607) Added missing info on UPGRADE.md doc. * [#610](https://github.com/shlinkio/shlink/issues/610) Fixed use of hardcoded quotes on a database migration which makes it fail on postgres. * [#605](https://github.com/shlinkio/shlink/issues/605) Fixed crashes occurring when migrating from old Shlink versions with nullable DB columns that are assigned to non-nullable entity typed props. -## 2.0.0 - 2020-01-08 - -#### Added - +## [2.0.0] - 2020-01-08 +### Added * [#429](https://github.com/shlinkio/shlink/issues/429) Added support for PHP 7.4 * [#529](https://github.com/shlinkio/shlink/issues/529) Created an UPGRADING.md file explaining how to upgrade from v1.x to v2.x * [#594](https://github.com/shlinkio/shlink/issues/594) Updated external shlink packages, including installer v4.0, which adds the option to ask for the redis cluster config. -#### Changed - +### Changed * [#592](https://github.com/shlinkio/shlink/issues/592) Updated coding styles to use [shlinkio/php-coding-standard](https://github.com/shlinkio/php-coding-standard) v2.1.0. * [#530](https://github.com/shlinkio/shlink/issues/530) Migrated project from deprecated `zendframework` components to the new `laminas` and `mezzio` ones. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * [#429](https://github.com/shlinkio/shlink/issues/429) Dropped support for PHP 7.2 and 7.3 * [#229](https://github.com/shlinkio/shlink/issues/229) Remove everything which was deprecated, including: @@ -472,38 +377,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this See [UPGRADE](UPGRADE.md) doc in order to get details on how to migrate to this version. -#### Fixed - +### Fixed * [#600](https://github.com/shlinkio/shlink/issues/600) Fixed health action so that it works with and without version in the path. -## 1.21.1 - 2020-01-02 - -#### Added - +## [1.21.1] - 2020-01-02 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#596](https://github.com/shlinkio/shlink/issues/596) Fixed error when trying to download GeoLite2 database due to changes on how to get the database files. -## 1.21.0 - 2019-12-29 - -#### Added - +## [1.21.0] - 2019-12-29 +### Added * [#118](https://github.com/shlinkio/shlink/issues/118) API errors now implement the [problem details](https://tools.ietf.org/html/rfc7807) standard. In order to make it backwards compatible, two things have been done: @@ -533,103 +429,79 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this > The `shortUrl` and `visit` props have the same shape as it is defined in the [API spec](https://api-spec.shlink.io). -#### Changed - +### Changed * [#492](https://github.com/shlinkio/shlink/issues/492) Updated to monolog 2, together with other dependencies, like Symfony 5 and infection-php. * [#527](https://github.com/shlinkio/shlink/issues/527) Increased minimum required mutation score for unit tests to 80%. * [#557](https://github.com/shlinkio/shlink/issues/557) Added a few php.ini configs for development and production docker images. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#570](https://github.com/shlinkio/shlink/issues/570) Fixed shlink version generated for docker images when building from `develop` branch. -## 1.20.3 - 2019-12-23 - -#### Added - +## [1.20.3] - 2019-12-23 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#585](https://github.com/shlinkio/shlink/issues/585) Fixed `PHP Fatal error: Uncaught Error: Class 'Shlinkio\Shlink\LocalLockFactory' not found` happening when running some CLI commands. -## 1.20.2 - 2019-12-06 - -#### Added - +## [1.20.2] - 2019-12-06 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#561](https://github.com/shlinkio/shlink/issues/561) Fixed `db:migrate` command failing because yaml extension is not installed, which makes config file not to be readable. * [#562](https://github.com/shlinkio/shlink/issues/562) Fixed internal server error being returned when renaming a tag to another tag's name. Now a meaningful API error with status 409 is returned. * [#555](https://github.com/shlinkio/shlink/issues/555) Fixed internal server error being returned when invalid dates are provided for new short URLs. Now a 400 is returned, as intended. -## 1.20.1 - 2019-11-17 - -#### Added - +## [1.20.1] - 2019-11-17 +### Added * [#519](https://github.com/shlinkio/shlink/issues/519) Documented how to customize web workers and task workers for the docker image. -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#512](https://github.com/shlinkio/shlink/issues/512) Fixed query params not being properly forwarded from short URL to long one. * [#540](https://github.com/shlinkio/shlink/issues/540) Fixed errors thrown when creating short URLs if the original URL has an internationalized domain name and URL validation is enabled. * [#528](https://github.com/shlinkio/shlink/issues/528) Ensured `db:create` and `db:migrate` commands do not silently fail when run as part of `install` or `update`. * [#518](https://github.com/shlinkio/shlink/issues/518) Fixed service which updates Geolite db file to use a local lock instead of a shared one, since every shlink instance holds its own db instance. -## 1.20.0 - 2019-11-02 - -#### Added - +## [1.20.0] - 2019-11-02 +### Added * [#491](https://github.com/shlinkio/shlink/issues/491) Added improved short code generation logic. Now, short codes are truly random, which removes the guessability factor existing in previous versions. @@ -646,30 +518,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#497](https://github.com/shlinkio/shlink/issues/497) Officially added support for MariaDB. -#### Changed - +### Changed * [#458](https://github.com/shlinkio/shlink/issues/458) Updated coding styles to use [shlinkio/php-coding-standard](https://github.com/shlinkio/php-coding-standard) v2.0.0. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#507](https://github.com/shlinkio/shlink/issues/507) Fixed error with too long original URLs by increasing size to the maximum value (2048) based on [the standard](https://stackoverflow.com/a/417184). * [#502](https://github.com/shlinkio/shlink/issues/502) Fixed error when providing the port as part of the domain on short URLs. * [#509](https://github.com/shlinkio/shlink/issues/509) Fixed error when trying to generate a QR code for a short URL which uses a custom domain. * [#522](https://github.com/shlinkio/shlink/issues/522) Highly mitigated errors thrown when lots of short URLs are created concurrently including new and existing tags. -## 1.19.0 - 2019-10-05 - -#### Added - +## [1.19.0] - 2019-10-05 +### Added * [#482](https://github.com/shlinkio/shlink/issues/482) Added support to serve shlink under a sub path. The `router.base_path` config option can be defined now to set the base path from which shlink is served. @@ -696,53 +562,41 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * If the domain is not known but the short code/slug is defined for default domain, the user is redirected there and the visit is tracked. * In any other case, no redirection happens and no visit is tracked (if a fall back redirection is configured for not-found URLs, it will still happen). -#### Changed - +### Changed * [#486](https://github.com/shlinkio/shlink/issues/486) Updated to [shlink-installer](https://github.com/shlinkio/shlink-installer) v2, which supports asking for base path in which shlink is served. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * [#435](https://github.com/shlinkio/shlink/issues/435) Removed translations for error pages. All error pages are in english now. -#### Fixed - +### Fixed * *Nothing* -## 1.18.1 - 2019-08-24 - -#### Added - +## [1.18.1] - 2019-08-24 +### Added * *Nothing* -#### Changed - +### Changed * [#450](https://github.com/shlinkio/shlink/issues/450) Added PHP 7.4 to the build matrix, as an allowed-to-fail env. * [#441](https://github.com/shlinkio/shlink/issues/441) and [#443](https://github.com/shlinkio/shlink/issues/443) Split some logic into independent modules. * [#451](https://github.com/shlinkio/shlink/issues/451) Updated to infection 0.13. * [#467](https://github.com/shlinkio/shlink/issues/467) Moved docker image config to main Shlink repo. -#### Deprecated - +### Deprecated * [#428](https://github.com/shlinkio/shlink/issues/428) Deprecated preview-generation feature. It will keep working but it will be removed in Shlink v2.0.0 -#### Removed - +### Removed * [#468](https://github.com/shlinkio/shlink/issues/468) Removed APCu extension from docker image. -#### Fixed - +### Fixed * [#449](https://github.com/shlinkio/shlink/issues/449) Fixed error when trying to save too big referrers on PostgreSQL. -## 1.18.0 - 2019-08-08 - -#### Added - +## [1.18.0] - 2019-08-08 +### Added * [#411](https://github.com/shlinkio/shlink/issues/411) Added new `meta` property on the `ShortUrl` REST API model. These endpoints are affected and include the new property when suitable: @@ -784,31 +638,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#442](https://github.com/shlinkio/shlink/pull/442) Created `db:migrate` command, which improves doctrine's migrations command by generating a lock, preventing it to be run concurrently. -#### Changed - +### Changed * [#430](https://github.com/shlinkio/shlink/issues/430) Updated to [shlinkio/php-coding-standard](https://github.com/shlinkio/php-coding-standard) 1.2.2 * [#305](https://github.com/shlinkio/shlink/issues/305) Implemented changes which will allow Shlink to be truly clusterizable. * [#262](https://github.com/shlinkio/shlink/issues/262) Increased mutation score to 75%. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#416](https://github.com/shlinkio/shlink/issues/416) Fixed error thrown when trying to locate visits after the GeoLite2 DB is downloaded for the first time. * [#424](https://github.com/shlinkio/shlink/issues/424) Updated wkhtmltoimage to version 0.12.5 * [#427](https://github.com/shlinkio/shlink/issues/427) and [#434](https://github.com/shlinkio/shlink/issues/434) Fixed shlink being unusable after a database error on swoole contexts. -## 1.17.0 - 2019-05-13 - -#### Added - +## [1.17.0] - 2019-05-13 +### Added * [#377](https://github.com/shlinkio/shlink/issues/377) Updated `visit:locate` command (formerly `visit:process`) to automatically update the GeoLite2 database if it is too old or it does not exist. This simplifies processing visits in a container-based infrastructure, since a fresh container is capable of getting an updated version of the file by itself. @@ -817,99 +665,75 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#373](https://github.com/shlinkio/shlink/issues/373) Added support for a simplified config. Specially useful to use with the docker container. -#### Changed - +### Changed * [#56](https://github.com/shlinkio/shlink/issues/56) Simplified supported cache, requiring APCu always. -#### Deprecated - +### Deprecated * [#406](https://github.com/shlinkio/shlink/issues/406) Deprecated `PUT /short-urls/{shortCode}` REST endpoint in favor of `PATCH /short-urls/{shortCode}`. -#### Removed - +### Removed * [#385](https://github.com/shlinkio/shlink/issues/385) Dropped support for PHP 7.1 * [#379](https://github.com/shlinkio/shlink/issues/379) Removed copyright from error templates. -#### Fixed - +### Fixed * *Nothing* -## 1.16.3 - 2019-03-30 - -#### Added - +## [1.16.3] - 2019-03-30 +### Added * *Nothing* -#### Changed - +### Changed * [#153](https://github.com/shlinkio/shlink/issues/153) Updated to [doctrine/migrations](https://github.com/doctrine/migrations) version 2.0.0 * [#376](https://github.com/shlinkio/shlink/issues/376) Allowed `visit:update-db` command to not return an error exit code even if download fails, by passing the `-i` flag. * [#341](https://github.com/shlinkio/shlink/issues/341) Improved database tests so that they are executed against all supported database engines. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#382](https://github.com/shlinkio/shlink/issues/382) Fixed existing short URLs not properly checked when providing the `findIfExists` flag. -## 1.16.2 - 2019-03-05 - -#### Added - +## [1.16.2] - 2019-03-05 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#368](https://github.com/shlinkio/shlink/issues/368) Fixed error produced when running a `SELECT COUNT(...)` with `ORDER BY` in PostgreSQL databases. -## 1.16.1 - 2019-02-26 - -#### Added - +## [1.16.1] - 2019-02-26 +### Added * *Nothing* -#### Changed - +### Changed * [#363](https://github.com/shlinkio/shlink/issues/363) Updated to `shlinkio/php-coding-standard` version 1.1.0 -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#362](https://github.com/shlinkio/shlink/issues/362) Fixed all visits without an IP address being processed every time the `visit:process` command is executed. -## 1.16.0 - 2019-02-23 - -#### Added - +## [1.16.0] - 2019-02-23 +### Added * [#304](https://github.com/shlinkio/shlink/issues/304) Added health endpoint to check healthiness of the service. Useful in container-based infrastructures. Call [GET /rest/health] in order to get a response like this: @@ -943,8 +767,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#336](https://github.com/shlinkio/shlink/issues/336) Added an API test suite which performs API calls to an actual instance of the web service. -#### Changed - +### Changed * [#342](https://github.com/shlinkio/shlink/issues/342) The installer no longer asks for a charset to be provided, and instead, it shuffles the base62 charset. * [#320](https://github.com/shlinkio/shlink/issues/320) Replaced query builder by plain DQL for all queries which do not need to be dynamically generated. * [#330](https://github.com/shlinkio/shlink/issues/330) No longer allow failures on PHP 7.3 envs during project CI build. @@ -952,51 +775,40 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#346](https://github.com/shlinkio/shlink/issues/346) Extracted installer as an independent tool. * [#261](https://github.com/shlinkio/shlink/issues/261) Increased mutation score to 70%. -#### Deprecated - +### Deprecated * [#351](https://github.com/shlinkio/shlink/issues/351) Deprecated `config:generate-charset` and `config:generate-secret` CLI commands. -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#317](https://github.com/shlinkio/shlink/issues/317) Fixed error while trying to generate previews because of global config file being deleted by mistake by build script. * [#307](https://github.com/shlinkio/shlink/issues/307) Fixed memory leak while trying to process huge amounts of visits due to the query not being properly paginated. -## 1.15.1 - 2018-12-16 - -#### Added - +## [1.15.1] - 2018-12-16 +### Added * [#162](https://github.com/shlinkio/shlink/issues/162) Added non-rest endpoints to swagger definition. -#### Changed - +### Changed * [#312](https://github.com/shlinkio/shlink/issues/312) Now all config files both in `php` and `json` format are loaded from `config/params` folder, easing users to provided customizations to docker image. * [#226](https://github.com/shlinkio/shlink/issues/226) Updated how table are rendered in CLI commands, making use of new features in Symfony 4.2. * [#321](https://github.com/shlinkio/shlink/issues/321) Extracted entities mappings from entities to external config files. * [#308](https://github.com/shlinkio/shlink/issues/308) Automated docker image building. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * [#301](https://github.com/shlinkio/shlink/issues/301) Removed custom `AccessLogFactory` in favor of the implementation included in [zendframework/zend-expressive-swoole](https://github.com/zendframework/zend-expressive-swoole) v2.2.0 -#### Fixed - +### Fixed * [#309](https://github.com/shlinkio/shlink/issues/309) Added missing favicon to prevent 404 errors logged when an error page is loaded in a browser. * [#310](https://github.com/shlinkio/shlink/issues/310) Fixed execution context not being properly detected, making `CloseDbConnectionMiddlware` to be always piped. Now the check is not even made, which simplifies everything. -## 1.15.0 - 2018-12-02 - -#### Added - +## [1.15.0] - 2018-12-02 +### Added * [#208](https://github.com/shlinkio/shlink/issues/208) Added initial support to run shlink using [swoole](https://www.swoole.co.uk/), a non-blocking IO server which improves the performance of shlink from 4 to 10 times. Run shlink with `./vendor/bin/zend-expressive-swoole start` to start-up the service, which will be exposed in port `8080`. @@ -1007,54 +819,42 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this In order to make it backwards compatible, it keeps returning all visits by default, but it now allows to provide the `page` and `itemsPerPage` query parameters in order to configure the number of items to get. -#### Changed - +### Changed * [#267](https://github.com/shlinkio/shlink/issues/267) API responses and the CLI interface is no longer translated and uses english always. Only not found error templates are still translated. * [#289](https://github.com/shlinkio/shlink/issues/289) Extracted coding standard rules to a separated package. * [#273](https://github.com/shlinkio/shlink/issues/273) Improved code coverage in repository classes. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#278](https://github.com/shlinkio/shlink/pull/278) Added missing `X-Api-Key` header to the list of valid cross domain headers. * [#295](https://github.com/shlinkio/shlink/pull/295) Fixed custom slugs so that they are case sensitive and do not try to lowercase provided values. -## 1.14.1 - 2018-11-17 - -#### Added - +## [1.14.1] - 2018-11-17 +### Added * *Nothing* -#### Changed - +### Changed * [#260](https://github.com/shlinkio/shlink/issues/260) Increased mutation score to 65%. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#271](https://github.com/shlinkio/shlink/issues/271) Fixed memory leak produced when processing high amounts of visits at the same time. * [#272](https://github.com/shlinkio/shlink/issues/272) Fixed errors produced when trying to process visits multiple times in parallel, by using a lock which ensures only one instance is run at a time. -## 1.14.0 - 2018-11-16 - -#### Added - +## [1.14.0] - 2018-11-16 +### Added * [#236](https://github.com/shlinkio/shlink/issues/236) Added option to define a redirection to a custom URL when a user hits an invalid short URL. It only affects URLs matched as "short URL" where the short code is invalid, not any 404 that happens in the app. For example, a request to the path `/foo/bar` will keep returning a 404. @@ -1067,8 +867,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this Previous service is still used as a fallback in case GeoLite DB does not contain any IP address. -#### Changed - +### Changed * [#241](https://github.com/shlinkio/shlink/issues/241) Fixed columns in `visit_locations` table, to be snake_case instead of camelCase. * [#228](https://github.com/shlinkio/shlink/issues/228) Updated how exceptions are serialized into logs, by using monlog's `PsrLogMessageProcessor`. * [#225](https://github.com/shlinkio/shlink/issues/225) Performance and maintainability slightly improved by enforcing via code sniffer that all global namespace classes, functions and constants are explicitly imported. @@ -1078,71 +877,54 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#256](https://github.com/shlinkio/shlink/issues/256) Updated to Infection v0.11. * [#202](https://github.com/shlinkio/shlink/issues/202) Added missing response examples to OpenAPI docs. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#223](https://github.com/shlinkio/shlink/issues/223) Fixed PHPStan errors produced with symfony/console 4.1.5 -## 1.13.2 - 2018-10-18 - -#### Added - +## [1.13.2] - 2018-10-18 +### Added * [#233](https://github.com/shlinkio/shlink/issues/233) Added PHP 7.3 to build matrix allowing its failure. -#### Changed - +### Changed * [#235](https://github.com/shlinkio/shlink/issues/235) Improved update instructions (thanks to [tivyhosting](https://github.com/tivyhosting)). -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#237](https://github.com/shlinkio/shlink/issues/233) Solved errors when trying to geo-locate `null` IP addresses. Also improved how visitor IP addresses are discovered, thanks to [akrabat/ip-address-middleware](https://github.com/akrabat/ip-address-middleware) package. -## 1.13.1 - 2018-10-16 - -#### Added - +## [1.13.1] - 2018-10-16 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#231](https://github.com/shlinkio/shlink/issues/197) Fixed error when processing visits. -## 1.13.0 - 2018-10-06 - -#### Added - +## [1.13.0] - 2018-10-06 +### Added * [#197](https://github.com/shlinkio/shlink/issues/197) Added [cakephp/chronos](https://book.cakephp.org/3.0/en/chronos.html) library for date manipulations. * [#214](https://github.com/shlinkio/shlink/issues/214) Improved build script, which allows builds to be done without "jumping" outside the project directory, and generates smaller dist files. @@ -1155,16 +937,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * Visits threshold to allow short URLs to be deleted. * Check the visits threshold when trying to delete a short URL via REST API. -#### Changed - +### Changed * [#211](https://github.com/shlinkio/shlink/issues/211) Extracted installer to its own module, which will simplify moving it to a separated package in the future. * [#200](https://github.com/shlinkio/shlink/issues/200) and [#201](https://github.com/shlinkio/shlink/issues/201) Renamed REST Action classes and CLI Command classes to use the concept of `ShortUrl` instead of the concept of `ShortCode` when referring to the entity, and left the `short code` concept to the identifier which is used as a unique code for a specific `Short URL`. * [#181](https://github.com/shlinkio/shlink/issues/181) When importing the configuration from a previous shlink installation, it no longer asks to import every block. Instead, it is capable of detecting only new config options introduced in the new version, and ask only for those. If no new options are found and you have selected to import config, no further questions will be asked and shlink will just import the old config. -#### Deprecated - +### Deprecated * [#205](https://github.com/shlinkio/shlink/issues/205) Deprecated `[POST /authenticate]` endpoint, and allowed any API request to be automatically authenticated using the `X-Api-Key` header with a valid API key. This effectively deprecates the `Authorization: Bearer ` authentication form, but it will keep working. @@ -1173,20 +953,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this In both cases, backwards compatibility has been retained and the old ones are aliases for the new ones, but the old ones are considered deprecated. -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#203](https://github.com/shlinkio/shlink/issues/203) Fixed some warnings thrown while unzipping distributable files. * [#206](https://github.com/shlinkio/shlink/issues/206) An error is now thrown during installation if any required param is left empty, making the installer display a message and ask again until a value is set. -## 1.12.0 - 2018-09-15 - -#### Added - +## [1.12.0] - 2018-09-15 +### Added * [#187](https://github.com/shlinkio/shlink/issues/187) Included an API endpoint and a CLI command to delete short URLs. Due to the implicit danger of this operation, the deletion includes a safety check. URLs cannot be deleted if they have more than a specific amount of visits. @@ -1215,31 +991,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#183](https://github.com/shlinkio/shlink/issues/183) and [#190](https://github.com/shlinkio/shlink/issues/190) Included important documentation improvements in the repository itself. You no longer need to go to the website in order to see how to install or use shlink. * [#186](https://github.com/shlinkio/shlink/issues/186) Added a small robots.txt file that prevents 404 errors to be logged due to search engines trying to index the domain where shlink is located. Thanks to [@robwent](https://github.com/robwent) for the contribution. -#### Changed - +### Changed * [#145](https://github.com/shlinkio/shlink/issues/145) Shlink now obfuscates IP addresses from visitors by replacing the latest octet by `0`, which does not affect geolocation and allows it to fulfil the GDPR. Other known services follow this same approach, like [Google Analytics](https://support.google.com/analytics/answer/2763052?hl=en) or [Matomo](https://matomo.org/docs/privacy/#step-1-automatically-anonymize-visitor-ips) * [#182](https://github.com/shlinkio/shlink/issues/182) The short URL creation API endpoints now return the same model used for lists and details endpoints. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#188](https://github.com/shlinkio/shlink/issues/188) Shlink now allows multiple short URLs to be created that resolve to the same long URL. -## 1.11.0 - 2018-08-13 - -#### Added - +## [1.11.0] - 2018-08-13 +### Added * [#170](https://github.com/shlinkio/shlink/issues/170) and [#171](https://github.com/shlinkio/shlink/issues/171) Updated `[GET /short-codes]` and `[GET /short-codes/{shortCode}]` endpoints to return more meaningful information and make their response consistent. The short URLs are now represented by this object in both cases: @@ -1260,43 +1030,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this The `originalUrl` property is considered deprecated and has been kept for backward compatibility purposes. It holds the same value as the `longUrl` property. -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * The `originalUrl` property in `[GET /short-codes]` and `[GET /short-codes/{shortCode}]` endpoints is now deprecated and replaced by the `longUrl` property. -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * *Nothing* -## 1.10.2 - 2018-08-04 - -#### Added - +## [1.10.2] - 2018-08-04 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#177](https://github.com/shlinkio/shlink/issues/177) Fixed `[GET] /short-codes` endpoint returning a 500 status code when trying to filter by `tags` and `searchTerm` at the same time. * [#175](https://github.com/shlinkio/shlink/issues/175) Fixed error introduced in previous version, where you could end up banned from the service used to resolve IP address locations. @@ -1305,26 +1065,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this In order to prevent this, after resolving 150 IP addresses, shlink now waits 1 minute before trying to resolve any more addresses. -## 1.10.1 - 2018-08-02 - -#### Added - +## [1.10.1] - 2018-08-02 +### Added * *Nothing* -#### Changed - +### Changed * [#167](https://github.com/shlinkio/shlink/issues/167) Shlink version is now set at build time to avoid older version numbers to be kept in newer builds. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#165](https://github.com/shlinkio/shlink/issues/165) Fixed custom slugs failing when they are longer than 10 characters. * [#166](https://github.com/shlinkio/shlink/issues/166) Fixed unusual edge case in which visits were not properly counted when ordering by visit and filtering by search term in `[GET] /short-codes` API endpoint. * [#174](https://github.com/shlinkio/shlink/issues/174) Fixed geolocation not working due to a deprecation on used service. @@ -1335,34 +1089,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#169](https://github.com/shlinkio/shlink/issues/169) Fixed unhandled error when parsing `ShortUrlMeta` and date fields are already `DateTime` instances. -## 1.10.0 - 2018-07-09 - -#### Added - +## [1.10.0] - 2018-07-09 +### Added * [#161](https://github.com/shlinkio/shlink/issues/161) AddED support for shlink to be run with [swoole](https://www.swoole.co.uk/) via [zend-expressive-swoole](https://github.com/zendframework/zend-expressive-swoole) package -#### Changed - +### Changed * [#159](https://github.com/shlinkio/shlink/issues/159) Updated CHANGELOG to follow the [keep-a-changelog](https://keepachangelog.com) format * [#160](https://github.com/shlinkio/shlink/issues/160) Update infection to v0.9 and phpstan to v 0.10 -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * *Nothing* -## 1.9.1 - 2018-06-18 - -#### Added - +## [1.9.1] - 2018-06-18 +### Added * [#155](https://github.com/shlinkio/shlink/issues/155) Improved the pagination object returned in lists, including more meaningful properties. * Old structure: @@ -1390,235 +1136,180 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this } ``` -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#154](https://github.com/shlinkio/shlink/issues/154) Fixed sizes of every result page when filtering by searchTerm * [#157](https://github.com/shlinkio/shlink/issues/157) Background commands executed by installation process now respect the originally used php binary -## 1.9.0 - 2018-05-07 - -#### Added - +## [1.9.0] - 2018-05-07 +### Added * [#147](https://github.com/shlinkio/shlink/issues/147) Allowed short URLs to be created on the fly using a single API request, including the API key in a query param. This eases integration with third party services. With this feature, a simple request to a URL like `https://doma.in/rest/v1/short-codes/shorten?apiKey=[YOUR_API_KEY]&longUrl=[URL_TO_BE_SHORTENED]` would return the shortened one in JSON or plain text format. -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#139](https://github.com/shlinkio/shlink/issues/139) Ensured all core actions log exceptions -## 1.8.1 - 2018-04-07 - -#### Added - +## [1.8.1] - 2018-04-07 +### Added * *Nothing* -#### Changed - +### Changed * [#141](https://github.com/shlinkio/shlink/issues/141) Removed workaround used in `PathVersionMiddleware`, since the bug in zend-stratigility has been fixed. -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#140](https://github.com/shlinkio/shlink/issues/140) Fixed warning thrown during installation while trying to include doctrine script -## 1.8.0 - 2018-03-29 - -#### Added - +## [1.8.0] - 2018-03-29 +### Added * [#125](https://github.com/shlinkio/shlink/issues/125) Implemented a path which returns a 1px image instead of a redirection. Useful to track emails. Just add an image pointing to a URL like `https://doma.in/abc123/track` to any email and an invisible image will be generated tracking every time the email is opened. * [#132](https://github.com/shlinkio/shlink/issues/132) Added infection to improve tests -#### Changed - +### Changed * [#130](https://github.com/shlinkio/shlink/issues/130) Updated to Expressive 3 * [#137](https://github.com/shlinkio/shlink/issues/137) Updated symfony components to v4 -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * [#131](https://github.com/shlinkio/shlink/issues/131) Dropped support for PHP 7 -#### Fixed - +### Fixed * *Nothing* -## 1.7.2 - 2018-03-26 - -#### Added - +## [1.7.2] - 2018-03-26 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#135](https://github.com/shlinkio/shlink/issues/135) Fixed `PathVersionMiddleware` being ignored when using expressive 2.2 -## 1.7.1 - 2018-03-21 - -#### Added - +## [1.7.1] - 2018-03-21 +### Added * *Nothing* -#### Changed - +### Changed * [#128](https://github.com/shlinkio/shlink/issues/128) Upgraded to expressive 2.2 This will ease the upcoming update to expressive 3 -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#126](https://github.com/shlinkio/shlink/issues/126) Fixed `E_USER_DEPRECATED` errors triggered when using Expressive 2.2 -## 1.7.0 - 2018-01-21 - -#### Added - +## [1.7.0] - 2018-01-21 +### Added * [#88](https://github.com/shlinkio/shlink/issues/88) Allowed tracking of short URLs to be disabled by including a configurable query param * [#108](https://github.com/shlinkio/shlink/issues/108) Allowed metadata to be defined when creating short codes -#### Changed - +### Changed * [#113](https://github.com/shlinkio/shlink/issues/113) Updated CLI commands to use `SymfonyStyle` * [#112](https://github.com/shlinkio/shlink/issues/112) Enabled Lazy loading in CLI commands * [#117](https://github.com/shlinkio/shlink/issues/117) Every module which throws exceptions has now its own `ExceptionInterface` extending `Throwable` * [#115](https://github.com/shlinkio/shlink/issues/115) Added phpstan to build matrix on PHP >=7.1 envs * [#114](https://github.com/shlinkio/shlink/issues/114) Replaced [vlucas/phpdotenv](https://github.com/vlucas/phpdotenv) dev requirement by [symfony/dotenv](https://github.com/symfony/dotenv) -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * *Nothing* -## 1.6.2 - 2017-10-25 - -#### Added - +## [1.6.2] - 2017-10-25 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#109](https://github.com/shlinkio/shlink/issues/109) Fixed installation error due to typo in latest migration -## 1.6.1 - 2017-10-24 - -#### Added - +## [1.6.1] - 2017-10-24 +### Added * *Nothing* -#### Changed - +### Changed * [#110](https://github.com/shlinkio/shlink/issues/110) Created `.gitattributes` file to define files to be excluded from distributable package -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * *Nothing* -## 1.6.0 - 2017-10-23 - -#### Added - +## [1.6.0] - 2017-10-23 +### Added * [#44](https://github.com/shlinkio/shlink/issues/44) Now it is possible to set custom slugs for short URLs instead of using a generated short code * [#47](https://github.com/shlinkio/shlink/issues/47) Allowed to limit short URLs availability by date range * [#48](https://github.com/shlinkio/shlink/issues/48) Allowed to limit the number of visits to a short URL * [#105](https://github.com/shlinkio/shlink/pull/105) Added option to enable/disable URL validation by response status code -#### Changed - +### Changed * [#27](https://github.com/shlinkio/shlink/issues/27) Added repository functional tests with dbunit * [#101](https://github.com/shlinkio/shlink/issues/101) Now specific actions just capture very specific exceptions, and let the `ErrorHandler` catch any other unhandled exception * [#104](https://github.com/shlinkio/shlink/issues/104) Used different templates for *requested-short-code-does-not-exist* and *route-could-not-be-match* @@ -1626,101 +1317,78 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#100](https://github.com/shlinkio/shlink/issues/100) Updated templates engine. Replaced twig by plates * [#102](https://github.com/shlinkio/shlink/issues/102) Improved coding standards strictness -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * [#86](https://github.com/shlinkio/shlink/issues/86) Dropped support for PHP 5 -#### Fixed - +### Fixed * [#103](https://github.com/shlinkio/shlink/issues/103) `NotFoundDelegate` now returns proper content types based on accepted content -## 1.5.0 - 2017-07-16 - -#### Added - +## [1.5.0] - 2017-07-16 +### Added * [#95](https://github.com/shlinkio/shlink/issues/95) Added tags CRUD to CLI * [#59](https://github.com/shlinkio/shlink/issues/59) Added tags CRUD to REST * [#66](https://github.com/shlinkio/shlink/issues/66) Allowed certain information to be imported from and older shlink instance directory when updating -#### Changed - +### Changed * [#96](https://github.com/shlinkio/shlink/issues/96) Added namespace to functions * [#76](https://github.com/shlinkio/shlink/issues/76) Added response examples to swagger docs * [#93](https://github.com/shlinkio/shlink/issues/93) Improved cross domain management by using the `ImplicitOptionsMiddleware` -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#92](https://github.com/shlinkio/shlink/issues/92) Fixed formatted dates, using an ISO compliant format -## 1.4.0 - 2017-03-25 - -#### Added - +## [1.4.0] - 2017-03-25 +### Added * *Nothing* -#### Changed - +### Changed * [#89](https://github.com/shlinkio/shlink/issues/89) Updated to expressive 2 -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * *Nothing* -## 1.3.1 - 2017-01-22 - -#### Added - +## [1.3.1] - 2017-01-22 +### Added * *Nothing* -#### Changed - +### Changed * [#82](https://github.com/shlinkio/shlink/issues/82) Enabled `FastRoute` routes cache * [#85](https://github.com/shlinkio/shlink/issues/85) Updated year in license file * [#81](https://github.com/shlinkio/shlink/issues/81) Added docker containers config -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#83](https://github.com/shlinkio/shlink/issues/83) Fixed short codes list: search in tags when filtering by query string * [#79](https://github.com/shlinkio/shlink/issues/79) Increased the number of followed redirects * [#75](https://github.com/shlinkio/shlink/issues/75) Applied `PathVersionMiddleware` only to rest routes defining it by configuration instead of code * [#77](https://github.com/shlinkio/shlink/issues/77) Allowed defining database server hostname and port -## 1.3.0 - 2016-10-23 - -#### Added - +## [1.3.0] - 2016-10-23 +### Added * [#67](https://github.com/shlinkio/shlink/issues/67) Allowed to order the short codes list * [#60](https://github.com/shlinkio/shlink/issues/60) Accepted JSON requests in REST and used a body parser middleware to set the request's `parsedBody` * [#72](https://github.com/shlinkio/shlink/issues/72) When listing API keys from CLI, use yellow color for enabled keys that have expired @@ -1729,73 +1397,55 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#73](https://github.com/shlinkio/shlink/issues/73) Added tag-related endpoints to swagger file * [#63](https://github.com/shlinkio/shlink/issues/63) Added path versioning to REST API routes -#### Changed - +### Changed * [#71](https://github.com/shlinkio/shlink/issues/71) Separated swagger docs into multiple files -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * *Nothing* -## 1.2.2 - 2016-08-29 - -#### Added - +## [1.2.2] - 2016-08-29 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * Fixed minor bugs on CORS requests -## 1.2.1 - 2016-08-21 - -#### Added - +## [1.2.1] - 2016-08-21 +### Added * *Nothing* -#### Changed - +### Changed * *Nothing* -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#62](https://github.com/shlinkio/shlink/issues/62) Fixed cross-domain requests in REST API -## 1.2.0 - 2016-08-21 - -#### Added - +## [1.2.0] - 2016-08-21 +### Added * [#45](https://github.com/shlinkio/shlink/issues/45) Allowed to define tags on short codes, to improve filtering and classification * [#7](https://github.com/shlinkio/shlink/issues/7) Added website previews while listing available URLs * [#57](https://github.com/shlinkio/shlink/issues/57) Added database migrations system to improve updating between versions @@ -1804,29 +1454,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#38](https://github.com/shlinkio/shlink/issues/38) Defined installation script. It will request dynamic data on the fly so that there is no need to define env vars * [#55](https://github.com/shlinkio/shlink/issues/55) Created update script which does not try to create a new database -#### Changed - +### Changed * [#54](https://github.com/shlinkio/shlink/issues/54) Added cache namespace to prevent name collisions with other apps in the same environment * [#29](https://github.com/shlinkio/shlink/issues/29) Used the [acelaya/ze-content-based-error-handler](https://github.com/acelaya/ze-content-based-error-handler) package instead of custom error handler implementation -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#53](https://github.com/shlinkio/shlink/issues/53) Fixed entities database interoperability * [#52](https://github.com/shlinkio/shlink/issues/52) Added missing htaccess file for apache environments -## 1.1.0 - 2016-08-09 - -#### Added - +## [1.1.0] - 2016-08-09 +### Added * [#46](https://github.com/shlinkio/shlink/issues/46) Defined a route that returns a QR code representing the shortened URL. In order to get the QR code URL, use a pattern like `https://doma.in/abc123/qr-code` @@ -1835,38 +1479,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#14](https://github.com/shlinkio/shlink/issues/14) Added logger and enabled errors logging * [#13](https://github.com/shlinkio/shlink/issues/13) Improved REST authentication -#### Changed - +### Changed * [#41](https://github.com/shlinkio/shlink/issues/41) Cached the "short code" => "URL" map to prevent extra DB hits * [#39](https://github.com/shlinkio/shlink/issues/39) Changed copyright from "Alejandro Celaya" to "Shlink" in error pages * [#42](https://github.com/shlinkio/shlink/issues/42) REST endpoints that need to find *something* now return a 404 when it is not found * [#35](https://github.com/shlinkio/shlink/issues/35) Updated CLI commands to use the same PHP namespace as the one used for the command name -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * [#40](https://github.com/shlinkio/shlink/issues/40) Taken into account the `X-Forwarded-For` header in order to get the visitor information, in case the server is behind a load balancer or proxy -## 1.0.0 - 2016-08-01 - -#### Added - +## [1.0.0] - 2016-08-01 +### Added * [#33](https://github.com/shlinkio/shlink/issues/33) Created a command that generates a short code charset by randomizing the default one * [#23](https://github.com/shlinkio/shlink/issues/23) Translated application literals * [#21](https://github.com/shlinkio/shlink/issues/21) Allowed to filter visits by date range * [#4](https://github.com/shlinkio/shlink/issues/4) Added installation steps * [#12](https://github.com/shlinkio/shlink/issues/12) Improved code coverage -#### Changed - +### Changed * [#15](https://github.com/shlinkio/shlink/issues/15) HTTP requests now return JSON/HTML responses for errors (4xx and 5xx) based on `Accept` header * [#22](https://github.com/shlinkio/shlink/issues/22) Now visits locations data is saved on a `visit_locations` table * [#20](https://github.com/shlinkio/shlink/issues/20) Injected cross domain headers in response only if the `Origin` header is present in the request @@ -1877,39 +1514,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#25](https://github.com/shlinkio/shlink/issues/25) Replaced "Middleware" suffix on routable middlewares by "Action" * [#19](https://github.com/shlinkio/shlink/issues/19) Changed the vendor and app namespace from `Acelaya\UrlShortener` to `Shlinkio\Shlink` -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * [#36](https://github.com/shlinkio/shlink/issues/36) Removed hhvm from the CI matrix since it doesn't support array constants and will fail -#### Fixed - +### Fixed * [#24](https://github.com/shlinkio/shlink/issues/24) Prevented duplicated short codes errors because of the case insensitive behavior on MySQL -## 0.2.0 - 2016-08-01 - -#### Added - +## [0.2.0] - 2016-08-01 +### Added * [#8](https://github.com/shlinkio/shlink/issues/8) Created a REST API * [#10](https://github.com/shlinkio/shlink/issues/10) Added more CLI functionality * [#5](https://github.com/shlinkio/shlink/issues/5) Created a CHANGELOG file -#### Changed - +### Changed * [#9](https://github.com/shlinkio/shlink/issues/9) Used [symfony/console](https://github.com/symfony/console) to dispatch console requests, instead of trying to integrate the process with expressive -#### Deprecated - +### Deprecated * *Nothing* -#### Removed - +### Removed * *Nothing* -#### Fixed - +### Fixed * *Nothing* From ebe6a5f4aa971025dc582d5b09cb5e0a8e01e2c8 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 1 Nov 2020 11:23:11 +0100 Subject: [PATCH 54/73] Moved github release creation from travis to github action --- .github/workflows/publish-release.yml | 29 +++++++++++++++++++++++++++ .travis.yml | 12 ----------- 2 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/publish-release.yml diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 00000000..1e904176 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,29 @@ +name: Publish release + +on: + push: + tags: + - 'v*' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Use PHP 7.4 + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' # Publish release with lowest supported PHP version + tools: composer + - name: Generate release assets + run: ./build.sh ${GITHUB_REF#refs/tags/v} + - name: Publish release with assets + uses: docker://antonyurchenko/git-release:latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ALLOW_TAG_PREFIX: "true" + ALLOW_EMPTY_CHANGELOG: "true" + with: + args: | + build/shlink_*_dist.zip diff --git a/.travis.yml b/.travis.yml index c9945281..3bf55b55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,18 +26,6 @@ jobs: php: '7.4' env: - COMPOSER_FLAGS='' - # Deploy release only on smallest supported PHP version - before_deploy: - - rm -f ocular.phar - - ./build.sh ${TRAVIS_TAG#?} - deploy: - - provider: releases - api_key: - secure: a9dbZchocqeuOViwUeNH54bQR5Sz7rEYXx5b9WPFtnFn9LGKKUaLbA2U91UQ9QKPrcTpsALubUYbw2CnNmvCwzaY+R8lCD3gkU4ohsEnbpnw3deOeixI74sqBHJAuCH9FSaRDGILoBMtUKx2xlzIymFxkIsgIukkGbdkWHDlRWY3oTUUuw1SQ2Xk9KDsbJQtjIc1+G/O6gHaV4qv/R9W8NPmJExKTNDrAZbC1vIUnxqp4UpVo1hst8qPd1at94CndDYM5rG+7imGbdtxTxzamt819qdTO1OfvtctKawNAm7YXZrrWft6c7gI6j6SI4hxd+ZrrPBqbaRFHkZHjnNssO/yn4SaOHFFzccmu0MzvpPCf0qWZwd3sGHVYer1MnR2mHYqU84QPlW3nrHwJjkrpq3+q0JcBY6GsJs+RskHNtkMTKV05Iz6QUI5YZGwTpuXaRm036SmavjGc4IDlMaYCk/NmbB9BKpthJxLdUpczOHpnjXXHziotWD6cfEnbjU3byfD8HY5WrxSjsNT7SKmXN3hRof7bk985ewQVjGT42O3NbnfnqjQQWr/B7/zFTpLR4f526Bkq12CdCyf5lvrbq+POkLVdJ+uFfR7ds248Ue/jBQy6kM1tWmKF9QiwisFlA84eQ4CW3I93Rp97URv+AQa9zmbD0Ve3Udp+g6nF5I= - file: "./build/shlink_${TRAVIS_TAG#?}_dist.zip" - skip_cleanup: true - on: - tags: true before_install: - echo 'extension = apcu.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini From 681b7c836d55753d2780e1d1f04aee40b1f61fef Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 1 Nov 2020 11:47:04 +0100 Subject: [PATCH 55/73] Added swoole extension to publish-release github action --- .github/workflows/publish-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 1e904176..78f981ab 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -16,6 +16,7 @@ jobs: with: php-version: '7.4' # Publish release with lowest supported PHP version tools: composer + extensions: swoole-4.5.5 - name: Generate release assets run: ./build.sh ${GITHUB_REF#refs/tags/v} - name: Publish release with assets From e71fb0ac7fc2f4288c8b87a06163e9f867a88281 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 Nov 2020 09:02:00 +0100 Subject: [PATCH 56/73] Added gmp extension to docker images, as it seems to be required by geolite in some cases --- Dockerfile | 6 +++++- data/infra/php.Dockerfile | 3 +++ data/infra/swoole.Dockerfile | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 41fce03a..ea0a1d9d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,7 @@ ENV LC_ALL "C" WORKDIR /etc/shlink +# Install required PHP extensions RUN \ # Install mysql and calendar docker-php-ext-install -j"$(nproc)" pdo_mysql calendar && \ @@ -21,7 +22,10 @@ RUN \ docker-php-ext-install -j"$(nproc)" intl && \ # Install zip and gd apk add --no-cache libzip-dev zlib-dev libpng-dev && \ - docker-php-ext-install -j"$(nproc)" zip gd + docker-php-ext-install -j"$(nproc)" zip gd && \ + # Install gmp + apk add --no-cache gmp-dev && \ + docker-php-ext-install -j"$(nproc)" gmp # Install sqlsrv driver RUN if [ $(uname -m) == "x86_64" ]; then \ diff --git a/data/infra/php.Dockerfile b/data/infra/php.Dockerfile index 218db4c5..e419ba6b 100644 --- a/data/infra/php.Dockerfile +++ b/data/infra/php.Dockerfile @@ -30,6 +30,9 @@ RUN docker-php-ext-install gd RUN apk add --no-cache postgresql-dev RUN docker-php-ext-install pdo_pgsql +RUN apk add --no-cache gmp-dev +RUN docker-php-ext-install gmp + # Install APCu extension ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz RUN mkdir -p /usr/src/php/ext/apcu\ diff --git a/data/infra/swoole.Dockerfile b/data/infra/swoole.Dockerfile index 5d8485ab..00d197ba 100644 --- a/data/infra/swoole.Dockerfile +++ b/data/infra/swoole.Dockerfile @@ -32,6 +32,9 @@ RUN docker-php-ext-install gd RUN apk add --no-cache postgresql-dev RUN docker-php-ext-install pdo_pgsql +RUN apk add --no-cache gmp-dev +RUN docker-php-ext-install gmp + # Install APCu extension ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz RUN mkdir -p /usr/src/php/ext/apcu\ From 51c8b804890efd8c06b223ff0eb3628bc7abbc46 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 Nov 2020 09:24:14 +0100 Subject: [PATCH 57/73] Changed to for consistency in the Dockerfile --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index ea0a1d9d..04441788 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,7 @@ RUN \ RUN if [ $(uname -m) == "x86_64" ]; then \ wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_17.5.1.1-1_amd64.apk && \ apk add --allow-untrusted msodbcsql17_17.5.1.1-1_amd64.apk && \ - apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \ + apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \ pecl install pdo_sqlsrv && \ docker-php-ext-enable pdo_sqlsrv && \ apk del .phpize-deps && \ @@ -39,7 +39,7 @@ RUN if [ $(uname -m) == "x86_64" ]; then \ fi # Install swoole -RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS && \ +RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} && \ pecl install swoole-${SWOOLE_VERSION} && \ docker-php-ext-enable swoole && \ apk del .phpize-deps From efa707c676e30a6c83d6d15f588a117409f40983 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 Nov 2020 09:25:17 +0100 Subject: [PATCH 58/73] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d4ad7e7..f3379e11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ### Fixed * [#837](https://github.com/shlinkio/shlink/issues/837) Drastically improved performance when creating a new shortUrl and providing `findIfExists = true`. +* [#837](https://github.com/shlinkio/shlink/issues/837) Added missing `gmp` extension to the official docker image. ## [2.3.0] - 2020-08-09 From 544836b986e86bb054f7901e57a92f343cb26408 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 Nov 2020 11:05:14 +0100 Subject: [PATCH 59/73] Deprecated tags creation --- docs/swagger/paths/v1_tags.json | 3 ++- module/CLI/src/Command/Tag/CreateTagCommand.php | 3 ++- module/Core/src/Tag/TagService.php | 1 + module/Core/src/Tag/TagServiceInterface.php | 1 + module/Rest/src/Action/Tag/CreateTagsAction.php | 1 + 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/swagger/paths/v1_tags.json b/docs/swagger/paths/v1_tags.json index 83bc7d68..cb6a6bb3 100644 --- a/docs/swagger/paths/v1_tags.json +++ b/docs/swagger/paths/v1_tags.json @@ -87,12 +87,13 @@ }, "post": { + "deprecated": true, "operationId": "createTags", "tags": [ "Tags" ], "summary": "Create tags", - "description": "Provided a list of tags, creates all that do not yet exist", + "description": "Provided a list of tags, creates all that do not yet exist
This endpoint is deprecated, as tags are automatically created while creating a short URL", "security": [ { "ApiKey": [] diff --git a/module/CLI/src/Command/Tag/CreateTagCommand.php b/module/CLI/src/Command/Tag/CreateTagCommand.php index 451eb81e..0003319d 100644 --- a/module/CLI/src/Command/Tag/CreateTagCommand.php +++ b/module/CLI/src/Command/Tag/CreateTagCommand.php @@ -12,6 +12,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +/** @deprecated */ class CreateTagCommand extends Command { public const NAME = 'tag:create'; @@ -28,7 +29,7 @@ class CreateTagCommand extends Command { $this ->setName(self::NAME) - ->setDescription('Creates one or more tags.') + ->setDescription('[Deprecated] Creates one or more tags.') ->addOption( 'name', 't', diff --git a/module/Core/src/Tag/TagService.php b/module/Core/src/Tag/TagService.php index 7137e885..4e0261a5 100644 --- a/module/Core/src/Tag/TagService.php +++ b/module/Core/src/Tag/TagService.php @@ -58,6 +58,7 @@ class TagService implements TagServiceInterface /** * Provided a list of tag names, creates all that do not exist yet * + * @deprecated * @param string[] $tagNames * @return Collection|Tag[] */ diff --git a/module/Core/src/Tag/TagServiceInterface.php b/module/Core/src/Tag/TagServiceInterface.php index ed643fc5..3c8c6e69 100644 --- a/module/Core/src/Tag/TagServiceInterface.php +++ b/module/Core/src/Tag/TagServiceInterface.php @@ -28,6 +28,7 @@ interface TagServiceInterface public function deleteTags(array $tagNames): void; /** + * @deprecated * @param string[] $tagNames * @return Collection|Tag[] */ diff --git a/module/Rest/src/Action/Tag/CreateTagsAction.php b/module/Rest/src/Action/Tag/CreateTagsAction.php index 08f617c2..8aaf907b 100644 --- a/module/Rest/src/Action/Tag/CreateTagsAction.php +++ b/module/Rest/src/Action/Tag/CreateTagsAction.php @@ -10,6 +10,7 @@ use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Core\Tag\TagServiceInterface; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; +/** @deprecated */ class CreateTagsAction extends AbstractRestAction { protected const ROUTE_PATH = '/tags'; From 1346d7902e870142cc605e4690940b621d7d7090 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 Nov 2020 11:06:41 +0100 Subject: [PATCH 60/73] Updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3379e11..c20ebcaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#858](https://github.com/shlinkio/shlink/issues/858) Updated to latest infection version. Updated docker images to PHP 7.4.11 and swoole 4.5.5 ### Deprecated -* *Nothing* +* [#883](https://github.com/shlinkio/shlink/issues/883) Deprecated `POST /tags` endpoint and `tag:create` command, as tags are created automatically while creating short URLs. ### Removed * *Nothing* From 1621f3a943ae2a96c80efa59ccc6ebfb35320b58 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 Nov 2020 11:50:19 +0100 Subject: [PATCH 61/73] Updated dependencies --- composer.json | 11 ++++++----- config/test/test_config.global.php | 7 +++++-- .../test/Command/Api/DisableKeyCommandTest.php | 3 +++ .../test/Command/Api/GenerateKeyCommandTest.php | 3 +++ .../test/Command/Api/ListKeysCommandTest.php | 3 +++ .../Command/Db/CreateDatabaseCommandTest.php | 10 ++++++---- .../Command/Db/MigrateDatabaseCommandTest.php | 3 +++ .../Command/Domain/ListDomainsCommandTest.php | 3 +++ .../ShortUrl/DeleteShortUrlCommandTest.php | 3 +++ .../ShortUrl/GenerateShortUrlCommandTest.php | 3 +++ .../Command/ShortUrl/GetVisitsCommandTest.php | 3 +++ .../ShortUrl/ListShortUrlsCommandTest.php | 3 +++ .../Command/ShortUrl/ResolveUrlCommandTest.php | 3 +++ .../test/Command/Tag/CreateTagCommandTest.php | 3 +++ .../test/Command/Tag/DeleteTagsCommandTest.php | 3 +++ .../test/Command/Tag/ListTagsCommandTest.php | 3 +++ .../test/Command/Tag/RenameTagCommandTest.php | 3 +++ .../Command/Visit/LocateVisitsCommandTest.php | 10 ++++++---- .../CLI/test/Factory/ApplicationFactoryTest.php | 3 +++ .../CLI/test/Util/GeolocationDbUpdaterTest.php | 17 +++++++++-------- module/CLI/test/Util/ShlinkTableTest.php | 3 +++ module/Core/test/Action/PixelActionTest.php | 3 +++ module/Core/test/Action/QrCodeActionTest.php | 3 +++ module/Core/test/Action/RedirectActionTest.php | 3 +++ module/Core/test/Domain/DomainServiceTest.php | 3 +++ .../Resolver/PersistenceDomainResolverTest.php | 3 +++ .../NotFoundRedirectHandlerTest.php | 3 +++ .../NotFoundTemplateHandlerTest.php | 3 +++ ...seDbConnectionEventListenerDelegatorTest.php | 5 ++++- .../CloseDbConnectionEventListenerTest.php | 3 +++ .../EventDispatcher/LocateShortUrlVisitTest.php | 3 +++ .../NotifyVisitToMercureTest.php | 3 +++ .../NotifyVisitToWebHooksTest.php | 3 +++ .../test/Exception/ValidationExceptionTest.php | 3 +++ .../Importer/ImportedLinksProcessorTest.php | 3 +++ .../Adapter/ShortUrlRepositoryAdapterTest.php | 3 +++ .../VisitsForTagPaginatorAdapterTest.php | 3 +++ .../Adapter/VisitsPaginatorAdapterTest.php | 3 +++ .../ShortUrl/DeleteShortUrlServiceTest.php | 3 +++ .../Service/ShortUrl/ShortCodeHelperTest.php | 3 +++ .../Service/ShortUrl/ShortUrlResolverTest.php | 3 +++ .../Core/test/Service/ShortUrlServiceTest.php | 3 +++ module/Core/test/Service/Tag/TagServiceTest.php | 3 +++ module/Core/test/Service/UrlShortenerTest.php | 3 +++ module/Core/test/Service/VisitsTrackerTest.php | 3 +++ .../Core/test/Util/DoctrineBatchHelperTest.php | 3 +++ module/Core/test/Util/UrlValidatorTest.php | 3 +++ module/Core/test/Visit/VisitLocatorTest.php | 3 +++ .../Core/test/Visit/VisitsStatsHelperTest.php | 3 +++ .../Action/Domain/ListDomainsActionTest.php | 3 +++ module/Rest/test/Action/HealthActionTest.php | 3 +++ .../Rest/test/Action/MercureInfoActionTest.php | 3 +++ .../ShortUrl/CreateShortUrlActionTest.php | 3 +++ .../ShortUrl/DeleteShortUrlActionTest.php | 3 +++ .../Action/ShortUrl/EditShortUrlActionTest.php | 3 +++ .../ShortUrl/EditShortUrlTagsActionTest.php | 3 +++ .../Action/ShortUrl/ListShortUrlsActionTest.php | 8 ++++---- .../ShortUrl/ResolveShortUrlActionTest.php | 3 +++ .../SingleStepCreateShortUrlActionTest.php | 3 +++ .../test/Action/Tag/CreateTagsActionTest.php | 3 +++ .../test/Action/Tag/DeleteTagsActionTest.php | 3 +++ .../Rest/test/Action/Tag/ListTagsActionTest.php | 3 +++ .../test/Action/Tag/UpdateTagActionTest.php | 3 +++ .../Action/Visit/GlobalVisitsActionTest.php | 3 +++ .../Action/Visit/ShortUrlVisitsActionTest.php | 3 +++ .../test/Action/Visit/TagVisitsActionTest.php | 3 +++ .../AuthenticationPluginManagerFactoryTest.php | 3 +++ .../Plugin/ApiKeyHeaderPluginTest.php | 3 +++ .../Authentication/RequestToAuthPluginTest.php | 3 +++ .../Middleware/AuthenticationMiddlewareTest.php | 13 ++++--------- .../Middleware/BodyParserMiddlewareTest.php | 3 +++ .../Middleware/CrossDomainMiddlewareTest.php | 3 +++ ...ShortUrlContentNegotiationMiddlewareTest.php | 3 +++ .../DefaultShortCodesLengthMiddlewareTest.php | 3 +++ ...opDefaultDomainFromRequestMiddlewareTest.php | 3 +++ module/Rest/test/Service/ApiKeyServiceTest.php | 3 +++ 76 files changed, 248 insertions(+), 37 deletions(-) diff --git a/composer.json b/composer.json index 1c558a25..0bbe7651 100644 --- a/composer.json +++ b/composer.json @@ -66,12 +66,13 @@ "devster/ubench": "^2.0", "dms/phpunit-arraysubset-asserts": "^0.2.0", "eaglewu/swoole-ide-helper": "dev-master", - "infection/infection": "^0.17.7", - "phpstan/phpstan": "^0.12.50", - "phpunit/php-code-coverage": "^8.0", - "phpunit/phpunit": "~9.0.1", + "infection/infection": "^0.20.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/phpstan": "^0.12.52", + "phpunit/php-code-coverage": "^9.2", + "phpunit/phpunit": "^9.4", "roave/security-advisories": "dev-master", - "shlinkio/php-coding-standard": "~2.1.0", + "shlinkio/php-coding-standard": "~2.1.1", "shlinkio/shlink-test-utils": "^1.5", "symfony/var-dumper": "^5.1" }, diff --git a/config/test/test_config.global.php b/config/test/test_config.global.php index e79651dd..6b3c6612 100644 --- a/config/test/test_config.global.php +++ b/config/test/test_config.global.php @@ -12,6 +12,8 @@ use Laminas\Stdlib\Glob; use PDO; use PHPUnit\Runner\Version; use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Driver\Selector; +use SebastianBergmann\CodeCoverage\Filter; use SebastianBergmann\CodeCoverage\Report\PHP; use SebastianBergmann\CodeCoverage\Report\Xml\Facade as Xml; @@ -25,10 +27,11 @@ use const ShlinkioTest\Shlink\SWOOLE_TESTING_PORT; $isApiTest = env('TEST_ENV') === 'api'; if ($isApiTest) { - $coverage = new CodeCoverage(); + $filter = new Filter(); foreach (Glob::glob(__DIR__ . '/../../module/*/src') as $item) { - $coverage->filter()->addDirectoryToWhitelist($item); + $filter->includeDirectory($item); } + $coverage = new CodeCoverage((new Selector())->forLineCoverage($filter), $filter); } $buildDbConnection = function (): array { diff --git a/module/CLI/test/Command/Api/DisableKeyCommandTest.php b/module/CLI/test/Command/Api/DisableKeyCommandTest.php index 2138fff6..49835f85 100644 --- a/module/CLI/test/Command/Api/DisableKeyCommandTest.php +++ b/module/CLI/test/Command/Api/DisableKeyCommandTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Api; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Api\DisableKeyCommand; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; @@ -14,6 +15,8 @@ use Symfony\Component\Console\Tester\CommandTester; class DisableKeyCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $apiKeyService; diff --git a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php index bb467337..7ff87a3f 100644 --- a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php +++ b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php @@ -7,6 +7,7 @@ namespace ShlinkioTest\Shlink\CLI\Command\Api; use Cake\Chronos\Chronos; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Api\GenerateKeyCommand; use Shlinkio\Shlink\Rest\Entity\ApiKey; @@ -16,6 +17,8 @@ use Symfony\Component\Console\Tester\CommandTester; class GenerateKeyCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $apiKeyService; diff --git a/module/CLI/test/Command/Api/ListKeysCommandTest.php b/module/CLI/test/Command/Api/ListKeysCommandTest.php index fc03a180..ccf3b0ee 100644 --- a/module/CLI/test/Command/Api/ListKeysCommandTest.php +++ b/module/CLI/test/Command/Api/ListKeysCommandTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Api; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Api\ListKeysCommand; use Shlinkio\Shlink\Rest\Entity\ApiKey; @@ -14,6 +15,8 @@ use Symfony\Component\Console\Tester\CommandTester; class ListKeysCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $apiKeyService; diff --git a/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php b/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php index b34ebb56..8c325278 100644 --- a/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php +++ b/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php @@ -9,6 +9,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\AbstractSchemaManager; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Db\CreateDatabaseCommand; use Symfony\Component\Console\Application; @@ -22,10 +23,11 @@ use Symfony\Component\Process\Process; class CreateDatabaseCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $processHelper; private ObjectProphecy $regularConn; - private ObjectProphecy $noDbNameConn; private ObjectProphecy $schemaManager; private ObjectProphecy $databasePlatform; @@ -48,15 +50,15 @@ class CreateDatabaseCommandTest extends TestCase $this->regularConn = $this->prophesize(Connection::class); $this->regularConn->getSchemaManager()->willReturn($this->schemaManager->reveal()); $this->regularConn->getDatabasePlatform()->willReturn($this->databasePlatform->reveal()); - $this->noDbNameConn = $this->prophesize(Connection::class); - $this->noDbNameConn->getSchemaManager()->willReturn($this->schemaManager->reveal()); + $noDbNameConn = $this->prophesize(Connection::class); + $noDbNameConn->getSchemaManager()->willReturn($this->schemaManager->reveal()); $command = new CreateDatabaseCommand( $locker->reveal(), $this->processHelper->reveal(), $phpExecutableFinder->reveal(), $this->regularConn->reveal(), - $this->noDbNameConn->reveal(), + $noDbNameConn->reveal(), ); $app = new Application(); $app->add($command); diff --git a/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php b/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php index 985a7e74..9875c2f6 100644 --- a/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php +++ b/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\CLI\Command\Db; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Db\MigrateDatabaseCommand; use Symfony\Component\Console\Application; @@ -19,6 +20,8 @@ use Symfony\Component\Process\Process; class MigrateDatabaseCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $processHelper; diff --git a/module/CLI/test/Command/Domain/ListDomainsCommandTest.php b/module/CLI/test/Command/Domain/ListDomainsCommandTest.php index ded8572f..500fed7f 100644 --- a/module/CLI/test/Command/Domain/ListDomainsCommandTest.php +++ b/module/CLI/test/Command/Domain/ListDomainsCommandTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Domain; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Domain\ListDomainsCommand; use Shlinkio\Shlink\CLI\Util\ExitCodes; @@ -15,6 +16,8 @@ use Symfony\Component\Console\Tester\CommandTester; class ListDomainsCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $domainService; diff --git a/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php index b72a27a1..83fd792d 100644 --- a/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\CLI\Command\ShortUrl; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\ShortUrl\DeleteShortUrlCommand; use Shlinkio\Shlink\Core\Exception; @@ -21,6 +22,8 @@ use const PHP_EOL; class DeleteShortUrlCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $service; diff --git a/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php index fd63fc2a..b11b455b 100644 --- a/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php @@ -7,6 +7,7 @@ namespace ShlinkioTest\Shlink\CLI\Command\ShortUrl; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\ShortUrl\GenerateShortUrlCommand; use Shlinkio\Shlink\CLI\Util\ExitCodes; @@ -20,6 +21,8 @@ use Symfony\Component\Console\Tester\CommandTester; class GenerateShortUrlCommandTest extends TestCase { + use ProphecyTrait; + private const DOMAIN_CONFIG = [ 'schema' => 'http', 'hostname' => 'foo.com', diff --git a/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php b/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php index d57deb2e..9239544e 100644 --- a/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php @@ -9,6 +9,7 @@ use Laminas\Paginator\Adapter\ArrayAdapter; use Laminas\Paginator\Paginator; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\ShortUrl\GetVisitsCommand; use Shlinkio\Shlink\Common\Util\DateRange; @@ -27,6 +28,8 @@ use function sprintf; class GetVisitsCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $visitsTracker; diff --git a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php index 4fbeec40..918dc39a 100644 --- a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php @@ -9,6 +9,7 @@ use Laminas\Paginator\Adapter\ArrayAdapter; use Laminas\Paginator\Paginator; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\ShortUrl\ListShortUrlsCommand; use Shlinkio\Shlink\Core\Entity\ShortUrl; @@ -21,6 +22,8 @@ use function explode; class ListShortUrlsCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $shortUrlService; diff --git a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php index 8beeecd1..a84a1ee3 100644 --- a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\ShortUrl; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\ShortUrl\ResolveUrlCommand; use Shlinkio\Shlink\Core\Entity\ShortUrl; @@ -20,6 +21,8 @@ use const PHP_EOL; class ResolveUrlCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $urlResolver; diff --git a/module/CLI/test/Command/Tag/CreateTagCommandTest.php b/module/CLI/test/Command/Tag/CreateTagCommandTest.php index c0a25371..2789c481 100644 --- a/module/CLI/test/Command/Tag/CreateTagCommandTest.php +++ b/module/CLI/test/Command/Tag/CreateTagCommandTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\CLI\Command\Tag; use Doctrine\Common\Collections\ArrayCollection; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Tag\CreateTagCommand; use Shlinkio\Shlink\Core\Tag\TagServiceInterface; @@ -14,6 +15,8 @@ use Symfony\Component\Console\Tester\CommandTester; class CreateTagCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $tagService; diff --git a/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php b/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php index d76f1c7d..6d3737c1 100644 --- a/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php +++ b/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Tag; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Tag\DeleteTagsCommand; use Shlinkio\Shlink\Core\Tag\TagServiceInterface; @@ -13,6 +14,8 @@ use Symfony\Component\Console\Tester\CommandTester; class DeleteTagsCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $tagService; diff --git a/module/CLI/test/Command/Tag/ListTagsCommandTest.php b/module/CLI/test/Command/Tag/ListTagsCommandTest.php index d9b28fb0..5b9e14e9 100644 --- a/module/CLI/test/Command/Tag/ListTagsCommandTest.php +++ b/module/CLI/test/Command/Tag/ListTagsCommandTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Tag; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Tag\ListTagsCommand; use Shlinkio\Shlink\Core\Entity\Tag; @@ -15,6 +16,8 @@ use Symfony\Component\Console\Tester\CommandTester; class ListTagsCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $tagService; diff --git a/module/CLI/test/Command/Tag/RenameTagCommandTest.php b/module/CLI/test/Command/Tag/RenameTagCommandTest.php index b9685668..9764a111 100644 --- a/module/CLI/test/Command/Tag/RenameTagCommandTest.php +++ b/module/CLI/test/Command/Tag/RenameTagCommandTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Tag; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Tag\RenameTagCommand; use Shlinkio\Shlink\Core\Entity\Tag; @@ -15,6 +16,8 @@ use Symfony\Component\Console\Tester\CommandTester; class RenameTagCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $tagService; diff --git a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php index 966a702c..bb9f4715 100644 --- a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\CLI\Command\Visit; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Visit\LocateVisitsCommand; use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException; @@ -32,10 +33,11 @@ use const PHP_EOL; class LocateVisitsCommandTest extends TestCase { + use ProphecyTrait; + private CommandTester $commandTester; private ObjectProphecy $visitService; private ObjectProphecy $ipResolver; - private ObjectProphecy $locker; private ObjectProphecy $lock; private ObjectProphecy $dbUpdater; @@ -45,17 +47,17 @@ class LocateVisitsCommandTest extends TestCase $this->ipResolver = $this->prophesize(IpLocationResolverInterface::class); $this->dbUpdater = $this->prophesize(GeolocationDbUpdaterInterface::class); - $this->locker = $this->prophesize(Lock\LockFactory::class); + $locker = $this->prophesize(Lock\LockFactory::class); $this->lock = $this->prophesize(Lock\LockInterface::class); $this->lock->acquire(false)->willReturn(true); $this->lock->release()->will(function (): void { }); - $this->locker->createLock(Argument::type('string'), 90.0, false)->willReturn($this->lock->reveal()); + $locker->createLock(Argument::type('string'), 90.0, false)->willReturn($this->lock->reveal()); $command = new LocateVisitsCommand( $this->visitService->reveal(), $this->ipResolver->reveal(), - $this->locker->reveal(), + $locker->reveal(), $this->dbUpdater->reveal(), ); $app = new Application(); diff --git a/module/CLI/test/Factory/ApplicationFactoryTest.php b/module/CLI/test/Factory/ApplicationFactoryTest.php index b05e291e..ee0793bc 100644 --- a/module/CLI/test/Factory/ApplicationFactoryTest.php +++ b/module/CLI/test/Factory/ApplicationFactoryTest.php @@ -7,6 +7,7 @@ namespace ShlinkioTest\Shlink\CLI\Factory; use Laminas\ServiceManager\ServiceManager; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Factory\ApplicationFactory; use Shlinkio\Shlink\Core\Options\AppOptions; @@ -15,6 +16,8 @@ use Symfony\Component\Console\Command\Command; class ApplicationFactoryTest extends TestCase { + use ProphecyTrait; + private ApplicationFactory $factory; public function setUp(): void diff --git a/module/CLI/test/Util/GeolocationDbUpdaterTest.php b/module/CLI/test/Util/GeolocationDbUpdaterTest.php index 91bf5627..71e05d8a 100644 --- a/module/CLI/test/Util/GeolocationDbUpdaterTest.php +++ b/module/CLI/test/Util/GeolocationDbUpdaterTest.php @@ -9,6 +9,7 @@ use GeoIp2\Database\Reader; use MaxMind\Db\Reader\Metadata; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException; use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdater; @@ -22,28 +23,28 @@ use function range; class GeolocationDbUpdaterTest extends TestCase { + use ProphecyTrait; + private GeolocationDbUpdater $geolocationDbUpdater; private ObjectProphecy $dbUpdater; private ObjectProphecy $geoLiteDbReader; - private ObjectProphecy $locker; - private ObjectProphecy $lock; public function setUp(): void { $this->dbUpdater = $this->prophesize(DbUpdaterInterface::class); $this->geoLiteDbReader = $this->prophesize(Reader::class); - $this->locker = $this->prophesize(Lock\LockFactory::class); - $this->lock = $this->prophesize(Lock\LockInterface::class); - $this->lock->acquire(true)->willReturn(true); - $this->lock->release()->will(function (): void { + $locker = $this->prophesize(Lock\LockFactory::class); + $lock = $this->prophesize(Lock\LockInterface::class); + $lock->acquire(true)->willReturn(true); + $lock->release()->will(function (): void { }); - $this->locker->createLock(Argument::type('string'))->willReturn($this->lock->reveal()); + $locker->createLock(Argument::type('string'))->willReturn($lock->reveal()); $this->geolocationDbUpdater = new GeolocationDbUpdater( $this->dbUpdater->reveal(), $this->geoLiteDbReader->reveal(), - $this->locker->reveal(), + $locker->reveal(), ); } diff --git a/module/CLI/test/Util/ShlinkTableTest.php b/module/CLI/test/Util/ShlinkTableTest.php index 4d270854..71bff82b 100644 --- a/module/CLI/test/Util/ShlinkTableTest.php +++ b/module/CLI/test/Util/ShlinkTableTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\CLI\Util; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use ReflectionObject; use Shlinkio\Shlink\CLI\Util\ShlinkTable; @@ -15,6 +16,8 @@ use Symfony\Component\Console\Output\OutputInterface; class ShlinkTableTest extends TestCase { + use ProphecyTrait; + private ShlinkTable $shlinkTable; private ObjectProphecy $baseTable; diff --git a/module/Core/test/Action/PixelActionTest.php b/module/Core/test/Action/PixelActionTest.php index c678fa3a..b1edd9ec 100644 --- a/module/Core/test/Action/PixelActionTest.php +++ b/module/Core/test/Action/PixelActionTest.php @@ -7,6 +7,7 @@ namespace ShlinkioTest\Shlink\Core\Action; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Common\Response\PixelResponse; @@ -19,6 +20,8 @@ use Shlinkio\Shlink\Core\Service\VisitsTracker; class PixelActionTest extends TestCase { + use ProphecyTrait; + private PixelAction $action; private ObjectProphecy $urlResolver; private ObjectProphecy $visitTracker; diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index 06e602e2..1237585c 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -9,6 +9,7 @@ use Laminas\Diactoros\ServerRequest; use Mezzio\Router\RouterInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Common\Response\QrCodeResponse; @@ -20,6 +21,8 @@ use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface; class QrCodeActionTest extends TestCase { + use ProphecyTrait; + private QrCodeAction $action; private ObjectProphecy $urlResolver; diff --git a/module/Core/test/Action/RedirectActionTest.php b/module/Core/test/Action/RedirectActionTest.php index 495162bb..c4785cf1 100644 --- a/module/Core/test/Action/RedirectActionTest.php +++ b/module/Core/test/Action/RedirectActionTest.php @@ -10,6 +10,7 @@ use Laminas\Diactoros\ServerRequest; use Mezzio\Router\Middleware\ImplicitHeadMiddleware; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Core\Action\RedirectAction; @@ -24,6 +25,8 @@ use function array_key_exists; class RedirectActionTest extends TestCase { + use ProphecyTrait; + private RedirectAction $action; private ObjectProphecy $urlResolver; private ObjectProphecy $visitTracker; diff --git a/module/Core/test/Domain/DomainServiceTest.php b/module/Core/test/Domain/DomainServiceTest.php index e91c3281..906088ea 100644 --- a/module/Core/test/Domain/DomainServiceTest.php +++ b/module/Core/test/Domain/DomainServiceTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Core\Domain; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Domain\DomainService; use Shlinkio\Shlink\Core\Domain\Repository\DomainRepositoryInterface; @@ -13,6 +14,8 @@ use Shlinkio\Shlink\Core\Entity\Domain; class DomainServiceTest extends TestCase { + use ProphecyTrait; + private DomainService $domainService; private ObjectProphecy $em; diff --git a/module/Core/test/Domain/Resolver/PersistenceDomainResolverTest.php b/module/Core/test/Domain/Resolver/PersistenceDomainResolverTest.php index c9f71851..d1fc5fbb 100644 --- a/module/Core/test/Domain/Resolver/PersistenceDomainResolverTest.php +++ b/module/Core/test/Domain/Resolver/PersistenceDomainResolverTest.php @@ -7,12 +7,15 @@ namespace ShlinkioTest\Shlink\Core\Domain\Resolver; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ObjectRepository; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Domain\Resolver\PersistenceDomainResolver; use Shlinkio\Shlink\Core\Entity\Domain; class PersistenceDomainResolverTest extends TestCase { + use ProphecyTrait; + private PersistenceDomainResolver $domainResolver; private ObjectProphecy $em; diff --git a/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php b/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php index 2dc5f16d..69cf6c98 100644 --- a/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php +++ b/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php @@ -10,6 +10,7 @@ use Laminas\Diactoros\Uri; use Mezzio\Router\Route; use Mezzio\Router\RouteResult; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -19,6 +20,8 @@ use Shlinkio\Shlink\Core\Options\NotFoundRedirectOptions; class NotFoundRedirectHandlerTest extends TestCase { + use ProphecyTrait; + private NotFoundRedirectHandler $middleware; private NotFoundRedirectOptions $redirectOptions; diff --git a/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php b/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php index a967c3f7..e10954ca 100644 --- a/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php +++ b/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php @@ -10,6 +10,7 @@ use Mezzio\Router\Route; use Mezzio\Router\RouteResult; use Mezzio\Template\TemplateRendererInterface; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; @@ -17,6 +18,8 @@ use Shlinkio\Shlink\Core\ErrorHandler\NotFoundTemplateHandler; class NotFoundTemplateHandlerTest extends TestCase { + use ProphecyTrait; + private NotFoundTemplateHandler $handler; private ObjectProphecy $renderer; diff --git a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php index bb396983..c928200e 100644 --- a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php +++ b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php @@ -2,9 +2,10 @@ declare(strict_types=1); -namespace ShlinkioTest\Shlink\Rest\EventDispatcher; +namespace ShlinkioTest\Shlink\Core\EventDispatcher; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Container\ContainerInterface; use Shlinkio\Shlink\Common\Doctrine\ReopeningEntityManagerInterface; @@ -12,6 +13,8 @@ use Shlinkio\Shlink\Core\EventDispatcher\CloseDbConnectionEventListenerDelegator class CloseDbConnectionEventListenerDelegatorTest extends TestCase { + use ProphecyTrait; + private CloseDbConnectionEventListenerDelegator $delegator; private ObjectProphecy $container; diff --git a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php index b75ec8dc..5f08e5fe 100644 --- a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php +++ b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Core\EventDispatcher; use Doctrine\DBAL\Connection; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use RuntimeException; use Shlinkio\Shlink\Common\Doctrine\ReopeningEntityManagerInterface; @@ -15,6 +16,8 @@ use Throwable; class CloseDbConnectionEventListenerTest extends TestCase { + use ProphecyTrait; + private ObjectProphecy $em; public function setUp(): void diff --git a/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php b/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php index fc87e98c..ab12a349 100644 --- a/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php +++ b/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php @@ -7,6 +7,7 @@ namespace ShlinkioTest\Shlink\Core\EventDispatcher; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; @@ -26,6 +27,8 @@ use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface; class LocateShortUrlVisitTest extends TestCase { + use ProphecyTrait; + private LocateShortUrlVisit $locateVisit; private ObjectProphecy $ipLocationResolver; private ObjectProphecy $em; diff --git a/module/Core/test/EventDispatcher/NotifyVisitToMercureTest.php b/module/Core/test/EventDispatcher/NotifyVisitToMercureTest.php index fce53344..90891db3 100644 --- a/module/Core/test/EventDispatcher/NotifyVisitToMercureTest.php +++ b/module/Core/test/EventDispatcher/NotifyVisitToMercureTest.php @@ -7,6 +7,7 @@ namespace ShlinkioTest\Shlink\Core\EventDispatcher; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Log\LoggerInterface; use RuntimeException; @@ -21,6 +22,8 @@ use Symfony\Component\Mercure\Update; class NotifyVisitToMercureTest extends TestCase { + use ProphecyTrait; + private NotifyVisitToMercure $listener; private ObjectProphecy $publisher; private ObjectProphecy $updatesGenerator; diff --git a/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php b/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php index 7a138960..8319f448 100644 --- a/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php +++ b/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php @@ -14,6 +14,7 @@ use GuzzleHttp\RequestOptions; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; @@ -28,6 +29,8 @@ use function Functional\contains; class NotifyVisitToWebHooksTest extends TestCase { + use ProphecyTrait; + private ObjectProphecy $httpClient; private ObjectProphecy $em; private ObjectProphecy $logger; diff --git a/module/Core/test/Exception/ValidationExceptionTest.php b/module/Core/test/Exception/ValidationExceptionTest.php index db689a11..44a46c1f 100644 --- a/module/Core/test/Exception/ValidationExceptionTest.php +++ b/module/Core/test/Exception/ValidationExceptionTest.php @@ -8,6 +8,7 @@ use Fig\Http\Message\StatusCodeInterface; use Laminas\InputFilter\InputFilterInterface; use LogicException; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use RuntimeException; use Shlinkio\Shlink\Core\Exception\ValidationException; use Throwable; @@ -17,6 +18,8 @@ use function print_r; class ValidationExceptionTest extends TestCase { + use ProphecyTrait; + /** * @test * @dataProvider provideExceptions diff --git a/module/Core/test/Importer/ImportedLinksProcessorTest.php b/module/Core/test/Importer/ImportedLinksProcessorTest.php index 7225071c..f8a54aee 100644 --- a/module/Core/test/Importer/ImportedLinksProcessorTest.php +++ b/module/Core/test/Importer/ImportedLinksProcessorTest.php @@ -8,6 +8,7 @@ use Cake\Chronos\Chronos; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Domain\Resolver\SimpleDomainResolver; use Shlinkio\Shlink\Core\Entity\ShortUrl; @@ -25,6 +26,8 @@ use function str_contains; class ImportedLinksProcessorTest extends TestCase { + use ProphecyTrait; + private ImportedLinksProcessor $processor; private ObjectProphecy $em; private ObjectProphecy $shortCodeHelper; diff --git a/module/Core/test/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php b/module/Core/test/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php index eb094c25..9f541ebe 100644 --- a/module/Core/test/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php +++ b/module/Core/test/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Core\Paginator\Adapter; use Cake\Chronos\Chronos; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter; @@ -13,6 +14,8 @@ use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; class ShortUrlRepositoryAdapterTest extends TestCase { + use ProphecyTrait; + private ObjectProphecy $repo; public function setUp(): void diff --git a/module/Core/test/Paginator/Adapter/VisitsForTagPaginatorAdapterTest.php b/module/Core/test/Paginator/Adapter/VisitsForTagPaginatorAdapterTest.php index e4418c5b..b3a47749 100644 --- a/module/Core/test/Paginator/Adapter/VisitsForTagPaginatorAdapterTest.php +++ b/module/Core/test/Paginator/Adapter/VisitsForTagPaginatorAdapterTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Paginator\Adapter; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Model\VisitsParams; @@ -13,6 +14,8 @@ use Shlinkio\Shlink\Core\Repository\VisitRepositoryInterface; class VisitsForTagPaginatorAdapterTest extends TestCase { + use ProphecyTrait; + private VisitsForTagPaginatorAdapter $adapter; private ObjectProphecy $repo; diff --git a/module/Core/test/Paginator/Adapter/VisitsPaginatorAdapterTest.php b/module/Core/test/Paginator/Adapter/VisitsPaginatorAdapterTest.php index 744582b7..508a0984 100644 --- a/module/Core/test/Paginator/Adapter/VisitsPaginatorAdapterTest.php +++ b/module/Core/test/Paginator/Adapter/VisitsPaginatorAdapterTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Paginator\Adapter; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; @@ -14,6 +15,8 @@ use Shlinkio\Shlink\Core\Repository\VisitRepositoryInterface; class VisitsPaginatorAdapterTest extends TestCase { + use ProphecyTrait; + private VisitsPaginatorAdapter $adapter; private ObjectProphecy $repo; diff --git a/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php b/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php index 0911cb7b..449220b4 100644 --- a/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php +++ b/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php @@ -8,6 +8,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; 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\Entity\Visit; @@ -24,6 +25,8 @@ use function sprintf; class DeleteShortUrlServiceTest extends TestCase { + use ProphecyTrait; + private ObjectProphecy $em; private ObjectProphecy $urlResolver; private string $shortCode; diff --git a/module/Core/test/Service/ShortUrl/ShortCodeHelperTest.php b/module/Core/test/Service/ShortUrl/ShortCodeHelperTest.php index d77aecd5..047dbc96 100644 --- a/module/Core/test/Service/ShortUrl/ShortCodeHelperTest.php +++ b/module/Core/test/Service/ShortUrl/ShortCodeHelperTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Core\Service\ShortUrl; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\Domain; use Shlinkio\Shlink\Core\Entity\ShortUrl; @@ -14,6 +15,8 @@ use Shlinkio\Shlink\Core\Service\ShortUrl\ShortCodeHelper; class ShortCodeHelperTest extends TestCase { + use ProphecyTrait; + private ShortCodeHelper $helper; private ObjectProphecy $em; private ObjectProphecy $shortUrl; diff --git a/module/Core/test/Service/ShortUrl/ShortUrlResolverTest.php b/module/Core/test/Service/ShortUrl/ShortUrlResolverTest.php index ed6569f2..3566b285 100644 --- a/module/Core/test/Service/ShortUrl/ShortUrlResolverTest.php +++ b/module/Core/test/Service/ShortUrl/ShortUrlResolverTest.php @@ -8,6 +8,7 @@ use Cake\Chronos\Chronos; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Visit; @@ -23,6 +24,8 @@ use function range; class ShortUrlResolverTest extends TestCase { + use ProphecyTrait; + private ShortUrlResolver $urlResolver; private ObjectProphecy $em; diff --git a/module/Core/test/Service/ShortUrlServiceTest.php b/module/Core/test/Service/ShortUrlServiceTest.php index 0b6c82b0..fc2de22b 100644 --- a/module/Core/test/Service/ShortUrlServiceTest.php +++ b/module/Core/test/Service/ShortUrlServiceTest.php @@ -9,6 +9,7 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; 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\Entity\Tag; @@ -24,6 +25,8 @@ use function count; class ShortUrlServiceTest extends TestCase { + use ProphecyTrait; + private ShortUrlService $service; private ObjectProphecy $em; private ObjectProphecy $urlResolver; diff --git a/module/Core/test/Service/Tag/TagServiceTest.php b/module/Core/test/Service/Tag/TagServiceTest.php index 4f17da32..16fd8683 100644 --- a/module/Core/test/Service/Tag/TagServiceTest.php +++ b/module/Core/test/Service/Tag/TagServiceTest.php @@ -7,6 +7,7 @@ namespace ShlinkioTest\Shlink\Core\Service\Tag; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Exception\TagConflictException; @@ -17,6 +18,8 @@ use Shlinkio\Shlink\Core\Tag\TagService; class TagServiceTest extends TestCase { + use ProphecyTrait; + private TagService $service; private ObjectProphecy $em; private ObjectProphecy $repo; diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index 7945c32b..467aeb7b 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -11,6 +11,7 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\ORMException; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Domain\Resolver\SimpleDomainResolver; use Shlinkio\Shlink\Core\Entity\ShortUrl; @@ -24,6 +25,8 @@ use Shlinkio\Shlink\Core\Util\UrlValidatorInterface; class UrlShortenerTest extends TestCase { + use ProphecyTrait; + private UrlShortener $urlShortener; private ObjectProphecy $em; private ObjectProphecy $urlValidator; diff --git a/module/Core/test/Service/VisitsTrackerTest.php b/module/Core/test/Service/VisitsTrackerTest.php index b1609282..1d9096e3 100644 --- a/module/Core/test/Service/VisitsTrackerTest.php +++ b/module/Core/test/Service/VisitsTrackerTest.php @@ -8,6 +8,7 @@ use Doctrine\ORM\EntityManager; use Laminas\Stdlib\ArrayUtils; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\EventDispatcher\EventDispatcherInterface; use Shlinkio\Shlink\Common\Util\DateRange; @@ -30,6 +31,8 @@ use function range; class VisitsTrackerTest extends TestCase { + use ProphecyTrait; + private VisitsTracker $visitsTracker; private ObjectProphecy $em; private ObjectProphecy $eventDispatcher; diff --git a/module/Core/test/Util/DoctrineBatchHelperTest.php b/module/Core/test/Util/DoctrineBatchHelperTest.php index a89a653f..b655c070 100644 --- a/module/Core/test/Util/DoctrineBatchHelperTest.php +++ b/module/Core/test/Util/DoctrineBatchHelperTest.php @@ -6,12 +6,15 @@ namespace ShlinkioTest\Shlink\Core\Util; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use RuntimeException; use Shlinkio\Shlink\Core\Util\DoctrineBatchHelper; class DoctrineBatchHelperTest extends TestCase { + use ProphecyTrait; + private DoctrineBatchHelper $helper; private ObjectProphecy $em; diff --git a/module/Core/test/Util/UrlValidatorTest.php b/module/Core/test/Util/UrlValidatorTest.php index 41541b3c..fab1db1e 100644 --- a/module/Core/test/Util/UrlValidatorTest.php +++ b/module/Core/test/Util/UrlValidatorTest.php @@ -11,6 +11,7 @@ use GuzzleHttp\RequestOptions; use Laminas\Diactoros\Response; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; @@ -18,6 +19,8 @@ use Shlinkio\Shlink\Core\Util\UrlValidator; class UrlValidatorTest extends TestCase { + use ProphecyTrait; + private UrlValidator $urlValidator; private ObjectProphecy $httpClient; private UrlShortenerOptions $options; diff --git a/module/Core/test/Visit/VisitLocatorTest.php b/module/Core/test/Visit/VisitLocatorTest.php index d856262c..e9f1a2d5 100644 --- a/module/Core/test/Visit/VisitLocatorTest.php +++ b/module/Core/test/Visit/VisitLocatorTest.php @@ -9,6 +9,7 @@ use Exception; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; @@ -31,6 +32,8 @@ use function sprintf; class VisitLocatorTest extends TestCase { + use ProphecyTrait; + private VisitLocator $visitService; private ObjectProphecy $em; private ObjectProphecy $repo; diff --git a/module/Core/test/Visit/VisitsStatsHelperTest.php b/module/Core/test/Visit/VisitsStatsHelperTest.php index 01569f04..2381a73a 100644 --- a/module/Core/test/Visit/VisitsStatsHelperTest.php +++ b/module/Core/test/Visit/VisitsStatsHelperTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Core\Visit; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\Repository\VisitRepository; @@ -17,6 +18,8 @@ use function range; class VisitsStatsHelperTest extends TestCase { + use ProphecyTrait; + private VisitsStatsHelper $helper; private ObjectProphecy $em; diff --git a/module/Rest/test/Action/Domain/ListDomainsActionTest.php b/module/Rest/test/Action/Domain/ListDomainsActionTest.php index fe8ffa07..6750d105 100644 --- a/module/Rest/test/Action/Domain/ListDomainsActionTest.php +++ b/module/Rest/test/Action/Domain/ListDomainsActionTest.php @@ -7,6 +7,7 @@ namespace ShlinkioTest\Shlink\Rest\Action\Domain; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequestFactory; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Domain\DomainServiceInterface; use Shlinkio\Shlink\Core\Entity\Domain; @@ -14,6 +15,8 @@ use Shlinkio\Shlink\Rest\Action\Domain\ListDomainsAction; class ListDomainsActionTest extends TestCase { + use ProphecyTrait; + private ListDomainsAction $action; private ObjectProphecy $domainService; diff --git a/module/Rest/test/Action/HealthActionTest.php b/module/Rest/test/Action/HealthActionTest.php index 57c0dd58..bdfc9ccd 100644 --- a/module/Rest/test/Action/HealthActionTest.php +++ b/module/Rest/test/Action/HealthActionTest.php @@ -10,12 +10,15 @@ use Exception; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Options\AppOptions; use Shlinkio\Shlink\Rest\Action\HealthAction; class HealthActionTest extends TestCase { + use ProphecyTrait; + private HealthAction $action; private ObjectProphecy $conn; diff --git a/module/Rest/test/Action/MercureInfoActionTest.php b/module/Rest/test/Action/MercureInfoActionTest.php index aaf9e089..eca4177d 100644 --- a/module/Rest/test/Action/MercureInfoActionTest.php +++ b/module/Rest/test/Action/MercureInfoActionTest.php @@ -9,6 +9,7 @@ use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequestFactory; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use RuntimeException; use Shlinkio\Shlink\Common\Mercure\JwtProviderInterface; @@ -17,6 +18,8 @@ use Shlinkio\Shlink\Rest\Exception\MercureException; class MercureInfoActionTest extends TestCase { + use ProphecyTrait; + private ObjectProphecy $provider; public function setUp(): void diff --git a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php index 78b28385..1adc01a2 100644 --- a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php @@ -9,6 +9,7 @@ use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\ServerRequestFactory; 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; @@ -20,6 +21,8 @@ use function strpos; class CreateShortUrlActionTest extends TestCase { + use ProphecyTrait; + private const DOMAIN_CONFIG = [ 'schema' => 'http', 'hostname' => 'foo.com', diff --git a/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php index 5c448561..6f724c4e 100644 --- a/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php @@ -7,12 +7,15 @@ namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlServiceInterface; use Shlinkio\Shlink\Rest\Action\ShortUrl\DeleteShortUrlAction; class DeleteShortUrlActionTest extends TestCase { + use ProphecyTrait; + private DeleteShortUrlAction $action; private ObjectProphecy $service; diff --git a/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php index 57d63cde..087b4298 100644 --- a/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php @@ -7,6 +7,7 @@ namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; use Laminas\Diactoros\ServerRequest; 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; @@ -15,6 +16,8 @@ use Shlinkio\Shlink\Rest\Action\ShortUrl\EditShortUrlAction; class EditShortUrlActionTest extends TestCase { + use ProphecyTrait; + private EditShortUrlAction $action; private ObjectProphecy $shortUrlService; diff --git a/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php b/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php index 527ffb71..2fa6f456 100644 --- a/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php +++ b/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\ValidationException; @@ -15,6 +16,8 @@ use Shlinkio\Shlink\Rest\Action\ShortUrl\EditShortUrlTagsAction; class EditShortUrlTagsActionTest extends TestCase { + use ProphecyTrait; + private EditShortUrlTagsAction $action; private ObjectProphecy $shortUrlService; diff --git a/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php b/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php index 423cd497..741eceb5 100644 --- a/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php +++ b/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php @@ -10,27 +10,27 @@ use Laminas\Diactoros\ServerRequest; use Laminas\Paginator\Adapter\ArrayAdapter; use Laminas\Paginator\Paginator; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; -use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\Service\ShortUrlService; use Shlinkio\Shlink\Rest\Action\ShortUrl\ListShortUrlsAction; class ListShortUrlsActionTest extends TestCase { + use ProphecyTrait; + private ListShortUrlsAction $action; private ObjectProphecy $service; - private ObjectProphecy $logger; public function setUp(): void { $this->service = $this->prophesize(ShortUrlService::class); - $this->logger = $this->prophesize(LoggerInterface::class); $this->action = new ListShortUrlsAction($this->service->reveal(), [ 'hostname' => 'doma.in', 'schema' => 'https', - ], $this->logger->reveal()); + ]); } /** diff --git a/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php index 5e6316a0..d61f0f64 100644 --- a/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; @@ -16,6 +17,8 @@ use function strpos; class ResolveShortUrlActionTest extends TestCase { + use ProphecyTrait; + private ResolveShortUrlAction $action; private ObjectProphecy $urlResolver; diff --git a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php index cb931570..53bb6d8b 100644 --- a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php @@ -8,6 +8,7 @@ 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; @@ -18,6 +19,8 @@ use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; class SingleStepCreateShortUrlActionTest extends TestCase { + use ProphecyTrait; + private SingleStepCreateShortUrlAction $action; private ObjectProphecy $urlShortener; private ObjectProphecy $apiKeyService; diff --git a/module/Rest/test/Action/Tag/CreateTagsActionTest.php b/module/Rest/test/Action/Tag/CreateTagsActionTest.php index ac74740e..f63c0afc 100644 --- a/module/Rest/test/Action/Tag/CreateTagsActionTest.php +++ b/module/Rest/test/Action/Tag/CreateTagsActionTest.php @@ -7,12 +7,15 @@ namespace ShlinkioTest\Shlink\Rest\Action\Tag; use Doctrine\Common\Collections\ArrayCollection; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Tag\TagServiceInterface; use Shlinkio\Shlink\Rest\Action\Tag\CreateTagsAction; class CreateTagsActionTest extends TestCase { + use ProphecyTrait; + private CreateTagsAction $action; private ObjectProphecy $tagService; diff --git a/module/Rest/test/Action/Tag/DeleteTagsActionTest.php b/module/Rest/test/Action/Tag/DeleteTagsActionTest.php index de145bf0..b167ee2c 100644 --- a/module/Rest/test/Action/Tag/DeleteTagsActionTest.php +++ b/module/Rest/test/Action/Tag/DeleteTagsActionTest.php @@ -6,12 +6,15 @@ namespace ShlinkioTest\Shlink\Rest\Action\Tag; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Tag\TagServiceInterface; use Shlinkio\Shlink\Rest\Action\Tag\DeleteTagsAction; class DeleteTagsActionTest extends TestCase { + use ProphecyTrait; + private DeleteTagsAction $action; private ObjectProphecy $tagService; diff --git a/module/Rest/test/Action/Tag/ListTagsActionTest.php b/module/Rest/test/Action/Tag/ListTagsActionTest.php index f881c75d..2f675536 100644 --- a/module/Rest/test/Action/Tag/ListTagsActionTest.php +++ b/module/Rest/test/Action/Tag/ListTagsActionTest.php @@ -7,6 +7,7 @@ namespace ShlinkioTest\Shlink\Rest\Action\Tag; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequestFactory; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Tag\Model\TagInfo; @@ -15,6 +16,8 @@ use Shlinkio\Shlink\Rest\Action\Tag\ListTagsAction; class ListTagsActionTest extends TestCase { + use ProphecyTrait; + private ListTagsAction $action; private ObjectProphecy $tagService; diff --git a/module/Rest/test/Action/Tag/UpdateTagActionTest.php b/module/Rest/test/Action/Tag/UpdateTagActionTest.php index 2caf20c3..b82c8c2e 100644 --- a/module/Rest/test/Action/Tag/UpdateTagActionTest.php +++ b/module/Rest/test/Action/Tag/UpdateTagActionTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Rest\Action\Tag; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Exception\ValidationException; @@ -14,6 +15,8 @@ use Shlinkio\Shlink\Rest\Action\Tag\UpdateTagAction; class UpdateTagActionTest extends TestCase { + use ProphecyTrait; + private UpdateTagAction $action; private ObjectProphecy $tagService; diff --git a/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php b/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php index 5c28451e..6b91ba56 100644 --- a/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php @@ -7,6 +7,7 @@ namespace ShlinkioTest\Shlink\Rest\Action\Visit; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequestFactory; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Visit\Model\VisitsStats; use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface; @@ -14,6 +15,8 @@ use Shlinkio\Shlink\Rest\Action\Visit\GlobalVisitsAction; class GlobalVisitsActionTest extends TestCase { + use ProphecyTrait; + private GlobalVisitsAction $action; private ObjectProphecy $helper; diff --git a/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php b/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php index c05cc84d..25e71006 100644 --- a/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php @@ -10,6 +10,7 @@ use Laminas\Paginator\Adapter\ArrayAdapter; use Laminas\Paginator\Paginator; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; @@ -19,6 +20,8 @@ use Shlinkio\Shlink\Rest\Action\Visit\ShortUrlVisitsAction; class ShortUrlVisitsActionTest extends TestCase { + use ProphecyTrait; + private ShortUrlVisitsAction $action; private ObjectProphecy $visitsTracker; diff --git a/module/Rest/test/Action/Visit/TagVisitsActionTest.php b/module/Rest/test/Action/Visit/TagVisitsActionTest.php index 7906f530..53dbf8f2 100644 --- a/module/Rest/test/Action/Visit/TagVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/TagVisitsActionTest.php @@ -9,6 +9,7 @@ use Laminas\Paginator\Adapter\ArrayAdapter; use Laminas\Paginator\Paginator; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Model\VisitsParams; use Shlinkio\Shlink\Core\Service\VisitsTracker; @@ -16,6 +17,8 @@ use Shlinkio\Shlink\Rest\Action\Visit\TagVisitsAction; class TagVisitsActionTest extends TestCase { + use ProphecyTrait; + private TagVisitsAction $action; private ObjectProphecy $visitsTracker; diff --git a/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php b/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php index 03ffc8dd..c3bd860a 100644 --- a/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php +++ b/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php @@ -6,12 +6,15 @@ namespace ShlinkioTest\Shlink\Rest\Authentication; use Laminas\ServiceManager\ServiceManager; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Shlinkio\Shlink\Rest\Authentication\AuthenticationPluginManager; use Shlinkio\Shlink\Rest\Authentication\AuthenticationPluginManagerFactory; use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthenticationPluginInterface; class AuthenticationPluginManagerFactoryTest extends TestCase { + use ProphecyTrait; + private AuthenticationPluginManagerFactory $factory; public function setUp(): void diff --git a/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php b/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php index 146957e4..53aca2dc 100644 --- a/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php +++ b/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php @@ -7,6 +7,7 @@ namespace ShlinkioTest\Shlink\Rest\Authentication\Plugin; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Rest\Authentication\Plugin\ApiKeyHeaderPlugin; @@ -15,6 +16,8 @@ use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; class ApiKeyHeaderPluginTest extends TestCase { + use ProphecyTrait; + private ApiKeyHeaderPlugin $plugin; private ObjectProphecy $apiKeyService; diff --git a/module/Rest/test/Authentication/RequestToAuthPluginTest.php b/module/Rest/test/Authentication/RequestToAuthPluginTest.php index 5fac50dc..db3dd2ce 100644 --- a/module/Rest/test/Authentication/RequestToAuthPluginTest.php +++ b/module/Rest/test/Authentication/RequestToAuthPluginTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Rest\Authentication; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Rest\Authentication\AuthenticationPluginManagerInterface; use Shlinkio\Shlink\Rest\Authentication\Plugin\ApiKeyHeaderPlugin; @@ -18,6 +19,8 @@ use function sprintf; class RequestToAuthPluginTest extends TestCase { + use ProphecyTrait; + private RequestToHttpAuthPlugin $requestToPlugin; private ObjectProphecy $pluginManager; diff --git a/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php b/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php index e1b8deca..1a95bbd4 100644 --- a/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php +++ b/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php @@ -11,12 +11,12 @@ use Mezzio\Router\Route; use Mezzio\Router\RouteResult; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Rest\Action\HealthAction; use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthenticationPluginInterface; use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPluginInterface; @@ -26,20 +26,15 @@ use function Laminas\Stratigility\middleware; class AuthenticationMiddlewareTest extends TestCase { + use ProphecyTrait; + private AuthenticationMiddleware $middleware; private ObjectProphecy $requestToPlugin; - private ObjectProphecy $logger; public function setUp(): void { $this->requestToPlugin = $this->prophesize(RequestToHttpAuthPluginInterface::class); - $this->logger = $this->prophesize(LoggerInterface::class); - - $this->middleware = new AuthenticationMiddleware( - $this->requestToPlugin->reveal(), - [HealthAction::class], - $this->logger->reveal(), - ); + $this->middleware = new AuthenticationMiddleware($this->requestToPlugin->reveal(), [HealthAction::class]); } /** diff --git a/module/Rest/test/Middleware/BodyParserMiddlewareTest.php b/module/Rest/test/Middleware/BodyParserMiddlewareTest.php index 8c047bb1..98549e70 100644 --- a/module/Rest/test/Middleware/BodyParserMiddlewareTest.php +++ b/module/Rest/test/Middleware/BodyParserMiddlewareTest.php @@ -9,6 +9,7 @@ use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\Stream; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ProphecyInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -18,6 +19,8 @@ use function array_shift; class BodyParserMiddlewareTest extends TestCase { + use ProphecyTrait; + private BodyParserMiddleware $middleware; public function setUp(): void diff --git a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php index 5e9398ea..446ede9d 100644 --- a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php +++ b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php @@ -10,6 +10,7 @@ use Mezzio\Router\Route; use Mezzio\Router\RouteResult; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Rest\Authentication; @@ -19,6 +20,8 @@ use function Laminas\Stratigility\middleware; class CrossDomainMiddlewareTest extends TestCase { + use ProphecyTrait; + private CrossDomainMiddleware $middleware; private ObjectProphecy $handler; diff --git a/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php index 3950282c..dc4733ff 100644 --- a/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php +++ b/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php @@ -9,6 +9,7 @@ use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -16,6 +17,8 @@ use Shlinkio\Shlink\Rest\Middleware\ShortUrl\CreateShortUrlContentNegotiationMid class CreateShortUrlContentNegotiationMiddlewareTest extends TestCase { + use ProphecyTrait; + private CreateShortUrlContentNegotiationMiddleware $middleware; private ObjectProphecy $requestHandler; diff --git a/module/Rest/test/Middleware/ShortUrl/DefaultShortCodesLengthMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/DefaultShortCodesLengthMiddlewareTest.php index c14a2f5c..918b0a5d 100644 --- a/module/Rest/test/Middleware/ShortUrl/DefaultShortCodesLengthMiddlewareTest.php +++ b/module/Rest/test/Middleware/ShortUrl/DefaultShortCodesLengthMiddlewareTest.php @@ -9,6 +9,7 @@ use Laminas\Diactoros\ServerRequestFactory; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -17,6 +18,8 @@ use Shlinkio\Shlink\Rest\Middleware\ShortUrl\DefaultShortCodesLengthMiddleware; class DefaultShortCodesLengthMiddlewareTest extends TestCase { + use ProphecyTrait; + private DefaultShortCodesLengthMiddleware $middleware; private ObjectProphecy $handler; diff --git a/module/Rest/test/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddlewareTest.php index 9b443602..24f3aecd 100644 --- a/module/Rest/test/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddlewareTest.php +++ b/module/Rest/test/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddlewareTest.php @@ -9,6 +9,7 @@ use Laminas\Diactoros\ServerRequestFactory; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -16,6 +17,8 @@ use Shlinkio\Shlink\Rest\Middleware\ShortUrl\DropDefaultDomainFromRequestMiddlew class DropDefaultDomainFromRequestMiddlewareTest extends TestCase { + use ProphecyTrait; + private DropDefaultDomainFromRequestMiddleware $middleware; private ObjectProphecy $next; diff --git a/module/Rest/test/Service/ApiKeyServiceTest.php b/module/Rest/test/Service/ApiKeyServiceTest.php index d4d40e4b..656541f0 100644 --- a/module/Rest/test/Service/ApiKeyServiceTest.php +++ b/module/Rest/test/Service/ApiKeyServiceTest.php @@ -9,6 +9,7 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Rest\Entity\ApiKey; @@ -16,6 +17,8 @@ use Shlinkio\Shlink\Rest\Service\ApiKeyService; class ApiKeyServiceTest extends TestCase { + use ProphecyTrait; + private ApiKeyService $service; private ObjectProphecy $em; From cb340b58670dbd29d97002d1e213e1dcfb2e0ea4 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 Nov 2020 12:07:45 +0100 Subject: [PATCH 62/73] Updated phpunit configs to use new schema introduced in v9.3 --- phpunit-api.xml | 10 +++++----- phpunit-db.xml | 10 +++++----- phpunit.xml.dist | 21 ++++++++++----------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/phpunit-api.xml b/phpunit-api.xml index 6e481fe5..b38a3c0f 100644 --- a/phpunit-api.xml +++ b/phpunit-api.xml @@ -1,7 +1,7 @@ @@ -11,9 +11,9 @@ - - + + ./module/*/src - - + + diff --git a/phpunit-db.xml b/phpunit-db.xml index 86cdbbc6..a995448f 100644 --- a/phpunit-db.xml +++ b/phpunit-db.xml @@ -1,7 +1,7 @@ @@ -11,11 +11,11 @@ - - + + ./module/*/src/Repository ./module/*/src/**/Repository ./module/*/src/**/**/Repository - - + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c45743cf..68f5263a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ @@ -17,15 +17,14 @@ - - + + ./module/*/src - - - ./module/Core/src/Repository - ./module/Core/src/**/Repository - ./module/Core/src/**/**/Repository - - - + + + ./module/Core/src/Repository + ./module/Core/src/**/Repository + ./module/Core/src/**/**/Repository + + From f90ea4bd9807753c8a9c050a258cf64a74856256 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 6 Nov 2020 18:58:07 +0100 Subject: [PATCH 63/73] Updated dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 0bbe7651..b2faf734 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "ext-json": "*", "ext-pdo": "*", "akrabat/ip-address-middleware": "^1.0", - "cakephp/chronos": "^1.2", + "cakephp/chronos": "^2.0", "cocur/slugify": "^4.0", "doctrine/cache": "^1.9", "doctrine/dbal": "^2.10", @@ -49,7 +49,7 @@ "predis/predis": "^1.1", "pugx/shortid-php": "^0.5", "ramsey/uuid": "^3.9", - "shlinkio/shlink-common": "^3.2.0", + "shlinkio/shlink-common": "^3.3.0", "shlinkio/shlink-config": "^1.0", "shlinkio/shlink-event-dispatcher": "^1.4", "shlinkio/shlink-importer": "^2.0.1", From 00255b04eb2b1601e14867dc6ecf028d87196656 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 6 Nov 2020 19:43:05 +0100 Subject: [PATCH 64/73] Added migration to create new author_api_key_id in short_urls --- data/migrations/Version20201102113208.php | 84 +++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 data/migrations/Version20201102113208.php diff --git a/data/migrations/Version20201102113208.php b/data/migrations/Version20201102113208.php new file mode 100644 index 00000000..14ce6504 --- /dev/null +++ b/data/migrations/Version20201102113208.php @@ -0,0 +1,84 @@ +getTable('short_urls'); + $this->skipIf($shortUrls->hasColumn(self::API_KEY_COLUMN)); + + $shortUrls->addColumn(self::API_KEY_COLUMN, Types::BIGINT, [ + 'unsigned' => true, + 'notnull' => false, + ]); + + $shortUrls->addForeignKeyConstraint('api_keys', [self::API_KEY_COLUMN], ['id'], [ + 'onDelete' => 'SET NULL', + 'onUpdate' => 'RESTRICT', + ], 'FK_' . self::API_KEY_COLUMN); + } + + public function postUp(Schema $schema): void + { + // If there's only one API key and it's active, link all existing URLs with it + $qb = $this->connection->createQueryBuilder(); + $qb->select('id') + ->from('api_keys') + ->where($qb->expr()->eq('enabled', ':enabled')) + ->andWhere($qb->expr()->or( + $qb->expr()->isNull('expiration_date'), + $qb->expr()->gt('expiration_date', ':expiration'), + )) + ->setParameters([ + 'enabled' => true, + 'expiration' => Chronos::now()->toDateTimeString(), + ]); + + $id = $this->resolveOneApiKeyId($qb->execute()); + if ($id === null) { + return; + } + + $qb = $this->connection->createQueryBuilder(); + $qb->update('short_urls') + ->set(self::API_KEY_COLUMN, ':apiKeyId') + ->setParameter('apiKeyId', $id) + ->execute(); + } + + private function resolveOneApiKeyId(Result $result): ?string + { + $results = []; + while ($row = $result->fetchAssociative()) { + // As soon as we have to iterate more than once, then we cannot resolve a single API key + if (! empty($results)) { + return null; + } + + $results[] = $row['id'] ?? null; + } + + return $results[0] ?? null; + } + + public function down(Schema $schema): void + { + $shortUrls = $schema->getTable('short_urls'); + $this->skipIf(! $shortUrls->hasColumn(self::API_KEY_COLUMN)); + + $shortUrls->removeForeignKey('FK_' . self::API_KEY_COLUMN); + $shortUrls->dropColumn(self::API_KEY_COLUMN); + } +} From 97f89bcede81fac7915c2f406c24f40b88f301b5 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 6 Nov 2020 20:05:57 +0100 Subject: [PATCH 65/73] Simplified transactional URL shortening --- config/autoload/redirects.global.php | 2 +- .../ShortUrl/GenerateShortUrlCommand.php | 2 +- .../ShortUrl/GenerateShortUrlCommandTest.php | 10 +++--- .../Shlinkio.Shlink.Core.Entity.ShortUrl.php | 5 +++ module/Core/src/Entity/ShortUrl.php | 2 ++ module/Core/src/Service/UrlShortener.php | 23 ++++-------- .../src/Service/UrlShortenerInterface.php | 2 +- module/Core/test/Service/UrlShortenerTest.php | 36 +++++-------------- .../ShortUrl/AbstractCreateShortUrlAction.php | 2 +- .../ShortUrl/CreateShortUrlActionTest.php | 4 +-- .../SingleStepCreateShortUrlActionTest.php | 2 +- 11 files changed, 35 insertions(+), 55 deletions(-) diff --git a/config/autoload/redirects.global.php b/config/autoload/redirects.global.php index 90137aa8..173c435c 100644 --- a/config/autoload/redirects.global.php +++ b/config/autoload/redirects.global.php @@ -5,7 +5,7 @@ declare(strict_types=1); return [ 'not_found_redirects' => [ - 'invalid_short_url' => null, // Formerly url_shortener.not_found_short_url.redirect_to + 'invalid_short_url' => null, 'regular_404' => null, 'base_url' => null, ], diff --git a/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php index 36293377..12bbb3fb 100644 --- a/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php @@ -142,7 +142,7 @@ class GenerateShortUrlCommand extends Command $doValidateUrl = $this->doValidateUrl($input); try { - $shortUrl = $this->urlShortener->urlToShortCode($longUrl, $tags, ShortUrlMeta::fromRawData([ + $shortUrl = $this->urlShortener->shorten($longUrl, $tags, ShortUrlMeta::fromRawData([ ShortUrlMetaInputFilter::VALID_SINCE => $input->getOption('validSince'), ShortUrlMetaInputFilter::VALID_UNTIL => $input->getOption('validUntil'), ShortUrlMetaInputFilter::CUSTOM_SLUG => $customSlug, diff --git a/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php index b11b455b..82f38713 100644 --- a/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php @@ -44,7 +44,7 @@ class GenerateShortUrlCommandTest extends TestCase public function properShortCodeIsCreatedIfLongUrlIsCorrect(): void { $shortUrl = new ShortUrl(''); - $urlToShortCode = $this->urlShortener->urlToShortCode(Argument::cetera())->willReturn($shortUrl); + $urlToShortCode = $this->urlShortener->shorten(Argument::cetera())->willReturn($shortUrl); $this->commandTester->execute([ 'longUrl' => 'http://domain.com/foo/bar', @@ -61,7 +61,7 @@ class GenerateShortUrlCommandTest extends TestCase public function exceptionWhileParsingLongUrlOutputsError(): void { $url = 'http://domain.com/invalid'; - $this->urlShortener->urlToShortCode(Argument::cetera())->willThrow(InvalidUrlException::fromUrl($url)) + $this->urlShortener->shorten(Argument::cetera())->willThrow(InvalidUrlException::fromUrl($url)) ->shouldBeCalledOnce(); $this->commandTester->execute(['longUrl' => $url]); @@ -74,7 +74,7 @@ class GenerateShortUrlCommandTest extends TestCase /** @test */ public function providingNonUniqueSlugOutputsError(): void { - $urlToShortCode = $this->urlShortener->urlToShortCode(Argument::cetera())->willThrow( + $urlToShortCode = $this->urlShortener->shorten(Argument::cetera())->willThrow( NonUniqueSlugException::fromSlug('my-slug'), ); @@ -90,7 +90,7 @@ class GenerateShortUrlCommandTest extends TestCase public function properlyProcessesProvidedTags(): void { $shortUrl = new ShortUrl(''); - $urlToShortCode = $this->urlShortener->urlToShortCode( + $urlToShortCode = $this->urlShortener->shorten( Argument::type('string'), Argument::that(function (array $tags) { Assert::assertEquals(['foo', 'bar', 'baz', 'boo', 'zar'], $tags); @@ -117,7 +117,7 @@ class GenerateShortUrlCommandTest extends TestCase public function urlValidationHasExpectedValueBasedOnProvidedTags(array $options, ?bool $expectedValidateUrl): void { $shortUrl = new ShortUrl(''); - $urlToShortCode = $this->urlShortener->urlToShortCode( + $urlToShortCode = $this->urlShortener->shorten( Argument::type('string'), Argument::type('array'), Argument::that(function (ShortUrlMeta $meta) use ($expectedValidateUrl) { diff --git a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php index 4f9b3747..da4506af 100644 --- a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php +++ b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php @@ -8,6 +8,7 @@ use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder; use Doctrine\ORM\Mapping\ClassMetadata; use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType; +use Shlinkio\Shlink\Rest\Entity\ApiKey; return static function (ClassMetadata $metadata, array $emConfig): void { $builder = new ClassMetadataBuilder($metadata); @@ -78,5 +79,9 @@ return static function (ClassMetadata $metadata, array $emConfig): void { ->cascadePersist() ->build(); + $builder->createManyToOne('authorApiKey', ApiKey::class) + ->addJoinColumn('author_api_key_id', 'id', true, false, 'SET NULL') + ->build(); + $builder->addUniqueConstraint(['short_code', 'domain_id'], 'unique_short_code_plus_domain'); }; diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 2d5cb6de..3d7ea0dd 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -16,6 +16,7 @@ use Shlinkio\Shlink\Core\Model\ShortUrlEdit; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; +use Shlinkio\Shlink\Rest\Entity\ApiKey; use function count; use function Shlinkio\Shlink\Core\generateRandomShortCode; @@ -37,6 +38,7 @@ class ShortUrl extends AbstractEntity private int $shortCodeLength; private ?string $importSource = null; private ?string $importOriginalShortCode = null; + private ?ApiKey $authorApiKey = null; public function __construct( string $longUrl, diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index d399b534..fb745114 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -43,7 +43,7 @@ class UrlShortener implements UrlShortenerInterface * @throws InvalidUrlException * @throws Throwable */ - public function urlToShortCode(string $url, array $tags, ShortUrlMeta $meta): ShortUrl + public function shorten(string $url, array $tags, ShortUrlMeta $meta): ShortUrl { // First, check if a short URL exists for all provided params $existingShortUrl = $this->findExistingShortUrlIfExists($url, $tags, $meta); @@ -52,25 +52,16 @@ class UrlShortener implements UrlShortenerInterface } $this->urlValidator->validateUrl($url, $meta->doValidateUrl()); - $this->em->beginTransaction(); - $shortUrl = new ShortUrl($url, $meta, $this->domainResolver); - $shortUrl->setTags($this->tagNamesToEntities($this->em, $tags)); - try { + return $this->em->transactional(function () use ($url, $tags, $meta) { + $shortUrl = new ShortUrl($url, $meta, $this->domainResolver); + $shortUrl->setTags($this->tagNamesToEntities($this->em, $tags)); + $this->verifyShortCodeUniqueness($meta, $shortUrl); $this->em->persist($shortUrl); - $this->em->flush(); - $this->em->commit(); - } catch (Throwable $e) { - if ($this->em->getConnection()->isTransactionActive()) { - $this->em->rollback(); - $this->em->close(); - } - throw $e; - } - - return $shortUrl; + return $shortUrl; + }); } private function findExistingShortUrlIfExists(string $url, array $tags, ShortUrlMeta $meta): ?ShortUrl diff --git a/module/Core/src/Service/UrlShortenerInterface.php b/module/Core/src/Service/UrlShortenerInterface.php index e26530ca..45b1eb8a 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 urlToShortCode(string $url, array $tags, ShortUrlMeta $meta): ShortUrl; + public function shorten(string $url, array $tags, ShortUrlMeta $meta): ShortUrl; } diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index 467aeb7b..436b42e3 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -8,7 +8,6 @@ use Cake\Chronos\Chronos; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\Connection; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\ORMException; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -42,16 +41,18 @@ class UrlShortenerTest extends TestCase $this->em = $this->prophesize(EntityManagerInterface::class); $conn = $this->prophesize(Connection::class); - $conn->isTransactionActive()->willReturn(false); $this->em->getConnection()->willReturn($conn->reveal()); - $this->em->flush()->willReturn(null); - $this->em->commit()->willReturn(null); - $this->em->beginTransaction()->willReturn(null); $this->em->persist(Argument::any())->will(function ($arguments): void { /** @var ShortUrl $shortUrl */ [$shortUrl] = $arguments; $shortUrl->setId('10'); }); + $this->em->transactional(Argument::type('callable'))->will(function (array $args) { + /** @var callable $callback */ + [$callback] = $args; + + return $callback(); + }); $repo = $this->prophesize(ShortUrlRepository::class); $repo->shortCodeIsInUse(Argument::cetera())->willReturn(false); $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); @@ -70,7 +71,7 @@ class UrlShortenerTest extends TestCase /** @test */ public function urlIsProperlyShortened(): void { - $shortUrl = $this->urlShortener->urlToShortCode( + $shortUrl = $this->urlShortener->shorten( 'http://foobar.com/12345/hello?foo=bar', [], ShortUrlMeta::createEmpty(), @@ -87,32 +88,13 @@ class UrlShortenerTest extends TestCase $ensureUniqueness->shouldBeCalledOnce(); $this->expectException(NonUniqueSlugException::class); - $this->urlShortener->urlToShortCode( + $this->urlShortener->shorten( 'http://foobar.com/12345/hello?foo=bar', [], ShortUrlMeta::fromRawData(['customSlug' => 'custom-slug']), ); } - /** @test */ - public function transactionIsRolledBackAndExceptionRethrownWhenExceptionIsThrown(): void - { - $conn = $this->prophesize(Connection::class); - $conn->isTransactionActive()->willReturn(true); - $this->em->getConnection()->willReturn($conn->reveal()); - $this->em->rollback()->shouldBeCalledOnce(); - $this->em->close()->shouldBeCalledOnce(); - - $this->em->flush()->willThrow(new ORMException()); - - $this->expectException(ORMException::class); - $this->urlShortener->urlToShortCode( - 'http://foobar.com/12345/hello?foo=bar', - [], - ShortUrlMeta::createEmpty(), - ); - } - /** * @test * @dataProvider provideExistingShortUrls @@ -127,7 +109,7 @@ class UrlShortenerTest extends TestCase $findExisting = $repo->findOneMatching(Argument::cetera())->willReturn($expected); $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); - $result = $this->urlShortener->urlToShortCode($url, $tags, $meta); + $result = $this->urlShortener->shorten($url, $tags, $meta); $findExisting->shouldHaveBeenCalledOnce(); $getRepo->shouldHaveBeenCalledOnce(); diff --git a/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php b/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php index feed626d..8d4ea777 100644 --- a/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php @@ -31,7 +31,7 @@ abstract class AbstractCreateShortUrlAction extends AbstractRestAction $tags = $shortUrlData->getTags(); $shortUrlMeta = $shortUrlData->getMeta(); - $shortUrl = $this->urlShortener->urlToShortCode($longUrl, $tags, $shortUrlMeta); + $shortUrl = $this->urlShortener->shorten($longUrl, $tags, $shortUrlMeta); $transformer = new ShortUrlDataTransformer($this->domainConfig); return new JsonResponse($transformer->transform($shortUrl)); diff --git a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php index 1adc01a2..fd26da99 100644 --- a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php @@ -51,7 +51,7 @@ class CreateShortUrlActionTest extends TestCase public function properShortcodeConversionReturnsData(array $body, ShortUrlMeta $expectedMeta): void { $shortUrl = new ShortUrl(''); - $shorten = $this->urlShortener->urlToShortCode( + $shorten = $this->urlShortener->shorten( Argument::type('string'), Argument::type('array'), $expectedMeta, @@ -88,7 +88,7 @@ class CreateShortUrlActionTest extends TestCase public function anInvalidDomainReturnsError(string $domain): void { $shortUrl = new ShortUrl(''); - $urlToShortCode = $this->urlShortener->urlToShortCode(Argument::cetera())->willReturn($shortUrl); + $urlToShortCode = $this->urlShortener->shorten(Argument::cetera())->willReturn($shortUrl); $request = (new ServerRequest())->withParsedBody([ 'longUrl' => 'http://www.domain.com/foo/bar', diff --git a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php index 53bb6d8b..d7b432c2 100644 --- a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php @@ -72,7 +72,7 @@ class SingleStepCreateShortUrlActionTest extends TestCase 'longUrl' => 'http://foobar.com', ]); $findApiKey = $this->apiKeyService->check('abc123')->willReturn(true); - $generateShortCode = $this->urlShortener->urlToShortCode( + $generateShortCode = $this->urlShortener->shorten( Argument::that(function (string $argument): string { Assert::assertEquals('http://foobar.com', $argument); return $argument; From 2732b05834c06f14c5d5d1da26b47381dda70067 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 7 Nov 2020 09:31:46 +0100 Subject: [PATCH 66/73] Added mechanisms to be able to provide the API key when creating a short URL --- module/Core/config/dependencies.config.php | 9 ++-- module/Core/src/Entity/ShortUrl.php | 14 ++++--- .../src/Importer/ImportedLinksProcessor.php | 10 ++--- module/Core/src/Model/ShortUrlMeta.php | 7 ++++ module/Core/src/Service/UrlShortener.php | 10 ++--- .../PersistenceShortUrlRelationResolver.php | 41 +++++++++++++++++++ .../ShortUrlRelationResolverInterface.php | 15 +++++++ .../SimpleShortUrlRelationResolver.php | 21 ++++++++++ .../Validation/ShortUrlMetaInputFilter.php | 3 ++ .../Importer/ImportedLinksProcessorTest.php | 4 +- module/Core/test/Service/UrlShortenerTest.php | 4 +- 11 files changed, 113 insertions(+), 25 deletions(-) create mode 100644 module/Core/src/ShortUrl/Resolver/PersistenceShortUrlRelationResolver.php create mode 100644 module/Core/src/ShortUrl/Resolver/ShortUrlRelationResolverInterface.php create mode 100644 module/Core/src/ShortUrl/Resolver/SimpleShortUrlRelationResolver.php diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 4d68101b..5c7c0b54 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Core; use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory; use Mezzio\Template\TemplateRendererInterface; use Psr\EventDispatcher\EventDispatcherInterface; -use Shlinkio\Shlink\Core\Domain\Resolver; use Shlinkio\Shlink\Core\ErrorHandler; use Shlinkio\Shlink\Core\Options\NotFoundRedirectOptions; use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface; @@ -42,7 +41,7 @@ return [ Action\PixelAction::class => ConfigAbstractFactory::class, Action\QrCodeAction::class => ConfigAbstractFactory::class, - Resolver\PersistenceDomainResolver::class => ConfigAbstractFactory::class, + ShortUrl\Resolver\PersistenceShortUrlRelationResolver::class => ConfigAbstractFactory::class, Mercure\MercureUpdatesGenerator::class => ConfigAbstractFactory::class, @@ -66,7 +65,7 @@ return [ Service\UrlShortener::class => [ Util\UrlValidator::class, 'em', - Resolver\PersistenceDomainResolver::class, + ShortUrl\Resolver\PersistenceShortUrlRelationResolver::class, Service\ShortUrl\ShortCodeHelper::class, ], Service\VisitsTracker::class => [ @@ -109,13 +108,13 @@ return [ 'Logger_Shlink', ], - Resolver\PersistenceDomainResolver::class => ['em'], + ShortUrl\Resolver\PersistenceShortUrlRelationResolver::class => ['em'], Mercure\MercureUpdatesGenerator::class => ['config.url_shortener.domain'], Importer\ImportedLinksProcessor::class => [ 'em', - Resolver\PersistenceDomainResolver::class, + ShortUrl\Resolver\PersistenceShortUrlRelationResolver::class, Service\ShortUrl\ShortCodeHelper::class, Util\DoctrineBatchHelper::class, ], diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 3d7ea0dd..6f7493aa 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -9,11 +9,11 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Laminas\Diactoros\Uri; use Shlinkio\Shlink\Common\Entity\AbstractEntity; -use Shlinkio\Shlink\Core\Domain\Resolver\DomainResolverInterface; -use Shlinkio\Shlink\Core\Domain\Resolver\SimpleDomainResolver; use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException; use Shlinkio\Shlink\Core\Model\ShortUrlEdit; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; +use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface; +use Shlinkio\Shlink\Core\ShortUrl\Resolver\SimpleShortUrlRelationResolver; use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Rest\Entity\ApiKey; @@ -43,9 +43,10 @@ class ShortUrl extends AbstractEntity public function __construct( string $longUrl, ?ShortUrlMeta $meta = null, - ?DomainResolverInterface $domainResolver = null + ?ShortUrlRelationResolverInterface $relationResolver = null ) { $meta = $meta ?? ShortUrlMeta::createEmpty(); + $relationResolver = $relationResolver ?? new SimpleShortUrlRelationResolver(); $this->longUrl = $longUrl; $this->dateCreated = Chronos::now(); @@ -57,13 +58,14 @@ class ShortUrl extends AbstractEntity $this->customSlugWasProvided = $meta->hasCustomSlug(); $this->shortCodeLength = $meta->getShortCodeLength(); $this->shortCode = $meta->getCustomSlug() ?? generateRandomShortCode($this->shortCodeLength); - $this->domain = ($domainResolver ?? new SimpleDomainResolver())->resolveDomain($meta->getDomain()); + $this->domain = $relationResolver->resolveDomain($meta->getDomain()); + $this->authorApiKey = $relationResolver->resolveApiKey($meta->getApiKey()); } public static function fromImport( ImportedShlinkUrl $url, bool $importShortCode, - ?DomainResolverInterface $domainResolver = null + ?ShortUrlRelationResolverInterface $relationResolver = null ): self { $meta = [ ShortUrlMetaInputFilter::DOMAIN => $url->domain(), @@ -73,7 +75,7 @@ class ShortUrl extends AbstractEntity $meta[ShortUrlMetaInputFilter::CUSTOM_SLUG] = $url->shortCode(); } - $instance = new self($url->longUrl(), ShortUrlMeta::fromRawData($meta), $domainResolver); + $instance = new self($url->longUrl(), ShortUrlMeta::fromRawData($meta), $relationResolver); $instance->importSource = $url->source(); $instance->importOriginalShortCode = $url->shortCode(); $instance->dateCreated = Chronos::instance($url->createdAt()); diff --git a/module/Core/src/Importer/ImportedLinksProcessor.php b/module/Core/src/Importer/ImportedLinksProcessor.php index e072fbb8..8bac7395 100644 --- a/module/Core/src/Importer/ImportedLinksProcessor.php +++ b/module/Core/src/Importer/ImportedLinksProcessor.php @@ -5,10 +5,10 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Importer; use Doctrine\ORM\EntityManagerInterface; -use Shlinkio\Shlink\Core\Domain\Resolver\DomainResolverInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Service\ShortUrl\ShortCodeHelperInterface; +use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface; use Shlinkio\Shlink\Core\Util\DoctrineBatchHelperInterface; use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface; @@ -22,18 +22,18 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface use TagManagerTrait; private EntityManagerInterface $em; - private DomainResolverInterface $domainResolver; + private ShortUrlRelationResolverInterface $relationResolver; private ShortCodeHelperInterface $shortCodeHelper; private DoctrineBatchHelperInterface $batchHelper; public function __construct( EntityManagerInterface $em, - DomainResolverInterface $domainResolver, + ShortUrlRelationResolverInterface $relationResolver, ShortCodeHelperInterface $shortCodeHelper, DoctrineBatchHelperInterface $batchHelper ) { $this->em = $em; - $this->domainResolver = $domainResolver; + $this->relationResolver = $relationResolver; $this->shortCodeHelper = $shortCodeHelper; $this->batchHelper = $batchHelper; } @@ -58,7 +58,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface continue; } - $shortUrl = ShortUrl::fromImport($url, $importShortCodes, $this->domainResolver); + $shortUrl = ShortUrl::fromImport($url, $importShortCodes, $this->relationResolver); $shortUrl->setTags($this->tagNamesToEntities($this->em, $url->tags())); if (! $this->handleShortCodeUniqueness($url, $shortUrl, $io, $importShortCodes)) { diff --git a/module/Core/src/Model/ShortUrlMeta.php b/module/Core/src/Model/ShortUrlMeta.php index 23121d71..fa82919e 100644 --- a/module/Core/src/Model/ShortUrlMeta.php +++ b/module/Core/src/Model/ShortUrlMeta.php @@ -24,6 +24,7 @@ final class ShortUrlMeta private ?string $domain = null; private int $shortCodeLength = 5; private ?bool $validateUrl = null; + private ?string $apiKey = null; // Enforce named constructors private function __construct() @@ -66,6 +67,7 @@ final class ShortUrlMeta $inputFilter, ShortUrlMetaInputFilter::SHORT_CODE_LENGTH, ) ?? DEFAULT_SHORT_CODES_LENGTH; + $this->apiKey = $inputFilter->getValue(ShortUrlMetaInputFilter::API_KEY); } public function getValidSince(): ?Chronos @@ -132,4 +134,9 @@ final class ShortUrlMeta { return $this->validateUrl; } + + public function getApiKey(): ?string + { + return $this->apiKey; + } } diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index fb745114..3ed4d2df 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service; use Doctrine\ORM\EntityManagerInterface; -use Shlinkio\Shlink\Core\Domain\Resolver\DomainResolverInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Service\ShortUrl\ShortCodeHelperInterface; +use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface; use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Shlinkio\Shlink\Core\Util\UrlValidatorInterface; use Throwable; @@ -22,18 +22,18 @@ class UrlShortener implements UrlShortenerInterface private EntityManagerInterface $em; private UrlValidatorInterface $urlValidator; - private DomainResolverInterface $domainResolver; + private ShortUrlRelationResolverInterface $relationResolver; private ShortCodeHelperInterface $shortCodeHelper; public function __construct( UrlValidatorInterface $urlValidator, EntityManagerInterface $em, - DomainResolverInterface $domainResolver, + ShortUrlRelationResolverInterface $relationResolver, ShortCodeHelperInterface $shortCodeHelper ) { $this->urlValidator = $urlValidator; $this->em = $em; - $this->domainResolver = $domainResolver; + $this->relationResolver = $relationResolver; $this->shortCodeHelper = $shortCodeHelper; } @@ -54,7 +54,7 @@ class UrlShortener implements UrlShortenerInterface $this->urlValidator->validateUrl($url, $meta->doValidateUrl()); return $this->em->transactional(function () use ($url, $tags, $meta) { - $shortUrl = new ShortUrl($url, $meta, $this->domainResolver); + $shortUrl = new ShortUrl($url, $meta, $this->relationResolver); $shortUrl->setTags($this->tagNamesToEntities($this->em, $tags)); $this->verifyShortCodeUniqueness($meta, $shortUrl); diff --git a/module/Core/src/ShortUrl/Resolver/PersistenceShortUrlRelationResolver.php b/module/Core/src/ShortUrl/Resolver/PersistenceShortUrlRelationResolver.php new file mode 100644 index 00000000..d898fb37 --- /dev/null +++ b/module/Core/src/ShortUrl/Resolver/PersistenceShortUrlRelationResolver.php @@ -0,0 +1,41 @@ +em = $em; + } + + public function resolveDomain(?string $domain): ?Domain + { + if ($domain === null) { + return null; + } + + /** @var Domain|null $existingDomain */ + $existingDomain = $this->em->getRepository(Domain::class)->findOneBy(['authority' => $domain]); + return $existingDomain ?? new Domain($domain); + } + + public function resolveApiKey(?string $key): ?ApiKey + { + if ($key === null) { + return null; + } + + /** @var ApiKey|null $existingApiKey */ + $existingApiKey = $this->em->getRepository(ApiKey::class)->findOneBy(['key' => $key]); + return $existingApiKey; + } +} diff --git a/module/Core/src/ShortUrl/Resolver/ShortUrlRelationResolverInterface.php b/module/Core/src/ShortUrl/Resolver/ShortUrlRelationResolverInterface.php new file mode 100644 index 00000000..0a708cf6 --- /dev/null +++ b/module/Core/src/ShortUrl/Resolver/ShortUrlRelationResolverInterface.php @@ -0,0 +1,15 @@ +createInput(self::DOMAIN, false); $domain->getValidatorChain()->attach(new Validation\HostAndPortValidator()); $this->add($domain); + + $this->add($this->createInput(self::API_KEY, false)); } private function createPositiveNumberInput(string $name, int $min = 1): Input diff --git a/module/Core/test/Importer/ImportedLinksProcessorTest.php b/module/Core/test/Importer/ImportedLinksProcessorTest.php index f8a54aee..174e9afc 100644 --- a/module/Core/test/Importer/ImportedLinksProcessorTest.php +++ b/module/Core/test/Importer/ImportedLinksProcessorTest.php @@ -10,11 +10,11 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; -use Shlinkio\Shlink\Core\Domain\Resolver\SimpleDomainResolver; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Importer\ImportedLinksProcessor; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Service\ShortUrl\ShortCodeHelperInterface; +use Shlinkio\Shlink\Core\ShortUrl\Resolver\SimpleShortUrlRelationResolver; use Shlinkio\Shlink\Core\Util\DoctrineBatchHelperInterface; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Symfony\Component\Console\Style\StyleInterface; @@ -46,7 +46,7 @@ class ImportedLinksProcessorTest extends TestCase $this->processor = new ImportedLinksProcessor( $this->em->reveal(), - new SimpleDomainResolver(), + new SimpleShortUrlRelationResolver(), $this->shortCodeHelper->reveal(), $batchHelper->reveal(), ); diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index 436b42e3..316b7557 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -12,7 +12,6 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; -use Shlinkio\Shlink\Core\Domain\Resolver\SimpleDomainResolver; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; @@ -20,6 +19,7 @@ use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Service\ShortUrl\ShortCodeHelperInterface; use Shlinkio\Shlink\Core\Service\UrlShortener; +use Shlinkio\Shlink\Core\ShortUrl\Resolver\SimpleShortUrlRelationResolver; use Shlinkio\Shlink\Core\Util\UrlValidatorInterface; class UrlShortenerTest extends TestCase @@ -63,7 +63,7 @@ class UrlShortenerTest extends TestCase $this->urlShortener = new UrlShortener( $this->urlValidator->reveal(), $this->em->reveal(), - new SimpleDomainResolver(), + new SimpleShortUrlRelationResolver(), $this->shortCodeHelper->reveal(), ); } From 7c9f572eb1d9eb8c948ac82743121dec9f9cdc70 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 7 Nov 2020 09:49:09 +0100 Subject: [PATCH 67/73] Deleted old domain resolvers and added tests for new short url relation resolvers --- .../Resolver/DomainResolverInterface.php | 12 --- .../Resolver/PersistenceDomainResolver.php | 29 ----- .../Domain/Resolver/SimpleDomainResolver.php | 15 --- .../PersistenceDomainResolverTest.php | 65 ------------ .../Resolver/SimpleDomainResolverTest.php | 41 ------- ...ersistenceShortUrlRelationResolverTest.php | 100 ++++++++++++++++++ .../SimpleShortUrlRelationResolverTest.php | 56 ++++++++++ 7 files changed, 156 insertions(+), 162 deletions(-) delete mode 100644 module/Core/src/Domain/Resolver/DomainResolverInterface.php delete mode 100644 module/Core/src/Domain/Resolver/PersistenceDomainResolver.php delete mode 100644 module/Core/src/Domain/Resolver/SimpleDomainResolver.php delete mode 100644 module/Core/test/Domain/Resolver/PersistenceDomainResolverTest.php delete mode 100644 module/Core/test/Domain/Resolver/SimpleDomainResolverTest.php create mode 100644 module/Core/test/ShortUrl/Resolver/PersistenceShortUrlRelationResolverTest.php create mode 100644 module/Core/test/ShortUrl/Resolver/SimpleShortUrlRelationResolverTest.php diff --git a/module/Core/src/Domain/Resolver/DomainResolverInterface.php b/module/Core/src/Domain/Resolver/DomainResolverInterface.php deleted file mode 100644 index c6ff55ff..00000000 --- a/module/Core/src/Domain/Resolver/DomainResolverInterface.php +++ /dev/null @@ -1,12 +0,0 @@ -em = $em; - } - - public function resolveDomain(?string $domain): ?Domain - { - if ($domain === null) { - return null; - } - - /** @var Domain|null $existingDomain */ - $existingDomain = $this->em->getRepository(Domain::class)->findOneBy(['authority' => $domain]); - return $existingDomain ?? new Domain($domain); - } -} diff --git a/module/Core/src/Domain/Resolver/SimpleDomainResolver.php b/module/Core/src/Domain/Resolver/SimpleDomainResolver.php deleted file mode 100644 index b0c0dd58..00000000 --- a/module/Core/src/Domain/Resolver/SimpleDomainResolver.php +++ /dev/null @@ -1,15 +0,0 @@ -em = $this->prophesize(EntityManagerInterface::class); - $this->domainResolver = new PersistenceDomainResolver($this->em->reveal()); - } - - /** @test */ - public function returnsEmptyWhenNoDomainIsProvided(): void - { - $getRepository = $this->em->getRepository(Domain::class); - - self::assertNull($this->domainResolver->resolveDomain(null)); - $getRepository->shouldNotHaveBeenCalled(); - } - - /** - * @test - * @dataProvider provideFoundDomains - */ - public function findsOrCreatesDomainWhenValueIsProvided(?Domain $foundDomain, string $authority): void - { - $repo = $this->prophesize(ObjectRepository::class); - $findDomain = $repo->findOneBy(['authority' => $authority])->willReturn($foundDomain); - $getRepository = $this->em->getRepository(Domain::class)->willReturn($repo->reveal()); - - $result = $this->domainResolver->resolveDomain($authority); - - if ($foundDomain !== null) { - self::assertSame($result, $foundDomain); - } - self::assertInstanceOf(Domain::class, $result); - self::assertEquals($authority, $result->getAuthority()); - $findDomain->shouldHaveBeenCalledOnce(); - $getRepository->shouldHaveBeenCalledOnce(); - } - - public function provideFoundDomains(): iterable - { - $authority = 'doma.in'; - - yield 'without found domain' => [null, $authority]; - yield 'with found domain' => [new Domain($authority), $authority]; - } -} diff --git a/module/Core/test/Domain/Resolver/SimpleDomainResolverTest.php b/module/Core/test/Domain/Resolver/SimpleDomainResolverTest.php deleted file mode 100644 index ff5b6b90..00000000 --- a/module/Core/test/Domain/Resolver/SimpleDomainResolverTest.php +++ /dev/null @@ -1,41 +0,0 @@ -domainResolver = new SimpleDomainResolver(); - } - - /** - * @test - * @dataProvider provideDomains - */ - public function resolvesExpectedDomain(?string $domain): void - { - $result = $this->domainResolver->resolveDomain($domain); - - if ($domain === null) { - self::assertNull($result); - } else { - self::assertInstanceOf(Domain::class, $result); - self::assertEquals($domain, $result->getAuthority()); - } - } - - public function provideDomains(): iterable - { - yield 'with empty domain' => [null]; - yield 'with non-empty domain' => ['domain.com']; - } -} diff --git a/module/Core/test/ShortUrl/Resolver/PersistenceShortUrlRelationResolverTest.php b/module/Core/test/ShortUrl/Resolver/PersistenceShortUrlRelationResolverTest.php new file mode 100644 index 00000000..5791d579 --- /dev/null +++ b/module/Core/test/ShortUrl/Resolver/PersistenceShortUrlRelationResolverTest.php @@ -0,0 +1,100 @@ +em = $this->prophesize(EntityManagerInterface::class); + $this->resolver = new PersistenceShortUrlRelationResolver($this->em->reveal()); + } + + /** @test */ + public function returnsEmptyWhenNoDomainIsProvided(): void + { + $getRepository = $this->em->getRepository(Domain::class); + + self::assertNull($this->resolver->resolveDomain(null)); + $getRepository->shouldNotHaveBeenCalled(); + } + + /** + * @test + * @dataProvider provideFoundDomains + */ + public function findsOrCreatesDomainWhenValueIsProvided(?Domain $foundDomain, string $authority): void + { + $repo = $this->prophesize(ObjectRepository::class); + $findDomain = $repo->findOneBy(['authority' => $authority])->willReturn($foundDomain); + $getRepository = $this->em->getRepository(Domain::class)->willReturn($repo->reveal()); + + $result = $this->resolver->resolveDomain($authority); + + if ($foundDomain !== null) { + self::assertSame($result, $foundDomain); + } + self::assertInstanceOf(Domain::class, $result); + self::assertEquals($authority, $result->getAuthority()); + $findDomain->shouldHaveBeenCalledOnce(); + $getRepository->shouldHaveBeenCalledOnce(); + } + + public function provideFoundDomains(): iterable + { + $authority = 'doma.in'; + + yield 'not found domain' => [null, $authority]; + yield 'found domain' => [new Domain($authority), $authority]; + } + + /** @test */ + public function returnsEmptyWhenNoApiKeyIsProvided(): void + { + $getRepository = $this->em->getRepository(ApiKey::class); + + self::assertNull($this->resolver->resolveApiKey(null)); + $getRepository->shouldNotHaveBeenCalled(); + } + + /** + * @test + * @dataProvider provideFoundApiKeys + */ + public function triesToFindApiKeyWhenValueIsProvided(?ApiKey $foundApiKey, string $key): void + { + $repo = $this->prophesize(ObjectRepository::class); + $find = $repo->findOneBy(['key' => $key])->willReturn($foundApiKey); + $getRepository = $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); + + $result = $this->resolver->resolveApiKey($key); + + self::assertSame($result, $foundApiKey); + $find->shouldHaveBeenCalledOnce(); + $getRepository->shouldHaveBeenCalledOnce(); + } + + public function provideFoundApiKeys(): iterable + { + $key = 'abc123'; + + yield 'not found api key' => [null, $key]; + yield 'found api key' => [new ApiKey(), $key]; + } +} diff --git a/module/Core/test/ShortUrl/Resolver/SimpleShortUrlRelationResolverTest.php b/module/Core/test/ShortUrl/Resolver/SimpleShortUrlRelationResolverTest.php new file mode 100644 index 00000000..e2d0822c --- /dev/null +++ b/module/Core/test/ShortUrl/Resolver/SimpleShortUrlRelationResolverTest.php @@ -0,0 +1,56 @@ +resolver = new SimpleShortUrlRelationResolver(); + } + + /** + * @test + * @dataProvider provideDomains + */ + public function resolvesExpectedDomain(?string $domain): void + { + $result = $this->resolver->resolveDomain($domain); + + if ($domain === null) { + self::assertNull($result); + } else { + self::assertInstanceOf(Domain::class, $result); + self::assertEquals($domain, $result->getAuthority()); + } + } + + public function provideDomains(): iterable + { + yield 'empty domain' => [null]; + yield 'non-empty domain' => ['domain.com']; + } + + /** + * @test + * @dataProvider provideKeys + */ + public function alwaysReturnsNullForApiKeys(?string $key): void + { + self::assertNull($this->resolver->resolveApiKey($key)); + } + + public function provideKeys(): iterable + { + yield 'empty api key' => [null]; + yield 'non-empty api key' => ['abc123']; + } +} From 27bc8d48235551dc3e821c06e392a75971a6dfdb Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 7 Nov 2020 10:23:08 +0100 Subject: [PATCH 68/73] Ensured API key is tracked when creating short URLs from the REST API --- .../src/Action/ShortUrl/CreateShortUrlAction.php | 12 ++++++++---- .../ShortUrl/SingleStepCreateShortUrlAction.php | 12 +++++++++--- .../Action/ShortUrl/CreateShortUrlActionTest.php | 16 +++++++++++++--- .../SingleStepCreateShortUrlActionTest.php | 2 +- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php b/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php index 97097808..af6b3ecf 100644 --- a/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php @@ -8,6 +8,8 @@ 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; +use Shlinkio\Shlink\Rest\Authentication\Plugin\ApiKeyHeaderPlugin; class CreateShortUrlAction extends AbstractCreateShortUrlAction { @@ -19,14 +21,16 @@ class CreateShortUrlAction extends AbstractCreateShortUrlAction */ protected function buildShortUrlData(Request $request): CreateShortUrlData { - $postData = (array) $request->getParsedBody(); - if (! isset($postData['longUrl'])) { + $payload = (array) $request->getParsedBody(); + if (! isset($payload['longUrl'])) { throw ValidationException::fromArray([ 'longUrl' => 'A URL was not provided', ]); } - $meta = ShortUrlMeta::fromRawData($postData); - return new CreateShortUrlData($postData['longUrl'], (array) ($postData['tags'] ?? []), $meta); + $payload[ShortUrlMetaInputFilter::API_KEY] = $request->getHeaderLine(ApiKeyHeaderPlugin::HEADER_NAME); + $meta = ShortUrlMeta::fromRawData($payload); + + return new CreateShortUrlData($payload['longUrl'], (array) ($payload['tags'] ?? []), $meta); } } diff --git a/module/Rest/src/Action/ShortUrl/SingleStepCreateShortUrlAction.php b/module/Rest/src/Action/ShortUrl/SingleStepCreateShortUrlAction.php index 46385556..fe8c44aa 100644 --- a/module/Rest/src/Action/ShortUrl/SingleStepCreateShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/SingleStepCreateShortUrlAction.php @@ -7,7 +7,9 @@ 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\Service\UrlShortenerInterface; +use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; class SingleStepCreateShortUrlAction extends AbstractCreateShortUrlAction @@ -32,19 +34,23 @@ class SingleStepCreateShortUrlAction extends AbstractCreateShortUrlAction protected function buildShortUrlData(Request $request): CreateShortUrlData { $query = $request->getQueryParams(); + $apiKey = $query['apiKey'] ?? ''; + $longUrl = $query['longUrl'] ?? null; - if (! $this->apiKeyService->check($query['apiKey'] ?? '')) { + if (! $this->apiKeyService->check($apiKey)) { throw ValidationException::fromArray([ 'apiKey' => 'No API key was provided or it is not valid', ]); } - if (! isset($query['longUrl'])) { + if ($longUrl === null) { throw ValidationException::fromArray([ 'longUrl' => 'A URL was not provided', ]); } - return new CreateShortUrlData($query['longUrl']); + return new CreateShortUrlData($longUrl, [], ShortUrlMeta::fromRawData([ + ShortUrlMetaInputFilter::API_KEY => $apiKey, + ])); } } diff --git a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php index fd26da99..91e6014c 100644 --- a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php @@ -48,7 +48,7 @@ class CreateShortUrlActionTest extends TestCase * @test * @dataProvider provideRequestBodies */ - public function properShortcodeConversionReturnsData(array $body, ShortUrlMeta $expectedMeta): void + public function properShortcodeConversionReturnsData(array $body, ShortUrlMeta $expectedMeta, ?string $apiKey): void { $shortUrl = new ShortUrl(''); $shorten = $this->urlShortener->shorten( @@ -58,6 +58,10 @@ class CreateShortUrlActionTest extends TestCase )->willReturn($shortUrl); $request = ServerRequestFactory::fromGlobals()->withParsedBody($body); + if ($apiKey !== null) { + $request = $request->withHeader('X-Api-Key', $apiKey); + } + $response = $this->action->handle($request); self::assertEquals(200, $response->getStatusCode()); @@ -77,8 +81,14 @@ class CreateShortUrlActionTest extends TestCase 'domain' => 'my-domain.com', ]; - yield [['longUrl' => 'http://www.domain.com/foo/bar'], ShortUrlMeta::createEmpty()]; - yield [$fullMeta, ShortUrlMeta::fromRawData($fullMeta)]; + yield 'no data' => [['longUrl' => 'http://www.domain.com/foo/bar'], ShortUrlMeta::createEmpty(), null]; + yield 'all data' => [$fullMeta, ShortUrlMeta::fromRawData($fullMeta), null]; + yield 'all data and API key' => (static function (array $meta): array { + $apiKey = 'abc123'; + $meta['apiKey'] = $apiKey; + + return [$meta, ShortUrlMeta::fromRawData($meta), $apiKey]; + })($fullMeta); } /** diff --git a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php index d7b432c2..62005c8d 100644 --- a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php @@ -78,7 +78,7 @@ class SingleStepCreateShortUrlActionTest extends TestCase return $argument; }), [], - ShortUrlMeta::createEmpty(), + ShortUrlMeta::fromRawData(['apiKey' => 'abc123']), )->willReturn(new ShortUrl('')); $resp = $this->action->handle($request); From d99ea8276139025e4dc9213638549cd8d7f727cf Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 7 Nov 2020 10:27:35 +0100 Subject: [PATCH 69/73] Added migrations folder to the static analysis --- composer.json | 2 +- data/migrations/Version20171021093246.php | 4 ++-- data/migrations/Version20201102113208.php | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index b2faf734..0fef97db 100644 --- a/composer.json +++ b/composer.json @@ -109,7 +109,7 @@ ], "cs": "phpcs", "cs:fix": "phpcbf", - "stan": "phpstan analyse module/*/src/ module/*/config config docker/config --level=6", + "stan": "phpstan analyse module/*/src/ module/*/config config docker/config data/migrations --level=6", "test": [ "@test:unit", "@test:db", diff --git a/data/migrations/Version20171021093246.php b/data/migrations/Version20171021093246.php index b66a2c3f..83f08e41 100644 --- a/data/migrations/Version20171021093246.php +++ b/data/migrations/Version20171021093246.php @@ -24,10 +24,10 @@ class Version20171021093246 extends AbstractMigration return; } - $shortUrls->addColumn('valid_since', Types::DATETIME, [ + $shortUrls->addColumn('valid_since', Types::DATETIME_MUTABLE, [ 'notnull' => false, ]); - $shortUrls->addColumn('valid_until', Types::DATETIME, [ + $shortUrls->addColumn('valid_until', Types::DATETIME_MUTABLE, [ 'notnull' => false, ]); } diff --git a/data/migrations/Version20201102113208.php b/data/migrations/Version20201102113208.php index 14ce6504..4b169532 100644 --- a/data/migrations/Version20201102113208.php +++ b/data/migrations/Version20201102113208.php @@ -46,7 +46,9 @@ final class Version20201102113208 extends AbstractMigration 'expiration' => Chronos::now()->toDateTimeString(), ]); - $id = $this->resolveOneApiKeyId($qb->execute()); + /** @var Result $result */ + $result = $qb->execute(); + $id = $this->resolveOneApiKeyId($result); if ($id === null) { return; } From fe4e171ecbcd2927cbc82c12aee1f240f90ff9be Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 7 Nov 2020 10:30:25 +0100 Subject: [PATCH 70/73] Removed unused mock --- module/Core/test/Service/UrlShortenerTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index 316b7557..9d8c5273 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -6,7 +6,6 @@ namespace ShlinkioTest\Shlink\Core\Service; use Cake\Chronos\Chronos; use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\DBAL\Connection; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -40,8 +39,6 @@ class UrlShortenerTest extends TestCase ); $this->em = $this->prophesize(EntityManagerInterface::class); - $conn = $this->prophesize(Connection::class); - $this->em->getConnection()->willReturn($conn->reveal()); $this->em->persist(Argument::any())->will(function ($arguments): void { /** @var ShortUrl $shortUrl */ [$shortUrl] = $arguments; From 098751d256a0abe280afe6cb001ba8a17337c9ff Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 7 Nov 2020 10:54:21 +0100 Subject: [PATCH 71/73] Fixed link in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c20ebcaf..affd9ae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ### Fixed * [#837](https://github.com/shlinkio/shlink/issues/837) Drastically improved performance when creating a new shortUrl and providing `findIfExists = true`. -* [#837](https://github.com/shlinkio/shlink/issues/837) Added missing `gmp` extension to the official docker image. +* [#878](https://github.com/shlinkio/shlink/issues/878) Added missing `gmp` extension to the official docker image. ## [2.3.0] - 2020-08-09 From d6395a3de8fab79fbb3854f00307a954c786713c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 7 Nov 2020 12:53:14 +0100 Subject: [PATCH 72/73] Deleted everything related with authentication plugins, as shlink only supports API key auth since v2.0.0 --- module/Rest/config/auth.config.php | 23 +---- .../Action/ShortUrl/CreateShortUrlAction.php | 4 +- .../AuthenticationPluginManager.php | 12 --- .../AuthenticationPluginManagerFactory.php | 16 --- .../AuthenticationPluginManagerInterface.php | 11 --- .../Plugin/ApiKeyHeaderPlugin.php | 38 ------- .../Plugin/AuthenticationPluginInterface.php | 19 ---- .../RequestToHttpAuthPlugin.php | 55 ----------- .../RequestToHttpAuthPluginInterface.php | 16 --- .../Middleware/AuthenticationMiddleware.php | 32 ++++-- .../src/Middleware/CrossDomainMiddleware.php | 5 +- .../Middleware/AuthenticationTest.php | 12 +-- ...AuthenticationPluginManagerFactoryTest.php | 60 ------------ .../Plugin/ApiKeyHeaderPluginTest.php | 69 ------------- .../RequestToAuthPluginTest.php | 72 -------------- .../AuthenticationMiddlewareTest.php | 98 +++++++++++++------ .../Middleware/CrossDomainMiddlewareTest.php | 11 +-- 17 files changed, 100 insertions(+), 453 deletions(-) delete mode 100644 module/Rest/src/Authentication/AuthenticationPluginManager.php delete mode 100644 module/Rest/src/Authentication/AuthenticationPluginManagerFactory.php delete mode 100644 module/Rest/src/Authentication/AuthenticationPluginManagerInterface.php delete mode 100644 module/Rest/src/Authentication/Plugin/ApiKeyHeaderPlugin.php delete mode 100644 module/Rest/src/Authentication/Plugin/AuthenticationPluginInterface.php delete mode 100644 module/Rest/src/Authentication/RequestToHttpAuthPlugin.php delete mode 100644 module/Rest/src/Authentication/RequestToHttpAuthPluginInterface.php delete mode 100644 module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php delete mode 100644 module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php delete mode 100644 module/Rest/test/Authentication/RequestToAuthPluginTest.php diff --git a/module/Rest/config/auth.config.php b/module/Rest/config/auth.config.php index 99141364..0779502f 100644 --- a/module/Rest/config/auth.config.php +++ b/module/Rest/config/auth.config.php @@ -14,37 +14,16 @@ return [ Action\ShortUrl\SingleStepCreateShortUrlAction::class, ConfigProvider::UNVERSIONED_HEALTH_ENDPOINT_NAME, ], - - 'plugins' => [ - 'factories' => [ - Authentication\Plugin\ApiKeyHeaderPlugin::class => ConfigAbstractFactory::class, - ], - 'aliases' => [ - Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME => - Authentication\Plugin\ApiKeyHeaderPlugin::class, - ], - ], ], 'dependencies' => [ 'factories' => [ - Authentication\AuthenticationPluginManager::class => - Authentication\AuthenticationPluginManagerFactory::class, - Authentication\RequestToHttpAuthPlugin::class => ConfigAbstractFactory::class, - Middleware\AuthenticationMiddleware::class => ConfigAbstractFactory::class, ], ], ConfigAbstractFactory::class => [ - Authentication\Plugin\ApiKeyHeaderPlugin::class => [Service\ApiKeyService::class], - - Authentication\RequestToHttpAuthPlugin::class => [Authentication\AuthenticationPluginManager::class], - - Middleware\AuthenticationMiddleware::class => [ - Authentication\RequestToHttpAuthPlugin::class, - 'config.auth.routes_whitelist', - ], + Middleware\AuthenticationMiddleware::class => [Service\ApiKeyService::class, 'config.auth.routes_whitelist'], ], ]; diff --git a/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php b/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php index af6b3ecf..28941579 100644 --- a/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php @@ -9,7 +9,7 @@ use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Model\CreateShortUrlData; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; -use Shlinkio\Shlink\Rest\Authentication\Plugin\ApiKeyHeaderPlugin; +use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware; class CreateShortUrlAction extends AbstractCreateShortUrlAction { @@ -28,7 +28,7 @@ class CreateShortUrlAction extends AbstractCreateShortUrlAction ]); } - $payload[ShortUrlMetaInputFilter::API_KEY] = $request->getHeaderLine(ApiKeyHeaderPlugin::HEADER_NAME); + $payload[ShortUrlMetaInputFilter::API_KEY] = AuthenticationMiddleware::apiKeyFromRequest($request); $meta = ShortUrlMeta::fromRawData($payload); return new CreateShortUrlData($payload['longUrl'], (array) ($payload['tags'] ?? []), $meta); diff --git a/module/Rest/src/Authentication/AuthenticationPluginManager.php b/module/Rest/src/Authentication/AuthenticationPluginManager.php deleted file mode 100644 index 9cd8894e..00000000 --- a/module/Rest/src/Authentication/AuthenticationPluginManager.php +++ /dev/null @@ -1,12 +0,0 @@ -has('config') ? $container->get('config') : []; - return new AuthenticationPluginManager($container, $config['auth']['plugins'] ?? []); - } -} diff --git a/module/Rest/src/Authentication/AuthenticationPluginManagerInterface.php b/module/Rest/src/Authentication/AuthenticationPluginManagerInterface.php deleted file mode 100644 index 838f4ae9..00000000 --- a/module/Rest/src/Authentication/AuthenticationPluginManagerInterface.php +++ /dev/null @@ -1,11 +0,0 @@ -apiKeyService = $apiKeyService; - } - - /** - * @throws VerifyAuthenticationException - */ - public function verify(ServerRequestInterface $request): void - { - $apiKey = $request->getHeaderLine(self::HEADER_NAME); - if (! $this->apiKeyService->check($apiKey)) { - throw VerifyAuthenticationException::forInvalidApiKey(); - } - } - - public function update(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface - { - return $response; - } -} diff --git a/module/Rest/src/Authentication/Plugin/AuthenticationPluginInterface.php b/module/Rest/src/Authentication/Plugin/AuthenticationPluginInterface.php deleted file mode 100644 index 9ae0949f..00000000 --- a/module/Rest/src/Authentication/Plugin/AuthenticationPluginInterface.php +++ /dev/null @@ -1,19 +0,0 @@ -authPluginManager = $authPluginManager; - } - - /** - * @throws MissingAuthenticationException - */ - public function fromRequest(ServerRequestInterface $request): Plugin\AuthenticationPluginInterface - { - if (! $this->hasAnySupportedHeader($request)) { - throw MissingAuthenticationException::fromExpectedTypes(self::SUPPORTED_AUTH_HEADERS); - } - - return $this->authPluginManager->get($this->getFirstAvailableHeader($request)); - } - - private function hasAnySupportedHeader(ServerRequestInterface $request): bool - { - return array_reduce( - self::SUPPORTED_AUTH_HEADERS, - fn (bool $carry, string $header) => $carry || $request->hasHeader($header), - false, - ); - } - - private function getFirstAvailableHeader(ServerRequestInterface $request): string - { - $foundHeaders = array_filter(self::SUPPORTED_AUTH_HEADERS, [$request, 'hasHeader']); - return array_shift($foundHeaders) ?? ''; - } -} diff --git a/module/Rest/src/Authentication/RequestToHttpAuthPluginInterface.php b/module/Rest/src/Authentication/RequestToHttpAuthPluginInterface.php deleted file mode 100644 index b8002431..00000000 --- a/module/Rest/src/Authentication/RequestToHttpAuthPluginInterface.php +++ /dev/null @@ -1,16 +0,0 @@ -apiKeyService = $apiKeyService; $this->routesWhitelist = $routesWhitelist; - $this->requestToAuthPlugin = $requestToAuthPlugin; } public function process(Request $request, RequestHandlerInterface $handler): Response @@ -39,10 +43,20 @@ class AuthenticationMiddleware implements MiddlewareInterface, StatusCodeInterfa return $handler->handle($request); } - $plugin = $this->requestToAuthPlugin->fromRequest($request); - $plugin->verify($request); - $response = $handler->handle($request); + $apiKey = self::apiKeyFromRequest($request); + if (empty($apiKey)) { + throw MissingAuthenticationException::fromExpectedTypes([self::API_KEY_HEADER]); + } - return $plugin->update($request, $response); + if (! $this->apiKeyService->check($apiKey)) { + throw VerifyAuthenticationException::forInvalidApiKey(); + } + + return $handler->handle($request); + } + + public static function apiKeyFromRequest(Request $request): string + { + return $request->getHeaderLine(self::API_KEY_HEADER); } } diff --git a/module/Rest/src/Middleware/CrossDomainMiddleware.php b/module/Rest/src/Middleware/CrossDomainMiddleware.php index f60c0ad1..171142a1 100644 --- a/module/Rest/src/Middleware/CrossDomainMiddleware.php +++ b/module/Rest/src/Middleware/CrossDomainMiddleware.php @@ -11,7 +11,6 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Shlinkio\Shlink\Rest\Authentication; use function array_merge; use function implode; @@ -27,9 +26,7 @@ class CrossDomainMiddleware implements MiddlewareInterface, RequestMethodInterfa // Add Allow-Origin header $response = $response->withHeader('Access-Control-Allow-Origin', $request->getHeader('Origin')) - ->withHeader('Access-Control-Expose-Headers', implode(', ', [ - Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME, - ])); + ->withHeader('Access-Control-Expose-Headers', AuthenticationMiddleware::API_KEY_HEADER); if ($request->getMethod() !== self::METHOD_OPTIONS) { return $response; } diff --git a/module/Rest/test-api/Middleware/AuthenticationTest.php b/module/Rest/test-api/Middleware/AuthenticationTest.php index f71ddfd1..61dbd2c5 100644 --- a/module/Rest/test-api/Middleware/AuthenticationTest.php +++ b/module/Rest/test-api/Middleware/AuthenticationTest.php @@ -4,22 +4,14 @@ declare(strict_types=1); namespace ShlinkioApiTest\Shlink\Rest\Middleware; -use Shlinkio\Shlink\Rest\Authentication\Plugin; -use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPlugin; use Shlinkio\Shlink\TestUtils\ApiTest\ApiTestCase; -use function implode; -use function sprintf; - class AuthenticationTest extends ApiTestCase { /** @test */ public function authorizationErrorIsReturnedIfNoApiKeyIsSent(): void { - $expectedDetail = sprintf( - 'Expected one of the following authentication headers, ["%s"], but none were provided', - implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS), - ); + $expectedDetail = 'Expected one of the following authentication headers, ["X-Api-Key"], but none were provided'; $resp = $this->callApi(self::METHOD_GET, '/short-urls'); $payload = $this->getJsonResponsePayload($resp); @@ -41,7 +33,7 @@ class AuthenticationTest extends ApiTestCase $resp = $this->callApi(self::METHOD_GET, '/short-urls', [ 'headers' => [ - Plugin\ApiKeyHeaderPlugin::HEADER_NAME => $apiKey, + 'X-Api-Key' => $apiKey, ], ]); $payload = $this->getJsonResponsePayload($resp); diff --git a/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php b/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php deleted file mode 100644 index c3bd860a..00000000 --- a/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php +++ /dev/null @@ -1,60 +0,0 @@ -factory = new AuthenticationPluginManagerFactory(); - } - - /** - * @test - * @dataProvider provideConfigs - */ - public function serviceIsProperlyCreatedWithExpectedPlugins(?array $config, array $expectedPlugins): void - { - $instance = ($this->factory)(new ServiceManager(['services' => [ - 'config' => $config, - ]])); - - self::assertEquals($expectedPlugins, $this->getPlugins($instance)); - } - - private function getPlugins(AuthenticationPluginManager $pluginManager): array - { - return (fn () => $this->services)->call($pluginManager); - } - - public function provideConfigs(): iterable - { - yield [null, []]; - yield [[], []]; - yield [['auth' => []], []]; - yield [['auth' => [ - 'plugins' => [], - ]], []]; - yield [['auth' => [ - 'plugins' => [ - 'services' => $plugins = [ - 'foo' => $this->prophesize(AuthenticationPluginInterface::class)->reveal(), - 'bar' => $this->prophesize(AuthenticationPluginInterface::class)->reveal(), - ], - ], - ]], $plugins]; - } -} diff --git a/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php b/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php deleted file mode 100644 index 53aca2dc..00000000 --- a/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php +++ /dev/null @@ -1,69 +0,0 @@ -apiKeyService = $this->prophesize(ApiKeyServiceInterface::class); - $this->plugin = new ApiKeyHeaderPlugin($this->apiKeyService->reveal()); - } - - /** @test */ - public function verifyThrowsExceptionWhenApiKeyIsNotValid(): void - { - $apiKey = 'abc-ABC'; - $check = $this->apiKeyService->check($apiKey)->willReturn(false); - $check->shouldBeCalledOnce(); - - $this->expectException(VerifyAuthenticationException::class); - $this->expectExceptionMessage('Provided API key does not exist or is invalid'); - - $this->plugin->verify($this->createRequest($apiKey)); - } - - /** @test */ - public function verifyDoesNotThrowExceptionWhenApiKeyIsValid(): void - { - $apiKey = 'abc-ABC'; - $check = $this->apiKeyService->check($apiKey)->willReturn(true); - - $this->plugin->verify($this->createRequest($apiKey)); - - $check->shouldHaveBeenCalledOnce(); - } - - /** @test */ - public function updateReturnsResponseAsIs(): void - { - $apiKey = 'abc-ABC'; - $response = new Response(); - - $returnedResponse = $this->plugin->update($this->createRequest($apiKey), $response); - - self::assertSame($response, $returnedResponse); - } - - private function createRequest(string $apiKey): ServerRequestInterface - { - return (new ServerRequest())->withHeader(ApiKeyHeaderPlugin::HEADER_NAME, $apiKey); - } -} diff --git a/module/Rest/test/Authentication/RequestToAuthPluginTest.php b/module/Rest/test/Authentication/RequestToAuthPluginTest.php deleted file mode 100644 index db3dd2ce..00000000 --- a/module/Rest/test/Authentication/RequestToAuthPluginTest.php +++ /dev/null @@ -1,72 +0,0 @@ -pluginManager = $this->prophesize(AuthenticationPluginManagerInterface::class); - $this->requestToPlugin = new RequestToHttpAuthPlugin($this->pluginManager->reveal()); - } - - /** @test */ - public function exceptionIsFoundWhenNoneOfTheSupportedMethodsIsFound(): void - { - $request = new ServerRequest(); - - $this->expectException(MissingAuthenticationException::class); - $this->expectExceptionMessage(sprintf( - 'Expected one of the following authentication headers, ["%s"], but none were provided', - implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS), - )); - - $this->requestToPlugin->fromRequest($request); - } - - /** - * @test - * @dataProvider provideHeaders - */ - public function properPluginIsFetchedWhenAnyAuthTypeIsFound(array $headers, string $expectedHeader): void - { - $request = new ServerRequest(); - foreach ($headers as $header => $value) { - $request = $request->withHeader($header, $value); - } - - $plugin = $this->prophesize(AuthenticationPluginInterface::class); - $getPlugin = $this->pluginManager->get($expectedHeader)->willReturn($plugin->reveal()); - - $this->requestToPlugin->fromRequest($request); - - $getPlugin->shouldHaveBeenCalledOnce(); - } - - public function provideHeaders(): iterable - { - yield 'API key header' => [[ - ApiKeyHeaderPlugin::HEADER_NAME => 'foobar', - ], ApiKeyHeaderPlugin::HEADER_NAME]; - } -} diff --git a/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php b/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php index 1a95bbd4..db721780 100644 --- a/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php +++ b/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php @@ -7,20 +7,21 @@ namespace ShlinkioTest\Shlink\Rest\Middleware; use Fig\Http\Message\RequestMethodInterface; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; +use Laminas\Diactoros\ServerRequestFactory; use Mezzio\Router\Route; use Mezzio\Router\RouteResult; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; -use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Rest\Action\HealthAction; -use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthenticationPluginInterface; -use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPluginInterface; +use Shlinkio\Shlink\Rest\Exception\MissingAuthenticationException; +use Shlinkio\Shlink\Rest\Exception\VerifyAuthenticationException; use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware; +use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; use function Laminas\Stratigility\middleware; @@ -29,12 +30,14 @@ class AuthenticationMiddlewareTest extends TestCase use ProphecyTrait; private AuthenticationMiddleware $middleware; - private ObjectProphecy $requestToPlugin; + private ObjectProphecy $apiKeyService; + private ObjectProphecy $handler; public function setUp(): void { - $this->requestToPlugin = $this->prophesize(RequestToHttpAuthPluginInterface::class); - $this->middleware = new AuthenticationMiddleware($this->requestToPlugin->reveal(), [HealthAction::class]); + $this->apiKeyService = $this->prophesize(ApiKeyServiceInterface::class); + $this->middleware = new AuthenticationMiddleware($this->apiKeyService->reveal(), [HealthAction::class]); + $this->handler = $this->prophesize(RequestHandlerInterface::class); } /** @@ -43,16 +46,13 @@ class AuthenticationMiddlewareTest extends TestCase */ public function someWhiteListedSituationsFallbackToNextMiddleware(ServerRequestInterface $request): void { - $handler = $this->prophesize(RequestHandlerInterface::class); - $handle = $handler->handle($request)->willReturn(new Response()); - $fromRequest = $this->requestToPlugin->fromRequest(Argument::any())->willReturn( - $this->prophesize(AuthenticationPluginInterface::class)->reveal(), - ); + $handle = $this->handler->handle($request)->willReturn(new Response()); + $checkApiKey = $this->apiKeyService->check(Argument::any()); - $this->middleware->process($request, $handler->reveal()); + $this->middleware->process($request, $this->handler->reveal()); $handle->shouldHaveBeenCalledOnce(); - $fromRequest->shouldNotHaveBeenCalled(); + $checkApiKey->shouldNotHaveBeenCalled(); } public function provideWhitelistedRequests(): iterable @@ -76,30 +76,70 @@ class AuthenticationMiddlewareTest extends TestCase )->withMethod(RequestMethodInterface::METHOD_OPTIONS)]; } - /** @test */ - public function updatedResponseIsReturnedWhenVerificationPasses(): void + /** + * @test + * @dataProvider provideRequestsWithoutApiKey + */ + public function throwsExceptionWhenNoApiKeyIsProvided(ServerRequestInterface $request): void { - $newResponse = new Response(); - $request = (new ServerRequest())->withAttribute( + $this->apiKeyService->check(Argument::any())->shouldNotBeCalled(); + $this->handler->handle($request)->shouldNotBeCalled(); + $this->expectException(MissingAuthenticationException::class); + $this->expectExceptionMessage( + 'Expected one of the following authentication headers, ["X-Api-Key"], but none were provided', + ); + + $this->middleware->process($request, $this->handler->reveal()); + } + + public function provideRequestsWithoutApiKey(): iterable + { + $baseRequest = ServerRequestFactory::fromGlobals()->withAttribute( RouteResult::class, RouteResult::fromRoute(new Route('bar', $this->getDummyMiddleware()), []), ); - $plugin = $this->prophesize(AuthenticationPluginInterface::class); - $verify = $plugin->verify($request)->will(function (): void { - }); - $update = $plugin->update($request, Argument::type(ResponseInterface::class))->willReturn($newResponse); - $fromRequest = $this->requestToPlugin->fromRequest(Argument::any())->willReturn($plugin->reveal()); + yield 'no api key' => [$baseRequest]; + yield 'empty api key' => [$baseRequest->withHeader('X-Api-Key', '')]; + } - $handler = $this->prophesize(RequestHandlerInterface::class); - $handle = $handler->handle($request)->willReturn(new Response()); - $response = $this->middleware->process($request, $handler->reveal()); + /** @test */ + public function throwsExceptionWhenProvidedApiKeyIsInvalid(): void + { + $apiKey = 'abc123'; + $request = ServerRequestFactory::fromGlobals() + ->withAttribute( + RouteResult::class, + RouteResult::fromRoute(new Route('bar', $this->getDummyMiddleware()), []), + ) + ->withHeader('X-Api-Key', $apiKey); + + $this->apiKeyService->check($apiKey)->willReturn(false)->shouldBeCalledOnce(); + $this->handler->handle($request)->shouldNotBeCalled(); + $this->expectException(VerifyAuthenticationException::class); + $this->expectExceptionMessage('Provided API key does not exist or is invalid'); + + $this->middleware->process($request, $this->handler->reveal()); + } + + /** @test */ + public function validApiKeyFallsBackToNextMiddleware(): void + { + $apiKey = 'abc123'; + $request = ServerRequestFactory::fromGlobals() + ->withAttribute( + RouteResult::class, + RouteResult::fromRoute(new Route('bar', $this->getDummyMiddleware()), []), + ) + ->withHeader('X-Api-Key', $apiKey); + + $handle = $this->handler->handle($request)->willReturn(new Response()); + $checkApiKey = $this->apiKeyService->check($apiKey)->willReturn(true); + + $this->middleware->process($request, $this->handler->reveal()); - self::assertSame($response, $newResponse); - $verify->shouldHaveBeenCalledOnce(); - $update->shouldHaveBeenCalledOnce(); $handle->shouldHaveBeenCalledOnce(); - $fromRequest->shouldHaveBeenCalledOnce(); + $checkApiKey->shouldHaveBeenCalledOnce(); } private function getDummyMiddleware(): MiddlewareInterface diff --git a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php index 446ede9d..03675fce 100644 --- a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php +++ b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php @@ -13,7 +13,6 @@ use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Server\RequestHandlerInterface; -use Shlinkio\Shlink\Rest\Authentication; use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware; use function Laminas\Stratigility\middleware; @@ -64,10 +63,7 @@ class CrossDomainMiddlewareTest extends TestCase $headers = $response->getHeaders(); self::assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin')); - self::assertEquals( - Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME, - $response->getHeaderLine('Access-Control-Expose-Headers'), - ); + self::assertEquals('X-Api-Key', $response->getHeaderLine('Access-Control-Expose-Headers')); self::assertArrayNotHasKey('Access-Control-Allow-Methods', $headers); self::assertArrayNotHasKey('Access-Control-Max-Age', $headers); self::assertArrayNotHasKey('Access-Control-Allow-Headers', $headers); @@ -89,10 +85,7 @@ class CrossDomainMiddlewareTest extends TestCase $headers = $response->getHeaders(); self::assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin')); - self::assertEquals( - Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME, - $response->getHeaderLine('Access-Control-Expose-Headers'), - ); + self::assertEquals('X-Api-Key', $response->getHeaderLine('Access-Control-Expose-Headers')); self::assertArrayHasKey('Access-Control-Allow-Methods', $headers); self::assertEquals('1000', $response->getHeaderLine('Access-Control-Max-Age')); self::assertEquals('foo, bar, baz', $response->getHeaderLine('Access-Control-Allow-Headers')); From 006ec7c1d000f4ee0695277f5f8cb2ac097216d6 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 8 Nov 2020 12:14:41 +0100 Subject: [PATCH 73/73] Added v2.4 to changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index affd9ae7..185a9ff3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). -## [Unreleased] +## [2.4.0] - 2020-11-08 ### Added * [#829](https://github.com/shlinkio/shlink/issues/829) Added support for QR codes in SVG format, by passing `?format=svg` to the QR code URL. * [#820](https://github.com/shlinkio/shlink/issues/820) Added new option to force enabling or disabling URL validation on a per-URL basis. @@ -35,6 +35,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#836](https://github.com/shlinkio/shlink/issues/836) Added support for the `-` notation while determining how to order the short URLs list, as in `?orderBy=shortCode-DESC`. This effectively deprecates the array notation (`?orderBy[shortCode]=DESC`), that will be removed in Shlink 3.0.0 * [#782](https://github.com/shlinkio/shlink/issues/782) Added code coverage to API tests. * [#858](https://github.com/shlinkio/shlink/issues/858) Updated to latest infection version. Updated docker images to PHP 7.4.11 and swoole 4.5.5 +* [#887](https://github.com/shlinkio/shlink/pull/887) Started tracking the API key used to create short URLs, in order to allow restrictions in future releases. ### Deprecated * [#883](https://github.com/shlinkio/shlink/issues/883) Deprecated `POST /tags` endpoint and `tag:create` command, as tags are created automatically while creating short URLs.