From 6f135ad6ab8e399608927a0aaf655e600cab1b99 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 30 Sep 2022 17:45:36 +0200 Subject: [PATCH 001/182] Fixed typo --- .github/workflows/docker-image-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image-build.yml b/.github/workflows/docker-image-build.yml index 96457033..9eb682d6 100644 --- a/.github/workflows/docker-image-build.yml +++ b/.github/workflows/docker-image-build.yml @@ -8,7 +8,7 @@ on: - 'v*' jobs: - build-openswool: + build-openswoole: uses: shlinkio/github-actions/.github/workflows/docker-build-and-publish.yml@main secrets: inherit with: From 96dbdbe7c94bbb9e7f39671d2e2c12545c3cbe82 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 3 Oct 2022 20:00:31 +0200 Subject: [PATCH 002/182] Updated to openswoole 4.12 --- .github/workflows/ci-db-tests.yml | 2 +- .github/workflows/ci-mutation-tests.yml | 2 +- .github/workflows/ci-tests.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/publish-release.yml | 2 +- .github/workflows/publish-swagger-spec.yml | 2 +- composer.json | 2 +- data/infra/swoole.Dockerfile | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci-db-tests.yml b/.github/workflows/ci-db-tests.yml index 50060383..aa3a2bc3 100644 --- a/.github/workflows/ci-db-tests.yml +++ b/.github/workflows/ci-db-tests.yml @@ -28,7 +28,7 @@ jobs: - uses: './.github/actions/ci-setup' with: php-version: ${{ matrix.php-version }} - php-extensions: openswoole-4.11.1, pdo_sqlsrv-5.10.1 + php-extensions: openswoole-4.12.0, pdo_sqlsrv-5.10.1 extensions-cache-key: db-tests-extensions-${{ matrix.php-version }}-${{ inputs.platform }} - name: Create test database if: ${{ inputs.platform == 'ms' }} diff --git a/.github/workflows/ci-mutation-tests.yml b/.github/workflows/ci-mutation-tests.yml index c7b361d8..5eeb9ac3 100644 --- a/.github/workflows/ci-mutation-tests.yml +++ b/.github/workflows/ci-mutation-tests.yml @@ -20,7 +20,7 @@ jobs: - uses: './.github/actions/ci-setup' with: php-version: ${{ matrix.php-version }} - php-extensions: openswoole-4.11.1 + php-extensions: openswoole-4.12.0 extensions-cache-key: mutation-tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }} - uses: actions/download-artifact@v3 with: diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index e20428c6..66aa666b 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -26,7 +26,7 @@ jobs: - uses: './.github/actions/ci-setup' with: php-version: ${{ matrix.php-version }} - php-extensions: openswoole-4.11.1 + php-extensions: openswoole-4.12.0 extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }} - run: composer test:${{ inputs.test-group }}:ci - uses: actions/upload-artifact@v3 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 013226a6..3b2db6ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: - uses: './.github/actions/ci-setup' with: php-version: ${{ matrix.php-version }} - php-extensions: openswoole-4.11.1 + php-extensions: openswoole-4.12.0 extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ matrix.command }} - run: composer ${{ matrix.command }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index b4ed7bba..d9625125 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -17,7 +17,7 @@ jobs: - uses: './.github/actions/ci-setup' with: php-version: ${{ matrix.php-version }} - php-extensions: openswoole-4.11.1 + php-extensions: openswoole-4.12.0 extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }} install-deps: 'no' - if: ${{ matrix.swoole == 'yes' }} diff --git a/.github/workflows/publish-swagger-spec.yml b/.github/workflows/publish-swagger-spec.yml index 9002353d..6e6cb925 100644 --- a/.github/workflows/publish-swagger-spec.yml +++ b/.github/workflows/publish-swagger-spec.yml @@ -20,7 +20,7 @@ jobs: - uses: './.github/actions/ci-setup' with: php-version: ${{ matrix.php-version }} - php-extensions: openswoole-4.11.1 + php-extensions: openswoole-4.12.0 extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }} - run: composer swagger:inline - run: mkdir ${{ steps.determine_version.outputs.version }} diff --git a/composer.json b/composer.json index 809ccfbb..5010753e 100644 --- a/composer.json +++ b/composer.json @@ -64,7 +64,7 @@ "devster/ubench": "^2.1", "dms/phpunit-arraysubset-asserts": "^0.4.0", "infection/infection": "^0.26.15", - "openswoole/ide-helper": "~4.11.1", + "openswoole/ide-helper": "~4.12.0", "phpspec/prophecy-phpunit": "^2.0", "phpstan/phpstan": "^1.8", "phpstan/phpstan-doctrine": "^1.3", diff --git a/data/infra/swoole.Dockerfile b/data/infra/swoole.Dockerfile index 21a2fe5e..294ad71b 100644 --- a/data/infra/swoole.Dockerfile +++ b/data/infra/swoole.Dockerfile @@ -3,7 +3,7 @@ MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.21 ENV INOTIFY_VERSION 3.0.0 -ENV OPENSWOOLE_VERSION 4.11.1 +ENV OPENSWOOLE_VERSION 4.12.0 ENV PDO_SQLSRV_VERSION 5.10.1 ENV MS_ODBC_SQL_VERSION 17.5.2.2 From ab8d42b609010ba95f78c1687408efe74cde4bae Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 3 Oct 2022 20:01:46 +0200 Subject: [PATCH 003/182] Updated to openswoole 4.12 in main Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2835d75f..c498894e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ ARG SHLINK_VERSION=latest ENV SHLINK_VERSION ${SHLINK_VERSION} ARG SHLINK_RUNTIME=openswoole ENV SHLINK_RUNTIME ${SHLINK_RUNTIME} -ENV OPENSWOOLE_VERSION 4.11.1 +ENV OPENSWOOLE_VERSION 4.12.0 ENV PDO_SQLSRV_VERSION 5.10.1 ENV MS_ODBC_SQL_VERSION 17.5.2.2 ENV LC_ALL "C" From 1e0791416db7f2fc6475f611c04afed61a0baea2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 3 Oct 2022 20:05:43 +0200 Subject: [PATCH 004/182] Downgraded openswoole ide helper --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5010753e..475ccbd4 100644 --- a/composer.json +++ b/composer.json @@ -64,7 +64,7 @@ "devster/ubench": "^2.1", "dms/phpunit-arraysubset-asserts": "^0.4.0", "infection/infection": "^0.26.15", - "openswoole/ide-helper": "~4.12.0", + "openswoole/ide-helper": "~4.11.5", "phpspec/prophecy-phpunit": "^2.0", "phpstan/phpstan": "^1.8", "phpstan/phpstan-doctrine": "^1.3", From 14314ef9392c1685f448332c98c0762a1df65d0a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 6 Oct 2022 19:49:32 +0200 Subject: [PATCH 005/182] Updated shlink deps --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 475ccbd4..277c6ba5 100644 --- a/composer.json +++ b/composer.json @@ -45,8 +45,8 @@ "php-middleware/request-id": "^4.1", "pugx/shortid-php": "^1.0", "ramsey/uuid": "^4.3", - "shlinkio/shlink-common": "^5.1", - "shlinkio/shlink-config": "^2.1", + "shlinkio/shlink-common": "dev-main#7515008 as 5.2", + "shlinkio/shlink-config": "dev-main#bcd8222 as 2.2", "shlinkio/shlink-event-dispatcher": "^2.6", "shlinkio/shlink-importer": "^4.0", "shlinkio/shlink-installer": "^8.2", From 27b680e0cdfda223d386c4a314554cf4727dc5a6 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 6 Oct 2022 21:01:11 +0200 Subject: [PATCH 006/182] Created CLI test for short URLs list --- .../test-cli/Command/ListShortUrlsTest.php | 66 +++++++++++++++++++ module/Rest/src/ApiKey/Role.php | 2 - 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 module/CLI/test-cli/Command/ListShortUrlsTest.php diff --git a/module/CLI/test-cli/Command/ListShortUrlsTest.php b/module/CLI/test-cli/Command/ListShortUrlsTest.php new file mode 100644 index 00000000..faa47a2f --- /dev/null +++ b/module/CLI/test-cli/Command/ListShortUrlsTest.php @@ -0,0 +1,66 @@ +exec([ListShortUrlsCommand::NAME, ...$flags], ['no']); + self::assertStringContainsString($expectedOutput, $output); + } + + public function provideFlagsAndOutput(): iterable + { + // phpcs:disable Generic.Files.LineLength + yield 'no flags' => [[], << [['--start-date=2019-01'], << [['-e 2018-12-01'], << [['-s 2018-06-20', '--end-date=2019-01-01T00:00:20+00:00'], << Date: Thu, 6 Oct 2022 21:29:27 +0200 Subject: [PATCH 007/182] Moved logic to reuse command options to option classes instead of base abstract command classes --- .../Command/Domain/GetDomainVisitsCommand.php | 2 +- .../ShortUrl/GetShortUrlVisitsCommand.php | 2 +- .../Command/ShortUrl/ListShortUrlsCommand.php | 31 ++++----- .../src/Command/Tag/GetTagVisitsCommand.php | 2 +- .../Util/AbstractWithDateRangeCommand.php | 69 ------------------- .../Visit/AbstractVisitsListCommand.php | 26 +++---- .../Visit/GetNonOrphanVisitsCommand.php | 2 +- .../Command/Visit/GetOrphanVisitsCommand.php | 2 +- module/CLI/src/Option/DateOption.php | 51 ++++++++++++++ module/CLI/src/Option/EndDateOption.php | 30 ++++++++ module/CLI/src/Option/StartDateOption.php | 30 ++++++++ 11 files changed, 141 insertions(+), 106 deletions(-) delete mode 100644 module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php create mode 100644 module/CLI/src/Option/DateOption.php create mode 100644 module/CLI/src/Option/EndDateOption.php create mode 100644 module/CLI/src/Option/StartDateOption.php diff --git a/module/CLI/src/Command/Domain/GetDomainVisitsCommand.php b/module/CLI/src/Command/Domain/GetDomainVisitsCommand.php index 676a2141..8d2eb8c9 100644 --- a/module/CLI/src/Command/Domain/GetDomainVisitsCommand.php +++ b/module/CLI/src/Command/Domain/GetDomainVisitsCommand.php @@ -25,7 +25,7 @@ class GetDomainVisitsCommand extends AbstractVisitsListCommand parent::__construct($visitsHelper); } - protected function doConfigure(): void + protected function configure(): void { $this ->setName(self::NAME) diff --git a/module/CLI/src/Command/ShortUrl/GetShortUrlVisitsCommand.php b/module/CLI/src/Command/ShortUrl/GetShortUrlVisitsCommand.php index 7f81e4da..a6a4f31d 100644 --- a/module/CLI/src/Command/ShortUrl/GetShortUrlVisitsCommand.php +++ b/module/CLI/src/Command/ShortUrl/GetShortUrlVisitsCommand.php @@ -20,7 +20,7 @@ class GetShortUrlVisitsCommand extends AbstractVisitsListCommand { public const NAME = 'short-url:visits'; - protected function doConfigure(): void + protected function configure(): void { $this ->setName(self::NAME) diff --git a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php index 0889bb03..11443abc 100644 --- a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php +++ b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php @@ -4,7 +4,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\ShortUrl; -use Shlinkio\Shlink\CLI\Command\Util\AbstractWithDateRangeCommand; +use Shlinkio\Shlink\CLI\Option\EndDateOption; +use Shlinkio\Shlink\CLI\Option\StartDateOption; use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\CLI\Util\ShlinkTable; use Shlinkio\Shlink\Common\Paginator\Paginator; @@ -15,6 +16,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode; use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlsParamsInputFilter; use Shlinkio\Shlink\Core\ShortUrl\ShortUrlServiceInterface; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -27,20 +29,25 @@ use function Functional\map; use function implode; use function sprintf; -class ListShortUrlsCommand extends AbstractWithDateRangeCommand +class ListShortUrlsCommand extends Command { use PagerfantaUtilsTrait; public const NAME = 'short-url:list'; + private readonly StartDateOption $startDateOption; + private readonly EndDateOption $endDateOption; + public function __construct( - private ShortUrlServiceInterface $shortUrlService, - private DataTransformerInterface $transformer, + private readonly ShortUrlServiceInterface $shortUrlService, + private readonly DataTransformerInterface $transformer, ) { parent::__construct(); + $this->startDateOption = new StartDateOption($this, 'short URLs'); + $this->endDateOption = new EndDateOption($this, 'short URLs'); } - protected function doConfigure(): void + protected function configure(): void { $this ->setName(self::NAME) @@ -104,16 +111,6 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand ); } - protected function getStartDateDesc(string $optionName): string - { - return sprintf('Allows to filter short URLs, returning only those created after "%s".', $optionName); - } - - protected function getEndDateDesc(string $optionName): string - { - return sprintf('Allows to filter short URLs, returning only those created before "%s".', $optionName); - } - protected function execute(InputInterface $input, OutputInterface $output): ?int { $io = new SymfonyStyle($input, $output); @@ -124,8 +121,8 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand $tagsMode = $input->getOption('including-all-tags') === true ? TagsMode::ALL->value : TagsMode::ANY->value; $tags = ! empty($tags) ? explode(',', $tags) : []; $all = $input->getOption('all'); - $startDate = $this->getStartDateOption($input, $output); - $endDate = $this->getEndDateOption($input, $output); + $startDate = $this->startDateOption->get($input, $output); + $endDate = $this->endDateOption->get($input, $output); $orderBy = $this->processOrderBy($input); $columnsMap = $this->resolveColumnsMap($input); diff --git a/module/CLI/src/Command/Tag/GetTagVisitsCommand.php b/module/CLI/src/Command/Tag/GetTagVisitsCommand.php index 842c9b45..290a172a 100644 --- a/module/CLI/src/Command/Tag/GetTagVisitsCommand.php +++ b/module/CLI/src/Command/Tag/GetTagVisitsCommand.php @@ -25,7 +25,7 @@ class GetTagVisitsCommand extends AbstractVisitsListCommand parent::__construct($visitsHelper); } - protected function doConfigure(): void + protected function configure(): void { $this ->setName(self::NAME) diff --git a/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php b/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php deleted file mode 100644 index c3e3c407..00000000 --- a/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php +++ /dev/null @@ -1,69 +0,0 @@ -doConfigure(); - $this - ->addOption(self::START_DATE, 's', InputOption::VALUE_REQUIRED, $this->getStartDateDesc(self::START_DATE)) - ->addOption(self::END_DATE, 'e', InputOption::VALUE_REQUIRED, $this->getEndDateDesc(self::END_DATE)); - } - - protected function getStartDateOption(InputInterface $input, OutputInterface $output): ?Chronos - { - return $this->getDateOption($input, $output, self::START_DATE); - } - - protected function getEndDateOption(InputInterface $input, OutputInterface $output): ?Chronos - { - return $this->getDateOption($input, $output, self::END_DATE); - } - - private function getDateOption(InputInterface $input, OutputInterface $output, string $key): ?Chronos - { - $value = $input->getOption($key); - if (empty($value) || ! is_string($value)) { - return null; - } - - try { - return Chronos::parse($value); - } catch (Throwable $e) { - $output->writeln(sprintf( - '> Ignored provided "%s" since its value "%s" is not a valid date. <', - $key, - $value, - )); - - if ($output->isVeryVerbose()) { - $this->getApplication()?->renderThrowable($e, $output); - } - - return null; - } - } - - abstract protected function doConfigure(): void; - - abstract protected function getStartDateDesc(string $optionName): string; - - abstract protected function getEndDateDesc(string $optionName): string; -} diff --git a/module/CLI/src/Command/Visit/AbstractVisitsListCommand.php b/module/CLI/src/Command/Visit/AbstractVisitsListCommand.php index 37a875c6..402d5ba4 100644 --- a/module/CLI/src/Command/Visit/AbstractVisitsListCommand.php +++ b/module/CLI/src/Command/Visit/AbstractVisitsListCommand.php @@ -4,13 +4,15 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\Visit; -use Shlinkio\Shlink\CLI\Command\Util\AbstractWithDateRangeCommand; +use Shlinkio\Shlink\CLI\Option\EndDateOption; +use Shlinkio\Shlink\CLI\Option\StartDateOption; use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\CLI\Util\ShlinkTable; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -19,29 +21,23 @@ use function Functional\map; use function Functional\select_keys; use function Shlinkio\Shlink\Common\buildDateRange; use function Shlinkio\Shlink\Core\camelCaseToHumanFriendly; -use function sprintf; -abstract class AbstractVisitsListCommand extends AbstractWithDateRangeCommand +abstract class AbstractVisitsListCommand extends Command { + private readonly StartDateOption $startDateOption; + private readonly EndDateOption $endDateOption; + public function __construct(protected readonly VisitsStatsHelperInterface $visitsHelper) { parent::__construct(); - } - - final protected function getStartDateDesc(string $optionName): string - { - return sprintf('Allows to filter visits, returning only those older than "%s".', $optionName); - } - - final protected function getEndDateDesc(string $optionName): string - { - return sprintf('Allows to filter visits, returning only those newer than "%s".', $optionName); + $this->startDateOption = new StartDateOption($this, 'visits'); + $this->endDateOption = new EndDateOption($this, 'visits'); } final protected function execute(InputInterface $input, OutputInterface $output): ?int { - $startDate = $this->getStartDateOption($input, $output); - $endDate = $this->getEndDateOption($input, $output); + $startDate = $this->startDateOption->get($input, $output); + $endDate = $this->endDateOption->get($input, $output); $paginator = $this->getVisitsPaginator($input, buildDateRange($startDate, $endDate)); [$rows, $headers] = $this->resolveRowsAndHeaders($paginator); diff --git a/module/CLI/src/Command/Visit/GetNonOrphanVisitsCommand.php b/module/CLI/src/Command/Visit/GetNonOrphanVisitsCommand.php index 0b4a4612..0dd32f3e 100644 --- a/module/CLI/src/Command/Visit/GetNonOrphanVisitsCommand.php +++ b/module/CLI/src/Command/Visit/GetNonOrphanVisitsCommand.php @@ -23,7 +23,7 @@ class GetNonOrphanVisitsCommand extends AbstractVisitsListCommand parent::__construct($visitsHelper); } - protected function doConfigure(): void + protected function configure(): void { $this ->setName(self::NAME) diff --git a/module/CLI/src/Command/Visit/GetOrphanVisitsCommand.php b/module/CLI/src/Command/Visit/GetOrphanVisitsCommand.php index c2d353af..618a35cd 100644 --- a/module/CLI/src/Command/Visit/GetOrphanVisitsCommand.php +++ b/module/CLI/src/Command/Visit/GetOrphanVisitsCommand.php @@ -14,7 +14,7 @@ class GetOrphanVisitsCommand extends AbstractVisitsListCommand { public const NAME = 'visit:orphan'; - protected function doConfigure(): void + protected function configure(): void { $this ->setName(self::NAME) diff --git a/module/CLI/src/Option/DateOption.php b/module/CLI/src/Option/DateOption.php new file mode 100644 index 00000000..a863696f --- /dev/null +++ b/module/CLI/src/Option/DateOption.php @@ -0,0 +1,51 @@ +addOption($name, $shortcut, InputOption::VALUE_REQUIRED, $description); + } + + public function get(InputInterface $input, OutputInterface $output): ?Chronos + { + $value = $input->getOption($this->name); + if (empty($value) || ! is_string($value)) { + return null; + } + + try { + return Chronos::parse($value); + } catch (Throwable $e) { + $output->writeln(sprintf( + '> Ignored provided "%s" since its value "%s" is not a valid date. <', + $this->name, + $value, + )); + + if ($output->isVeryVerbose()) { + $this->command->getApplication()?->renderThrowable($e, $output); + } + + return null; + } + } +} diff --git a/module/CLI/src/Option/EndDateOption.php b/module/CLI/src/Option/EndDateOption.php new file mode 100644 index 00000000..72421981 --- /dev/null +++ b/module/CLI/src/Option/EndDateOption.php @@ -0,0 +1,30 @@ +dateOption = new DateOption($command, 'end-date', 'e', sprintf( + 'Allows to filter %s, returning only those newer than provided date.', + $descriptionHint, + )); + } + + public function get(InputInterface $input, OutputInterface $output): ?Chronos + { + return $this->dateOption->get($input, $output); + } +} diff --git a/module/CLI/src/Option/StartDateOption.php b/module/CLI/src/Option/StartDateOption.php new file mode 100644 index 00000000..2da5aaee --- /dev/null +++ b/module/CLI/src/Option/StartDateOption.php @@ -0,0 +1,30 @@ +dateOption = new DateOption($command, 'start-date', 's', sprintf( + 'Allows to filter %s, returning only those older than provided date.', + $descriptionHint, + )); + } + + public function get(InputInterface $input, OutputInterface $output): ?Chronos + { + return $this->dateOption->get($input, $output); + } +} From 14bf3a134b927ab1e67516ec61f2ce87a48336b0 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 6 Oct 2022 21:30:23 +0200 Subject: [PATCH 008/182] Updated changelog --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d4838a2..816b5e16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ 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 +* [#1563](https://github.com/shlinkio/shlink/issues/1563) Moved logic to reuse command options to option classes instead of base abstract command classes. + +### Deprecated +* *Nothing* + +### Removed +* *Nothing* + +### Fixed +* *Nothing* + + ## [3.3.1] - 2022-09-30 ### Added * *Nothing* From b59cbeceac3835f2899cc503f55dc31803f7d704 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 12 Oct 2022 08:49:58 +0200 Subject: [PATCH 009/182] Updated deps --- composer.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/composer.json b/composer.json index 277c6ba5..d90986ef 100644 --- a/composer.json +++ b/composer.json @@ -20,39 +20,39 @@ "akrabat/ip-address-middleware": "^2.1", "cakephp/chronos": "^2.3", "doctrine/migrations": "^3.5", - "doctrine/orm": "^2.12", - "endroid/qr-code": "^4.4", - "geoip2/geoip2": "^2.12", - "guzzlehttp/guzzle": "^7.4", + "doctrine/orm": "^2.13", + "endroid/qr-code": "^4.6", + "geoip2/geoip2": "^2.13", + "guzzlehttp/guzzle": "^7.5", "happyr/doctrine-specification": "^2.0", - "jaybizzle/crawler-detect": "^1.2.110", + "jaybizzle/crawler-detect": "^1.2.112", "laminas/laminas-config": "^3.7", - "laminas/laminas-config-aggregator": "^1.8", - "laminas/laminas-diactoros": "^2.14", - "laminas/laminas-inputfilter": "^2.19", - "laminas/laminas-servicemanager": "^3.16", - "laminas/laminas-stdlib": "^3.11", - "lcobucci/jwt": "^4.1", - "league/uri": "^6.7", + "laminas/laminas-config-aggregator": "^1.11", + "laminas/laminas-diactoros": "^2.19", + "laminas/laminas-inputfilter": "^2.22", + "laminas/laminas-servicemanager": "^3.19", + "laminas/laminas-stdlib": "^3.15", + "lcobucci/jwt": "^4.2", + "league/uri": "^6.8", "lstrojny/functional-php": "^1.17", - "mezzio/mezzio": "^3.11", - "mezzio/mezzio-fastroute": "^3.5", - "mezzio/mezzio-problem-details": "^1.6", - "mezzio/mezzio-swoole": "^4.3", + "mezzio/mezzio": "^3.13", + "mezzio/mezzio-fastroute": "^3.7", + "mezzio/mezzio-problem-details": "^1.7", + "mezzio/mezzio-swoole": "^4.5", "mlocati/ip-lib": "^1.18", "ocramius/proxy-manager": "^2.14", "pagerfanta/core": "^3.6", "php-middleware/request-id": "^4.1", - "pugx/shortid-php": "^1.0", - "ramsey/uuid": "^4.3", + "pugx/shortid-php": "^1.1", + "ramsey/uuid": "^4.5", "shlinkio/shlink-common": "dev-main#7515008 as 5.2", - "shlinkio/shlink-config": "dev-main#bcd8222 as 2.2", + "shlinkio/shlink-config": "dev-main#db02e84 as 2.2", "shlinkio/shlink-event-dispatcher": "^2.6", "shlinkio/shlink-importer": "^4.0", "shlinkio/shlink-installer": "^8.2", "shlinkio/shlink-ip-geolocation": "^3.1", "spiral/roadrunner": "^2.11", - "spiral/roadrunner-jobs": "^2.3", + "spiral/roadrunner-jobs": "^2.5", "symfony/console": "^6.1", "symfony/filesystem": "^6.1", "symfony/lock": "^6.1", From bcd5d2848d7681407376046cf6aea2159df93464 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 12 Oct 2022 12:47:58 +0200 Subject: [PATCH 010/182] Used PHPUnit mocks in RoleResolverTest instead of prophezy --- module/CLI/src/ApiKey/RoleResolver.php | 5 +- .../CLI/src/ApiKey/RoleResolverInterface.php | 3 - .../src/Command/Api/GenerateKeyCommand.php | 4 +- .../CLI/src/Command/Api/ListKeysCommand.php | 4 +- module/CLI/test/ApiKey/RoleResolverTest.php | 64 +++++++++++-------- module/Rest/src/ApiKey/Role.php | 24 ++++--- module/Rest/test/ApiKey/RoleTest.php | 4 +- 7 files changed, 61 insertions(+), 47 deletions(-) diff --git a/module/CLI/src/ApiKey/RoleResolver.php b/module/CLI/src/ApiKey/RoleResolver.php index 588a2fa2..c1ae8f05 100644 --- a/module/CLI/src/ApiKey/RoleResolver.php +++ b/module/CLI/src/ApiKey/RoleResolver.php @@ -7,6 +7,7 @@ namespace Shlinkio\Shlink\CLI\ApiKey; use Shlinkio\Shlink\CLI\Exception\InvalidRoleConfigException; use Shlinkio\Shlink\Core\Domain\DomainServiceInterface; use Shlinkio\Shlink\Rest\ApiKey\Model\RoleDefinition; +use Shlinkio\Shlink\Rest\ApiKey\Role; use Symfony\Component\Console\Input\InputInterface; use function is_string; @@ -19,8 +20,8 @@ class RoleResolver implements RoleResolverInterface public function determineRoles(InputInterface $input): array { - $domainAuthority = $input->getOption(self::DOMAIN_ONLY_PARAM); - $author = $input->getOption(self::AUTHOR_ONLY_PARAM); + $domainAuthority = $input->getOption(Role::DOMAIN_SPECIFIC->paramName()); + $author = $input->getOption(Role::AUTHORED_SHORT_URLS->paramName()); $roleDefinitions = []; if ($author) { diff --git a/module/CLI/src/ApiKey/RoleResolverInterface.php b/module/CLI/src/ApiKey/RoleResolverInterface.php index 98d50483..92a04594 100644 --- a/module/CLI/src/ApiKey/RoleResolverInterface.php +++ b/module/CLI/src/ApiKey/RoleResolverInterface.php @@ -9,9 +9,6 @@ use Symfony\Component\Console\Input\InputInterface; interface RoleResolverInterface { - public const AUTHOR_ONLY_PARAM = 'author-only'; - public const DOMAIN_ONLY_PARAM = 'domain-only'; - /** * @return RoleDefinition[] */ diff --git a/module/CLI/src/Command/Api/GenerateKeyCommand.php b/module/CLI/src/Command/Api/GenerateKeyCommand.php index b24619ef..12adcd57 100644 --- a/module/CLI/src/Command/Api/GenerateKeyCommand.php +++ b/module/CLI/src/Command/Api/GenerateKeyCommand.php @@ -32,8 +32,8 @@ class GenerateKeyCommand extends Command protected function configure(): void { - $authorOnly = RoleResolverInterface::AUTHOR_ONLY_PARAM; - $domainOnly = RoleResolverInterface::DOMAIN_ONLY_PARAM; + $authorOnly = Role::AUTHORED_SHORT_URLS->paramName(); + $domainOnly = Role::DOMAIN_SPECIFIC->paramName(); $help = <<%command.name% generates a new valid API key. diff --git a/module/CLI/src/Command/Api/ListKeysCommand.php b/module/CLI/src/Command/Api/ListKeysCommand.php index 0e98af31..59f5b534 100644 --- a/module/CLI/src/Command/Api/ListKeysCommand.php +++ b/module/CLI/src/Command/Api/ListKeysCommand.php @@ -62,8 +62,8 @@ class ListKeysCommand extends Command $rowData[] = $apiKey->isAdmin() ? 'Admin' : implode("\n", $apiKey->mapRoles( fn (Role $role, array $meta) => empty($meta) - ? Role::toFriendlyName($role) - : sprintf('%s: %s', Role::toFriendlyName($role), Role::domainAuthorityFromMeta($meta)), + ? $role->toFriendlyName() + : sprintf('%s: %s', $role->toFriendlyName(), Role::domainAuthorityFromMeta($meta)), )); return $rowData; diff --git a/module/CLI/test/ApiKey/RoleResolverTest.php b/module/CLI/test/ApiKey/RoleResolverTest.php index 21f541a2..245a7106 100644 --- a/module/CLI/test/ApiKey/RoleResolverTest.php +++ b/module/CLI/test/ApiKey/RoleResolverTest.php @@ -4,27 +4,27 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\ApiKey; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\ApiKey\RoleResolver; use Shlinkio\Shlink\CLI\Exception\InvalidRoleConfigException; use Shlinkio\Shlink\Core\Domain\DomainServiceInterface; use Shlinkio\Shlink\Core\Domain\Entity\Domain; use Shlinkio\Shlink\Rest\ApiKey\Model\RoleDefinition; +use Shlinkio\Shlink\Rest\ApiKey\Role; use Symfony\Component\Console\Input\InputInterface; +use function Functional\map; + class RoleResolverTest extends TestCase { - use ProphecyTrait; - private RoleResolver $resolver; - private ObjectProphecy $domainService; + private MockObject $domainService; protected function setUp(): void { - $this->domainService = $this->prophesize(DomainServiceInterface::class); - $this->resolver = new RoleResolver($this->domainService->reveal(), 'default.com'); + $this->domainService = $this->createMock(DomainServiceInterface::class); + $this->resolver = new RoleResolver($this->domainService, 'default.com'); } /** @@ -36,61 +36,65 @@ class RoleResolverTest extends TestCase array $expectedRoles, int $expectedDomainCalls, ): void { - $getDomain = $this->domainService->getOrCreate('example.com')->willReturn( - Domain::withAuthority('example.com')->setId('1'), - ); + $this->domainService + ->expects($this->exactly($expectedDomainCalls)) + ->method('getOrCreate') + ->with($this->equalTo('example.com')) + ->willReturn(Domain::withAuthority('example.com')->setId('1')); $result = $this->resolver->determineRoles($input); self::assertEquals($expectedRoles, $result); - $getDomain->shouldHaveBeenCalledTimes($expectedDomainCalls); } public function provideRoles(): iterable { $domain = Domain::withAuthority('example.com')->setId('1'); $buildInput = function (array $definition): InputInterface { - $input = $this->prophesize(InputInterface::class); + $input = $this->createStub(InputInterface::class); + $input->method('getOption')->willReturnMap( + map($definition, static fn (mixed $returnValue, string $param) => [$param, $returnValue]), + ); - foreach ($definition as $name => $value) { - $input->getOption($name)->willReturn($value); - } - - return $input->reveal(); + return $input; }; yield 'no roles' => [ - $buildInput([RoleResolver::DOMAIN_ONLY_PARAM => null, RoleResolver::AUTHOR_ONLY_PARAM => false]), + $buildInput([Role::DOMAIN_SPECIFIC->paramName() => null, Role::AUTHORED_SHORT_URLS->paramName() => false]), [], 0, ]; yield 'domain role only' => [ - $buildInput([RoleResolver::DOMAIN_ONLY_PARAM => 'example.com', RoleResolver::AUTHOR_ONLY_PARAM => false]), + $buildInput( + [Role::DOMAIN_SPECIFIC->paramName() => 'example.com', Role::AUTHORED_SHORT_URLS->paramName() => false], + ), [RoleDefinition::forDomain($domain)], 1, ]; yield 'false domain role' => [ - $buildInput([RoleResolver::DOMAIN_ONLY_PARAM => false]), + $buildInput([Role::DOMAIN_SPECIFIC->paramName() => false]), [], 0, ]; yield 'true domain role' => [ - $buildInput([RoleResolver::DOMAIN_ONLY_PARAM => true]), + $buildInput([Role::DOMAIN_SPECIFIC->paramName() => true]), [], 0, ]; yield 'string array domain role' => [ - $buildInput([RoleResolver::DOMAIN_ONLY_PARAM => ['foo', 'bar']]), + $buildInput([Role::DOMAIN_SPECIFIC->paramName() => ['foo', 'bar']]), [], 0, ]; yield 'author role only' => [ - $buildInput([RoleResolver::DOMAIN_ONLY_PARAM => null, RoleResolver::AUTHOR_ONLY_PARAM => true]), + $buildInput([Role::DOMAIN_SPECIFIC->paramName() => null, Role::AUTHORED_SHORT_URLS->paramName() => true]), [RoleDefinition::forAuthoredShortUrls()], 0, ]; yield 'both roles' => [ - $buildInput([RoleResolver::DOMAIN_ONLY_PARAM => 'example.com', RoleResolver::AUTHOR_ONLY_PARAM => true]), + $buildInput( + [Role::DOMAIN_SPECIFIC->paramName() => 'example.com', Role::AUTHORED_SHORT_URLS->paramName() => true], + ), [RoleDefinition::forAuthoredShortUrls(), RoleDefinition::forDomain($domain)], 1, ]; @@ -99,12 +103,16 @@ class RoleResolverTest extends TestCase /** @test */ public function exceptionIsThrownWhenTryingToAddDomainOnlyLinkedToDefaultDomain(): void { - $input = $this->prophesize(InputInterface::class); - $input->getOption(RoleResolver::DOMAIN_ONLY_PARAM)->willReturn('default.com'); - $input->getOption(RoleResolver::AUTHOR_ONLY_PARAM)->willReturn(null); + $input = $this->createStub(InputInterface::class); + $input + ->method('getOption') + ->willReturnMap([ + [Role::DOMAIN_SPECIFIC->paramName(), 'default.com'], + [Role::AUTHORED_SHORT_URLS->paramName(), null], + ]); $this->expectException(InvalidRoleConfigException::class); - $this->resolver->determineRoles($input->reveal()); + $this->resolver->determineRoles($input); } } diff --git a/module/Rest/src/ApiKey/Role.php b/module/Rest/src/ApiKey/Role.php index f852571d..5a4edb81 100644 --- a/module/Rest/src/ApiKey/Role.php +++ b/module/Rest/src/ApiKey/Role.php @@ -17,6 +17,22 @@ enum Role: string case AUTHORED_SHORT_URLS = 'AUTHORED_SHORT_URLS'; case DOMAIN_SPECIFIC = 'DOMAIN_SPECIFIC'; + public function toFriendlyName(): string + { + return match ($this) { + self::AUTHORED_SHORT_URLS => 'Author only', + self::DOMAIN_SPECIFIC => 'Domain only', + }; + } + + public function paramName(): string + { + return match ($this) { + self::AUTHORED_SHORT_URLS => 'author-only', + self::DOMAIN_SPECIFIC => 'domain-only', + }; + } + public static function toSpec(ApiKeyRole $role, ?string $context = null): Specification { return match ($role->role()) { @@ -42,12 +58,4 @@ enum Role: string { return $meta['authority'] ?? ''; } - - public static function toFriendlyName(Role $role): string - { - return match ($role) { - self::AUTHORED_SHORT_URLS => 'Author only', - self::DOMAIN_SPECIFIC => 'Domain only', - }; - } } diff --git a/module/Rest/test/ApiKey/RoleTest.php b/module/Rest/test/ApiKey/RoleTest.php index f3cc64b2..715b89b8 100644 --- a/module/Rest/test/ApiKey/RoleTest.php +++ b/module/Rest/test/ApiKey/RoleTest.php @@ -99,9 +99,9 @@ class RoleTest extends TestCase * @test * @dataProvider provideRoleNames */ - public function getsExpectedRoleFriendlyName(Role $roleName, string $expectedFriendlyName): void + public function getsExpectedRoleFriendlyName(Role $role, string $expectedFriendlyName): void { - self::assertEquals($expectedFriendlyName, Role::toFriendlyName($roleName)); + self::assertEquals($expectedFriendlyName, $role->toFriendlyName()); } public function provideRoleNames(): iterable From 8cfa0b595c5c2eb7faadf656d8a2b920e80eeee2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 12 Oct 2022 18:23:36 +0200 Subject: [PATCH 011/182] Migrated to PHPUnit mocks in RobotsActionTest --- .github/actions/ci-setup/action.yml | 2 +- .github/workflows/ci.yml | 2 +- indocker | 2 +- module/Core/src/Action/RobotsAction.php | 2 +- .../src/Crawling/CrawlingHelperInterface.php | 2 +- module/Core/test/Action/RobotsActionTest.php | 17 ++++++++--------- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/actions/ci-setup/action.yml b/.github/actions/ci-setup/action.yml index 0a8a09d7..7ff7ad8a 100644 --- a/.github/actions/ci-setup/action.yml +++ b/.github/actions/ci-setup/action.yml @@ -41,7 +41,7 @@ runs: extensions: ${{ inputs.php-extensions }} coverage: pcov ini-values: pcov.directory=module - - run: echo "::set-output name=composerArgs::${{ inputs.php-version == '8.2' && '--ignore-platform-req=php' || '' }}" + - run: echo "::set-output name=composerArgs::${{ inputs.php-version == '8.2' && '--ignore-platform-req=php+' || '' }}" id: composer_args shell: bash - name: Install dependencies diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b2db6ac..b6986061 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: with: php-version: ${{ matrix.php-version }} tools: composer - - run: echo "::set-output name=composerArgs::${{ matrix.php-version == '8.2' && '--ignore-platform-req=php' || '' }}" + - run: echo "::set-output name=composerArgs::${{ matrix.php-version == '8.2' && '--ignore-platform-req=php+' || '' }}" id: composer_args shell: bash - run: composer install --no-interaction --prefer-dist ${{ steps.composer_args.outputs.composerArgs }} diff --git a/indocker b/indocker index 03061e2f..789386ac 100755 --- a/indocker +++ b/indocker @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Run docker containers if they are not up yet -if ! [[ $(docker ps | grep shlink) ]]; then +if ! [[ $(docker ps | grep shlink_swoole) ]]; then docker-compose up -d fi diff --git a/module/Core/src/Action/RobotsAction.php b/module/Core/src/Action/RobotsAction.php index 12baa7b3..214dc7a0 100644 --- a/module/Core/src/Action/RobotsAction.php +++ b/module/Core/src/Action/RobotsAction.php @@ -17,7 +17,7 @@ use const PHP_EOL; class RobotsAction implements RequestHandlerInterface, StatusCodeInterface { - public function __construct(private CrawlingHelperInterface $crawlingHelper) + public function __construct(private readonly CrawlingHelperInterface $crawlingHelper) { } diff --git a/module/Core/src/Crawling/CrawlingHelperInterface.php b/module/Core/src/Crawling/CrawlingHelperInterface.php index 635a4fc9..3438b2ba 100644 --- a/module/Core/src/Crawling/CrawlingHelperInterface.php +++ b/module/Core/src/Crawling/CrawlingHelperInterface.php @@ -7,7 +7,7 @@ namespace Shlinkio\Shlink\Core\Crawling; interface CrawlingHelperInterface { /** - * @return string[]|iterable + * @return iterable */ public function listCrawlableShortCodes(): iterable; } diff --git a/module/Core/test/Action/RobotsActionTest.php b/module/Core/test/Action/RobotsActionTest.php index ad8a02d1..4f405506 100644 --- a/module/Core/test/Action/RobotsActionTest.php +++ b/module/Core/test/Action/RobotsActionTest.php @@ -5,23 +5,20 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Action; use Laminas\Diactoros\ServerRequestFactory; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Action\RobotsAction; use Shlinkio\Shlink\Core\Crawling\CrawlingHelperInterface; class RobotsActionTest extends TestCase { - use ProphecyTrait; - private RobotsAction $action; - private ObjectProphecy $helper; + private MockObject $helper; protected function setUp(): void { - $this->helper = $this->prophesize(CrawlingHelperInterface::class); - $this->action = new RobotsAction($this->helper->reveal()); + $this->helper = $this->createMock(CrawlingHelperInterface::class); + $this->action = new RobotsAction($this->helper); } /** @@ -30,14 +27,16 @@ class RobotsActionTest extends TestCase */ public function buildsRobotsLinesFromCrawlableShortCodes(array $shortCodes, string $expected): void { - $getShortCodes = $this->helper->listCrawlableShortCodes()->willReturn($shortCodes); + $this->helper + ->expects($this->once()) + ->method('listCrawlableShortCodes') + ->willReturn($shortCodes); $response = $this->action->handle(ServerRequestFactory::fromGlobals()); self::assertEquals(200, $response->getStatusCode()); self::assertEquals($expected, $response->getBody()->__toString()); self::assertEquals('text/plain', $response->getHeaderLine('Content-Type')); - $getShortCodes->shouldHaveBeenCalledOnce(); } public function provideShortCodes(): iterable From 23bcba4fd9460342d1ca2edb48ef563183eb6356 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 12 Oct 2022 19:07:05 +0200 Subject: [PATCH 012/182] Updated shlink-ip-geolocation --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d90986ef..181093f0 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "shlinkio/shlink-event-dispatcher": "^2.6", "shlinkio/shlink-importer": "^4.0", "shlinkio/shlink-installer": "^8.2", - "shlinkio/shlink-ip-geolocation": "^3.1", + "shlinkio/shlink-ip-geolocation": "dev-main#e208963 as 3.2", "spiral/roadrunner": "^2.11", "spiral/roadrunner-jobs": "^2.5", "symfony/console": "^6.1", From 847cc2bc5050b17f02d1ddc51f7caf09e070cf8c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 19 Oct 2022 14:19:03 +0200 Subject: [PATCH 013/182] Updated shlink-config --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 755b0f17..5f72b1db 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ "pugx/shortid-php": "^1.1", "ramsey/uuid": "^4.5", "shlinkio/shlink-common": "dev-main#7515008 as 5.2", - "shlinkio/shlink-config": "dev-main#db02e84 as 2.2", + "shlinkio/shlink-config": "dev-main#e75d27a as 2.2", "shlinkio/shlink-event-dispatcher": "^2.6", "shlinkio/shlink-importer": "^4.0", "shlinkio/shlink-installer": "^8.2", From 843754b7e7cdfe6df7cebe6f7f4f24768dec58f1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 21 Oct 2022 18:32:34 +0200 Subject: [PATCH 014/182] Migrated PixelActionTest to use PHPUnit mocks --- module/Core/test/Action/PixelActionTest.php | 27 +++++++++------------ 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/module/Core/test/Action/PixelActionTest.php b/module/Core/test/Action/PixelActionTest.php index 3d301945..7a150970 100644 --- a/module/Core/test/Action/PixelActionTest.php +++ b/module/Core/test/Action/PixelActionTest.php @@ -5,10 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Action; use Laminas\Diactoros\ServerRequest; +use PHPUnit\Framework\MockObject\MockObject; 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; use Shlinkio\Shlink\Core\Action\PixelAction; @@ -19,32 +17,29 @@ use Shlinkio\Shlink\Core\Visit\RequestTrackerInterface; class PixelActionTest extends TestCase { - use ProphecyTrait; - private PixelAction $action; - private ObjectProphecy $urlResolver; - private ObjectProphecy $requestTracker; + private MockObject $urlResolver; + private MockObject $requestTracker; protected function setUp(): void { - $this->urlResolver = $this->prophesize(ShortUrlResolverInterface::class); - $this->requestTracker = $this->prophesize(RequestTrackerInterface::class); + $this->urlResolver = $this->createMock(ShortUrlResolverInterface::class); + $this->requestTracker = $this->createMock(RequestTrackerInterface::class); - $this->action = new PixelAction($this->urlResolver->reveal(), $this->requestTracker->reveal()); + $this->action = new PixelAction($this->urlResolver, $this->requestTracker); } /** @test */ public function imageIsReturned(): void { $shortCode = 'abc123'; - $this->urlResolver->resolveEnabledShortUrl( - ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''), - )->willReturn(ShortUrl::withLongUrl('http://domain.com/foo/bar')) - ->shouldBeCalledOnce(); - $this->requestTracker->trackIfApplicable(Argument::cetera())->shouldBeCalledOnce(); + $this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')), + )->willReturn(ShortUrl::withLongUrl('http://domain.com/foo/bar')); + $this->requestTracker->expects($this->once())->method('trackIfApplicable')->withAnyParameters(); $request = (new ServerRequest())->withAttribute('shortCode', $shortCode); - $response = $this->action->process($request, $this->prophesize(RequestHandlerInterface::class)->reveal()); + $response = $this->action->process($request, $this->createMock(RequestHandlerInterface::class)); self::assertInstanceOf(PixelResponse::class, $response); self::assertEquals(200, $response->getStatusCode()); From cd4b632d753b58f0103c00e4e51b0c418ffa7c63 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 21 Oct 2022 18:39:22 +0200 Subject: [PATCH 015/182] Migrated QrActionTest to use PHPUnit mocks --- module/Core/test/Action/QrCodeActionTest.php | 71 +++++++++----------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index 90635a3c..8bec70e4 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Core\Action; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\ServerRequestFactory; +use PHPUnit\Framework\MockObject\MockObject; 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; use Psr\Log\NullLogger; @@ -29,50 +27,43 @@ use function imagecreatefromstring; class QrCodeActionTest extends TestCase { - use ProphecyTrait; - private const WHITE = 0xFFFFFF; private const BLACK = 0x0; - private ObjectProphecy $urlResolver; + private MockObject $urlResolver; protected function setUp(): void { - $this->urlResolver = $this->prophesize(ShortUrlResolverInterface::class); + $this->urlResolver = $this->createMock(ShortUrlResolverInterface::class); } /** @test */ public function aNotFoundShortCodeWillDelegateIntoNextMiddleware(): void { $shortCode = 'abc123'; - $this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')) - ->willThrow(ShortUrlNotFoundException::class) - ->shouldBeCalledOnce(); - $delegate = $this->prophesize(RequestHandlerInterface::class); - $process = $delegate->handle(Argument::any())->willReturn(new Response()); + $this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')) + )->willThrowException(ShortUrlNotFoundException::fromNotFound(ShortUrlIdentifier::fromShortCodeAndDomain(''))); + $delegate = $this->createMock(RequestHandlerInterface::class); + $delegate->expects($this->once())->method('handle')->withAnyParameters()->willReturn(new Response()); - $this->action()->process((new ServerRequest())->withAttribute('shortCode', $shortCode), $delegate->reveal()); - - $process->shouldHaveBeenCalledOnce(); + $this->action()->process((new ServerRequest())->withAttribute('shortCode', $shortCode), $delegate); } /** @test */ public function aCorrectRequestReturnsTheQrCodeResponse(): void { $shortCode = 'abc123'; - $this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')) - ->willReturn(ShortUrl::createEmpty()) - ->shouldBeCalledOnce(); - $delegate = $this->prophesize(RequestHandlerInterface::class); + $this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')), + )->willReturn(ShortUrl::createEmpty()); + $delegate = $this->createMock(RequestHandlerInterface::class); + $delegate->expects($this->never())->method('handle'); - $resp = $this->action()->process( - (new ServerRequest())->withAttribute('shortCode', $shortCode), - $delegate->reveal(), - ); + $resp = $this->action()->process((new ServerRequest())->withAttribute('shortCode', $shortCode), $delegate); self::assertInstanceOf(QrCodeResponse::class, $resp); self::assertEquals(200, $resp->getStatusCode()); - $delegate->handle(Argument::any())->shouldHaveBeenCalledTimes(0); } /** @@ -85,13 +76,13 @@ class QrCodeActionTest extends TestCase string $expectedContentType, ): void { $code = 'abc123'; - $this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($code, ''))->willReturn( - ShortUrl::createEmpty(), - ); - $delegate = $this->prophesize(RequestHandlerInterface::class); + $this->urlResolver->method('resolveEnabledShortUrl')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($code, '')), + )->willReturn(ShortUrl::createEmpty()); + $delegate = $this->createMock(RequestHandlerInterface::class); $req = (new ServerRequest())->withAttribute('shortCode', $code)->withQueryParams($query); - $resp = $this->action(new QrCodeOptions(format: $defaultFormat))->process($req, $delegate->reveal()); + $resp = $this->action(new QrCodeOptions(format: $defaultFormat))->process($req, $delegate); self::assertEquals($expectedContentType, $resp->getHeaderLine('Content-Type')); } @@ -118,12 +109,12 @@ class QrCodeActionTest extends TestCase int $expectedSize, ): void { $code = 'abc123'; - $this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($code, ''))->willReturn( - ShortUrl::createEmpty(), - ); - $delegate = $this->prophesize(RequestHandlerInterface::class); + $this->urlResolver->method('resolveEnabledShortUrl')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($code, '')), + )->willReturn(ShortUrl::createEmpty()); + $delegate = $this->createMock(RequestHandlerInterface::class); - $resp = $this->action($defaultOptions)->process($req->withAttribute('shortCode', $code), $delegate->reveal()); + $resp = $this->action($defaultOptions)->process($req->withAttribute('shortCode', $code), $delegate); [$size] = getimagesizefromstring($resp->getBody()->__toString()); self::assertEquals($expectedSize, $size); @@ -209,12 +200,12 @@ class QrCodeActionTest extends TestCase ->withQueryParams(['size' => 250, 'roundBlockSize' => $roundBlockSize]) ->withAttribute('shortCode', $code); - $this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($code, ''))->willReturn( - ShortUrl::withLongUrl('https://shlink.io'), - ); - $delegate = $this->prophesize(RequestHandlerInterface::class); + $this->urlResolver->method('resolveEnabledShortUrl')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($code, '')), + )->willReturn(ShortUrl::withLongUrl('https://shlink.io')); + $delegate = $this->createMock(RequestHandlerInterface::class); - $resp = $this->action($defaultOptions)->process($req, $delegate->reveal()); + $resp = $this->action($defaultOptions)->process($req, $delegate); $image = imagecreatefromstring($resp->getBody()->__toString()); $color = imagecolorat($image, 1, 1); @@ -246,7 +237,7 @@ class QrCodeActionTest extends TestCase public function action(?QrCodeOptions $options = null): QrCodeAction { return new QrCodeAction( - $this->urlResolver->reveal(), + $this->urlResolver, new ShortUrlStringifier(['domain' => 'doma.in']), new NullLogger(), $options ?? new QrCodeOptions(), From a8f82971311a240d8f1d50ed93f99a156a028b9b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 21 Oct 2022 18:44:55 +0200 Subject: [PATCH 016/182] Migrated RedirectActionTest to use PHPUnit mocks --- .../Core/test/Action/RedirectActionTest.php | 62 ++++++++----------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/module/Core/test/Action/RedirectActionTest.php b/module/Core/test/Action/RedirectActionTest.php index f7449ad9..f17d32ba 100644 --- a/module/Core/test/Action/RedirectActionTest.php +++ b/module/Core/test/Action/RedirectActionTest.php @@ -6,10 +6,8 @@ namespace ShlinkioTest\Shlink\Core\Action; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; +use PHPUnit\Framework\MockObject\MockObject; 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; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; @@ -22,29 +20,27 @@ use Shlinkio\Shlink\Core\Visit\RequestTrackerInterface; class RedirectActionTest extends TestCase { - use ProphecyTrait; - private const LONG_URL = 'https://domain.com/foo/bar?some=thing'; private RedirectAction $action; - private ObjectProphecy $urlResolver; - private ObjectProphecy $requestTracker; - private ObjectProphecy $redirectRespHelper; + private MockObject $urlResolver; + private MockObject $requestTracker; + private MockObject $redirectRespHelper; protected function setUp(): void { - $this->urlResolver = $this->prophesize(ShortUrlResolverInterface::class); - $this->requestTracker = $this->prophesize(RequestTrackerInterface::class); - $this->redirectRespHelper = $this->prophesize(RedirectResponseHelperInterface::class); + $this->urlResolver = $this->createMock(ShortUrlResolverInterface::class); + $this->requestTracker = $this->createMock(RequestTrackerInterface::class); + $this->redirectRespHelper = $this->createMock(RedirectResponseHelperInterface::class); - $redirectBuilder = $this->prophesize(ShortUrlRedirectionBuilderInterface::class); - $redirectBuilder->buildShortUrlRedirect(Argument::cetera())->willReturn(self::LONG_URL); + $redirectBuilder = $this->createMock(ShortUrlRedirectionBuilderInterface::class); + $redirectBuilder->method('buildShortUrlRedirect')->withAnyParameters()->willReturn(self::LONG_URL); $this->action = new RedirectAction( - $this->urlResolver->reveal(), - $this->requestTracker->reveal(), - $redirectBuilder->reveal(), - $this->redirectRespHelper->reveal(), + $this->urlResolver, + $this->requestTracker, + $redirectBuilder, + $this->redirectRespHelper, ); } @@ -53,38 +49,34 @@ class RedirectActionTest extends TestCase { $shortCode = 'abc123'; $shortUrl = ShortUrl::withLongUrl(self::LONG_URL); - $shortCodeToUrl = $this->urlResolver->resolveEnabledShortUrl( - ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''), + $this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')), )->willReturn($shortUrl); - $track = $this->requestTracker->trackIfApplicable(Argument::cetera())->will(function (): void { - }); + $this->requestTracker->expects($this->once())->method('trackIfApplicable'); $expectedResp = new Response\RedirectResponse(self::LONG_URL); - $buildResp = $this->redirectRespHelper->buildRedirectResponse(self::LONG_URL)->willReturn($expectedResp); + $this->redirectRespHelper->expects($this->once())->method('buildRedirectResponse')->with( + $this->equalTo(self::LONG_URL), + )->willReturn($expectedResp); $request = (new ServerRequest())->withAttribute('shortCode', $shortCode); - $response = $this->action->process($request, $this->prophesize(RequestHandlerInterface::class)->reveal()); + $response = $this->action->process($request, $this->createMock(RequestHandlerInterface::class)); self::assertSame($expectedResp, $response); - $buildResp->shouldHaveBeenCalledOnce(); - $shortCodeToUrl->shouldHaveBeenCalledOnce(); - $track->shouldHaveBeenCalledOnce(); } /** @test */ public function nextMiddlewareIsInvokedIfLongUrlIsNotFound(): void { $shortCode = 'abc123'; - $this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')) - ->willThrow(ShortUrlNotFoundException::class) - ->shouldBeCalledOnce(); - $this->requestTracker->trackIfApplicable(Argument::cetera())->shouldNotBeCalled(); + $this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')) + )->willThrowException(ShortUrlNotFoundException::fromNotFound(ShortUrlIdentifier::fromShortCodeAndDomain(''))); + $this->requestTracker->expects($this->never())->method('trackIfApplicable'); - $handler = $this->prophesize(RequestHandlerInterface::class); - $handle = $handler->handle(Argument::any())->willReturn(new Response()); + $handler = $this->createMock(RequestHandlerInterface::class); + $handler->expects($this->once())->method('handle')->withAnyParameters()->willReturn(new Response()); $request = (new ServerRequest())->withAttribute('shortCode', $shortCode); - $this->action->process($request, $handler->reveal()); - - $handle->shouldHaveBeenCalledOnce(); + $this->action->process($request, $handler); } } From 29d50cabc29215e9c929650a795dcd6ed5ad9336 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 21 Oct 2022 18:47:10 +0200 Subject: [PATCH 017/182] Migrated NotFoundRedirectResolverTest to use PHPUnit mocks --- .../Config/NotFoundRedirectResolverTest.php | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/module/Core/test/Config/NotFoundRedirectResolverTest.php b/module/Core/test/Config/NotFoundRedirectResolverTest.php index 912e17a5..3cc9f691 100644 --- a/module/Core/test/Config/NotFoundRedirectResolverTest.php +++ b/module/Core/test/Config/NotFoundRedirectResolverTest.php @@ -9,10 +9,8 @@ use Laminas\Diactoros\ServerRequestFactory; use Laminas\Diactoros\Uri; use Mezzio\Router\Route; use Mezzio\Router\RouteResult; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UriInterface; use Psr\Http\Server\MiddlewareInterface; @@ -25,15 +23,13 @@ use Shlinkio\Shlink\Core\Util\RedirectResponseHelperInterface; class NotFoundRedirectResolverTest extends TestCase { - use ProphecyTrait; - private NotFoundRedirectResolver $resolver; - private ObjectProphecy $helper; + private MockObject $helper; protected function setUp(): void { - $this->helper = $this->prophesize(RedirectResponseHelperInterface::class); - $this->resolver = new NotFoundRedirectResolver($this->helper->reveal(), new NullLogger()); + $this->helper = $this->createMock(RedirectResponseHelperInterface::class); + $this->resolver = new NotFoundRedirectResolver($this->helper, new NullLogger()); } /** @@ -47,12 +43,13 @@ class NotFoundRedirectResolverTest extends TestCase string $expectedRedirectTo, ): void { $expectedResp = new Response(); - $buildResp = $this->helper->buildRedirectResponse($expectedRedirectTo)->willReturn($expectedResp); + $this->helper->expects($this->once())->method('buildRedirectResponse')->with( + $this->equalTo($expectedRedirectTo), + )->willReturn($expectedResp); $resp = $this->resolver->resolveRedirectResponse($notFoundType, $redirectConfig, $uri); self::assertSame($expectedResp, $resp); - $buildResp->shouldHaveBeenCalledOnce(); } public function provideRedirects(): iterable @@ -119,11 +116,11 @@ class NotFoundRedirectResolverTest extends TestCase public function noResponseIsReturnedIfNoConditionsMatch(): void { $notFoundType = $this->notFoundType($this->requestForRoute('foo')); + $this->helper->expects($this->never())->method('buildRedirectResponse'); $result = $this->resolver->resolveRedirectResponse($notFoundType, new NotFoundRedirectOptions(), new Uri()); self::assertNull($result); - $this->helper->buildRedirectResponse(Argument::cetera())->shouldNotHaveBeenCalled(); } private function notFoundType(ServerRequestInterface $req): NotFoundType @@ -139,7 +136,7 @@ class NotFoundRedirectResolverTest extends TestCase RouteResult::fromRoute( new Route( '', - $this->prophesize(MiddlewareInterface::class)->reveal(), + $this->createMock(MiddlewareInterface::class), ['GET'], $routeName, ), From 148f7a9cfeccdd9dfee9aa6070b6663481248854 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 21 Oct 2022 18:49:47 +0200 Subject: [PATCH 018/182] Migrated CrawlingHelperTest to use PHPUnit mocks --- .../Core/test/Crawling/CrawlingHelperTest.php | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/module/Core/test/Crawling/CrawlingHelperTest.php b/module/Core/test/Crawling/CrawlingHelperTest.php index 92901efc..7667fb93 100644 --- a/module/Core/test/Crawling/CrawlingHelperTest.php +++ b/module/Core/test/Crawling/CrawlingHelperTest.php @@ -5,39 +5,35 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Crawling; use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Crawling\CrawlingHelper; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface; class CrawlingHelperTest extends TestCase { - use ProphecyTrait; - private CrawlingHelper $helper; - private ObjectProphecy $em; + private MockObject $em; protected function setUp(): void { - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->helper = new CrawlingHelper($this->em->reveal()); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->helper = new CrawlingHelper($this->em); } /** @test */ public function listCrawlableShortCodesDelegatesIntoRepository(): void { - $repo = $this->prophesize(ShortUrlRepositoryInterface::class); - $findCrawlableShortCodes = $repo->findCrawlableShortCodes()->willReturn([]); - $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + $repo = $this->createMock(ShortUrlRepositoryInterface::class); + $repo->expects($this->once())->method('findCrawlableShortCodes')->willReturn([]); + $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn( + $repo, + ); $result = $this->helper->listCrawlableShortCodes(); foreach ($result as $shortCode) { - // Result is a generator and therefore, it needs to be iterated + // $result is a generator and therefore, it needs to be iterated } - - $findCrawlableShortCodes->shouldHaveBeenCalledOnce(); - $getRepo->shouldHaveBeenCalledOnce(); } } From a8514a9ae4257291c3d50f41c86f35582aa9b3c5 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 21 Oct 2022 19:01:41 +0200 Subject: [PATCH 019/182] Migrated DomainServiceTest to use PHPUnit mocks --- module/Core/test/Domain/DomainServiceTest.php | 87 ++++++++++--------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/module/Core/test/Domain/DomainServiceTest.php b/module/Core/test/Domain/DomainServiceTest.php index a4ec22f3..307fbcb3 100644 --- a/module/Core/test/Domain/DomainServiceTest.php +++ b/module/Core/test/Domain/DomainServiceTest.php @@ -5,10 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Domain; use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Config\EmptyNotFoundRedirectConfig; use Shlinkio\Shlink\Core\Config\NotFoundRedirects; use Shlinkio\Shlink\Core\Domain\DomainService; @@ -22,15 +20,13 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class DomainServiceTest extends TestCase { - use ProphecyTrait; - private DomainService $domainService; - private ObjectProphecy $em; + private MockObject $em; protected function setUp(): void { - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->domainService = new DomainService($this->em->reveal(), 'default.com'); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->domainService = new DomainService($this->em, 'default.com'); } /** @@ -39,15 +35,15 @@ class DomainServiceTest extends TestCase */ public function listDomainsDelegatesIntoRepository(array $domains, array $expectedResult, ?ApiKey $apiKey): void { - $repo = $this->prophesize(DomainRepositoryInterface::class); - $getRepo = $this->em->getRepository(Domain::class)->willReturn($repo->reveal()); - $findDomains = $repo->findDomains($apiKey)->willReturn($domains); + $repo = $this->createMock(DomainRepositoryInterface::class); + $repo->expects($this->once())->method('findDomains')->with($this->equalTo($apiKey))->willReturn($domains); + $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(Domain::class))->willReturn( + $repo, + ); $result = $this->domainService->listDomains($apiKey); self::assertEquals($expectedResult, $result); - $getRepo->shouldHaveBeenCalledOnce(); - $findDomains->shouldHaveBeenCalledOnce(); } public function provideExcludedDomains(): iterable @@ -109,10 +105,12 @@ class DomainServiceTest extends TestCase /** @test */ public function getDomainThrowsExceptionWhenDomainIsNotFound(): void { - $find = $this->em->find(Domain::class, '123')->willReturn(null); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Domain::class), + $this->equalTo('123'), + )->willReturn(null); $this->expectException(DomainNotFoundException::class); - $find->shouldBeCalledOnce(); $this->domainService->getDomain('123'); } @@ -121,12 +119,14 @@ class DomainServiceTest extends TestCase public function getDomainReturnsEntityWhenFound(): void { $domain = Domain::withAuthority(''); - $find = $this->em->find(Domain::class, '123')->willReturn($domain); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Domain::class), + $this->equalTo('123'), + )->willReturn($domain); $result = $this->domainService->getDomain('123'); self::assertSame($domain, $result); - $find->shouldHaveBeenCalledOnce(); } /** @@ -136,20 +136,23 @@ class DomainServiceTest extends TestCase public function getOrCreateAlwaysPersistsDomain(?Domain $foundDomain, ?ApiKey $apiKey): void { $authority = 'example.com'; - $repo = $this->prophesize(DomainRepositoryInterface::class); - $repo->findOneByAuthority($authority, $apiKey)->willReturn($foundDomain); - $getRepo = $this->em->getRepository(Domain::class)->willReturn($repo->reveal()); - $persist = $this->em->persist($foundDomain ?? Argument::type(Domain::class)); - $flush = $this->em->flush(); + $repo = $this->createMock(DomainRepositoryInterface::class); + $repo->method('findOneByAuthority')->with($this->equalTo($authority), $this->equalTo($apiKey))->willReturn( + $foundDomain, + ); + $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(Domain::class))->willReturn( + $repo, + ); + $this->em->expects($this->once())->method('persist')->with( + $foundDomain !== null ? $this->equalTo($foundDomain) : $this->isInstanceOf(Domain::class), + ); + $this->em->expects($this->once())->method('flush'); $result = $this->domainService->getOrCreate($authority, $apiKey); if ($foundDomain !== null) { self::assertSame($result, $foundDomain); } - $getRepo->shouldHaveBeenCalledOnce(); - $persist->shouldHaveBeenCalledOnce(); - $flush->shouldHaveBeenCalledOnce(); } /** @test */ @@ -158,14 +161,17 @@ class DomainServiceTest extends TestCase $authority = 'example.com'; $domain = Domain::withAuthority($authority)->setId('1'); $apiKey = ApiKey::fromMeta(ApiKeyMeta::withRoles(RoleDefinition::forDomain($domain))); - $repo = $this->prophesize(DomainRepositoryInterface::class); - $repo->findOneByAuthority($authority, $apiKey)->willReturn(null); - $getRepo = $this->em->getRepository(Domain::class)->willReturn($repo->reveal()); + $repo = $this->createMock(DomainRepositoryInterface::class); + $repo->method('findOneByAuthority')->with($this->equalTo($authority), $this->equalTo($apiKey))->willReturn( + null, + ); + $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(Domain::class))->willReturn( + $repo, + ); + $this->em->expects($this->never())->method('persist'); + $this->em->expects($this->never())->method('flush'); $this->expectException(DomainNotFoundException::class); - $getRepo->shouldBeCalledOnce(); - $this->em->persist(Argument::cetera())->shouldNotBeCalled(); - $this->em->flush()->shouldNotBeCalled(); $this->domainService->getOrCreate($authority, $apiKey); } @@ -177,11 +183,17 @@ class DomainServiceTest extends TestCase public function configureNotFoundRedirectsConfiguresFetchedDomain(?Domain $foundDomain, ?ApiKey $apiKey): void { $authority = 'example.com'; - $repo = $this->prophesize(DomainRepositoryInterface::class); - $repo->findOneByAuthority($authority, $apiKey)->willReturn($foundDomain); - $getRepo = $this->em->getRepository(Domain::class)->willReturn($repo->reveal()); - $persist = $this->em->persist($foundDomain ?? Argument::type(Domain::class)); - $flush = $this->em->flush(); + $repo = $this->createMock(DomainRepositoryInterface::class); + $repo->method('findOneByAuthority')->with($this->equalTo($authority), $this->equalTo($apiKey))->willReturn( + $foundDomain, + ); + $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(Domain::class))->willReturn( + $repo, + ); + $this->em->expects($this->once())->method('persist')->with( + $foundDomain !== null ? $this->equalTo($foundDomain) : $this->isInstanceOf(Domain::class), + ); + $this->em->expects($this->once())->method('flush'); $result = $this->domainService->configureNotFoundRedirects($authority, NotFoundRedirects::withRedirects( 'foo.com', @@ -195,9 +207,6 @@ class DomainServiceTest extends TestCase self::assertEquals('foo.com', $result->baseUrlRedirect()); self::assertEquals('bar.com', $result->regular404Redirect()); self::assertEquals('baz.com', $result->invalidShortUrlRedirect()); - $getRepo->shouldHaveBeenCalledOnce(); - $persist->shouldHaveBeenCalledOnce(); - $flush->shouldHaveBeenCalledOnce(); } public function provideFoundDomains(): iterable From 230e56370a084e24071beca436f9f291474a8398 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 21 Oct 2022 19:24:39 +0200 Subject: [PATCH 020/182] Migrated NotFoundRedirectHandlerTest to use PHPUnit mocks --- module/Core/test/Action/QrCodeActionTest.php | 2 +- .../Core/test/Action/RedirectActionTest.php | 2 +- .../NotFoundRedirectHandlerTest.php | 119 +++++++++--------- 3 files changed, 61 insertions(+), 62 deletions(-) diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index 8bec70e4..06df1ce8 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -42,7 +42,7 @@ class QrCodeActionTest extends TestCase { $shortCode = 'abc123'; $this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')) + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')), )->willThrowException(ShortUrlNotFoundException::fromNotFound(ShortUrlIdentifier::fromShortCodeAndDomain(''))); $delegate = $this->createMock(RequestHandlerInterface::class); $delegate->expects($this->once())->method('handle')->withAnyParameters()->willReturn(new Response()); diff --git a/module/Core/test/Action/RedirectActionTest.php b/module/Core/test/Action/RedirectActionTest.php index f17d32ba..06b7ac40 100644 --- a/module/Core/test/Action/RedirectActionTest.php +++ b/module/Core/test/Action/RedirectActionTest.php @@ -69,7 +69,7 @@ class RedirectActionTest extends TestCase { $shortCode = 'abc123'; $this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')) + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')), )->willThrowException(ShortUrlNotFoundException::fromNotFound(ShortUrlIdentifier::fromShortCodeAndDomain(''))); $this->requestTracker->expects($this->never())->method('trackIfApplicable'); diff --git a/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php b/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php index d08073e2..964f5da4 100644 --- a/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php +++ b/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php @@ -6,10 +6,8 @@ namespace ShlinkioTest\Shlink\Core\ErrorHandler; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequestFactory; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UriInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -22,31 +20,25 @@ use Shlinkio\Shlink\Core\Options\NotFoundRedirectOptions; class NotFoundRedirectHandlerTest extends TestCase { - use ProphecyTrait; - private NotFoundRedirectHandler $middleware; private NotFoundRedirectOptions $redirectOptions; - private ObjectProphecy $resolver; - private ObjectProphecy $domainService; - private ObjectProphecy $next; + private MockObject $resolver; + private MockObject $domainService; + private MockObject $next; private ServerRequestInterface $req; protected function setUp(): void { $this->redirectOptions = new NotFoundRedirectOptions(); - $this->resolver = $this->prophesize(NotFoundRedirectResolverInterface::class); - $this->domainService = $this->prophesize(DomainServiceInterface::class); + $this->resolver = $this->createMock(NotFoundRedirectResolverInterface::class); + $this->domainService = $this->createMock(DomainServiceInterface::class); - $this->middleware = new NotFoundRedirectHandler( - $this->redirectOptions, - $this->resolver->reveal(), - $this->domainService->reveal(), - ); + $this->middleware = new NotFoundRedirectHandler($this->redirectOptions, $this->resolver, $this->domainService); - $this->next = $this->prophesize(RequestHandlerInterface::class); + $this->next = $this->createMock(RequestHandlerInterface::class); $this->req = ServerRequestFactory::fromGlobals()->withAttribute( NotFoundType::class, - $this->prophesize(NotFoundType::class)->reveal(), + $this->createMock(NotFoundType::class), ); } @@ -59,40 +51,49 @@ class NotFoundRedirectHandlerTest extends TestCase $expectedResp = new Response(); $setUp($this->domainService, $this->resolver); - $handle = $this->next->handle($this->req)->willReturn($expectedResp); + $this->next->expects($this->once())->method('handle')->with($this->equalTo($this->req))->willReturn( + $expectedResp, + ); - $result = $this->middleware->process($this->req, $this->next->reveal()); + $result = $this->middleware->process($this->req, $this->next); self::assertSame($expectedResp, $result); - $handle->shouldHaveBeenCalledOnce(); } public function provideNonRedirectScenarios(): iterable { - yield 'no domain' => [function (ObjectProphecy $domainService, ObjectProphecy $resolver): void { - $domainService->findByAuthority(Argument::cetera()) - ->willReturn(null) - ->shouldBeCalledOnce(); - $resolver->resolveRedirectResponse( - Argument::type(NotFoundType::class), - Argument::type(NotFoundRedirectOptions::class), - Argument::type(UriInterface::class), - )->willReturn(null)->shouldBeCalledOnce(); + yield 'no domain' => [function ( + MockObject&DomainServiceInterface $domainService, + MockObject&NotFoundRedirectResolverInterface $resolver, + ): void { + $domainService->expects($this->once())->method('findByAuthority')->withAnyParameters()->willReturn( + null, + ); + $resolver->expects($this->once())->method('resolveRedirectResponse')->with( + $this->isInstanceOf(NotFoundType::class), + $this->isInstanceOf(NotFoundRedirectOptions::class), + $this->isInstanceOf(UriInterface::class), + )->willReturn(null); }]; - yield 'non-redirecting domain' => [function (ObjectProphecy $domainService, ObjectProphecy $resolver): void { - $domainService->findByAuthority(Argument::cetera()) - ->willReturn(Domain::withAuthority('')) - ->shouldBeCalledOnce(); - $resolver->resolveRedirectResponse( - Argument::type(NotFoundType::class), - Argument::type(NotFoundRedirectOptions::class), - Argument::type(UriInterface::class), - )->willReturn(null)->shouldBeCalledOnce(); - $resolver->resolveRedirectResponse( - Argument::type(NotFoundType::class), - Argument::type(Domain::class), - Argument::type(UriInterface::class), - )->willReturn(null)->shouldBeCalledOnce(); + yield 'non-redirecting domain' => [function ( + MockObject&DomainServiceInterface $domainService, + MockObject&NotFoundRedirectResolverInterface $resolver, + ): void { + $domainService->expects($this->once())->method('findByAuthority')->withAnyParameters()->willReturn( + Domain::withAuthority(''), + ); + $resolver->expects($this->exactly(2))->method('resolveRedirectResponse')->withConsecutive( + [ + $this->isInstanceOf(NotFoundType::class), + $this->isInstanceOf(Domain::class), + $this->isInstanceOf(UriInterface::class), + ], + [ + $this->isInstanceOf(NotFoundType::class), + $this->isInstanceOf(NotFoundRedirectOptions::class), + $this->isInstanceOf(UriInterface::class), + ], + )->willReturn(null); }]; } @@ -101,19 +102,17 @@ class NotFoundRedirectHandlerTest extends TestCase { $expectedResp = new Response(); - $findDomain = $this->domainService->findByAuthority(Argument::cetera())->willReturn(null); - $resolveRedirect = $this->resolver->resolveRedirectResponse( - Argument::type(NotFoundType::class), - $this->redirectOptions, - Argument::type(UriInterface::class), + $this->domainService->expects($this->once())->method('findByAuthority')->withAnyParameters()->willReturn(null); + $this->resolver->expects($this->once())->method('resolveRedirectResponse')->with( + $this->isInstanceOf(NotFoundType::class), + $this->equalTo($this->redirectOptions), + $this->isInstanceOf(UriInterface::class), )->willReturn($expectedResp); + $this->next->expects($this->never())->method('handle'); - $result = $this->middleware->process($this->req, $this->next->reveal()); + $result = $this->middleware->process($this->req, $this->next); self::assertSame($expectedResp, $result); - $findDomain->shouldHaveBeenCalledOnce(); - $resolveRedirect->shouldHaveBeenCalledOnce(); - $this->next->handle(Argument::cetera())->shouldNotHaveBeenCalled(); } /** @test */ @@ -122,18 +121,18 @@ class NotFoundRedirectHandlerTest extends TestCase $expectedResp = new Response(); $domain = Domain::withAuthority(''); - $findDomain = $this->domainService->findByAuthority(Argument::cetera())->willReturn($domain); - $resolveRedirect = $this->resolver->resolveRedirectResponse( - Argument::type(NotFoundType::class), + $this->domainService->expects($this->once())->method('findByAuthority')->withAnyParameters()->willReturn( $domain, - Argument::type(UriInterface::class), + ); + $this->resolver->expects($this->once())->method('resolveRedirectResponse')->with( + $this->isInstanceOf(NotFoundType::class), + $this->equalTo($domain), + $this->isInstanceOf(UriInterface::class), )->willReturn($expectedResp); + $this->next->expects($this->never())->method('handle'); - $result = $this->middleware->process($this->req, $this->next->reveal()); + $result = $this->middleware->process($this->req, $this->next); self::assertSame($expectedResp, $result); - $findDomain->shouldHaveBeenCalledOnce(); - $resolveRedirect->shouldHaveBeenCalledOnce(); - $this->next->handle(Argument::cetera())->shouldNotHaveBeenCalled(); } } From d8420258352e0e1045e1a3f3c471f1687f5e6972 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 21 Oct 2022 19:25:29 +0200 Subject: [PATCH 021/182] Migrated NotFoundTemplateHandlerTest to use PHPUnit mocks --- module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php b/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php index 12865171..aa6302a3 100644 --- a/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php +++ b/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php @@ -56,7 +56,7 @@ class NotFoundTemplateHandlerTest extends TestCase RouteResult::fromRoute( new Route( '', - $this->prophesize(MiddlewareInterface::class)->reveal(), + $this->createMock(MiddlewareInterface::class), ['GET'], RedirectAction::class, ), From ff543b151cd05d90b982d591092ac9a1b4363347 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 21 Oct 2022 19:29:02 +0200 Subject: [PATCH 022/182] Migrated NotFoundTrackerMiddlewareTest to use PHPUnit mocks --- .../NotFoundTrackerMiddlewareTest.php | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/module/Core/test/ErrorHandler/NotFoundTrackerMiddlewareTest.php b/module/Core/test/ErrorHandler/NotFoundTrackerMiddlewareTest.php index 81fef1a6..823a7546 100644 --- a/module/Core/test/ErrorHandler/NotFoundTrackerMiddlewareTest.php +++ b/module/Core/test/ErrorHandler/NotFoundTrackerMiddlewareTest.php @@ -4,12 +4,9 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\ErrorHandler; -use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequestFactory; +use PHPUnit\Framework\MockObject\MockObject; 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; use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType; @@ -18,35 +15,31 @@ use Shlinkio\Shlink\Core\Visit\RequestTrackerInterface; class NotFoundTrackerMiddlewareTest extends TestCase { - use ProphecyTrait; - private NotFoundTrackerMiddleware $middleware; private ServerRequestInterface $request; - private ObjectProphecy $requestTracker; - private ObjectProphecy $notFoundType; - private ObjectProphecy $handler; + private MockObject $handler; + private MockObject $requestTracker; protected function setUp(): void { - $this->notFoundType = $this->prophesize(NotFoundType::class); - $this->handler = $this->prophesize(RequestHandlerInterface::class); - $this->handler->handle(Argument::cetera())->willReturn(new Response()); - - $this->requestTracker = $this->prophesize(RequestTrackerInterface::class); - $this->middleware = new NotFoundTrackerMiddleware($this->requestTracker->reveal()); + $this->handler = $this->createMock(RequestHandlerInterface::class); + $this->requestTracker = $this->createMock(RequestTrackerInterface::class); + $this->middleware = new NotFoundTrackerMiddleware($this->requestTracker); $this->request = ServerRequestFactory::fromGlobals()->withAttribute( NotFoundType::class, - $this->notFoundType->reveal(), + $this->createMock(NotFoundType::class), ); } /** @test */ public function delegatesIntoRequestTracker(): void { - $this->middleware->process($this->request, $this->handler->reveal()); + $this->handler->expects($this->once())->method('handle')->with($this->equalTo($this->request)); + $this->requestTracker->expects($this->once())->method('trackNotFoundIfApplicable')->with( + $this->equalTo($this->request), + ); - $this->requestTracker->trackNotFoundIfApplicable($this->request)->shouldHaveBeenCalledOnce(); - $this->handler->handle($this->request)->shouldHaveBeenCalledOnce(); + $this->middleware->process($this->request, $this->handler); } } From bf0b58b344a5aaca68e393178962de2ec4de2a9a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 21 Oct 2022 19:32:25 +0200 Subject: [PATCH 023/182] Migrated NotFoundTypeResolverMiddlewareTest to use PHPUnit mocks --- .../NotFoundTypeResolverMiddlewareTest.php | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/module/Core/test/ErrorHandler/NotFoundTypeResolverMiddlewareTest.php b/module/Core/test/ErrorHandler/NotFoundTypeResolverMiddlewareTest.php index c5d9be79..c2ef419b 100644 --- a/module/Core/test/ErrorHandler/NotFoundTypeResolverMiddlewareTest.php +++ b/module/Core/test/ErrorHandler/NotFoundTypeResolverMiddlewareTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Core\ErrorHandler; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequestFactory; use PHPUnit\Framework\Assert; +use PHPUnit\Framework\MockObject\MockObject; 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; use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType; @@ -18,30 +16,28 @@ use Shlinkio\Shlink\Core\ErrorHandler\NotFoundTypeResolverMiddleware; class NotFoundTypeResolverMiddlewareTest extends TestCase { - use ProphecyTrait; - private NotFoundTypeResolverMiddleware $middleware; - private ObjectProphecy $handler; + private MockObject $handler; protected function setUp(): void { $this->middleware = new NotFoundTypeResolverMiddleware(''); - $this->handler = $this->prophesize(RequestHandlerInterface::class); + $this->handler = $this->createMock(RequestHandlerInterface::class); } /** @test */ public function notFoundTypeIsAddedToRequest(): void { $request = ServerRequestFactory::fromGlobals(); - $handle = $this->handler->handle(Argument::that(function (ServerRequestInterface $req) { - Assert::assertArrayHasKey(NotFoundType::class, $req->getAttributes()); + $this->handler->expects($this->once())->method('handle')->with( + $this->callback(function (ServerRequestInterface $req): bool { + Assert::assertArrayHasKey(NotFoundType::class, $req->getAttributes()); + return true; + }), + )->willReturn(new Response()); - return true; - }))->willReturn(new Response()); - - $this->middleware->process($request, $this->handler->reveal()); + $this->middleware->process($request, $this->handler); self::assertArrayNotHasKey(NotFoundType::class, $request->getAttributes()); - $handle->shouldHaveBeenCalledOnce(); } } From 3608a6d068d768be8bd75e68b2e050ad8eb9a866 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 07:28:15 +0200 Subject: [PATCH 024/182] Migrated CloseDbConnectionEventListenerDelegatorTest to use PHPUnit mocks --- ...seDbConnectionEventListenerDelegatorTest.php | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php index b826802b..7123aa29 100644 --- a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php +++ b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php @@ -4,23 +4,20 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\EventDispatcher; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Container\ContainerInterface; use Shlinkio\Shlink\Common\Doctrine\ReopeningEntityManagerInterface; use Shlinkio\Shlink\Core\EventDispatcher\CloseDbConnectionEventListenerDelegator; class CloseDbConnectionEventListenerDelegatorTest extends TestCase { - use ProphecyTrait; - private CloseDbConnectionEventListenerDelegator $delegator; - private ObjectProphecy $container; + private MockObject $container; protected function setUp(): void { - $this->container = $this->prophesize(ContainerInterface::class); + $this->container = $this->createMock(ContainerInterface::class); $this->delegator = new CloseDbConnectionEventListenerDelegator(); } @@ -35,12 +32,12 @@ class CloseDbConnectionEventListenerDelegatorTest extends TestCase }; }; - $em = $this->prophesize(ReopeningEntityManagerInterface::class); - $getEm = $this->container->get('em')->willReturn($em->reveal()); + $this->container->expects($this->once())->method('get')->with($this->equalTo('em'))->willReturn( + $this->createMock(ReopeningEntityManagerInterface::class), + ); - ($this->delegator)($this->container->reveal(), '', $callback); + ($this->delegator)($this->container, '', $callback); self::assertTrue($callbackInvoked); - $getEm->shouldHaveBeenCalledOnce(); } } From cad53e397a20851fc2d6210fa3f1ba159a9aee0c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 07:32:37 +0200 Subject: [PATCH 025/182] Migrated CloseDbConnectionEventListenerTest to use PHPUnit mocks --- .../CloseDbConnectionEventListenerTest.php | 37 +++++++------------ 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php index 7c4d74c8..76b433e1 100644 --- a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php +++ b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php @@ -5,9 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\EventDispatcher; use Doctrine\DBAL\Connection; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use RuntimeException; use Shlinkio\Shlink\Common\Doctrine\ReopeningEntityManagerInterface; use Shlinkio\Shlink\Core\EventDispatcher\CloseDbConnectionEventListener; @@ -16,13 +15,11 @@ use Throwable; class CloseDbConnectionEventListenerTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $em; + private MockObject $em; protected function setUp(): void { - $this->em = $this->prophesize(ReopeningEntityManagerInterface::class); + $this->em = $this->createMock(ReopeningEntityManagerInterface::class); } /** @@ -31,16 +28,14 @@ class CloseDbConnectionEventListenerTest extends TestCase */ public function connectionIsOpenedBeforeAndClosedAfter(callable $wrapped, bool &$wrappedWasCalled): void { - $conn = $this->prophesize(Connection::class); - $close = $conn->close()->will(function (): void { - }); - $getConn = $this->em->getConnection()->willReturn($conn->reveal()); - $close = $this->em->close()->will(function (): void { - }); - $open = $this->em->open()->will(function (): void { - }); + $conn = $this->createMock(Connection::class); + $conn->expects($this->once())->method('close'); - $eventListener = new CloseDbConnectionEventListener($this->em->reveal(), $wrapped); + $this->em->expects($this->once())->method('getConnection')->willReturn($conn); + $this->em->expects($this->once())->method('close'); + $this->em->expects($this->once())->method('open'); + + $eventListener = new CloseDbConnectionEventListener($this->em, $wrapped); try { ($eventListener)(new stdClass()); @@ -49,25 +44,21 @@ class CloseDbConnectionEventListenerTest extends TestCase } self::assertTrue($wrappedWasCalled); - $close->shouldHaveBeenCalledOnce(); - $getConn->shouldHaveBeenCalledOnce(); - $close->shouldHaveBeenCalledOnce(); - $open->shouldHaveBeenCalledOnce(); } public function provideWrapped(): iterable { - yield 'does not throw exception' => (function (): array { + yield 'does not throw exception' => (static function (): array { $wrappedWasCalled = false; - $wrapped = function () use (&$wrappedWasCalled): void { + $wrapped = static function () use (&$wrappedWasCalled): void { $wrappedWasCalled = true; }; return [$wrapped, &$wrappedWasCalled]; })(); - yield 'throws exception' => (function (): array { + yield 'throws exception' => (static function (): array { $wrappedWasCalled = false; - $wrapped = function () use (&$wrappedWasCalled): void { + $wrapped = static function () use (&$wrappedWasCalled): void { $wrappedWasCalled = true; throw new RuntimeException('Some error'); }; From d07104b8d914dfc9d116606dbe9b249491939aeb Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 07:34:38 +0200 Subject: [PATCH 026/182] Migrated LocateUnlocatedVisitsTest to use PHPUnit mocks --- .../LocateUnlocatedVisitsTest.php | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/module/Core/test/EventDispatcher/LocateUnlocatedVisitsTest.php b/module/Core/test/EventDispatcher/LocateUnlocatedVisitsTest.php index 5eb97592..b3063374 100644 --- a/module/Core/test/EventDispatcher/LocateUnlocatedVisitsTest.php +++ b/module/Core/test/EventDispatcher/LocateUnlocatedVisitsTest.php @@ -4,9 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\EventDispatcher; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\EventDispatcher\Event\GeoLiteDbCreated; use Shlinkio\Shlink\Core\EventDispatcher\LocateUnlocatedVisits; use Shlinkio\Shlink\Core\Visit\Entity\Visit; @@ -17,25 +16,23 @@ use Shlinkio\Shlink\IpGeolocation\Model\Location; class LocateUnlocatedVisitsTest extends TestCase { - use ProphecyTrait; - private LocateUnlocatedVisits $listener; - private ObjectProphecy $locator; - private ObjectProphecy $visitToLocation; + private MockObject $locator; + private MockObject $visitToLocation; protected function setUp(): void { - $this->locator = $this->prophesize(VisitLocatorInterface::class); - $this->visitToLocation = $this->prophesize(VisitToLocationHelperInterface::class); + $this->locator = $this->createMock(VisitLocatorInterface::class); + $this->visitToLocation = $this->createMock(VisitToLocationHelperInterface::class); - $this->listener = new LocateUnlocatedVisits($this->locator->reveal(), $this->visitToLocation->reveal()); + $this->listener = new LocateUnlocatedVisits($this->locator, $this->visitToLocation); } /** @test */ public function locatorIsCalledWhenInvoked(): void { + $this->locator->expects($this->once())->method('locateUnlocatedVisits')->with($this->equalTo($this->listener)); ($this->listener)(new GeoLiteDbCreated()); - $this->locator->locateUnlocatedVisits($this->listener)->shouldHaveBeenCalledOnce(); } /** @test */ @@ -44,11 +41,12 @@ class LocateUnlocatedVisitsTest extends TestCase $visit = Visit::forBasePath(Visitor::emptyInstance()); $location = Location::emptyInstance(); - $resolve = $this->visitToLocation->resolveVisitLocation($visit)->willReturn($location); + $this->visitToLocation->expects($this->once())->method('resolveVisitLocation')->with( + $this->equalTo($visit), + )->willReturn($location); $result = $this->listener->geolocateVisit($visit); self::assertSame($location, $result); - $resolve->shouldHaveBeenCalledOnce(); } } From 736ac8ba90c7a4b06e149338e5fda91377218e6c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 07:54:57 +0200 Subject: [PATCH 027/182] Migrated LocateVisitTest to use PHPUnit mocks --- .../test/EventDispatcher/LocateVisitTest.php | 198 +++++++++--------- 1 file changed, 95 insertions(+), 103 deletions(-) diff --git a/module/Core/test/EventDispatcher/LocateVisitTest.php b/module/Core/test/EventDispatcher/LocateVisitTest.php index 068df028..b90b5b7f 100644 --- a/module/Core/test/EventDispatcher/LocateVisitTest.php +++ b/module/Core/test/EventDispatcher/LocateVisitTest.php @@ -6,10 +6,8 @@ namespace ShlinkioTest\Shlink\Core\EventDispatcher; use Doctrine\ORM\EntityManagerInterface; use OutOfRangeException; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Common\Util\IpAddress; @@ -27,31 +25,27 @@ use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface; class LocateVisitTest extends TestCase { - use ProphecyTrait; - private LocateVisit $locateVisit; - private ObjectProphecy $ipLocationResolver; - private ObjectProphecy $em; - private ObjectProphecy $logger; - private ObjectProphecy $dbUpdater; - private ObjectProphecy $eventDispatcher; + private MockObject $ipLocationResolver; + private MockObject $em; + private MockObject $logger; + private MockObject $eventDispatcher; + private MockObject $dbUpdater; protected function setUp(): void { - $this->ipLocationResolver = $this->prophesize(IpLocationResolverInterface::class); - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->logger = $this->prophesize(LoggerInterface::class); - $this->eventDispatcher = $this->prophesize(EventDispatcherInterface::class); - - $this->dbUpdater = $this->prophesize(DbUpdaterInterface::class); - $this->dbUpdater->databaseFileExists()->willReturn(true); + $this->ipLocationResolver = $this->createMock(IpLocationResolverInterface::class); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); + $this->dbUpdater = $this->createMock(DbUpdaterInterface::class); $this->locateVisit = new LocateVisit( - $this->ipLocationResolver->reveal(), - $this->em->reveal(), - $this->logger->reveal(), - $this->dbUpdater->reveal(), - $this->eventDispatcher->reveal(), + $this->ipLocationResolver, + $this->em, + $this->logger, + $this->dbUpdater, + $this->eventDispatcher, ); } @@ -59,97 +53,91 @@ class LocateVisitTest extends TestCase public function invalidVisitLogsWarning(): void { $event = new UrlVisited('123'); - $findVisit = $this->em->find(Visit::class, '123')->willReturn(null); - $logWarning = $this->logger->warning('Tried to locate visit with id "{visitId}", but it does not exist.', [ - 'visitId' => 123, - ]); - $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function (): void { - }); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo('123'), + )->willReturn(null); + $this->em->expects($this->never())->method('flush'); + $this->logger->expects($this->once())->method('warning')->with( + $this->equalTo('Tried to locate visit with id "{visitId}", but it does not exist.'), + $this->equalTo(['visitId' => 123]), + ); + $this->eventDispatcher->expects($this->never())->method('dispatch')->with( + $this->equalTo(new VisitLocated('123')), + ); + $this->ipLocationResolver->expects($this->never())->method('resolveIpLocation'); ($this->locateVisit)($event); - - $findVisit->shouldHaveBeenCalledOnce(); - $this->em->flush()->shouldNotHaveBeenCalled(); - $this->ipLocationResolver->resolveIpLocation(Argument::cetera())->shouldNotHaveBeenCalled(); - $logWarning->shouldHaveBeenCalled(); - $dispatch->shouldNotHaveBeenCalled(); } /** @test */ public function nonExistingGeoLiteDbLogsWarning(): void { $event = new UrlVisited('123'); - $findVisit = $this->em->find(Visit::class, '123')->willReturn( - Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', '')), + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo('123'), + )->willReturn(Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', ''))); + $this->em->expects($this->never())->method('flush'); + $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(false); + $this->logger->expects($this->once())->method('warning')->with( + $this->equalTo('Tried to locate visit with id "{visitId}", but a GeoLite2 db was not found.'), + $this->equalTo(['visitId' => 123]), ); - $dbExists = $this->dbUpdater->databaseFileExists()->willReturn(false); - $logWarning = $this->logger->warning( - 'Tried to locate visit with id "{visitId}", but a GeoLite2 db was not found.', - ['visitId' => 123], + $this->eventDispatcher->expects($this->once())->method('dispatch')->with( + $this->equalTo(new VisitLocated('123')), ); - $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function (): void { - }); + $this->ipLocationResolver->expects($this->never())->method('resolveIpLocation'); ($this->locateVisit)($event); - - $findVisit->shouldHaveBeenCalledOnce(); - $dbExists->shouldHaveBeenCalledOnce(); - $this->em->flush()->shouldNotHaveBeenCalled(); - $this->ipLocationResolver->resolveIpLocation(Argument::cetera())->shouldNotHaveBeenCalled(); - $logWarning->shouldHaveBeenCalled(); - $dispatch->shouldHaveBeenCalledOnce(); } /** @test */ public function invalidAddressLogsWarning(): void { $event = new UrlVisited('123'); - $findVisit = $this->em->find(Visit::class, '123')->willReturn( - Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', '')), + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo('123'), + )->willReturn(Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', ''))); + $this->em->expects($this->never())->method('flush'); + $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(true); + $this->ipLocationResolver->expects( + $this->once(), + )->method('resolveIpLocation')->withAnyParameters()->willThrowException(WrongIpException::fromIpAddress('')); + $this->logger->expects($this->once())->method('warning')->with( + $this->equalTo('Tried to locate visit with id "{visitId}", but its address seems to be wrong. {e}'), + $this->isType('array'), ); - $resolveLocation = $this->ipLocationResolver->resolveIpLocation(Argument::cetera())->willThrow( - WrongIpException::class, + $this->eventDispatcher->expects($this->once())->method('dispatch')->with( + $this->equalTo(new VisitLocated('123')), ); - $logWarning = $this->logger->warning( - 'Tried to locate visit with id "{visitId}", but its address seems to be wrong. {e}', - Argument::type('array'), - ); - $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function (): void { - }); ($this->locateVisit)($event); - - $findVisit->shouldHaveBeenCalledOnce(); - $resolveLocation->shouldHaveBeenCalledOnce(); - $logWarning->shouldHaveBeenCalled(); - $this->em->flush()->shouldNotHaveBeenCalled(); - $dispatch->shouldHaveBeenCalledOnce(); } /** @test */ public function unhandledExceptionLogsError(): void { $event = new UrlVisited('123'); - $findVisit = $this->em->find(Visit::class, '123')->willReturn( - Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', '')), + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo('123'), + )->willReturn(Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', ''))); + $this->em->expects($this->never())->method('flush'); + $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(true); + $this->ipLocationResolver->expects( + $this->once(), + )->method('resolveIpLocation')->withAnyParameters()->willThrowException(new OutOfRangeException()); + $this->logger->expects($this->once())->method('error')->with( + $this->equalTo('An unexpected error occurred while trying to locate visit with id "{visitId}". {e}'), + $this->isType('array'), ); - $resolveLocation = $this->ipLocationResolver->resolveIpLocation(Argument::cetera())->willThrow( - OutOfRangeException::class, + $this->eventDispatcher->expects($this->once())->method('dispatch')->with( + $this->equalTo(new VisitLocated('123')), ); - $logError = $this->logger->error( - 'An unexpected error occurred while trying to locate visit with id "{visitId}". {e}', - Argument::type('array'), - ); - $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function (): void { - }); ($this->locateVisit)($event); - - $findVisit->shouldHaveBeenCalledOnce(); - $resolveLocation->shouldHaveBeenCalledOnce(); - $logError->shouldHaveBeenCalled(); - $this->em->flush()->shouldNotHaveBeenCalled(); - $dispatch->shouldHaveBeenCalledOnce(); } /** @@ -159,21 +147,22 @@ class LocateVisitTest extends TestCase public function nonLocatableVisitsResolveToEmptyLocations(Visit $visit): void { $event = new UrlVisited('123'); - $findVisit = $this->em->find(Visit::class, '123')->willReturn($visit); - $flush = $this->em->flush()->will(function (): void { - }); - $resolveIp = $this->ipLocationResolver->resolveIpLocation(Argument::any()); - $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function (): void { - }); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo('123'), + )->willReturn($visit); + $this->em->expects($this->once())->method('flush'); + $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(true); + $this->ipLocationResolver->expects($this->never())->method('resolveIpLocation'); + + $this->eventDispatcher->expects($this->once())->method('dispatch')->with( + $this->equalTo(new VisitLocated('123')), + ); + $this->logger->expects($this->never())->method('warning'); ($this->locateVisit)($event); self::assertEquals($visit->getVisitLocation(), VisitLocation::fromGeolocation(Location::emptyInstance())); - $findVisit->shouldHaveBeenCalledOnce(); - $flush->shouldHaveBeenCalledOnce(); - $resolveIp->shouldNotHaveBeenCalled(); - $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled(); - $dispatch->shouldHaveBeenCalledOnce(); } public function provideNonLocatableVisits(): iterable @@ -195,21 +184,24 @@ class LocateVisitTest extends TestCase $location = new Location('', '', '', '', 0.0, 0.0, ''); $event = UrlVisited::withOriginalIpAddress('123', $originalIpAddress); - $findVisit = $this->em->find(Visit::class, '123')->willReturn($visit); - $flush = $this->em->flush()->will(function (): void { - }); - $resolveIp = $this->ipLocationResolver->resolveIpLocation($ipAddr)->willReturn($location); - $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function (): void { - }); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo('123'), + )->willReturn($visit); + $this->em->expects($this->once())->method('flush'); + $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(true); + $this->ipLocationResolver->expects($this->once())->method('resolveIpLocation')->with( + $this->equalTo($ipAddr), + )->willReturn($location); + + $this->eventDispatcher->expects($this->once())->method('dispatch')->with( + $this->equalTo(new VisitLocated('123')), + ); + $this->logger->expects($this->never())->method('warning'); ($this->locateVisit)($event); self::assertEquals($visit->getVisitLocation(), VisitLocation::fromGeolocation($location)); - $findVisit->shouldHaveBeenCalledOnce(); - $flush->shouldHaveBeenCalledOnce(); - $resolveIp->shouldHaveBeenCalledOnce(); - $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled(); - $dispatch->shouldHaveBeenCalledOnce(); } public function provideIpAddresses(): iterable From e01e370d16278affe2fbe32f29c8314f17370eed Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 08:08:49 +0200 Subject: [PATCH 028/182] Migrated NotifyVisitToWebHooksTest to use PHPUnit mocks --- .../NotifyVisitToWebHooksTest.php | 101 +++++++----------- 1 file changed, 41 insertions(+), 60 deletions(-) diff --git a/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php b/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php index 2751dbfd..5148748a 100644 --- a/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php +++ b/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php @@ -12,10 +12,8 @@ use GuzzleHttp\Promise\FulfilledPromise; use GuzzleHttp\Promise\RejectedPromise; use GuzzleHttp\RequestOptions; use PHPUnit\Framework\Assert; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\EventDispatcher\Event\VisitLocated; use Shlinkio\Shlink\Core\EventDispatcher\NotifyVisitToWebHooks; @@ -32,66 +30,52 @@ use function Functional\contains; class NotifyVisitToWebHooksTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $httpClient; - private ObjectProphecy $em; - private ObjectProphecy $logger; + private MockObject $httpClient; + private MockObject $em; + private MockObject $logger; protected function setUp(): void { - $this->httpClient = $this->prophesize(ClientInterface::class); - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->logger = $this->prophesize(LoggerInterface::class); + $this->httpClient = $this->createMock(ClientInterface::class); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); } /** @test */ public function emptyWebhooksMakeNoFurtherActions(): void { - $find = $this->em->find(Visit::class, '1')->willReturn(null); + $this->em->expects($this->never())->method('find'); $this->createListener([])(new VisitLocated('1')); - - $find->shouldNotHaveBeenCalled(); } /** @test */ public function invalidVisitDoesNotPerformAnyRequest(): void { - $find = $this->em->find(Visit::class, '1')->willReturn(null); - $requestAsync = $this->httpClient->requestAsync( - RequestMethodInterface::METHOD_POST, - Argument::type('string'), - Argument::type('array'), - )->willReturn(new FulfilledPromise('')); - $logWarning = $this->logger->warning( - 'Tried to notify webhooks for visit with id "{visitId}", but it does not exist.', - ['visitId' => '1'], + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo('1'), + )->willReturn(null); + $this->httpClient->expects($this->never())->method('requestAsync'); + $this->logger->expects($this->once())->method('warning')->with( + $this->equalTo('Tried to notify webhooks for visit with id "{visitId}", but it does not exist.'), + $this->equalTo(['visitId' => '1']), ); $this->createListener(['foo', 'bar'])(new VisitLocated('1')); - - $find->shouldHaveBeenCalledOnce(); - $logWarning->shouldHaveBeenCalledOnce(); - $requestAsync->shouldNotHaveBeenCalled(); } /** @test */ public function orphanVisitDoesNotPerformAnyRequestWhenDisabled(): void { - $find = $this->em->find(Visit::class, '1')->willReturn(Visit::forBasePath(Visitor::emptyInstance())); - $requestAsync = $this->httpClient->requestAsync( - RequestMethodInterface::METHOD_POST, - Argument::type('string'), - Argument::type('array'), - )->willReturn(new FulfilledPromise('')); - $logWarning = $this->logger->warning(Argument::cetera()); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo('1'), + )->willReturn(Visit::forBasePath(Visitor::emptyInstance())); + $this->httpClient->expects($this->never())->method('requestAsync'); + $this->logger->expects($this->never())->method('warning'); $this->createListener(['foo', 'bar'], false)(new VisitLocated('1')); - - $find->shouldHaveBeenCalledOnce(); - $logWarning->shouldNotHaveBeenCalled(); - $requestAsync->shouldNotHaveBeenCalled(); } /** @@ -103,16 +87,19 @@ class NotifyVisitToWebHooksTest extends TestCase $webhooks = ['foo', 'invalid', 'bar', 'baz']; $invalidWebhooks = ['invalid', 'baz']; - $find = $this->em->find(Visit::class, '1')->willReturn($visit); - $requestAsync = $this->httpClient->requestAsync( - RequestMethodInterface::METHOD_POST, - Argument::type('string'), - Argument::that(function (array $requestOptions) use ($expectedResponseKeys) { + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo('1'), + )->willReturn($visit); + $this->httpClient->expects($this->exactly(count($webhooks)))->method('requestAsync')->with( + $this->equalTo(RequestMethodInterface::METHOD_POST), + $this->istype('string'), + $this->callback(function (array $requestOptions) use ($expectedResponseKeys) { Assert::assertArrayHasKey(RequestOptions::HEADERS, $requestOptions); Assert::assertArrayHasKey(RequestOptions::JSON, $requestOptions); Assert::assertArrayHasKey(RequestOptions::TIMEOUT, $requestOptions); - Assert::assertEquals($requestOptions[RequestOptions::TIMEOUT], 10); - Assert::assertEquals($requestOptions[RequestOptions::HEADERS], ['User-Agent' => 'Shlink:v1.2.3']); + Assert::assertEquals(10, $requestOptions[RequestOptions::TIMEOUT]); + Assert::assertEquals(['User-Agent' => 'Shlink:v1.2.3'], $requestOptions[RequestOptions::HEADERS]); $json = $requestOptions[RequestOptions::JSON]; Assert::assertCount(count($expectedResponseKeys), $json); @@ -120,30 +107,24 @@ class NotifyVisitToWebHooksTest extends TestCase Assert::assertArrayHasKey($key, $json); } - return $requestOptions; + return true; }), - )->will(function (array $args) use ($invalidWebhooks) { - [, $webhook] = $args; + )->willReturnCallback(function ($_, $webhook) use ($invalidWebhooks) { $shouldReject = contains($invalidWebhooks, $webhook); - return $shouldReject ? new RejectedPromise(new Exception('')) : new FulfilledPromise(''); }); - $logWarning = $this->logger->warning( - 'Failed to notify visit with id "{visitId}" to webhook "{webhook}". {e}', - Argument::that(function (array $extra) { + $this->logger->expects($this->exactly(count($invalidWebhooks)))->method('warning')->with( + $this->equalTo('Failed to notify visit with id "{visitId}" to webhook "{webhook}". {e}'), + $this->callback(function (array $extra): bool { Assert::assertArrayHasKey('webhook', $extra); Assert::assertArrayHasKey('visitId', $extra); Assert::assertArrayHasKey('e', $extra); - return $extra; + return true; }), ); $this->createListener($webhooks)(new VisitLocated('1')); - - $find->shouldHaveBeenCalledOnce(); - $requestAsync->shouldHaveBeenCalledTimes(count($webhooks)); - $logWarning->shouldHaveBeenCalledTimes(count($invalidWebhooks)); } public function provideVisits(): iterable @@ -158,9 +139,9 @@ class NotifyVisitToWebHooksTest extends TestCase private function createListener(array $webhooks, bool $notifyOrphanVisits = true): NotifyVisitToWebHooks { return new NotifyVisitToWebHooks( - $this->httpClient->reveal(), - $this->em->reveal(), - $this->logger->reveal(), + $this->httpClient, + $this->em, + $this->logger, new WebhookOptions( ['webhooks' => $webhooks, 'notify_orphan_visits_to_webhooks' => $notifyOrphanVisits], ), From 51fcbfb3c2dd64765d922f5828ff86df8c574e11 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 09:42:21 +0200 Subject: [PATCH 029/182] Migrated UpdateGeoLiteDbTest to use PHPUnit mocks --- .../EventDispatcher/UpdateGeoLiteDbTest.php | 81 +++++++------------ 1 file changed, 30 insertions(+), 51 deletions(-) diff --git a/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php b/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php index 9ce20801..5d916a90 100644 --- a/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php +++ b/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php @@ -4,10 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\EventDispatcher; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; use RuntimeException; @@ -20,24 +18,18 @@ use function Functional\map; class UpdateGeoLiteDbTest extends TestCase { - use ProphecyTrait; - private UpdateGeoLiteDb $listener; - private ObjectProphecy $dbUpdater; - private ObjectProphecy $logger; - private ObjectProphecy $eventDispatcher; + private MockObject $dbUpdater; + private MockObject $logger; + private MockObject $eventDispatcher; protected function setUp(): void { - $this->dbUpdater = $this->prophesize(GeolocationDbUpdaterInterface::class); - $this->logger = $this->prophesize(LoggerInterface::class); - $this->eventDispatcher = $this->prophesize(EventDispatcherInterface::class); + $this->dbUpdater = $this->createMock(GeolocationDbUpdaterInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); - $this->listener = new UpdateGeoLiteDb( - $this->dbUpdater->reveal(), - $this->logger->reveal(), - $this->eventDispatcher->reveal(), - ); + $this->listener = new UpdateGeoLiteDb($this->dbUpdater, $this->logger, $this->eventDispatcher); } /** @test */ @@ -45,15 +37,15 @@ class UpdateGeoLiteDbTest extends TestCase { $e = new RuntimeException(); - $checkDbUpdate = $this->dbUpdater->checkDbUpdate(Argument::cetera())->willThrow($e); - $logError = $this->logger->error('GeoLite2 database download failed. {e}', ['e' => $e]); + $this->dbUpdater->expects($this->once())->method('checkDbUpdate')->withAnyParameters()->willThrowException($e); + $this->logger->expects($this->once())->method('error')->with( + $this->equalTo('GeoLite2 database download failed. {e}'), + $this->equalTo(['e' => $e]), + ); + $this->logger->expects($this->never())->method('notice'); + $this->eventDispatcher->expects($this->never())->method('dispatch'); ($this->listener)(); - - $checkDbUpdate->shouldHaveBeenCalledOnce(); - $logError->shouldHaveBeenCalledOnce(); - $this->logger->notice(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->eventDispatcher->dispatch(Argument::cetera())->shouldNotHaveBeenCalled(); } /** @@ -62,22 +54,17 @@ class UpdateGeoLiteDbTest extends TestCase */ public function noticeMessageIsPrintedWhenFirstCallbackIsInvoked(bool $oldDbExists, string $expectedMessage): void { - $checkDbUpdate = $this->dbUpdater->checkDbUpdate(Argument::cetera())->will( - function (array $args) use ($oldDbExists): GeolocationResult { - [$firstCallback] = $args; + $this->dbUpdater->expects($this->once())->method('checkDbUpdate')->withAnyParameters()->willReturnCallback( + function (callable $firstCallback) use ($oldDbExists): GeolocationResult { $firstCallback($oldDbExists); - return GeolocationResult::DB_IS_UP_TO_DATE; }, ); - $logNotice = $this->logger->notice($expectedMessage); + $this->logger->expects($this->once())->method('notice')->with($this->equalTo($expectedMessage)); + $this->logger->expects($this->never())->method('error'); + $this->eventDispatcher->expects($this->never())->method('dispatch'); ($this->listener)(); - - $checkDbUpdate->shouldHaveBeenCalledOnce(); - $logNotice->shouldHaveBeenCalledOnce(); - $this->logger->error(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->eventDispatcher->dispatch(Argument::cetera())->shouldNotHaveBeenCalled(); } public function provideFlags(): iterable @@ -96,10 +83,8 @@ class UpdateGeoLiteDbTest extends TestCase bool $oldDbExists, ?string $expectedMessage, ): void { - $checkDbUpdate = $this->dbUpdater->checkDbUpdate(Argument::cetera())->will( - function (array $args) use ($total, $downloaded, $oldDbExists): GeolocationResult { - [, $secondCallback] = $args; - + $this->dbUpdater->expects($this->once())->method('checkDbUpdate')->withAnyParameters()->willReturnCallback( + function ($_, callable $secondCallback) use ($total, $downloaded, $oldDbExists): GeolocationResult { // Invoke several times to ensure the log is printed only once $secondCallback($total, $downloaded, $oldDbExists); $secondCallback($total, $downloaded, $oldDbExists); @@ -108,18 +93,12 @@ class UpdateGeoLiteDbTest extends TestCase return GeolocationResult::DB_UPDATED; }, ); - $logNotice = $this->logger->notice($expectedMessage ?? Argument::cetera()); + $logNoticeExpectation = $expectedMessage !== null ? $this->once() : $this->never(); + $this->logger->expects($logNoticeExpectation)->method('notice')->with($this->equalTo($expectedMessage)); + $this->logger->expects($this->never())->method('error'); + $this->eventDispatcher->expects($this->never())->method('dispatch'); ($this->listener)(); - - if ($expectedMessage !== null) { - $logNotice->shouldHaveBeenCalledOnce(); - } else { - $logNotice->shouldNotHaveBeenCalled(); - } - $checkDbUpdate->shouldHaveBeenCalledOnce(); - $this->logger->error(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->eventDispatcher->dispatch(Argument::cetera())->shouldNotHaveBeenCalled(); } public function provideDownloaded(): iterable @@ -142,12 +121,12 @@ class UpdateGeoLiteDbTest extends TestCase GeolocationResult $result, int $expectedDispatches, ): void { - $checkDbUpdate = $this->dbUpdater->checkDbUpdate(Argument::cetera())->willReturn($result); + $this->dbUpdater->expects($this->once())->method('checkDbUpdate')->withAnyParameters()->willReturn($result); + $this->eventDispatcher->expects($this->exactly($expectedDispatches))->method('dispatch')->with( + $this->equalTo(new GeoLiteDbCreated()) + ); ($this->listener)(); - - $checkDbUpdate->shouldHaveBeenCalledOnce(); - $this->eventDispatcher->dispatch(new GeoLiteDbCreated())->shouldHaveBeenCalledTimes($expectedDispatches); } public function provideGeolocationResults(): iterable From 16a951b938d90c9a7695314854647be95e94b8f4 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 09:50:12 +0200 Subject: [PATCH 030/182] Migrated NotifyNewShortUrlToMercureTest to use PHPUnit mocks --- .../NotifyNewShortUrlToMercureTest.php | 96 ++++++++++--------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php b/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php index 8e15a3e0..6ae76f8c 100644 --- a/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php +++ b/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php @@ -6,10 +6,8 @@ namespace ShlinkioTest\Shlink\Core\EventDispatcher\Mercure; use Doctrine\ORM\EntityManagerInterface; use Exception; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface; use Shlinkio\Shlink\Common\UpdatePublishing\Update; @@ -20,44 +18,43 @@ use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; class NotifyNewShortUrlToMercureTest extends TestCase { - use ProphecyTrait; - private NotifyNewShortUrlToMercure $listener; - private ObjectProphecy $helper; - private ObjectProphecy $updatesGenerator; - private ObjectProphecy $em; - private ObjectProphecy $logger; + private MockObject $helper; + private MockObject $updatesGenerator; + private MockObject $em; + private MockObject $logger; protected function setUp(): void { - $this->helper = $this->prophesize(PublishingHelperInterface::class); - $this->updatesGenerator = $this->prophesize(PublishingUpdatesGeneratorInterface::class); - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->logger = $this->prophesize(LoggerInterface::class); + $this->helper = $this->createMock(PublishingHelperInterface::class); + $this->updatesGenerator = $this->createMock(PublishingUpdatesGeneratorInterface::class); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); $this->listener = new NotifyNewShortUrlToMercure( - $this->helper->reveal(), - $this->updatesGenerator->reveal(), - $this->em->reveal(), - $this->logger->reveal(), + $this->helper, + $this->updatesGenerator, + $this->em, + $this->logger, ); } /** @test */ public function messageIsLoggedWhenShortUrlIsNotFound(): void { - $find = $this->em->find(ShortUrl::class, '123')->willReturn(null); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(ShortUrl::class), + $this->equalTo('123'), + )->willReturn(null); + $this->helper->expects($this->never())->method('publishUpdate'); + $this->updatesGenerator->expects($this->never())->method('newShortUrlUpdate'); + $this->logger->expects($this->once())->method('warning')->with( + $this->equalTo('Tried to notify {name} for new short URL with id "{shortUrlId}", but it does not exist.'), + $this->equalTo(['shortUrlId' => '123', 'name' => 'Mercure']), + ); + $this->logger->expects($this->never())->method('debug'); ($this->listener)(new ShortUrlCreated('123')); - - $find->shouldHaveBeenCalledOnce(); - $this->logger->warning( - 'Tried to notify {name} for new short URL with id "{shortUrlId}", but it does not exist.', - ['shortUrlId' => '123', 'name' => 'Mercure'], - )->shouldHaveBeenCalledOnce(); - $this->helper->publishUpdate(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->updatesGenerator->newShortUrlUpdate(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); } /** @test */ @@ -66,16 +63,18 @@ class NotifyNewShortUrlToMercureTest extends TestCase $shortUrl = ShortUrl::withLongUrl(''); $update = Update::forTopicAndPayload('', []); - $find = $this->em->find(ShortUrl::class, '123')->willReturn($shortUrl); - $newUpdate = $this->updatesGenerator->newShortUrlUpdate($shortUrl)->willReturn($update); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(ShortUrl::class), + $this->equalTo('123'), + )->willReturn($shortUrl); + $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with( + $this->equalTo($shortUrl), + )->willReturn($update); + $this->helper->expects($this->once())->method('publishUpdate')->with($this->equalTo($update)); + $this->logger->expects($this->never())->method('warning'); + $this->logger->expects($this->never())->method('debug'); ($this->listener)(new ShortUrlCreated('123')); - - $find->shouldHaveBeenCalledOnce(); - $newUpdate->shouldHaveBeenCalledOnce(); - $this->helper->publishUpdate($update)->shouldHaveBeenCalledOnce(); - $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); } /** @test */ @@ -85,19 +84,22 @@ class NotifyNewShortUrlToMercureTest extends TestCase $update = Update::forTopicAndPayload('', []); $e = new Exception('Error'); - $find = $this->em->find(ShortUrl::class, '123')->willReturn($shortUrl); - $newUpdate = $this->updatesGenerator->newShortUrlUpdate($shortUrl)->willReturn($update); - $publish = $this->helper->publishUpdate($update)->willThrow($e); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(ShortUrl::class), + $this->equalTo('123'), + )->willReturn($shortUrl); + $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with( + $this->equalTo($shortUrl), + )->willReturn($update); + $this->helper->expects($this->once())->method('publishUpdate')->with( + $this->equalTo($update), + )->willThrowException($e); + $this->logger->expects($this->never())->method('warning'); + $this->logger->expects($this->once())->method('debug')->with( + $this->equalTo('Error while trying to notify {name} with new short URL. {e}'), + $this->equalTo(['e' => $e, 'name' => 'Mercure']), + ); ($this->listener)(new ShortUrlCreated('123')); - - $find->shouldHaveBeenCalledOnce(); - $newUpdate->shouldHaveBeenCalledOnce(); - $publish->shouldHaveBeenCalledOnce(); - $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->logger->debug( - 'Error while trying to notify {name} with new short URL. {e}', - ['e' => $e, 'name' => 'Mercure'], - )->shouldHaveBeenCalledOnce(); } } From 8298f9d49110d383d8d9c59b816886a9f6a5a52a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 10:03:05 +0200 Subject: [PATCH 031/182] Migrated NotifyVisitToMercureTest to use PHPUnit mocks --- .../Mercure/NotifyVisitToMercureTest.php | 153 ++++++++---------- 1 file changed, 67 insertions(+), 86 deletions(-) diff --git a/module/Core/test/EventDispatcher/Mercure/NotifyVisitToMercureTest.php b/module/Core/test/EventDispatcher/Mercure/NotifyVisitToMercureTest.php index 726e272c..193adac3 100644 --- a/module/Core/test/EventDispatcher/Mercure/NotifyVisitToMercureTest.php +++ b/module/Core/test/EventDispatcher/Mercure/NotifyVisitToMercureTest.php @@ -5,10 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\EventDispatcher\Mercure; use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Log\LoggerInterface; use RuntimeException; use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface; @@ -23,55 +21,41 @@ use Shlinkio\Shlink\Core\Visit\Model\VisitType; class NotifyVisitToMercureTest extends TestCase { - use ProphecyTrait; - private NotifyVisitToMercure $listener; - private ObjectProphecy $helper; - private ObjectProphecy $updatesGenerator; - private ObjectProphecy $em; - private ObjectProphecy $logger; + private MockObject $helper; + private MockObject $updatesGenerator; + private MockObject $em; + private MockObject $logger; protected function setUp(): void { - $this->helper = $this->prophesize(PublishingHelperInterface::class); - $this->updatesGenerator = $this->prophesize(PublishingUpdatesGeneratorInterface::class); - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->logger = $this->prophesize(LoggerInterface::class); + $this->helper = $this->createMock(PublishingHelperInterface::class); + $this->updatesGenerator = $this->createMock(PublishingUpdatesGeneratorInterface::class); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); - $this->listener = new NotifyVisitToMercure( - $this->helper->reveal(), - $this->updatesGenerator->reveal(), - $this->em->reveal(), - $this->logger->reveal(), - ); + $this->listener = new NotifyVisitToMercure($this->helper, $this->updatesGenerator, $this->em, $this->logger); } /** @test */ public function notificationsAreNotSentWhenVisitCannotBeFound(): void { $visitId = '123'; - $findVisit = $this->em->find(Visit::class, $visitId)->willReturn(null); - $logWarning = $this->logger->warning( - 'Tried to notify {name} for visit with id "{visitId}", but it does not exist.', - ['visitId' => $visitId, 'name' => 'Mercure'], + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo($visitId), + )->willReturn(null); + $this->logger->expects($this->once())->method('warning')->with( + $this->equalTo('Tried to notify {name} for visit with id "{visitId}", but it does not exist.'), + $this->equalTo(['visitId' => $visitId, 'name' => 'Mercure']), ); - $logDebug = $this->logger->debug(Argument::cetera()); - $buildNewShortUrlVisitUpdate = $this->updatesGenerator->newShortUrlVisitUpdate( - Argument::type(Visit::class), - ); - $buildNewOrphanVisitUpdate = $this->updatesGenerator->newOrphanVisitUpdate(Argument::type(Visit::class)); - $buildNewVisitUpdate = $this->updatesGenerator->newVisitUpdate(Argument::type(Visit::class)); - $publish = $this->helper->publishUpdate(Argument::type(Update::class)); + $this->logger->expects($this->never())->method('debug'); + $this->updatesGenerator->expects($this->never())->method('newShortUrlVisitUpdate'); + $this->updatesGenerator->expects($this->never())->method('newOrphanVisitUpdate'); + $this->updatesGenerator->expects($this->never())->method('newVisitUpdate'); + $this->helper->expects($this->never())->method('publishUpdate'); ($this->listener)(new VisitLocated($visitId)); - - $findVisit->shouldHaveBeenCalledOnce(); - $logWarning->shouldHaveBeenCalledOnce(); - $logDebug->shouldNotHaveBeenCalled(); - $buildNewShortUrlVisitUpdate->shouldNotHaveBeenCalled(); - $buildNewVisitUpdate->shouldNotHaveBeenCalled(); - $buildNewOrphanVisitUpdate->shouldNotHaveBeenCalled(); - $publish->shouldNotHaveBeenCalled(); } /** @test */ @@ -81,23 +65,22 @@ class NotifyVisitToMercureTest extends TestCase $visit = Visit::forValidShortUrl(ShortUrl::createEmpty(), Visitor::emptyInstance()); $update = Update::forTopicAndPayload('', []); - $findVisit = $this->em->find(Visit::class, $visitId)->willReturn($visit); - $logWarning = $this->logger->warning(Argument::cetera()); - $logDebug = $this->logger->debug(Argument::cetera()); - $buildNewShortUrlVisitUpdate = $this->updatesGenerator->newShortUrlVisitUpdate($visit)->willReturn($update); - $buildNewOrphanVisitUpdate = $this->updatesGenerator->newOrphanVisitUpdate($visit)->willReturn($update); - $buildNewVisitUpdate = $this->updatesGenerator->newVisitUpdate($visit)->willReturn($update); - $publish = $this->helper->publishUpdate($update); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo($visitId), + )->willReturn($visit); + $this->logger->expects($this->never())->method('warning'); + $this->logger->expects($this->never())->method('debug'); + $this->updatesGenerator->expects($this->once())->method('newShortUrlVisitUpdate')->with( + $this->equalTo($visit), + )->willReturn($update); + $this->updatesGenerator->expects($this->never())->method('newOrphanVisitUpdate'); + $this->updatesGenerator->expects($this->once())->method('newVisitUpdate')->with( + $this->equalTo($visit), + )->willReturn($update); + $this->helper->expects($this->exactly(2))->method('publishUpdate')->with($this->equalTo($update)); ($this->listener)(new VisitLocated($visitId)); - - $findVisit->shouldHaveBeenCalledOnce(); - $logWarning->shouldNotHaveBeenCalled(); - $logDebug->shouldNotHaveBeenCalled(); - $buildNewShortUrlVisitUpdate->shouldHaveBeenCalledOnce(); - $buildNewVisitUpdate->shouldHaveBeenCalledOnce(); - $buildNewOrphanVisitUpdate->shouldNotHaveBeenCalled(); - $publish->shouldHaveBeenCalledTimes(2); } /** @test */ @@ -108,26 +91,27 @@ class NotifyVisitToMercureTest extends TestCase $update = Update::forTopicAndPayload('', []); $e = new RuntimeException('Error'); - $findVisit = $this->em->find(Visit::class, $visitId)->willReturn($visit); - $logWarning = $this->logger->warning(Argument::cetera()); - $logDebug = $this->logger->debug('Error while trying to notify {name} with new visit. {e}', [ - 'e' => $e, - 'name' => 'Mercure', - ]); - $buildNewShortUrlVisitUpdate = $this->updatesGenerator->newShortUrlVisitUpdate($visit)->willReturn($update); - $buildNewOrphanVisitUpdate = $this->updatesGenerator->newOrphanVisitUpdate($visit)->willReturn($update); - $buildNewVisitUpdate = $this->updatesGenerator->newVisitUpdate($visit)->willReturn($update); - $publish = $this->helper->publishUpdate($update)->willThrow($e); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo($visitId), + )->willReturn($visit); + $this->logger->expects($this->never())->method('warning'); + $this->logger->expects($this->once())->method('debug')->with( + $this->equalTo('Error while trying to notify {name} with new visit. {e}'), + $this->equalTo(['e' => $e, 'name' => 'Mercure']), + ); + $this->updatesGenerator->expects($this->once())->method('newShortUrlVisitUpdate')->with( + $this->equalTo($visit), + )->willReturn($update); + $this->updatesGenerator->expects($this->never())->method('newOrphanVisitUpdate'); + $this->updatesGenerator->expects($this->once())->method('newVisitUpdate')->with( + $this->equalTo($visit), + )->willReturn($update); + $this->helper->expects($this->once())->method('publishUpdate')->with( + $this->equalTo($update), + )->willThrowException($e); ($this->listener)(new VisitLocated($visitId)); - - $findVisit->shouldHaveBeenCalledOnce(); - $logWarning->shouldNotHaveBeenCalled(); - $logDebug->shouldHaveBeenCalledOnce(); - $buildNewShortUrlVisitUpdate->shouldHaveBeenCalledOnce(); - $buildNewVisitUpdate->shouldHaveBeenCalledOnce(); - $buildNewOrphanVisitUpdate->shouldNotHaveBeenCalled(); - $publish->shouldHaveBeenCalledOnce(); } /** @@ -139,23 +123,20 @@ class NotifyVisitToMercureTest extends TestCase $visitId = '123'; $update = Update::forTopicAndPayload('', []); - $findVisit = $this->em->find(Visit::class, $visitId)->willReturn($visit); - $logWarning = $this->logger->warning(Argument::cetera()); - $logDebug = $this->logger->debug(Argument::cetera()); - $buildNewShortUrlVisitUpdate = $this->updatesGenerator->newShortUrlVisitUpdate($visit)->willReturn($update); - $buildNewOrphanVisitUpdate = $this->updatesGenerator->newOrphanVisitUpdate($visit)->willReturn($update); - $buildNewVisitUpdate = $this->updatesGenerator->newVisitUpdate($visit)->willReturn($update); - $publish = $this->helper->publishUpdate($update); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo($visitId), + )->willReturn($visit); + $this->logger->expects($this->never())->method('warning'); + $this->logger->expects($this->never())->method('debug'); + $this->updatesGenerator->expects($this->never())->method('newShortUrlVisitUpdate'); + $this->updatesGenerator->expects($this->once())->method('newOrphanVisitUpdate')->with( + $this->equalTo($visit), + )->willReturn($update); + $this->updatesGenerator->expects($this->never())->method('newVisitUpdate'); + $this->helper->expects($this->once())->method('publishUpdate')->with($this->equalTo($update)); ($this->listener)(new VisitLocated($visitId)); - - $findVisit->shouldHaveBeenCalledOnce(); - $logWarning->shouldNotHaveBeenCalled(); - $logDebug->shouldNotHaveBeenCalled(); - $buildNewShortUrlVisitUpdate->shouldNotHaveBeenCalled(); - $buildNewVisitUpdate->shouldNotHaveBeenCalled(); - $buildNewOrphanVisitUpdate->shouldHaveBeenCalledOnce(); - $publish->shouldHaveBeenCalledOnce(); } public function provideOrphanVisits(): iterable From dbe35cf567a8883a94e469b694219fa061b65d15 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 10:03:28 +0200 Subject: [PATCH 032/182] Fixed coding styles --- module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php b/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php index 5d916a90..93cde57c 100644 --- a/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php +++ b/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php @@ -123,7 +123,7 @@ class UpdateGeoLiteDbTest extends TestCase ): void { $this->dbUpdater->expects($this->once())->method('checkDbUpdate')->withAnyParameters()->willReturn($result); $this->eventDispatcher->expects($this->exactly($expectedDispatches))->method('dispatch')->with( - $this->equalTo(new GeoLiteDbCreated()) + $this->equalTo(new GeoLiteDbCreated()), ); ($this->listener)(); From 58db902084f16c0bdef3aaf655b6e860793e1e33 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 12:46:16 +0200 Subject: [PATCH 033/182] Migrated CliTestUtilsTrait to use PHPUnit mocks --- module/CLI/test/CliTestUtilsTrait.php | 24 +++++++++---------- .../Command/Visit/LocateVisitsCommandTest.php | 17 +++++++++---- .../test/Factory/ApplicationFactoryTest.php | 4 ++-- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/module/CLI/test/CliTestUtilsTrait.php b/module/CLI/test/CliTestUtilsTrait.php index ec7dd9d9..e69eb71e 100644 --- a/module/CLI/test/CliTestUtilsTrait.php +++ b/module/CLI/test/CliTestUtilsTrait.php @@ -4,9 +4,9 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI; -use Prophecy\Argument; +use PHPUnit\Framework\Assert; +use PHPUnit\Framework\MockObject\MockObject; use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputDefinition; @@ -14,21 +14,19 @@ use Symfony\Component\Console\Tester\CommandTester; trait CliTestUtilsTrait { - use ProphecyTrait; + use ProphecyTrait; // TODO Remove /** - * @return ObjectProphecy|Command + * @return MockObject & Command */ - private function createCommandMock(string $name): ObjectProphecy + private function createCommandMock(string $name): MockObject { - $command = $this->prophesize(Command::class); - $command->getName()->willReturn($name); - $command->getDefinition()->willReturn($name); - $command->isEnabled()->willReturn(true); - $command->getAliases()->willReturn([]); - $command->getDefinition()->willReturn(new InputDefinition()); - $command->setApplication(Argument::type(Application::class))->willReturn(function (): void { - }); + $command = $this->createMock(Command::class); + $command->method('getName')->willReturn($name); + $command->method('isEnabled')->willReturn(true); + $command->method('getAliases')->willReturn([]); + $command->method('getDefinition')->willReturn(new InputDefinition()); + $command->method('setApplication')->with(Assert::isInstanceOf(Application::class)); return $command; } diff --git a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php index 2c866689..0db2f493 100644 --- a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Visit; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -38,7 +39,7 @@ class LocateVisitsCommandTest extends TestCase private ObjectProphecy $visitService; private ObjectProphecy $visitToLocation; private ObjectProphecy $lock; - private ObjectProphecy $downloadDbCommand; + private MockObject $downloadDbCommand; protected function setUp(): void { @@ -59,9 +60,7 @@ class LocateVisitsCommandTest extends TestCase ); $this->downloadDbCommand = $this->createCommandMock(DownloadGeoLiteDbCommand::NAME); - $this->downloadDbCommand->run(Argument::cetera())->willReturn(ExitCodes::EXIT_SUCCESS); - - $this->commandTester = $this->testerForCommand($command, $this->downloadDbCommand->reveal()); + $this->commandTester = $this->testerForCommand($command, $this->downloadDbCommand); } /** @@ -87,6 +86,7 @@ class LocateVisitsCommandTest extends TestCase $resolveIpLocation = $this->visitToLocation->resolveVisitLocation(Argument::any())->willReturn( Location::emptyInstance(), ); + $this->downloadDbCommand->method('run')->withAnyParameters()->willReturn(ExitCodes::EXIT_SUCCESS); $this->commandTester->setInputs(['y']); $this->commandTester->execute($args); @@ -126,6 +126,7 @@ class LocateVisitsCommandTest extends TestCase $this->invokeHelperMethods($visit, $location), ); $resolveIpLocation = $this->visitToLocation->resolveVisitLocation(Argument::any())->willThrow($e); + $this->downloadDbCommand->method('run')->withAnyParameters()->willReturn(ExitCodes::EXIT_SUCCESS); $this->commandTester->execute([], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]); @@ -154,6 +155,7 @@ class LocateVisitsCommandTest extends TestCase $resolveIpLocation = $this->visitToLocation->resolveVisitLocation(Argument::any())->willThrow( IpCannotBeLocatedException::forError(WrongIpException::fromIpAddress('1.2.3.4')), ); + $this->downloadDbCommand->method('run')->withAnyParameters()->willReturn(ExitCodes::EXIT_SUCCESS); $this->commandTester->execute([], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]); @@ -183,6 +185,7 @@ class LocateVisitsCommandTest extends TestCase $locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will(function (): void { }); $resolveIpLocation = $this->visitToLocation->resolveVisitLocation(Argument::any()); + $this->downloadDbCommand->method('run')->withAnyParameters()->willReturn(ExitCodes::EXIT_SUCCESS); $this->commandTester->execute([], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]); $output = $this->commandTester->getDisplay(); @@ -198,7 +201,7 @@ class LocateVisitsCommandTest extends TestCase /** @test */ public function showsProperMessageWhenGeoLiteUpdateFails(): void { - $this->downloadDbCommand->run(Argument::cetera())->willReturn(ExitCodes::EXIT_FAILURE); + $this->downloadDbCommand->method('run')->withAnyParameters()->willReturn(ExitCodes::EXIT_FAILURE); $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); @@ -210,6 +213,8 @@ class LocateVisitsCommandTest extends TestCase /** @test */ public function providingAllFlagOnItsOwnDisplaysNotice(): void { + $this->downloadDbCommand->method('run')->withAnyParameters()->willReturn(ExitCodes::EXIT_SUCCESS); + $this->commandTester->execute(['--all' => true]); $output = $this->commandTester->getDisplay(); @@ -222,6 +227,8 @@ class LocateVisitsCommandTest extends TestCase */ public function processingAllCancelsCommandIfUserDoesNotActivelyAgreeToConfirmation(array $inputs): void { + $this->downloadDbCommand->method('run')->withAnyParameters()->willReturn(ExitCodes::EXIT_SUCCESS); + $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Execution aborted'); diff --git a/module/CLI/test/Factory/ApplicationFactoryTest.php b/module/CLI/test/Factory/ApplicationFactoryTest.php index cb08a692..c93731c1 100644 --- a/module/CLI/test/Factory/ApplicationFactoryTest.php +++ b/module/CLI/test/Factory/ApplicationFactoryTest.php @@ -31,8 +31,8 @@ class ApplicationFactoryTest extends TestCase 'baz' => 'baz', ], ]); - $sm->setService('foo', $this->createCommandMock('foo')->reveal()); - $sm->setService('bar', $this->createCommandMock('bar')->reveal()); + $sm->setService('foo', $this->createCommandMock('foo')); + $sm->setService('bar', $this->createCommandMock('bar')); $instance = ($this->factory)($sm); From 2b7b5e9a8f237adcd12f4537afd80a86e8d93916 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 12:48:17 +0200 Subject: [PATCH 034/182] Migrated DisableKeyCommandTest to use PHPUnit mocks --- .../test/Command/Api/DisableKeyCommandTest.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/module/CLI/test/Command/Api/DisableKeyCommandTest.php b/module/CLI/test/Command/Api/DisableKeyCommandTest.php index 41a4f982..78f5bfa6 100644 --- a/module/CLI/test/Command/Api/DisableKeyCommandTest.php +++ b/module/CLI/test/Command/Api/DisableKeyCommandTest.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Api; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Api\DisableKeyCommand; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; @@ -17,19 +17,19 @@ class DisableKeyCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $apiKeyService; + private MockObject $apiKeyService; protected function setUp(): void { - $this->apiKeyService = $this->prophesize(ApiKeyServiceInterface::class); - $this->commandTester = $this->testerForCommand(new DisableKeyCommand($this->apiKeyService->reveal())); + $this->apiKeyService = $this->createMock(ApiKeyServiceInterface::class); + $this->commandTester = $this->testerForCommand(new DisableKeyCommand($this->apiKeyService)); } /** @test */ public function providedApiKeyIsDisabled(): void { $apiKey = 'abcd1234'; - $this->apiKeyService->disable($apiKey)->shouldBeCalledOnce(); + $this->apiKeyService->expects($this->once())->method('disable')->with($this->equalTo($apiKey)); $this->commandTester->execute([ 'apiKey' => $apiKey, @@ -44,7 +44,9 @@ class DisableKeyCommandTest extends TestCase { $apiKey = 'abcd1234'; $expectedMessage = 'API key "abcd1234" does not exist.'; - $disable = $this->apiKeyService->disable($apiKey)->willThrow(new InvalidArgumentException($expectedMessage)); + $this->apiKeyService->expects($this->once())->method('disable')->with( + $this->equalTo($apiKey), + )->willThrowException(new InvalidArgumentException($expectedMessage)); $this->commandTester->execute([ 'apiKey' => $apiKey, @@ -52,6 +54,5 @@ class DisableKeyCommandTest extends TestCase $output = $this->commandTester->getDisplay(); self::assertStringContainsString($expectedMessage, $output); - $disable->shouldHaveBeenCalledOnce(); } } From a4c34ff7beb16918e52689f9677fe9db2167233e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 12:52:11 +0200 Subject: [PATCH 035/182] Migrated GenerateKeyCommandTest to use PHPUnit mocks --- .../Command/Api/GenerateKeyCommandTest.php | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php index 6db8581b..02e704ee 100644 --- a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php +++ b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php @@ -5,9 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Api; use Cake\Chronos\Chronos; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\ApiKey\RoleResolverInterface; use Shlinkio\Shlink\CLI\Command\Api\GenerateKeyCommand; use Shlinkio\Shlink\Rest\Entity\ApiKey; @@ -21,22 +20,25 @@ class GenerateKeyCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $apiKeyService; + private MockObject $apiKeyService; protected function setUp(): void { - $this->apiKeyService = $this->prophesize(ApiKeyServiceInterface::class); - $roleResolver = $this->prophesize(RoleResolverInterface::class); - $roleResolver->determineRoles(Argument::type(InputInterface::class))->willReturn([]); + $this->apiKeyService = $this->createMock(ApiKeyServiceInterface::class); + $roleResolver = $this->createMock(RoleResolverInterface::class); + $roleResolver->method('determineRoles')->with($this->isInstanceOf(InputInterface::class))->willReturn([]); - $command = new GenerateKeyCommand($this->apiKeyService->reveal(), $roleResolver->reveal()); + $command = new GenerateKeyCommand($this->apiKeyService, $roleResolver); $this->commandTester = $this->testerForCommand($command); } /** @test */ public function noExpirationDateIsDefinedIfNotProvided(): void { - $this->apiKeyService->create(null, null)->shouldBeCalledOnce()->willReturn(ApiKey::create()); + $this->apiKeyService->expects($this->once())->method('create')->with( + $this->isNull(), + $this->isNull(), + )->willReturn(ApiKey::create()); $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); @@ -47,9 +49,10 @@ class GenerateKeyCommandTest extends TestCase /** @test */ public function expirationDateIsDefinedIfProvided(): void { - $this->apiKeyService->create(Argument::type(Chronos::class), null)->shouldBeCalledOnce()->willReturn( - ApiKey::create(), - ); + $this->apiKeyService->expects($this->once())->method('create')->with( + $this->isInstanceOf(Chronos::class), + $this->isNull(), + )->willReturn(ApiKey::create()); $this->commandTester->execute([ '--expiration-date' => '2016-01-01', @@ -59,9 +62,10 @@ class GenerateKeyCommandTest extends TestCase /** @test */ public function nameIsDefinedIfProvided(): void { - $this->apiKeyService->create(null, Argument::type('string'))->shouldBeCalledOnce()->willReturn( - ApiKey::create(), - ); + $this->apiKeyService->expects($this->once())->method('create')->with( + $this->isNull(), + $this->isType('string'), + )->willReturn(ApiKey::create()); $this->commandTester->execute([ '--name' => 'Alice', From 4cdcad29df6f6ee9de96883092e27645ceb5e546 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 12:53:28 +0200 Subject: [PATCH 036/182] Migrated ListKeysCommandTest to use PHPUnit mocks --- module/CLI/test/Command/Api/ListKeysCommandTest.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/module/CLI/test/Command/Api/ListKeysCommandTest.php b/module/CLI/test/Command/Api/ListKeysCommandTest.php index c52f466f..39da39a6 100644 --- a/module/CLI/test/Command/Api/ListKeysCommandTest.php +++ b/module/CLI/test/Command/Api/ListKeysCommandTest.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Api; use Cake\Chronos\Chronos; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Api\ListKeysCommand; use Shlinkio\Shlink\Core\Domain\Entity\Domain; use Shlinkio\Shlink\Rest\ApiKey\Model\ApiKeyMeta; @@ -21,12 +21,12 @@ class ListKeysCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $apiKeyService; + private MockObject $apiKeyService; protected function setUp(): void { - $this->apiKeyService = $this->prophesize(ApiKeyServiceInterface::class); - $this->commandTester = $this->testerForCommand(new ListKeysCommand($this->apiKeyService->reveal())); + $this->apiKeyService = $this->createMock(ApiKeyServiceInterface::class); + $this->commandTester = $this->testerForCommand(new ListKeysCommand($this->apiKeyService)); } /** @@ -35,13 +35,14 @@ class ListKeysCommandTest extends TestCase */ public function returnsExpectedOutput(array $keys, bool $enabledOnly, string $expected): void { - $listKeys = $this->apiKeyService->listKeys($enabledOnly)->willReturn($keys); + $this->apiKeyService->expects($this->once())->method('listKeys')->with( + $this->equalTo($enabledOnly), + )->willReturn($keys); $this->commandTester->execute(['--enabled-only' => $enabledOnly]); $output = $this->commandTester->getDisplay(); self::assertEquals($expected, $output); - $listKeys->shouldHaveBeenCalledOnce(); } public function provideKeysAndOutputs(): iterable From 13431ff8cf851994860bba4f212b3005478f53c2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 13:05:36 +0200 Subject: [PATCH 037/182] Migrated CreateDatabaseCommandTest to use PHPUnit mocks --- .../Command/Db/CreateDatabaseCommandTest.php | 134 ++++++++---------- 1 file changed, 59 insertions(+), 75 deletions(-) diff --git a/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php b/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php index f500775a..b335e19e 100644 --- a/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php +++ b/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php @@ -9,9 +9,8 @@ use Doctrine\DBAL\Driver; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\AbstractSchemaManager; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Db\CreateDatabaseCommand; use Shlinkio\Shlink\CLI\Util\ProcessRunnerInterface; use ShlinkioTest\Shlink\CLI\CliTestUtilsTrait; @@ -28,40 +27,37 @@ class CreateDatabaseCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $processHelper; - private ObjectProphecy $regularConn; - private ObjectProphecy $schemaManager; - private ObjectProphecy $driver; + private MockObject $processHelper; + private MockObject $regularConn; + private MockObject $schemaManager; + private MockObject $driver; protected function setUp(): void { - $locker = $this->prophesize(LockFactory::class); - $lock = $this->prophesize(LockInterface::class); - $lock->acquire(Argument::any())->willReturn(true); - $lock->release()->will(function (): void { - }); - $locker->createLock(Argument::cetera())->willReturn($lock->reveal()); + $locker = $this->createMock(LockFactory::class); + $lock = $this->createMock(LockInterface::class); + $lock->method('acquire')->withAnyParameters()->willReturn(true); + $locker->method('createLock')->withAnyParameters()->willReturn($lock); - $phpExecutableFinder = $this->prophesize(PhpExecutableFinder::class); - $phpExecutableFinder->find(false)->willReturn('/usr/local/bin/php'); + $phpExecutableFinder = $this->createMock(PhpExecutableFinder::class); + $phpExecutableFinder->method('find')->with($this->isFalse())->willReturn('/usr/local/bin/php'); - $this->processHelper = $this->prophesize(ProcessRunnerInterface::class); - $this->schemaManager = $this->prophesize(AbstractSchemaManager::class); + $this->processHelper = $this->createMock(ProcessRunnerInterface::class); + $this->schemaManager = $this->createMock(AbstractSchemaManager::class); - $this->regularConn = $this->prophesize(Connection::class); - $this->regularConn->createSchemaManager()->willReturn($this->schemaManager->reveal()); - $this->driver = $this->prophesize(Driver::class); - $this->regularConn->getDriver()->willReturn($this->driver->reveal()); - $this->driver->getDatabasePlatform()->willReturn($this->prophesize(AbstractPlatform::class)->reveal()); - $noDbNameConn = $this->prophesize(Connection::class); - $noDbNameConn->createSchemaManager()->willReturn($this->schemaManager->reveal()); + $this->regularConn = $this->createMock(Connection::class); + $this->regularConn->method('createSchemaManager')->willReturn($this->schemaManager); + $this->driver = $this->createMock(Driver::class); + $this->regularConn->method('getDriver')->willReturn($this->driver); + $noDbNameConn = $this->createMock(Connection::class); + $noDbNameConn->method('createSchemaManager')->withAnyParameters()->willReturn($this->schemaManager); $command = new CreateDatabaseCommand( - $locker->reveal(), - $this->processHelper->reveal(), - $phpExecutableFinder->reveal(), - $this->regularConn->reveal(), - $noDbNameConn->reveal(), + $locker, + $this->processHelper, + $phpExecutableFinder, + $this->regularConn, + $noDbNameConn, ); $this->commandTester = $this->testerForCommand($command); @@ -71,38 +67,33 @@ class CreateDatabaseCommandTest extends TestCase public function successMessageIsPrintedIfDatabaseAlreadyExists(): void { $shlinkDatabase = 'shlink_database'; - $getDatabase = $this->regularConn->getParams()->willReturn(['dbname' => $shlinkDatabase]); - $listDatabases = $this->schemaManager->listDatabases()->willReturn(['foo', $shlinkDatabase, 'bar']); - $createDatabase = $this->schemaManager->createDatabase($shlinkDatabase)->will(function (): void { - }); - $listTables = $this->schemaManager->listTableNames()->willReturn(['foo_table', 'bar_table']); + $this->regularConn->expects($this->once())->method('getParams')->willReturn(['dbname' => $shlinkDatabase]); + $this->schemaManager->expects($this->once())->method('listDatabases')->willReturn( + ['foo', $shlinkDatabase, 'bar'], + ); + $this->schemaManager->expects($this->never())->method('createDatabase'); + $this->schemaManager->expects($this->once())->method('listTableNames')->willReturn(['foo_table', 'bar_table']); + $this->driver->method('getDatabasePlatform')->willReturn($this->createMock(AbstractPlatform::class)); $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); self::assertStringContainsString('Database already exists. Run "db:migrate" command', $output); - $getDatabase->shouldHaveBeenCalledOnce(); - $listDatabases->shouldHaveBeenCalledOnce(); - $createDatabase->shouldNotHaveBeenCalled(); - $listTables->shouldHaveBeenCalledOnce(); } /** @test */ public function databaseIsCreatedIfItDoesNotExist(): void { $shlinkDatabase = 'shlink_database'; - $getDatabase = $this->regularConn->getParams()->willReturn(['dbname' => $shlinkDatabase]); - $listDatabases = $this->schemaManager->listDatabases()->willReturn(['foo', 'bar']); - $createDatabase = $this->schemaManager->createDatabase($shlinkDatabase)->will(function (): void { - }); - $listTables = $this->schemaManager->listTableNames()->willReturn(['foo_table', 'bar_table', MIGRATIONS_TABLE]); + $this->regularConn->expects($this->once())->method('getParams')->willReturn(['dbname' => $shlinkDatabase]); + $this->schemaManager->expects($this->once())->method('listDatabases')->willReturn(['foo', 'bar']); + $this->schemaManager->expects($this->once())->method('createDatabase')->with($this->equalTo($shlinkDatabase)); + $this->schemaManager->expects($this->once())->method('listTableNames')->willReturn( + ['foo_table', 'bar_table', MIGRATIONS_TABLE], + ); + $this->driver->method('getDatabasePlatform')->willReturn($this->createMock(AbstractPlatform::class)); $this->commandTester->execute([]); - - $getDatabase->shouldHaveBeenCalledOnce(); - $listDatabases->shouldHaveBeenCalledOnce(); - $createDatabase->shouldHaveBeenCalledOnce(); - $listTables->shouldHaveBeenCalledOnce(); } /** @@ -112,28 +103,28 @@ class CreateDatabaseCommandTest extends TestCase public function tablesAreCreatedIfDatabaseIsEmpty(array $tables): void { $shlinkDatabase = 'shlink_database'; - $getDatabase = $this->regularConn->getParams()->willReturn(['dbname' => $shlinkDatabase]); - $listDatabases = $this->schemaManager->listDatabases()->willReturn(['foo', $shlinkDatabase, 'bar']); - $createDatabase = $this->schemaManager->createDatabase($shlinkDatabase)->will(function (): void { - }); - $listTables = $this->schemaManager->listTableNames()->willReturn($tables); - $runCommand = $this->processHelper->run(Argument::type(OutputInterface::class), [ - '/usr/local/bin/php', - CreateDatabaseCommand::DOCTRINE_SCRIPT, - CreateDatabaseCommand::DOCTRINE_CREATE_SCHEMA_COMMAND, - '--no-interaction', - ]); + $this->regularConn->expects($this->once())->method('getParams')->willReturn(['dbname' => $shlinkDatabase]); + $this->schemaManager->expects($this->once())->method('listDatabases')->willReturn( + ['foo', $shlinkDatabase, 'bar'], + ); + $this->schemaManager->expects($this->never())->method('createDatabase'); + $this->schemaManager->expects($this->once())->method('listTableNames')->willReturn($tables); + $this->processHelper->expects($this->once())->method('run')->with( + $this->isInstanceOf(OutputInterface::class), + $this->equalTo([ + '/usr/local/bin/php', + CreateDatabaseCommand::DOCTRINE_SCRIPT, + CreateDatabaseCommand::DOCTRINE_CREATE_SCHEMA_COMMAND, + '--no-interaction', + ]), + ); + $this->driver->method('getDatabasePlatform')->willReturn($this->createMock(AbstractPlatform::class)); $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); self::assertStringContainsString('Creating database tables...', $output); self::assertStringContainsString('Database properly created!', $output); - $getDatabase->shouldHaveBeenCalledOnce(); - $listDatabases->shouldHaveBeenCalledOnce(); - $createDatabase->shouldNotHaveBeenCalled(); - $listTables->shouldHaveBeenCalledOnce(); - $runCommand->shouldHaveBeenCalledOnce(); } public function provideEmptyDatabase(): iterable @@ -145,20 +136,13 @@ class CreateDatabaseCommandTest extends TestCase /** @test */ public function databaseCheckIsSkippedForSqlite(): void { - $this->driver->getDatabasePlatform()->willReturn($this->prophesize(SqlitePlatform::class)->reveal()); + $this->driver->method('getDatabasePlatform')->willReturn($this->createMock(SqlitePlatform::class)); - $shlinkDatabase = 'shlink_database'; - $getDatabase = $this->regularConn->getParams()->willReturn(['dbname' => $shlinkDatabase]); - $listDatabases = $this->schemaManager->listDatabases()->willReturn(['foo', 'bar']); - $createDatabase = $this->schemaManager->createDatabase($shlinkDatabase)->will(function (): void { - }); - $listTables = $this->schemaManager->listTableNames()->willReturn(['foo_table', 'bar_table']); + $this->regularConn->expects($this->never())->method('getParams'); + $this->schemaManager->expects($this->never())->method('listDatabases'); + $this->schemaManager->expects($this->never())->method('createDatabase'); + $this->schemaManager->expects($this->once())->method('listTableNames')->willReturn(['foo_table', 'bar_table']); $this->commandTester->execute([]); - - $getDatabase->shouldNotHaveBeenCalled(); - $listDatabases->shouldNotHaveBeenCalled(); - $createDatabase->shouldNotHaveBeenCalled(); - $listTables->shouldHaveBeenCalledOnce(); } } From 101b4daff40711f2cafe98a5525a59608d05b066 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 13:08:05 +0200 Subject: [PATCH 038/182] Migrated MigrateDatabaseCommandTest to use PHPUnit mocks --- .../Command/Db/MigrateDatabaseCommandTest.php | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php b/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php index 1a8dfb0e..6390d044 100644 --- a/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php +++ b/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php @@ -4,9 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Db; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Db\MigrateDatabaseCommand; use Shlinkio\Shlink\CLI\Util\ProcessRunnerInterface; use ShlinkioTest\Shlink\CLI\CliTestUtilsTrait; @@ -21,45 +20,41 @@ class MigrateDatabaseCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $processHelper; + private MockObject $processHelper; protected function setUp(): void { - $locker = $this->prophesize(LockFactory::class); - $lock = $this->prophesize(LockInterface::class); - $lock->acquire(Argument::any())->willReturn(true); - $lock->release()->will(function (): void { - }); - $locker->createLock(Argument::cetera())->willReturn($lock->reveal()); + $locker = $this->createMock(LockFactory::class); + $lock = $this->createMock(LockInterface::class); + $lock->method('acquire')->withAnyParameters()->willReturn(true); + $locker->method('createLock')->withAnyParameters()->willReturn($lock); - $phpExecutableFinder = $this->prophesize(PhpExecutableFinder::class); - $phpExecutableFinder->find(false)->willReturn('/usr/local/bin/php'); + $phpExecutableFinder = $this->createMock(PhpExecutableFinder::class); + $phpExecutableFinder->method('find')->with($this->isFalse())->willReturn('/usr/local/bin/php'); - $this->processHelper = $this->prophesize(ProcessRunnerInterface::class); + $this->processHelper = $this->createMock(ProcessRunnerInterface::class); - $command = new MigrateDatabaseCommand( - $locker->reveal(), - $this->processHelper->reveal(), - $phpExecutableFinder->reveal(), - ); + $command = new MigrateDatabaseCommand($locker, $this->processHelper, $phpExecutableFinder); $this->commandTester = $this->testerForCommand($command); } /** @test */ public function migrationsCommandIsRunWithProperVerbosity(): void { - $runCommand = $this->processHelper->run(Argument::type(OutputInterface::class), [ - '/usr/local/bin/php', - MigrateDatabaseCommand::DOCTRINE_MIGRATIONS_SCRIPT, - MigrateDatabaseCommand::DOCTRINE_MIGRATE_COMMAND, - '--no-interaction', - ]); + $this->processHelper->expects($this->once())->method('run')->with( + $this->isInstanceOf(OutputInterface::class), + $this->equalTo([ + '/usr/local/bin/php', + MigrateDatabaseCommand::DOCTRINE_MIGRATIONS_SCRIPT, + MigrateDatabaseCommand::DOCTRINE_MIGRATE_COMMAND, + '--no-interaction', + ]), + ); $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); self::assertStringContainsString('Migrating database...', $output); self::assertStringContainsString('Database properly migrated!', $output); - $runCommand->shouldHaveBeenCalledOnce(); } } From 960bdfc232bd468f39021d8327dec5412811eede Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 13:17:12 +0200 Subject: [PATCH 039/182] Migrated DomainRedirectsCommandTest to use PHPUnit mocks --- .../Domain/DomainRedirectsCommandTest.php | 81 +++++++++---------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/module/CLI/test/Command/Domain/DomainRedirectsCommandTest.php b/module/CLI/test/Command/Domain/DomainRedirectsCommandTest.php index 0f8c76af..5d9f5bd4 100644 --- a/module/CLI/test/Command/Domain/DomainRedirectsCommandTest.php +++ b/module/CLI/test/Command/Domain/DomainRedirectsCommandTest.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Domain; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Domain\DomainRedirectsCommand; use Shlinkio\Shlink\Core\Config\NotFoundRedirects; use Shlinkio\Shlink\Core\Domain\DomainServiceInterface; @@ -22,12 +22,12 @@ class DomainRedirectsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $domainService; + private MockObject $domainService; protected function setUp(): void { - $this->domainService = $this->prophesize(DomainServiceInterface::class); - $this->commandTester = $this->testerForCommand(new DomainRedirectsCommand($this->domainService->reveal())); + $this->domainService = $this->createMock(DomainServiceInterface::class); + $this->commandTester = $this->testerForCommand(new DomainRedirectsCommand($this->domainService)); } /** @@ -37,11 +37,14 @@ class DomainRedirectsCommandTest extends TestCase public function onlyPlainQuestionsAreAskedForNewDomainsAndDomainsWithNoRedirects(?Domain $domain): void { $domainAuthority = 'my-domain.com'; - $findDomain = $this->domainService->findByAuthority($domainAuthority)->willReturn($domain); - $configureRedirects = $this->domainService->configureNotFoundRedirects( - $domainAuthority, - NotFoundRedirects::withRedirects('foo.com', null, 'baz.com'), + $this->domainService->expects($this->once())->method('findByAuthority')->with( + $this->equalTo($domainAuthority), + )->willReturn($domain); + $this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with( + $this->equalTo($domainAuthority), + $this->equalTo(NotFoundRedirects::withRedirects('foo.com', null, 'baz.com')), )->willReturn(Domain::withAuthority('')); + $this->domainService->expects($this->never())->method('listDomains'); $this->commandTester->setInputs(['foo.com', '', 'baz.com']); $this->commandTester->execute(['domain' => $domainAuthority]); @@ -55,9 +58,6 @@ class DomainRedirectsCommandTest extends TestCase ); self::assertStringContainsString('URL to redirect to when a user hits an invalid short URL', $output); self::assertEquals(3, substr_count($output, '(Leave empty for no redirect)')); - $findDomain->shouldHaveBeenCalledOnce(); - $configureRedirects->shouldHaveBeenCalledOnce(); - $this->domainService->listDomains()->shouldNotHaveBeenCalled(); } public function provideDomains(): iterable @@ -73,11 +73,14 @@ class DomainRedirectsCommandTest extends TestCase $domain = Domain::withAuthority($domainAuthority); $domain->configureNotFoundRedirects(NotFoundRedirects::withRedirects('foo.com', 'bar.com', 'baz.com')); - $findDomain = $this->domainService->findByAuthority($domainAuthority)->willReturn($domain); - $configureRedirects = $this->domainService->configureNotFoundRedirects( - $domainAuthority, - NotFoundRedirects::withRedirects(null, 'edited.com', 'baz.com'), + $this->domainService->expects($this->once())->method('findByAuthority')->with( + $this->equalTo($domainAuthority), )->willReturn($domain); + $this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with( + $this->equalTo($domainAuthority), + $this->equalTo(NotFoundRedirects::withRedirects(null, 'edited.com', 'baz.com')), + )->willReturn($domain); + $this->domainService->expects($this->never())->method('listDomains'); $this->commandTester->setInputs(['2', '1', 'edited.com', '0']); $this->commandTester->execute(['domain' => $domainAuthority]); @@ -90,9 +93,6 @@ class DomainRedirectsCommandTest extends TestCase self::assertStringNotContainsStringIgnoringCase('(Leave empty for no redirect)', $output); self::assertEquals(3, substr_count($output, 'Set new redirect URL')); self::assertEquals(3, substr_count($output, 'Remove redirect')); - $findDomain->shouldHaveBeenCalledOnce(); - $configureRedirects->shouldHaveBeenCalledOnce(); - $this->domainService->listDomains()->shouldNotHaveBeenCalled(); } /** @test */ @@ -101,11 +101,13 @@ class DomainRedirectsCommandTest extends TestCase $domainAuthority = 'example.com'; $domain = Domain::withAuthority($domainAuthority); - $listDomains = $this->domainService->listDomains()->willReturn([]); - $findDomain = $this->domainService->findByAuthority($domainAuthority)->willReturn($domain); - $configureRedirects = $this->domainService->configureNotFoundRedirects( - $domainAuthority, - NotFoundRedirects::withoutRedirects(), + $this->domainService->expects($this->once())->method('listDomains')->with()->willReturn([]); + $this->domainService->expects($this->once())->method('findByAuthority')->with( + $this->equalTo($domainAuthority), + )->willReturn($domain); + $this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with( + $this->equalTo($domainAuthority), + $this->equalTo(NotFoundRedirects::withoutRedirects()), )->willReturn($domain); $this->commandTester->setInputs([$domainAuthority, '', '', '']); @@ -113,9 +115,6 @@ class DomainRedirectsCommandTest extends TestCase $output = $this->commandTester->getDisplay(); self::assertStringContainsString('Domain authority for which you want to set specific redirects', $output); - $listDomains->shouldHaveBeenCalledOnce(); - $findDomain->shouldHaveBeenCalledOnce(); - $configureRedirects->shouldHaveBeenCalledOnce(); } /** @test */ @@ -124,15 +123,17 @@ class DomainRedirectsCommandTest extends TestCase $domainAuthority = 'existing-two.com'; $domain = Domain::withAuthority($domainAuthority); - $listDomains = $this->domainService->listDomains()->willReturn([ + $this->domainService->expects($this->once())->method('listDomains')->with()->willReturn([ DomainItem::forDefaultDomain('default-domain.com', new NotFoundRedirectOptions()), DomainItem::forNonDefaultDomain(Domain::withAuthority('existing-one.com')), DomainItem::forNonDefaultDomain(Domain::withAuthority($domainAuthority)), ]); - $findDomain = $this->domainService->findByAuthority($domainAuthority)->willReturn($domain); - $configureRedirects = $this->domainService->configureNotFoundRedirects( - $domainAuthority, - NotFoundRedirects::withoutRedirects(), + $this->domainService->expects($this->once())->method('findByAuthority')->with( + $this->equalTo($domainAuthority), + )->willReturn($domain); + $this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with( + $this->equalTo($domainAuthority), + $this->equalTo(NotFoundRedirects::withoutRedirects()), )->willReturn($domain); $this->commandTester->setInputs(['1', '', '', '']); @@ -143,9 +144,6 @@ class DomainRedirectsCommandTest extends TestCase self::assertStringNotContainsString('default-domain.com', $output); self::assertStringContainsString('existing-one.com', $output); self::assertStringContainsString($domainAuthority, $output); - $listDomains->shouldHaveBeenCalledOnce(); - $findDomain->shouldHaveBeenCalledOnce(); - $configureRedirects->shouldHaveBeenCalledOnce(); } /** @test */ @@ -154,15 +152,17 @@ class DomainRedirectsCommandTest extends TestCase $domainAuthority = 'new-domain.com'; $domain = Domain::withAuthority($domainAuthority); - $listDomains = $this->domainService->listDomains()->willReturn([ + $this->domainService->expects($this->once())->method('listDomains')->with()->willReturn([ DomainItem::forDefaultDomain('default-domain.com', new NotFoundRedirectOptions()), DomainItem::forNonDefaultDomain(Domain::withAuthority('existing-one.com')), DomainItem::forNonDefaultDomain(Domain::withAuthority('existing-two.com')), ]); - $findDomain = $this->domainService->findByAuthority($domainAuthority)->willReturn($domain); - $configureRedirects = $this->domainService->configureNotFoundRedirects( - $domainAuthority, - NotFoundRedirects::withoutRedirects(), + $this->domainService->expects($this->once())->method('findByAuthority')->with( + $this->equalTo($domainAuthority), + )->willReturn($domain); + $this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with( + $this->equalTo($domainAuthority), + $this->equalTo(NotFoundRedirects::withoutRedirects()), )->willReturn($domain); $this->commandTester->setInputs(['2', $domainAuthority, '', '', '']); @@ -173,8 +173,5 @@ class DomainRedirectsCommandTest extends TestCase self::assertStringNotContainsString('default-domain.com', $output); self::assertStringContainsString('existing-one.com', $output); self::assertStringContainsString('existing-two.com', $output); - $listDomains->shouldHaveBeenCalledOnce(); - $findDomain->shouldHaveBeenCalledOnce(); - $configureRedirects->shouldHaveBeenCalledOnce(); } } From 3d358ab046cf275ccf2196f7ce730b0f7e67f7c9 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 13:21:00 +0200 Subject: [PATCH 040/182] Migrated GetDomainVisitsCommandTest to use PHPUnit mocks --- .../Domain/GetDomainVisitsCommandTest.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/module/CLI/test/Command/Domain/GetDomainVisitsCommandTest.php b/module/CLI/test/Command/Domain/GetDomainVisitsCommandTest.php index 85c5ef0c..a50138b6 100644 --- a/module/CLI/test/Command/Domain/GetDomainVisitsCommandTest.php +++ b/module/CLI/test/Command/Domain/GetDomainVisitsCommandTest.php @@ -5,9 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Domain; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Domain\GetDomainVisitsCommand; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; @@ -25,16 +24,16 @@ class GetDomainVisitsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $visitsHelper; - private ObjectProphecy $stringifier; + private MockObject $visitsHelper; + private MockObject $stringifier; protected function setUp(): void { - $this->visitsHelper = $this->prophesize(VisitsStatsHelperInterface::class); - $this->stringifier = $this->prophesize(ShortUrlStringifierInterface::class); + $this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class); + $this->stringifier = $this->createMock(ShortUrlStringifierInterface::class); $this->commandTester = $this->testerForCommand( - new GetDomainVisitsCommand($this->visitsHelper->reveal(), $this->stringifier->reveal()), + new GetDomainVisitsCommand($this->visitsHelper, $this->stringifier), ); } @@ -46,10 +45,13 @@ class GetDomainVisitsCommandTest extends TestCase VisitLocation::fromGeolocation(new Location('', 'Spain', '', 'Madrid', 0, 0, '')), ); $domain = 'doma.in'; - $getVisits = $this->visitsHelper->visitsForDomain($domain, Argument::any())->willReturn( - new Paginator(new ArrayAdapter([$visit])), + $this->visitsHelper->expects($this->once())->method('visitsForDomain')->with( + $this->equalTo($domain), + $this->anything(), + )->willReturn(new Paginator(new ArrayAdapter([$visit]))); + $this->stringifier->expects($this->once())->method('stringify')->with($this->equalTo($shortUrl))->willReturn( + 'the_short_url', ); - $stringify = $this->stringifier->stringify($shortUrl)->willReturn('the_short_url'); $this->commandTester->execute(['domain' => $domain]); $output = $this->commandTester->getDisplay(); @@ -65,7 +67,5 @@ class GetDomainVisitsCommandTest extends TestCase OUTPUT, $output, ); - $getVisits->shouldHaveBeenCalledOnce(); - $stringify->shouldHaveBeenCalledOnce(); } } From d8be3c28cbdbb99f43f711d4ae7c8636a38a8d28 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 13:21:54 +0200 Subject: [PATCH 041/182] Migrated ListDomainsCommandTest to use PHPUnit mocks --- .../test/Command/Domain/ListDomainsCommandTest.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/module/CLI/test/Command/Domain/ListDomainsCommandTest.php b/module/CLI/test/Command/Domain/ListDomainsCommandTest.php index efaa25ed..ef8b276c 100644 --- a/module/CLI/test/Command/Domain/ListDomainsCommandTest.php +++ b/module/CLI/test/Command/Domain/ListDomainsCommandTest.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Domain; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Domain\ListDomainsCommand; use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\Core\Config\NotFoundRedirects; @@ -21,12 +21,12 @@ class ListDomainsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $domainService; + private MockObject $domainService; protected function setUp(): void { - $this->domainService = $this->prophesize(DomainServiceInterface::class); - $this->commandTester = $this->testerForCommand(new ListDomainsCommand($this->domainService->reveal())); + $this->domainService = $this->createMock(DomainServiceInterface::class); + $this->commandTester = $this->testerForCommand(new ListDomainsCommand($this->domainService)); } /** @@ -42,7 +42,7 @@ class ListDomainsCommandTest extends TestCase 'https://foo.com/baz-domain/invalid', )); - $listDomains = $this->domainService->listDomains()->willReturn([ + $this->domainService->expects($this->once())->method('listDomains')->with()->willReturn([ DomainItem::forDefaultDomain('foo.com', new NotFoundRedirectOptions( invalidShortUrl: 'https://foo.com/default/invalid', baseUrl: 'https://foo.com/default/base', @@ -55,7 +55,6 @@ class ListDomainsCommandTest extends TestCase self::assertEquals($expectedOutput, $this->commandTester->getDisplay()); self::assertEquals(ExitCodes::EXIT_SUCCESS, $this->commandTester->getStatusCode()); - $listDomains->shouldHaveBeenCalledOnce(); } public function provideInputsAndOutputs(): iterable From 184ff90b9f7949e82530c700e943d552ec910205 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 13:27:48 +0200 Subject: [PATCH 042/182] Migrated CreateShortUrlCommandTest to use PHPUnit mocks --- .../ShortUrl/CreateShortUrlCommandTest.php | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php index 985a50a4..b1bbad77 100644 --- a/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php @@ -5,9 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\ShortUrl; use PHPUnit\Framework\Assert; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\ShortUrl\CreateShortUrlCommand; use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; @@ -27,18 +26,17 @@ class CreateShortUrlCommandTest extends TestCase private const DEFAULT_DOMAIN = 'default.com'; private CommandTester $commandTester; - private ObjectProphecy $urlShortener; - private ObjectProphecy $stringifier; + private MockObject $urlShortener; + private MockObject $stringifier; protected function setUp(): void { - $this->urlShortener = $this->prophesize(UrlShortener::class); - $this->stringifier = $this->prophesize(ShortUrlStringifierInterface::class); - $this->stringifier->stringify(Argument::type(ShortUrl::class))->willReturn(''); + $this->urlShortener = $this->createMock(UrlShortener::class); + $this->stringifier = $this->createMock(ShortUrlStringifierInterface::class); $command = new CreateShortUrlCommand( - $this->urlShortener->reveal(), - $this->stringifier->reveal(), + $this->urlShortener, + $this->stringifier, new UrlShortenerOptions(domain: ['hostname' => self::DEFAULT_DOMAIN], defaultShortCodesLength: 5), ); $this->commandTester = $this->testerForCommand($command); @@ -48,8 +46,10 @@ class CreateShortUrlCommandTest extends TestCase public function properShortCodeIsCreatedIfLongUrlIsCorrect(): void { $shortUrl = ShortUrl::createEmpty(); - $urlToShortCode = $this->urlShortener->shorten(Argument::cetera())->willReturn($shortUrl); - $stringify = $this->stringifier->stringify($shortUrl)->willReturn('stringified_short_url'); + $this->urlShortener->expects($this->once())->method('shorten')->withAnyParameters()->willReturn($shortUrl); + $this->stringifier->expects($this->once())->method('stringify')->with($this->equalTo($shortUrl))->willReturn( + 'stringified_short_url', + ); $this->commandTester->execute([ 'longUrl' => 'http://domain.com/foo/bar', @@ -59,16 +59,16 @@ class CreateShortUrlCommandTest extends TestCase self::assertEquals(ExitCodes::EXIT_SUCCESS, $this->commandTester->getStatusCode()); self::assertStringContainsString('stringified_short_url', $output); - $urlToShortCode->shouldHaveBeenCalledOnce(); - $stringify->shouldHaveBeenCalledOnce(); } /** @test */ public function exceptionWhileParsingLongUrlOutputsError(): void { $url = 'http://domain.com/invalid'; - $this->urlShortener->shorten(Argument::cetera())->willThrow(InvalidUrlException::fromUrl($url)) - ->shouldBeCalledOnce(); + $this->urlShortener->expects($this->once())->method('shorten')->withAnyParameters()->willThrowException( + InvalidUrlException::fromUrl($url), + ); + $this->stringifier->method('stringify')->with($this->isInstanceOf(ShortUrl::class))->willReturn(''); $this->commandTester->execute(['longUrl' => $url]); $output = $this->commandTester->getDisplay(); @@ -80,30 +80,32 @@ class CreateShortUrlCommandTest extends TestCase /** @test */ public function providingNonUniqueSlugOutputsError(): void { - $urlToShortCode = $this->urlShortener->shorten(Argument::cetera())->willThrow( + $this->urlShortener->expects($this->once())->method('shorten')->withAnyParameters()->willThrowException( NonUniqueSlugException::fromSlug('my-slug'), ); + $this->stringifier->method('stringify')->with($this->isInstanceOf(ShortUrl::class))->willReturn(''); $this->commandTester->execute(['longUrl' => 'http://domain.com/invalid', '--custom-slug' => 'my-slug']); $output = $this->commandTester->getDisplay(); self::assertEquals(ExitCodes::EXIT_FAILURE, $this->commandTester->getStatusCode()); self::assertStringContainsString('Provided slug "my-slug" is already in use', $output); - $urlToShortCode->shouldHaveBeenCalledOnce(); } /** @test */ public function properlyProcessesProvidedTags(): void { $shortUrl = ShortUrl::createEmpty(); - $urlToShortCode = $this->urlShortener->shorten( - Argument::that(function (ShortUrlCreation $meta) { + $this->urlShortener->expects($this->once())->method('shorten')->with( + $this->callback(function (ShortUrlCreation $meta) { $tags = $meta->getTags(); Assert::assertEquals(['foo', 'bar', 'baz', 'boo', 'zar'], $tags); return true; }), )->willReturn($shortUrl); - $stringify = $this->stringifier->stringify($shortUrl)->willReturn('stringified_short_url'); + $this->stringifier->expects($this->once())->method('stringify')->with($this->equalTo($shortUrl))->willReturn( + 'stringified_short_url', + ); $this->commandTester->execute([ 'longUrl' => 'http://domain.com/foo/bar', @@ -113,8 +115,6 @@ class CreateShortUrlCommandTest extends TestCase self::assertEquals(ExitCodes::EXIT_SUCCESS, $this->commandTester->getStatusCode()); self::assertStringContainsString('stringified_short_url', $output); - $urlToShortCode->shouldHaveBeenCalledOnce(); - $stringify->shouldHaveBeenCalledOnce(); } /** @@ -123,18 +123,18 @@ class CreateShortUrlCommandTest extends TestCase */ public function properlyProcessesProvidedDomain(array $input, ?string $expectedDomain): void { - $shorten = $this->urlShortener->shorten( - Argument::that(function (ShortUrlCreation $meta) use ($expectedDomain) { + $this->urlShortener->expects($this->once())->method('shorten')->with( + $this->callback(function (ShortUrlCreation $meta) use ($expectedDomain) { Assert::assertEquals($expectedDomain, $meta->getDomain()); return true; }), )->willReturn(ShortUrl::createEmpty()); + $this->stringifier->method('stringify')->with($this->isInstanceOf(ShortUrl::class))->willReturn(''); $input['longUrl'] = 'http://domain.com/foo/bar'; $this->commandTester->execute($input); self::assertEquals(ExitCodes::EXIT_SUCCESS, $this->commandTester->getStatusCode()); - $shorten->shouldHaveBeenCalledOnce(); } public function provideDomains(): iterable @@ -152,17 +152,16 @@ class CreateShortUrlCommandTest extends TestCase public function urlValidationHasExpectedValueBasedOnProvidedFlags(array $options, ?bool $expectedValidateUrl): void { $shortUrl = ShortUrl::createEmpty(); - $urlToShortCode = $this->urlShortener->shorten( - Argument::that(function (ShortUrlCreation $meta) use ($expectedValidateUrl) { + $this->urlShortener->expects($this->once())->method('shorten')->with( + $this->callback(function (ShortUrlCreation $meta) use ($expectedValidateUrl) { Assert::assertEquals($expectedValidateUrl, $meta->doValidateUrl()); - return $meta; + return true; }), )->willReturn($shortUrl); + $this->stringifier->method('stringify')->with($this->isInstanceOf(ShortUrl::class))->willReturn(''); $options['longUrl'] = 'http://domain.com/foo/bar'; $this->commandTester->execute($options); - - $urlToShortCode->shouldHaveBeenCalledOnce(); } public function provideFlags(): iterable From acda7f02c64e28564af901874e0a23e979043632 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 13:36:33 +0200 Subject: [PATCH 043/182] Migrated DeleteShortUrlCommandTest to use PHPUnit mocks --- .../ShortUrl/DeleteShortUrlCommandTest.php | 61 ++++++++----------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php index 92aca306..fce9197a 100644 --- a/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php @@ -4,9 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\ShortUrl; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\ShortUrl\DeleteShortUrlCommand; use Shlinkio\Shlink\Core\Exception; use Shlinkio\Shlink\Core\ShortUrl\DeleteShortUrlServiceInterface; @@ -14,7 +13,6 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; use ShlinkioTest\Shlink\CLI\CliTestUtilsTrait; use Symfony\Component\Console\Tester\CommandTester; -use function array_pop; use function sprintf; use const PHP_EOL; @@ -24,23 +22,22 @@ class DeleteShortUrlCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $service; + private MockObject $service; protected function setUp(): void { - $this->service = $this->prophesize(DeleteShortUrlServiceInterface::class); - $this->commandTester = $this->testerForCommand(new DeleteShortUrlCommand($this->service->reveal())); + $this->service = $this->createMock(DeleteShortUrlServiceInterface::class); + $this->commandTester = $this->testerForCommand(new DeleteShortUrlCommand($this->service)); } /** @test */ public function successMessageIsPrintedIfUrlIsProperlyDeleted(): void { $shortCode = 'abc123'; - $deleteByShortCode = $this->service->deleteByShortCode( - ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), - false, - )->will(function (): void { - }); + $this->service->expects($this->once())->method('deleteByShortCode')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), + $this->isFalse(), + ); $this->commandTester->execute(['shortCode' => $shortCode]); $output = $this->commandTester->getDisplay(); @@ -49,7 +46,6 @@ class DeleteShortUrlCommandTest extends TestCase sprintf('Short URL with short code "%s" successfully deleted.', $shortCode), $output, ); - $deleteByShortCode->shouldHaveBeenCalledOnce(); } /** @test */ @@ -57,15 +53,15 @@ class DeleteShortUrlCommandTest extends TestCase { $shortCode = 'abc123'; $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); - $deleteByShortCode = $this->service->deleteByShortCode($identifier, false)->willThrow( - Exception\ShortUrlNotFoundException::fromNotFound($identifier), - ); + $this->service->expects($this->once())->method('deleteByShortCode')->with( + $this->equalTo($identifier), + $this->isFalse(), + )->willThrowException(Exception\ShortUrlNotFoundException::fromNotFound($identifier)); $this->commandTester->execute(['shortCode' => $shortCode]); $output = $this->commandTester->getDisplay(); self::assertStringContainsString(sprintf('No URL found with short code "%s"', $shortCode), $output); - $deleteByShortCode->shouldHaveBeenCalledOnce(); } /** @@ -79,18 +75,17 @@ class DeleteShortUrlCommandTest extends TestCase ): void { $shortCode = 'abc123'; $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); - $deleteByShortCode = $this->service->deleteByShortCode($identifier, Argument::type('bool'))->will( - function (array $args) use ($shortCode): void { - $ignoreThreshold = array_pop($args); - - if (!$ignoreThreshold) { - throw Exception\DeleteShortUrlException::fromVisitsThreshold( - 10, - ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), - ); - } - }, - ); + $this->service->expects($this->exactly($expectedDeleteCalls))->method('deleteByShortCode')->with( + $this->equalTo($identifier), + $this->isType('bool'), + )->willReturnCallback(function ($_, bool $ignoreThreshold) use ($shortCode): void { + if (!$ignoreThreshold) { + throw Exception\DeleteShortUrlException::fromVisitsThreshold( + 10, + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), + ); + } + }); $this->commandTester->setInputs($retryAnswer); $this->commandTester->execute(['shortCode' => $shortCode]); @@ -101,7 +96,6 @@ class DeleteShortUrlCommandTest extends TestCase $shortCode, ), $output); self::assertStringContainsString($expectedMessage, $output); - $deleteByShortCode->shouldHaveBeenCalledTimes($expectedDeleteCalls); } public function provideRetryDeleteAnswers(): iterable @@ -115,10 +109,10 @@ class DeleteShortUrlCommandTest extends TestCase public function deleteIsNotRetriedWhenThresholdIsReachedAndQuestionIsDeclined(): void { $shortCode = 'abc123'; - $deleteByShortCode = $this->service->deleteByShortCode( - ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), - false, - )->willThrow(Exception\DeleteShortUrlException::fromVisitsThreshold( + $this->service->expects($this->once())->method('deleteByShortCode')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), + $this->isFalse(), + )->willThrowException(Exception\DeleteShortUrlException::fromVisitsThreshold( 10, ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), )); @@ -132,6 +126,5 @@ class DeleteShortUrlCommandTest extends TestCase $shortCode, ), $output); self::assertStringContainsString('Short URL was not deleted.', $output); - $deleteByShortCode->shouldHaveBeenCalledOnce(); } } From 8b675f55cc0edb867d852a49ea210c212982cee3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 13:38:46 +0200 Subject: [PATCH 044/182] Migrated GetShortUrlVisitsCommandTest to use PHPUnit mocks --- .../ShortUrl/GetShortUrlVisitsCommandTest.php | 46 ++++++++----------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php b/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php index 00fca968..f271e26b 100644 --- a/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php @@ -6,9 +6,8 @@ namespace ShlinkioTest\Shlink\CLI\Command\ShortUrl; use Cake\Chronos\Chronos; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\ShortUrl\GetShortUrlVisitsCommand; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Common\Util\DateRange; @@ -31,12 +30,12 @@ class GetShortUrlVisitsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $visitsHelper; + private MockObject $visitsHelper; protected function setUp(): void { - $this->visitsHelper = $this->prophesize(VisitsStatsHelperInterface::class); - $command = new GetShortUrlVisitsCommand($this->visitsHelper->reveal()); + $this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class); + $command = new GetShortUrlVisitsCommand($this->visitsHelper); $this->commandTester = $this->testerForCommand($command); } @@ -44,12 +43,10 @@ class GetShortUrlVisitsCommandTest extends TestCase public function noDateFlagsTriesToListWithoutDateRange(): void { $shortCode = 'abc123'; - $this->visitsHelper->visitsForShortUrl( - ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), - new VisitsParams(DateRange::allTime()), - ) - ->willReturn(new Paginator(new ArrayAdapter([]))) - ->shouldBeCalledOnce(); + $this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), + $this->equalTo(new VisitsParams(DateRange::allTime())), + )->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->execute(['shortCode' => $shortCode]); } @@ -60,12 +57,10 @@ class GetShortUrlVisitsCommandTest extends TestCase $shortCode = 'abc123'; $startDate = '2016-01-01'; $endDate = '2016-02-01'; - $this->visitsHelper->visitsForShortUrl( - ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), - new VisitsParams(buildDateRange(Chronos::parse($startDate), Chronos::parse($endDate))), - ) - ->willReturn(new Paginator(new ArrayAdapter([]))) - ->shouldBeCalledOnce(); + $this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), + $this->equalTo(new VisitsParams(buildDateRange(Chronos::parse($startDate), Chronos::parse($endDate)))), + )->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->execute([ 'shortCode' => $shortCode, @@ -79,9 +74,9 @@ class GetShortUrlVisitsCommandTest extends TestCase { $shortCode = 'abc123'; $startDate = 'foo'; - $info = $this->visitsHelper->visitsForShortUrl( - ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), - new VisitsParams(DateRange::allTime()), + $this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), + $this->equalTo(new VisitsParams(DateRange::allTime())), )->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->execute([ @@ -90,7 +85,6 @@ class GetShortUrlVisitsCommandTest extends TestCase ]); $output = $this->commandTester->getDisplay(); - $info->shouldHaveBeenCalledOnce(); self::assertStringContainsString( sprintf('Ignored provided "start-date" since its value "%s" is not a valid date', $startDate), $output, @@ -104,12 +98,10 @@ class GetShortUrlVisitsCommandTest extends TestCase VisitLocation::fromGeolocation(new Location('', 'Spain', '', 'Madrid', 0, 0, '')), ); $shortCode = 'abc123'; - $this->visitsHelper->visitsForShortUrl( - ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), - Argument::any(), - )->willReturn( - new Paginator(new ArrayAdapter([$visit])), - )->shouldBeCalledOnce(); + $this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), + $this->anything(), + )->willReturn(new Paginator(new ArrayAdapter([$visit]))); $this->commandTester->execute(['shortCode' => $shortCode]); $output = $this->commandTester->getDisplay(); From 4872bd3a92682ed9ea5905809dc41cf4a3e17850 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 13:42:46 +0200 Subject: [PATCH 045/182] Migrated ListShortUrlsCommandTest to use PHPUnit mocks --- .../ShortUrl/ListShortUrlsCommandTest.php | 98 +++++++++---------- 1 file changed, 48 insertions(+), 50 deletions(-) diff --git a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php index 5659059a..dfd1a028 100644 --- a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php @@ -6,9 +6,8 @@ namespace ShlinkioTest\Shlink\CLI\Command\ShortUrl; use Cake\Chronos\Chronos; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\ShortUrl\ListShortUrlsCommand; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; @@ -31,12 +30,12 @@ class ListShortUrlsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $shortUrlService; + private MockObject $shortUrlService; protected function setUp(): void { - $this->shortUrlService = $this->prophesize(ShortUrlServiceInterface::class); - $command = new ListShortUrlsCommand($this->shortUrlService->reveal(), new ShortUrlDataTransformer( + $this->shortUrlService = $this->createMock(ShortUrlServiceInterface::class); + $command = new ListShortUrlsCommand($this->shortUrlService, new ShortUrlDataTransformer( new ShortUrlStringifier([]), )); $this->commandTester = $this->testerForCommand($command); @@ -51,9 +50,8 @@ class ListShortUrlsCommandTest extends TestCase $data[] = ShortUrl::withLongUrl('url_' . $i); } - $this->shortUrlService->listShortUrls(Argument::cetera()) - ->will(fn () => new Paginator(new ArrayAdapter($data))) - ->shouldBeCalledTimes(3); + $this->shortUrlService->expects($this->exactly(3))->method('listShortUrls')->withAnyParameters() + ->willReturnCallback(fn () => new Paginator(new ArrayAdapter($data))); $this->commandTester->setInputs(['y', 'y', 'n']); $this->commandTester->execute([]); @@ -74,9 +72,9 @@ class ListShortUrlsCommandTest extends TestCase $data[] = ShortUrl::withLongUrl('url_' . $i); } - $this->shortUrlService->listShortUrls(ShortUrlsParams::emptyInstance()) - ->willReturn(new Paginator(new ArrayAdapter($data))) - ->shouldBeCalledOnce(); + $this->shortUrlService->expects($this->once())->method('listShortUrls')->with( + $this->equalTo(ShortUrlsParams::emptyInstance()) + )->willReturn(new Paginator(new ArrayAdapter($data))); $this->commandTester->setInputs(['n']); $this->commandTester->execute([]); @@ -95,9 +93,9 @@ class ListShortUrlsCommandTest extends TestCase public function passingPageWillMakeListStartOnThatPage(): void { $page = 5; - $this->shortUrlService->listShortUrls(ShortUrlsParams::fromRawData(['page' => $page])) - ->willReturn(new Paginator(new ArrayAdapter([]))) - ->shouldBeCalledOnce(); + $this->shortUrlService->expects($this->once())->method('listShortUrls')->with( + $this->equalTo(ShortUrlsParams::fromRawData(['page' => $page])), + )->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->setInputs(['y']); $this->commandTester->execute(['--page' => $page]); @@ -113,15 +111,15 @@ class ListShortUrlsCommandTest extends TestCase array $notExpectedContents, ApiKey $apiKey, ): void { - $this->shortUrlService->listShortUrls(ShortUrlsParams::emptyInstance()) - ->willReturn(new Paginator(new ArrayAdapter([ - ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ - 'longUrl' => 'foo.com', - 'tags' => ['foo', 'bar', 'baz'], - 'apiKey' => $apiKey, - ])), - ]))) - ->shouldBeCalledOnce(); + $this->shortUrlService->expects($this->once())->method('listShortUrls')->with( + $this->equalTo(ShortUrlsParams::emptyInstance()), + )->willReturn(new Paginator(new ArrayAdapter([ + ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo.com', + 'tags' => ['foo', 'bar', 'baz'], + 'apiKey' => $apiKey, + ])), + ]))); $this->commandTester->setInputs(['y']); $this->commandTester->execute($input); @@ -189,19 +187,19 @@ class ListShortUrlsCommandTest extends TestCase ?string $startDate = null, ?string $endDate = null, ): void { - $listShortUrls = $this->shortUrlService->listShortUrls(ShortUrlsParams::fromRawData([ - 'page' => $page, - 'searchTerm' => $searchTerm, - 'tags' => $tags, - 'tagsMode' => $tagsMode, - 'startDate' => $startDate !== null ? Chronos::parse($startDate)->toAtomString() : null, - 'endDate' => $endDate !== null ? Chronos::parse($endDate)->toAtomString() : null, - ]))->willReturn(new Paginator(new ArrayAdapter([]))); + $this->shortUrlService->expects($this->once())->method('listShortUrls')->with( + $this->equalTo(ShortUrlsParams::fromRawData([ + 'page' => $page, + 'searchTerm' => $searchTerm, + 'tags' => $tags, + 'tagsMode' => $tagsMode, + 'startDate' => $startDate !== null ? Chronos::parse($startDate)->toAtomString() : null, + 'endDate' => $endDate !== null ? Chronos::parse($endDate)->toAtomString() : null, + ])), + )->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->setInputs(['n']); $this->commandTester->execute($commandArgs); - - $listShortUrls->shouldHaveBeenCalledOnce(); } public function provideArgs(): iterable @@ -251,14 +249,14 @@ class ListShortUrlsCommandTest extends TestCase */ public function orderByIsProperlyComputed(array $commandArgs, ?string $expectedOrderBy): void { - $listShortUrls = $this->shortUrlService->listShortUrls(ShortUrlsParams::fromRawData([ - 'orderBy' => $expectedOrderBy, - ]))->willReturn(new Paginator(new ArrayAdapter([]))); + $this->shortUrlService->expects($this->once())->method('listShortUrls')->with( + $this->equalTo(ShortUrlsParams::fromRawData([ + 'orderBy' => $expectedOrderBy, + ])), + )->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->setInputs(['n']); $this->commandTester->execute($commandArgs); - - $listShortUrls->shouldHaveBeenCalledOnce(); } public function provideOrderBy(): iterable @@ -273,19 +271,19 @@ class ListShortUrlsCommandTest extends TestCase /** @test */ public function requestingAllElementsWillSetItemsPerPage(): void { - $listShortUrls = $this->shortUrlService->listShortUrls(ShortUrlsParams::fromRawData([ - 'page' => 1, - 'searchTerm' => null, - 'tags' => [], - 'tagsMode' => TagsMode::ANY->value, - 'startDate' => null, - 'endDate' => null, - 'orderBy' => null, - 'itemsPerPage' => Paginator::ALL_ITEMS, - ]))->willReturn(new Paginator(new ArrayAdapter([]))); + $this->shortUrlService->expects($this->once())->method('listShortUrls')->with( + $this->equalTo(ShortUrlsParams::fromRawData([ + 'page' => 1, + 'searchTerm' => null, + 'tags' => [], + 'tagsMode' => TagsMode::ANY->value, + 'startDate' => null, + 'endDate' => null, + 'orderBy' => null, + 'itemsPerPage' => Paginator::ALL_ITEMS, + ])), + )->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->execute(['--all' => true]); - - $listShortUrls->shouldHaveBeenCalledOnce(); } } From 41e903cf267f1666a2a4705333f439b3b3595c3f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 13:44:10 +0200 Subject: [PATCH 046/182] Migrated ResolveUrlCommandTest to use PHPUnit mocks --- .../ShortUrl/ResolveUrlCommandTest.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php index 6050f736..3b063ab4 100644 --- a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\ShortUrl; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\ShortUrl\ResolveUrlCommand; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; @@ -23,12 +23,12 @@ class ResolveUrlCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $urlResolver; + private MockObject $urlResolver; protected function setUp(): void { - $this->urlResolver = $this->prophesize(ShortUrlResolverInterface::class); - $this->commandTester = $this->testerForCommand(new ResolveUrlCommand($this->urlResolver->reveal())); + $this->urlResolver = $this->createMock(ShortUrlResolverInterface::class); + $this->commandTester = $this->testerForCommand(new ResolveUrlCommand($this->urlResolver)); } /** @test */ @@ -37,9 +37,9 @@ class ResolveUrlCommandTest extends TestCase $shortCode = 'abc123'; $expectedUrl = 'http://domain.com/foo/bar'; $shortUrl = ShortUrl::withLongUrl($expectedUrl); - $this->urlResolver->resolveShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode))->willReturn( - $shortUrl, - )->shouldBeCalledOnce(); + $this->urlResolver->expects($this->once())->method('resolveShortUrl')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), + )->willReturn($shortUrl); $this->commandTester->execute(['shortCode' => $shortCode]); $output = $this->commandTester->getDisplay(); @@ -52,9 +52,9 @@ class ResolveUrlCommandTest extends TestCase $identifier = ShortUrlIdentifier::fromShortCodeAndDomain('abc123'); $shortCode = $identifier->shortCode; - $this->urlResolver->resolveShortUrl($identifier) - ->willThrow(ShortUrlNotFoundException::fromNotFound($identifier)) - ->shouldBeCalledOnce(); + $this->urlResolver->expects($this->once())->method('resolveShortUrl')->with( + $this->equalTo($identifier) + )->willThrowException(ShortUrlNotFoundException::fromNotFound($identifier)); $this->commandTester->execute(['shortCode' => $shortCode]); $output = $this->commandTester->getDisplay(); From 2d168565820b5d925e23dc4d21e5b16a780328d0 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 13:45:11 +0200 Subject: [PATCH 047/182] Migrated DeleteTagsCommandTest to use PHPUnit mocks --- .../CLI/test/Command/Tag/DeleteTagsCommandTest.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php b/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php index b03bf1ee..fbaa248f 100644 --- a/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php +++ b/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Tag; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Tag\DeleteTagsCommand; use Shlinkio\Shlink\Core\Tag\TagServiceInterface; use ShlinkioTest\Shlink\CLI\CliTestUtilsTrait; @@ -16,12 +16,12 @@ class DeleteTagsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $tagService; + private MockObject $tagService; protected function setUp(): void { - $this->tagService = $this->prophesize(TagServiceInterface::class); - $this->commandTester = $this->testerForCommand(new DeleteTagsCommand($this->tagService->reveal())); + $this->tagService = $this->createMock(TagServiceInterface::class); + $this->commandTester = $this->testerForCommand(new DeleteTagsCommand($this->tagService)); } /** @test */ @@ -37,8 +37,7 @@ class DeleteTagsCommandTest extends TestCase public function serviceIsInvokedOnSuccess(): void { $tagNames = ['foo', 'bar']; - $deleteTags = $this->tagService->deleteTags($tagNames)->will(function (): void { - }); + $this->tagService->expects($this->once())->method('deleteTags')->with($this->equalTo($tagNames)); $this->commandTester->execute([ '--name' => $tagNames, @@ -46,6 +45,5 @@ class DeleteTagsCommandTest extends TestCase $output = $this->commandTester->getDisplay(); self::assertStringContainsString('Tags properly deleted', $output); - $deleteTags->shouldHaveBeenCalled(); } } From 8c6f97c4e26a9c6027348c95721ac0ddc7bcce8f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 13:47:28 +0200 Subject: [PATCH 048/182] Migrated GetTagVisitsCommandTest to use PHPUnit mocks --- .../Command/Tag/GetTagVisitsCommandTest.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/module/CLI/test/Command/Tag/GetTagVisitsCommandTest.php b/module/CLI/test/Command/Tag/GetTagVisitsCommandTest.php index dedbbf83..f2832541 100644 --- a/module/CLI/test/Command/Tag/GetTagVisitsCommandTest.php +++ b/module/CLI/test/Command/Tag/GetTagVisitsCommandTest.php @@ -5,9 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Tag; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Tag\GetTagVisitsCommand; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; @@ -25,16 +24,16 @@ class GetTagVisitsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $visitsHelper; - private ObjectProphecy $stringifier; + private MockObject $visitsHelper; + private MockObject $stringifier; protected function setUp(): void { - $this->visitsHelper = $this->prophesize(VisitsStatsHelperInterface::class); - $this->stringifier = $this->prophesize(ShortUrlStringifierInterface::class); + $this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class); + $this->stringifier = $this->createMock(ShortUrlStringifierInterface::class); $this->commandTester = $this->testerForCommand( - new GetTagVisitsCommand($this->visitsHelper->reveal(), $this->stringifier->reveal()), + new GetTagVisitsCommand($this->visitsHelper, $this->stringifier), ); } @@ -46,10 +45,13 @@ class GetTagVisitsCommandTest extends TestCase VisitLocation::fromGeolocation(new Location('', 'Spain', '', 'Madrid', 0, 0, '')), ); $tag = 'abc123'; - $getVisits = $this->visitsHelper->visitsForTag($tag, Argument::any())->willReturn( - new Paginator(new ArrayAdapter([$visit])), + $this->visitsHelper->expects($this->once())->method('visitsForTag')->with( + $this->equalTo($tag), + $this->anything(), + )->willReturn(new Paginator(new ArrayAdapter([$visit]))); + $this->stringifier->expects($this->once())->method('stringify')->with($this->equalTo($shortUrl))->willReturn( + 'the_short_url', ); - $stringify = $this->stringifier->stringify($shortUrl)->willReturn('the_short_url'); $this->commandTester->execute(['tag' => $tag]); $output = $this->commandTester->getDisplay(); @@ -65,7 +67,5 @@ class GetTagVisitsCommandTest extends TestCase OUTPUT, $output, ); - $getVisits->shouldHaveBeenCalledOnce(); - $stringify->shouldHaveBeenCalledOnce(); } } From 085510406842c6031898c72d6246dcda90784a87 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 13:49:11 +0200 Subject: [PATCH 049/182] Migrated ListTagsCommandTest to use PHPUnit mocks --- .../test/Command/Tag/ListTagsCommandTest.php | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/module/CLI/test/Command/Tag/ListTagsCommandTest.php b/module/CLI/test/Command/Tag/ListTagsCommandTest.php index 58ae1ef1..e3802a1a 100644 --- a/module/CLI/test/Command/Tag/ListTagsCommandTest.php +++ b/module/CLI/test/Command/Tag/ListTagsCommandTest.php @@ -5,9 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Tag; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Tag\ListTagsCommand; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\Tag\Model\TagInfo; @@ -20,33 +19,36 @@ class ListTagsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $tagService; + private MockObject $tagService; protected function setUp(): void { - $this->tagService = $this->prophesize(TagServiceInterface::class); - $this->commandTester = $this->testerForCommand(new ListTagsCommand($this->tagService->reveal())); + $this->tagService = $this->createMock(TagServiceInterface::class); + $this->commandTester = $this->testerForCommand(new ListTagsCommand($this->tagService)); } /** @test */ public function noTagsPrintsEmptyMessage(): void { - $tagsInfo = $this->tagService->tagsInfo(Argument::any())->willReturn(new Paginator(new ArrayAdapter([]))); + $this->tagService->expects($this->once())->method('tagsInfo')->withAnyParameters()->willReturn( + new Paginator(new ArrayAdapter([])), + ); $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); self::assertStringContainsString('No tags found', $output); - $tagsInfo->shouldHaveBeenCalled(); } /** @test */ public function listOfTagsIsPrinted(): void { - $tagsInfo = $this->tagService->tagsInfo(Argument::any())->willReturn(new Paginator(new ArrayAdapter([ - new TagInfo('foo', 10, 2), - new TagInfo('bar', 7, 32), - ]))); + $this->tagService->expects($this->once())->method('tagsInfo')->withAnyParameters()->willReturn( + new Paginator(new ArrayAdapter([ + new TagInfo('foo', 10, 2), + new TagInfo('bar', 7, 32), + ])), + ); $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); @@ -63,6 +65,5 @@ class ListTagsCommandTest extends TestCase OUTPUT, $output, ); - $tagsInfo->shouldHaveBeenCalled(); } } From 59de5a5f551a6ae41195c2adab3698c05c003b17 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 13:53:45 +0200 Subject: [PATCH 050/182] Migrated RenameTagCommandTest to use PHPUnit mocks --- .../test/Command/Tag/RenameTagCommandTest.php | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/module/CLI/test/Command/Tag/RenameTagCommandTest.php b/module/CLI/test/Command/Tag/RenameTagCommandTest.php index 9de8d154..a3429e40 100644 --- a/module/CLI/test/Command/Tag/RenameTagCommandTest.php +++ b/module/CLI/test/Command/Tag/RenameTagCommandTest.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Tag; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Tag\RenameTagCommand; use Shlinkio\Shlink\Core\Exception\TagNotFoundException; use Shlinkio\Shlink\Core\Tag\Entity\Tag; @@ -19,12 +19,12 @@ class RenameTagCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $tagService; + private MockObject $tagService; protected function setUp(): void { - $this->tagService = $this->prophesize(TagServiceInterface::class); - $this->commandTester = $this->testerForCommand(new RenameTagCommand($this->tagService->reveal())); + $this->tagService = $this->createMock(TagServiceInterface::class); + $this->commandTester = $this->testerForCommand(new RenameTagCommand($this->tagService)); } /** @test */ @@ -32,9 +32,9 @@ class RenameTagCommandTest extends TestCase { $oldName = 'foo'; $newName = 'bar'; - $renameTag = $this->tagService->renameTag(TagRenaming::fromNames($oldName, $newName))->willThrow( - TagNotFoundException::fromTag('foo'), - ); + $this->tagService->expects($this->once())->method('renameTag')->with( + $this->equalTo(TagRenaming::fromNames($oldName, $newName)), + )->willThrowException(TagNotFoundException::fromTag('foo')); $this->commandTester->execute([ 'oldName' => $oldName, @@ -43,7 +43,6 @@ class RenameTagCommandTest extends TestCase $output = $this->commandTester->getDisplay(); self::assertStringContainsString('Tag with name "foo" could not be found', $output); - $renameTag->shouldHaveBeenCalled(); } /** @test */ @@ -51,9 +50,9 @@ class RenameTagCommandTest extends TestCase { $oldName = 'foo'; $newName = 'bar'; - $renameTag = $this->tagService->renameTag(TagRenaming::fromNames($oldName, $newName))->willReturn( - new Tag($newName), - ); + $this->tagService->expects($this->once())->method('renameTag')->with( + $this->equalTo(TagRenaming::fromNames($oldName, $newName)), + )->willReturn(new Tag($newName)); $this->commandTester->execute([ 'oldName' => $oldName, @@ -62,6 +61,5 @@ class RenameTagCommandTest extends TestCase $output = $this->commandTester->getDisplay(); self::assertStringContainsString('Tag properly renamed', $output); - $renameTag->shouldHaveBeenCalled(); } } From 5d367da626e2d3c8b254801ce8913423c5f128d1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 14:02:38 +0200 Subject: [PATCH 051/182] Migrated DownloadGeoLiteDbCommandTest to use PHPUnit mocks --- .../Visit/DownloadGeoLiteDbCommandTest.php | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/module/CLI/test/Command/Visit/DownloadGeoLiteDbCommandTest.php b/module/CLI/test/Command/Visit/DownloadGeoLiteDbCommandTest.php index 93405799..b5197dde 100644 --- a/module/CLI/test/Command/Visit/DownloadGeoLiteDbCommandTest.php +++ b/module/CLI/test/Command/Visit/DownloadGeoLiteDbCommandTest.php @@ -4,9 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Visit; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Visit\DownloadGeoLiteDbCommand; use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException; use Shlinkio\Shlink\CLI\GeoLite\GeolocationDbUpdaterInterface; @@ -22,12 +21,12 @@ class DownloadGeoLiteDbCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $dbUpdater; + private MockObject $dbUpdater; protected function setUp(): void { - $this->dbUpdater = $this->prophesize(GeolocationDbUpdaterInterface::class); - $this->commandTester = $this->testerForCommand(new DownloadGeoLiteDbCommand($this->dbUpdater->reveal())); + $this->dbUpdater = $this->createMock(GeolocationDbUpdaterInterface::class); + $this->commandTester = $this->testerForCommand(new DownloadGeoLiteDbCommand($this->dbUpdater)); } /** @@ -39,10 +38,8 @@ class DownloadGeoLiteDbCommandTest extends TestCase string $expectedMessage, int $expectedExitCode, ): void { - $checkDbUpdate = $this->dbUpdater->checkDbUpdate(Argument::cetera())->will( - function (array $args) use ($olderDbExists): void { - [$beforeDownload, $handleProgress] = $args; - + $this->dbUpdater->expects($this->once())->method('checkDbUpdate')->withAnyParameters()->willReturnCallback( + function (callable $beforeDownload, callable $handleProgress) use ($olderDbExists): void { $beforeDownload($olderDbExists); $handleProgress(100, 50); @@ -62,7 +59,6 @@ class DownloadGeoLiteDbCommandTest extends TestCase ); self::assertStringContainsString($expectedMessage, $output); self::assertSame($expectedExitCode, $exitCode); - $checkDbUpdate->shouldHaveBeenCalledOnce(); } public function provideFailureParams(): iterable @@ -85,7 +81,9 @@ class DownloadGeoLiteDbCommandTest extends TestCase */ public function printsExpectedMessageWhenNoErrorOccurs(callable $checkUpdateBehavior, string $expectedMessage): void { - $checkDbUpdate = $this->dbUpdater->checkDbUpdate(Argument::cetera())->will($checkUpdateBehavior); + $this->dbUpdater->expects($this->once())->method('checkDbUpdate')->withAnyParameters()->willReturnCallback( + $checkUpdateBehavior, + ); $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); @@ -93,16 +91,13 @@ class DownloadGeoLiteDbCommandTest extends TestCase self::assertStringContainsString($expectedMessage, $output); self::assertSame(ExitCodes::EXIT_SUCCESS, $exitCode); - $checkDbUpdate->shouldHaveBeenCalledOnce(); } public function provideSuccessParams(): iterable { yield 'up to date db' => [fn () => GeolocationResult::CHECK_SKIPPED, '[INFO] GeoLite2 db file is up to date.']; - yield 'outdated db' => [function (array $args): GeolocationResult { - [$beforeDownload] = $args; + yield 'outdated db' => [function (callable $beforeDownload): GeolocationResult { $beforeDownload(true); - return GeolocationResult::DB_CREATED; }, '[OK] GeoLite2 db file properly downloaded.']; } From 82e04800aadb6ce85972d573f355bc16e8e9ba33 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 14:06:00 +0200 Subject: [PATCH 052/182] Migrated GetNonOrphanVisitsCommandTest to use PHPUnit mocks --- .../Visit/GetNonOrphanVisitsCommandTest.php | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/module/CLI/test/Command/Visit/GetNonOrphanVisitsCommandTest.php b/module/CLI/test/Command/Visit/GetNonOrphanVisitsCommandTest.php index 6f83b8b5..23f20da9 100644 --- a/module/CLI/test/Command/Visit/GetNonOrphanVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/GetNonOrphanVisitsCommandTest.php @@ -5,9 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Visit; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Visit\GetNonOrphanVisitsCommand; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; @@ -25,16 +24,16 @@ class GetNonOrphanVisitsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $visitsHelper; - private ObjectProphecy $stringifier; + private MockObject $visitsHelper; + private MockObject $stringifier; protected function setUp(): void { - $this->visitsHelper = $this->prophesize(VisitsStatsHelperInterface::class); - $this->stringifier = $this->prophesize(ShortUrlStringifierInterface::class); + $this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class); + $this->stringifier = $this->createMock(ShortUrlStringifierInterface::class); $this->commandTester = $this->testerForCommand( - new GetNonOrphanVisitsCommand($this->visitsHelper->reveal(), $this->stringifier->reveal()), + new GetNonOrphanVisitsCommand($this->visitsHelper, $this->stringifier), ); } @@ -45,10 +44,12 @@ class GetNonOrphanVisitsCommandTest extends TestCase $visit = Visit::forValidShortUrl($shortUrl, new Visitor('bar', 'foo', '', ''))->locate( VisitLocation::fromGeolocation(new Location('', 'Spain', '', 'Madrid', 0, 0, '')), ); - $getVisits = $this->visitsHelper->nonOrphanVisits(Argument::any())->willReturn( + $this->visitsHelper->expects($this->once())->method('nonOrphanVisits')->withAnyParameters()->willReturn( new Paginator(new ArrayAdapter([$visit])), ); - $stringify = $this->stringifier->stringify($shortUrl)->willReturn('the_short_url'); + $this->stringifier->expects($this->once())->method('stringify')->with($this->equalTo($shortUrl))->willReturn( + 'the_short_url', + ); $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); @@ -64,7 +65,5 @@ class GetNonOrphanVisitsCommandTest extends TestCase OUTPUT, $output, ); - $getVisits->shouldHaveBeenCalledOnce(); - $stringify->shouldHaveBeenCalledOnce(); } } From e2986a7b4c56bd9597f716388de8fc4837c18aac Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 14:06:54 +0200 Subject: [PATCH 053/182] Migrated GetOrphanVisitsCommandTest to use PHPUnit mocks --- .../Command/Visit/GetOrphanVisitsCommandTest.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/module/CLI/test/Command/Visit/GetOrphanVisitsCommandTest.php b/module/CLI/test/Command/Visit/GetOrphanVisitsCommandTest.php index 8c4a717d..71f8f7b2 100644 --- a/module/CLI/test/Command/Visit/GetOrphanVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/GetOrphanVisitsCommandTest.php @@ -5,9 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Visit; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Visit\GetOrphanVisitsCommand; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\Visit\Entity\Visit; @@ -23,12 +22,12 @@ class GetOrphanVisitsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $visitsHelper; + private MockObject $visitsHelper; protected function setUp(): void { - $this->visitsHelper = $this->prophesize(VisitsStatsHelperInterface::class); - $this->commandTester = $this->testerForCommand(new GetOrphanVisitsCommand($this->visitsHelper->reveal())); + $this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class); + $this->commandTester = $this->testerForCommand(new GetOrphanVisitsCommand($this->visitsHelper)); } /** @test */ @@ -37,7 +36,7 @@ class GetOrphanVisitsCommandTest extends TestCase $visit = Visit::forBasePath(new Visitor('bar', 'foo', '', ''))->locate( VisitLocation::fromGeolocation(new Location('', 'Spain', '', 'Madrid', 0, 0, '')), ); - $getVisits = $this->visitsHelper->orphanVisits(Argument::any())->willReturn( + $this->visitsHelper->expects($this->once())->method('orphanVisits')->withAnyParameters()->willReturn( new Paginator(new ArrayAdapter([$visit])), ); @@ -55,6 +54,5 @@ class GetOrphanVisitsCommandTest extends TestCase OUTPUT, $output, ); - $getVisits->shouldHaveBeenCalledOnce(); } } From 4b3ed2b7ba85a6db0ddce9b1a0174dedbc4d17ff Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 14:16:42 +0200 Subject: [PATCH 054/182] Migrated LocateVisitsCommandTest to use PHPUnit mocks --- .../Command/Visit/LocateVisitsCommandTest.php | 99 +++++++++---------- 1 file changed, 44 insertions(+), 55 deletions(-) diff --git a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php index 0db2f493..418e3af6 100644 --- a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php @@ -6,8 +6,6 @@ namespace ShlinkioTest\Shlink\CLI\Command\Visit; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Visit\DownloadGeoLiteDbCommand; use Shlinkio\Shlink\CLI\Command\Visit\LocateVisitsCommand; use Shlinkio\Shlink\CLI\Util\ExitCodes; @@ -36,28 +34,21 @@ class LocateVisitsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private ObjectProphecy $visitService; - private ObjectProphecy $visitToLocation; - private ObjectProphecy $lock; + private MockObject $visitService; + private MockObject $visitToLocation; + private MockObject $lock; private MockObject $downloadDbCommand; protected function setUp(): void { - $this->visitService = $this->prophesize(VisitLocator::class); - $this->visitToLocation = $this->prophesize(VisitToLocationHelperInterface::class); + $this->visitService = $this->createMock(VisitLocator::class); + $this->visitToLocation = $this->createMock(VisitToLocationHelperInterface::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 { - }); - $locker->createLock(Argument::type('string'), 600.0, false)->willReturn($this->lock->reveal()); + $locker = $this->createMock(Lock\LockFactory::class); + $this->lock = $this->createMock(Lock\LockInterface::class); + $locker->method('createLock')->with($this->isType('string'), 600.0, false)->willReturn($this->lock); - $command = new LocateVisitsCommand( - $this->visitService->reveal(), - $this->visitToLocation->reveal(), - $locker->reveal(), - ); + $command = new LocateVisitsCommand($this->visitService, $this->visitToLocation, $locker); $this->downloadDbCommand = $this->createCommandMock(DownloadGeoLiteDbCommand::NAME); $this->commandTester = $this->testerForCommand($command, $this->downloadDbCommand); @@ -78,14 +69,22 @@ class LocateVisitsCommandTest extends TestCase $location = VisitLocation::fromGeolocation(Location::emptyInstance()); $mockMethodBehavior = $this->invokeHelperMethods($visit, $location); - $locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will($mockMethodBehavior); - $locateEmptyVisits = $this->visitService->locateVisitsWithEmptyLocation(Argument::cetera())->will( - $mockMethodBehavior, - ); - $locateAllVisits = $this->visitService->locateAllVisits(Argument::cetera())->will($mockMethodBehavior); - $resolveIpLocation = $this->visitToLocation->resolveVisitLocation(Argument::any())->willReturn( - Location::emptyInstance(), - ); + $this->lock->method('acquire')->with($this->isFalse())->willReturn(true); + $this->visitService->expects($this->exactly($expectedUnlocatedCalls)) + ->method('locateUnlocatedVisits') + ->withAnyParameters() + ->willReturnCallback($mockMethodBehavior); + $this->visitService->expects($this->exactly($expectedEmptyCalls)) + ->method('locateVisitsWithEmptyLocation') + ->withAnyParameters() + ->willReturnCallback($mockMethodBehavior); + $this->visitService->expects($this->exactly($expectedAllCalls)) + ->method('locateAllVisits') + ->withAnyParameters() + ->willReturnCallback($mockMethodBehavior); + $this->visitToLocation->expects( + $this->exactly($expectedUnlocatedCalls + $expectedEmptyCalls + $expectedAllCalls), + )->method('resolveVisitLocation')->withAnyParameters()->willReturn(Location::emptyInstance()); $this->downloadDbCommand->method('run')->withAnyParameters()->willReturn(ExitCodes::EXIT_SUCCESS); $this->commandTester->setInputs(['y']); @@ -98,12 +97,6 @@ class LocateVisitsCommandTest extends TestCase } else { self::assertStringNotContainsString('Continue at your own', $output); } - $locateVisits->shouldHaveBeenCalledTimes($expectedUnlocatedCalls); - $locateEmptyVisits->shouldHaveBeenCalledTimes($expectedEmptyCalls); - $locateAllVisits->shouldHaveBeenCalledTimes($expectedAllCalls); - $resolveIpLocation->shouldHaveBeenCalledTimes( - $expectedUnlocatedCalls + $expectedEmptyCalls + $expectedAllCalls, - ); } public function provideArgs(): iterable @@ -122,10 +115,12 @@ class LocateVisitsCommandTest extends TestCase $visit = Visit::forValidShortUrl(ShortUrl::createEmpty(), Visitor::emptyInstance()); $location = VisitLocation::fromGeolocation(Location::emptyInstance()); - $locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will( - $this->invokeHelperMethods($visit, $location), - ); - $resolveIpLocation = $this->visitToLocation->resolveVisitLocation(Argument::any())->willThrow($e); + $this->lock->method('acquire')->with($this->isFalse())->willReturn(true); + $this->visitService->expects($this->once()) + ->method('locateUnlocatedVisits') + ->withAnyParameters() + ->willReturnCallback($this->invokeHelperMethods($visit, $location)); + $this->visitToLocation->expects($this->once())->method('resolveVisitLocation')->willThrowException($e); $this->downloadDbCommand->method('run')->withAnyParameters()->willReturn(ExitCodes::EXIT_SUCCESS); $this->commandTester->execute([], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]); @@ -133,8 +128,6 @@ class LocateVisitsCommandTest extends TestCase $output = $this->commandTester->getDisplay(); self::assertStringContainsString('Processing IP', $output); self::assertStringContainsString($message, $output); - $locateVisits->shouldHaveBeenCalledOnce(); - $resolveIpLocation->shouldHaveBeenCalledOnce(); } public function provideIgnoredAddresses(): iterable @@ -149,10 +142,12 @@ class LocateVisitsCommandTest extends TestCase $visit = Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', '')); $location = VisitLocation::fromGeolocation(Location::emptyInstance()); - $locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will( - $this->invokeHelperMethods($visit, $location), - ); - $resolveIpLocation = $this->visitToLocation->resolveVisitLocation(Argument::any())->willThrow( + $this->lock->method('acquire')->with($this->isFalse())->willReturn(true); + $this->visitService->expects($this->once()) + ->method('locateUnlocatedVisits') + ->withAnyParameters() + ->willReturnCallback($this->invokeHelperMethods($visit, $location)); + $this->visitToLocation->expects($this->once())->method('resolveVisitLocation')->willThrowException( IpCannotBeLocatedException::forError(WrongIpException::fromIpAddress('1.2.3.4')), ); $this->downloadDbCommand->method('run')->withAnyParameters()->willReturn(ExitCodes::EXIT_SUCCESS); @@ -162,16 +157,11 @@ class LocateVisitsCommandTest extends TestCase $output = $this->commandTester->getDisplay(); self::assertStringContainsString('An error occurred while locating IP. Skipped', $output); - $locateVisits->shouldHaveBeenCalledOnce(); - $resolveIpLocation->shouldHaveBeenCalledOnce(); } private function invokeHelperMethods(Visit $visit, VisitLocation $location): callable { - return function (array $args) use ($visit, $location): void { - /** @var VisitGeolocationHelperInterface $helper */ - [$helper] = $args; - + return static function (VisitGeolocationHelperInterface $helper) use ($visit, $location): void { $helper->geolocateVisit($visit); $helper->onVisitLocated($location, $visit); }; @@ -180,11 +170,10 @@ class LocateVisitsCommandTest extends TestCase /** @test */ public function noActionIsPerformedIfLockIsAcquired(): void { - $this->lock->acquire(false)->willReturn(false); + $this->lock->method('acquire')->with($this->isFalse())->willReturn(false); - $locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will(function (): void { - }); - $resolveIpLocation = $this->visitToLocation->resolveVisitLocation(Argument::any()); + $this->visitService->expects($this->never())->method('locateUnlocatedVisits'); + $this->visitToLocation->expects($this->never())->method('resolveVisitLocation'); $this->downloadDbCommand->method('run')->withAnyParameters()->willReturn(ExitCodes::EXIT_SUCCESS); $this->commandTester->execute([], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]); @@ -194,25 +183,25 @@ class LocateVisitsCommandTest extends TestCase sprintf('Command "%s" is already in progress. Skipping.', LocateVisitsCommand::NAME), $output, ); - $locateVisits->shouldNotHaveBeenCalled(); - $resolveIpLocation->shouldNotHaveBeenCalled(); } /** @test */ public function showsProperMessageWhenGeoLiteUpdateFails(): void { + $this->lock->method('acquire')->with($this->isFalse())->willReturn(true); $this->downloadDbCommand->method('run')->withAnyParameters()->willReturn(ExitCodes::EXIT_FAILURE); + $this->visitService->expects($this->never())->method('locateUnlocatedVisits'); $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); self::assertStringContainsString('It is not possible to locate visits without a GeoLite2 db file.', $output); - $this->visitService->locateUnlocatedVisits(Argument::cetera())->shouldNotHaveBeenCalled(); } /** @test */ public function providingAllFlagOnItsOwnDisplaysNotice(): void { + $this->lock->method('acquire')->with($this->isFalse())->willReturn(true); $this->downloadDbCommand->method('run')->withAnyParameters()->willReturn(ExitCodes::EXIT_SUCCESS); $this->commandTester->execute(['--all' => true]); From a484455b0bd04e8b13d26b96a3cd22843656af28 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 14:27:07 +0200 Subject: [PATCH 055/182] Migrated GeolocationDbUpdaterTest to use PHPUnit mocks --- .../test/GeoLite/GeolocationDbUpdaterTest.php | 95 ++++++++----------- 1 file changed, 39 insertions(+), 56 deletions(-) diff --git a/module/CLI/test/GeoLite/GeolocationDbUpdaterTest.php b/module/CLI/test/GeoLite/GeolocationDbUpdaterTest.php index 61056922..0b890e46 100644 --- a/module/CLI/test/GeoLite/GeolocationDbUpdaterTest.php +++ b/module/CLI/test/GeoLite/GeolocationDbUpdaterTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\CLI\GeoLite; use Cake\Chronos\Chronos; use GeoIp2\Database\Reader; use MaxMind\Db\Reader\Metadata; +use PHPUnit\Framework\MockObject\MockObject; 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\GeoLite\GeolocationDbUpdater; use Shlinkio\Shlink\CLI\GeoLite\GeolocationResult; @@ -25,23 +23,18 @@ use function range; class GeolocationDbUpdaterTest extends TestCase { - use ProphecyTrait; - - private GeolocationDbUpdater $geolocationDbUpdater; - private ObjectProphecy $dbUpdater; - private ObjectProphecy $geoLiteDbReader; - private ObjectProphecy $lock; + private MockObject $dbUpdater; + private MockObject $geoLiteDbReader; + private MockObject $lock; protected function setUp(): void { - $this->dbUpdater = $this->prophesize(DbUpdaterInterface::class); - $this->geoLiteDbReader = $this->prophesize(Reader::class); + $this->dbUpdater = $this->createMock(DbUpdaterInterface::class); + $this->geoLiteDbReader = $this->createMock(Reader::class); $this->trackingOptions = new TrackingOptions(); - $this->lock = $this->prophesize(Lock\LockInterface::class); - $this->lock->acquire(true)->willReturn(true); - $this->lock->release()->will(function (): void { - }); + $this->lock = $this->createMock(Lock\LockInterface::class); + $this->lock->method('acquire')->with($this->isTrue())->willReturn(true); } /** @test */ @@ -50,25 +43,21 @@ class GeolocationDbUpdaterTest extends TestCase $mustBeUpdated = fn () => self::assertTrue(true); $prev = new DbUpdateException(''); - $fileExists = $this->dbUpdater->databaseFileExists()->willReturn(false); - $getMeta = $this->geoLiteDbReader->metadata(); - $download = $this->dbUpdater->downloadFreshCopy(null)->willThrow($prev); + $this->dbUpdater->expects($this->once())->method('databaseFileExists')->willReturn(false); + $this->dbUpdater->expects($this->once())->method('downloadFreshCopy')->with( + $this->isNull(), + )->willThrowException($prev); + $this->geoLiteDbReader->expects($this->never())->method('metadata'); try { $this->geolocationDbUpdater()->checkDbUpdate($mustBeUpdated); - self::assertTrue(false); // If this is reached, the test will fail + self::fail(); } catch (Throwable $e) { /** @var GeolocationDbUpdateFailedException $e */ self::assertInstanceOf(GeolocationDbUpdateFailedException::class, $e); self::assertSame($prev, $e->getPrevious()); self::assertFalse($e->olderDbExists()); } - - $fileExists->shouldHaveBeenCalledOnce(); - $getMeta->shouldNotHaveBeenCalled(); - $download->shouldHaveBeenCalledOnce(); - $this->lock->acquire(true)->shouldHaveBeenCalledOnce(); - $this->lock->release()->shouldHaveBeenCalledOnce(); } /** @@ -77,26 +66,24 @@ class GeolocationDbUpdaterTest extends TestCase */ public function exceptionIsThrownWhenOlderDbIsTooOldAndDownloadFails(int $days): void { - $fileExists = $this->dbUpdater->databaseFileExists()->willReturn(true); - $getMeta = $this->geoLiteDbReader->metadata()->willReturn($this->buildMetaWithBuildEpoch( - Chronos::now()->subDays($days)->getTimestamp(), - )); $prev = new DbUpdateException(''); - $download = $this->dbUpdater->downloadFreshCopy(null)->willThrow($prev); + $this->dbUpdater->expects($this->once())->method('databaseFileExists')->willReturn(true); + $this->dbUpdater->expects($this->once())->method('downloadFreshCopy')->with( + $this->isNull(), + )->willThrowException($prev); + $this->geoLiteDbReader->expects($this->once())->method('metadata')->with()->willReturn( + $this->buildMetaWithBuildEpoch(Chronos::now()->subDays($days)->getTimestamp()), + ); try { $this->geolocationDbUpdater()->checkDbUpdate(); - self::assertTrue(false); // If this is reached, the test will fail + self::fail(); } catch (Throwable $e) { /** @var GeolocationDbUpdateFailedException $e */ self::assertInstanceOf(GeolocationDbUpdateFailedException::class, $e); self::assertSame($prev, $e->getPrevious()); self::assertTrue($e->olderDbExists()); } - - $fileExists->shouldHaveBeenCalledOnce(); - $getMeta->shouldHaveBeenCalledOnce(); - $download->shouldHaveBeenCalledOnce(); } public function provideBigDays(): iterable @@ -113,17 +100,15 @@ class GeolocationDbUpdaterTest extends TestCase */ public function databaseIsNotUpdatedIfItIsNewEnough(string|int $buildEpoch): void { - $fileExists = $this->dbUpdater->databaseFileExists()->willReturn(true); - $getMeta = $this->geoLiteDbReader->metadata()->willReturn($this->buildMetaWithBuildEpoch($buildEpoch)); - $download = $this->dbUpdater->downloadFreshCopy(null)->will(function (): void { - }); + $this->dbUpdater->expects($this->once())->method('databaseFileExists')->willReturn(true); + $this->dbUpdater->expects($this->never())->method('downloadFreshCopy'); + $this->geoLiteDbReader->expects($this->once())->method('metadata')->with()->willReturn( + $this->buildMetaWithBuildEpoch($buildEpoch), + ); $result = $this->geolocationDbUpdater()->checkDbUpdate(); self::assertEquals(GeolocationResult::DB_IS_UP_TO_DATE, $result); - $fileExists->shouldHaveBeenCalledOnce(); - $getMeta->shouldHaveBeenCalledOnce(); - $download->shouldNotHaveBeenCalled(); } public function provideSmallDays(): iterable @@ -139,18 +124,16 @@ class GeolocationDbUpdaterTest extends TestCase /** @test */ public function exceptionIsThrownWhenCheckingExistingDatabaseWithInvalidBuildEpoch(): void { - $fileExists = $this->dbUpdater->databaseFileExists()->willReturn(true); - $getMeta = $this->geoLiteDbReader->metadata()->willReturn($this->buildMetaWithBuildEpoch('invalid')); - $download = $this->dbUpdater->downloadFreshCopy(null)->will(function (): void { - }); + $this->dbUpdater->expects($this->once())->method('databaseFileExists')->willReturn(true); + $this->dbUpdater->expects($this->never())->method('downloadFreshCopy'); + $this->geoLiteDbReader->expects($this->once())->method('metadata')->with()->willReturn( + $this->buildMetaWithBuildEpoch('invalid'), + ); $this->expectException(GeolocationDbUpdateFailedException::class); $this->expectExceptionMessage( 'Build epoch with value "invalid" from existing geolocation database, could not be parsed to integer.', ); - $fileExists->shouldBeCalledOnce(); - $getMeta->shouldBeCalledOnce(); - $download->shouldNotBeCalled(); $this->geolocationDbUpdater()->checkDbUpdate(); } @@ -177,10 +160,10 @@ class GeolocationDbUpdaterTest extends TestCase public function downloadDbIsSkippedIfTrackingIsDisabled(TrackingOptions $options): void { $result = $this->geolocationDbUpdater($options)->checkDbUpdate(); + $this->dbUpdater->expects($this->never())->method('databaseFileExists'); + $this->geoLiteDbReader->expects($this->never())->method('metadata'); self::assertEquals(GeolocationResult::CHECK_SKIPPED, $result); - $this->dbUpdater->databaseFileExists(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->geoLiteDbReader->metadata(Argument::cetera())->shouldNotHaveBeenCalled(); } public function provideTrackingOptions(): iterable @@ -192,13 +175,13 @@ class GeolocationDbUpdaterTest extends TestCase private function geolocationDbUpdater(?TrackingOptions $options = null): GeolocationDbUpdater { - $locker = $this->prophesize(Lock\LockFactory::class); - $locker->createLock(Argument::type('string'))->willReturn($this->lock->reveal()); + $locker = $this->createMock(Lock\LockFactory::class); + $locker->method('createLock')->with($this->isType('string'))->willReturn($this->lock); return new GeolocationDbUpdater( - $this->dbUpdater->reveal(), - $this->geoLiteDbReader->reveal(), - $locker->reveal(), + $this->dbUpdater, + $this->geoLiteDbReader, + $locker, $options ?? new TrackingOptions(), ); } From 4cb44be9a0c0a507f2d8afb8aa00dd4799fdbebc Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 14:37:13 +0200 Subject: [PATCH 056/182] Migrated ProcessRunnerTest to use PHPUnit mocks --- module/CLI/test/Util/ProcessRunnerTest.php | 111 +++++++++------------ 1 file changed, 46 insertions(+), 65 deletions(-) diff --git a/module/CLI/test/Util/ProcessRunnerTest.php b/module/CLI/test/Util/ProcessRunnerTest.php index 05ac5dd7..276b5abb 100644 --- a/module/CLI/test/Util/ProcessRunnerTest.php +++ b/module/CLI/test/Util/ProcessRunnerTest.php @@ -4,10 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Util; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Util\ProcessRunner; use Symfony\Component\Console\Helper\DebugFormatterHelper; use Symfony\Component\Console\Helper\HelperSet; @@ -17,90 +15,73 @@ use Symfony\Component\Process\Process; class ProcessRunnerTest extends TestCase { - use ProphecyTrait; - private ProcessRunner $runner; - private ObjectProphecy $helper; - private ObjectProphecy $formatter; - private ObjectProphecy $process; - private ObjectProphecy $output; + private MockObject $helper; + private MockObject $formatter; + private MockObject $process; + private MockObject $output; protected function setUp(): void { - $this->helper = $this->prophesize(ProcessHelper::class); - $this->formatter = $this->prophesize(DebugFormatterHelper::class); - $helperSet = $this->prophesize(HelperSet::class); - $helperSet->get('debug_formatter')->willReturn($this->formatter->reveal()); - $this->helper->getHelperSet()->willReturn($helperSet->reveal()); - $this->process = $this->prophesize(Process::class); + $this->helper = $this->createMock(ProcessHelper::class); + $this->formatter = $this->createMock(DebugFormatterHelper::class); + $helperSet = $this->createMock(HelperSet::class); + $helperSet->method('get')->with($this->equalTo('debug_formatter'))->willReturn($this->formatter); + $this->helper->method('getHelperSet')->with()->willReturn($helperSet); + $this->process = $this->createMock(Process::class); + $this->output = $this->createMock(OutputInterface::class); - $this->runner = new ProcessRunner($this->helper->reveal(), fn () => $this->process->reveal()); - $this->output = $this->prophesize(OutputInterface::class); + $this->runner = new ProcessRunner($this->helper, fn () => $this->process); } /** @test */ public function noMessagesAreWrittenWhenOutputIsNotVerbose(): void { - $isVeryVerbose = $this->output->isVeryVerbose()->willReturn(false); - $isDebug = $this->output->isDebug()->willReturn(false); - $mustRun = $this->process->mustRun(Argument::cetera())->willReturn($this->process->reveal()); + $this->output->expects($this->exactly(2))->method('isVeryVerbose')->with()->willReturn(false); + $this->output->expects($this->once())->method('isDebug')->with()->willReturn(false); + $this->output->expects($this->never())->method('write'); + $this->process->expects($this->once())->method('mustRun')->withAnyParameters()->willReturn($this->process); + $this->process->expects($this->never())->method('isSuccessful'); + $this->process->expects($this->never())->method('getCommandLine'); + $this->helper->expects($this->never())->method('wrapCallback'); + $this->formatter->expects($this->never())->method('start'); + $this->formatter->expects($this->never())->method('stop'); - $this->runner->run($this->output->reveal(), []); - - $isVeryVerbose->shouldHaveBeenCalledTimes(2); - $isDebug->shouldHaveBeenCalledOnce(); - $mustRun->shouldHaveBeenCalledOnce(); - $this->process->isSuccessful()->shouldNotHaveBeenCalled(); - $this->process->getCommandLine()->shouldNotHaveBeenCalled(); - $this->output->write(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->helper->wrapCallback(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->formatter->start(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->formatter->stop(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->runner->run($this->output, []); } /** @test */ public function someMessagesAreWrittenWhenOutputIsVerbose(): void { - $isVeryVerbose = $this->output->isVeryVerbose()->willReturn(true); - $isDebug = $this->output->isDebug()->willReturn(false); - $mustRun = $this->process->mustRun(Argument::cetera())->willReturn($this->process->reveal()); - $isSuccessful = $this->process->isSuccessful()->willReturn(true); - $getCommandLine = $this->process->getCommandLine()->willReturn('true'); - $start = $this->formatter->start(Argument::cetera())->willReturn(''); - $stop = $this->formatter->stop(Argument::cetera())->willReturn(''); + $this->output->expects($this->exactly(2))->method('isVeryVerbose')->with()->willReturn(true); + $this->output->expects($this->once())->method('isDebug')->with()->willReturn(false); + $this->output->expects($this->exactly(2))->method('write')->withAnyParameters(); + $this->process->expects($this->once())->method('mustRun')->withAnyParameters()->willReturn($this->process); + $this->process->expects($this->exactly(2))->method('isSuccessful')->with()->willReturn(true); + $this->process->expects($this->once())->method('getCommandLine')->with()->willReturn('true'); + $this->formatter->expects($this->once())->method('start')->withAnyParameters()->willReturn(''); + $this->formatter->expects($this->once())->method('stop')->withAnyParameters()->willReturn(''); + $this->helper->expects($this->never())->method('wrapCallback'); - $this->runner->run($this->output->reveal(), []); - - $isVeryVerbose->shouldHaveBeenCalledTimes(2); - $isDebug->shouldHaveBeenCalledOnce(); - $mustRun->shouldHaveBeenCalledOnce(); - $this->output->write(Argument::cetera())->shouldHaveBeenCalledTimes(2); - $this->helper->wrapCallback(Argument::cetera())->shouldNotHaveBeenCalled(); - $isSuccessful->shouldHaveBeenCalledTimes(2); - $getCommandLine->shouldHaveBeenCalledOnce(); - $start->shouldHaveBeenCalledOnce(); - $stop->shouldHaveBeenCalledOnce(); + $this->runner->run($this->output, []); } /** @test */ public function wrapsCallbackWhenOutputIsDebug(): void { - $isVeryVerbose = $this->output->isVeryVerbose()->willReturn(false); - $isDebug = $this->output->isDebug()->willReturn(true); - $mustRun = $this->process->mustRun(Argument::cetera())->willReturn($this->process->reveal()); - $wrapCallback = $this->helper->wrapCallback(Argument::cetera())->willReturn(function (): void { - }); + $this->output->expects($this->exactly(2))->method('isVeryVerbose')->with()->willReturn(false); + $this->output->expects($this->once())->method('isDebug')->with()->willReturn(true); + $this->output->expects($this->never())->method('write'); + $this->process->expects($this->once())->method('mustRun')->withAnyParameters()->willReturn($this->process); + $this->process->expects($this->never())->method('isSuccessful'); + $this->process->expects($this->never())->method('getCommandLine'); + $this->helper->expects($this->once())->method('wrapCallback')->withAnyParameters()->willReturn( + function (): void { + }, + ); + $this->formatter->expects($this->never())->method('start'); + $this->formatter->expects($this->never())->method('stop'); - $this->runner->run($this->output->reveal(), []); - - $isVeryVerbose->shouldHaveBeenCalledTimes(2); - $isDebug->shouldHaveBeenCalledOnce(); - $mustRun->shouldHaveBeenCalledOnce(); - $wrapCallback->shouldHaveBeenCalledOnce(); - $this->process->isSuccessful()->shouldNotHaveBeenCalled(); - $this->process->getCommandLine()->shouldNotHaveBeenCalled(); - $this->output->write(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->formatter->start(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->formatter->stop(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->runner->run($this->output, []); } } From 32417e40cbe527d9e520594fb74fe6a80110cc53 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 14:40:35 +0200 Subject: [PATCH 057/182] Migrated ShlinkTableTest to use PHPUnit mocks --- module/CLI/test/Util/ProcessRunnerTest.php | 6 +-- module/CLI/test/Util/ShlinkTableTest.php | 43 ++++++++++------------ 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/module/CLI/test/Util/ProcessRunnerTest.php b/module/CLI/test/Util/ProcessRunnerTest.php index 276b5abb..3e2e5ed0 100644 --- a/module/CLI/test/Util/ProcessRunnerTest.php +++ b/module/CLI/test/Util/ProcessRunnerTest.php @@ -40,7 +40,7 @@ class ProcessRunnerTest extends TestCase $this->output->expects($this->exactly(2))->method('isVeryVerbose')->with()->willReturn(false); $this->output->expects($this->once())->method('isDebug')->with()->willReturn(false); $this->output->expects($this->never())->method('write'); - $this->process->expects($this->once())->method('mustRun')->withAnyParameters()->willReturn($this->process); + $this->process->expects($this->once())->method('mustRun')->withAnyParameters()->willReturnSelf(); $this->process->expects($this->never())->method('isSuccessful'); $this->process->expects($this->never())->method('getCommandLine'); $this->helper->expects($this->never())->method('wrapCallback'); @@ -56,7 +56,7 @@ class ProcessRunnerTest extends TestCase $this->output->expects($this->exactly(2))->method('isVeryVerbose')->with()->willReturn(true); $this->output->expects($this->once())->method('isDebug')->with()->willReturn(false); $this->output->expects($this->exactly(2))->method('write')->withAnyParameters(); - $this->process->expects($this->once())->method('mustRun')->withAnyParameters()->willReturn($this->process); + $this->process->expects($this->once())->method('mustRun')->withAnyParameters()->willReturnSelf(); $this->process->expects($this->exactly(2))->method('isSuccessful')->with()->willReturn(true); $this->process->expects($this->once())->method('getCommandLine')->with()->willReturn('true'); $this->formatter->expects($this->once())->method('start')->withAnyParameters()->willReturn(''); @@ -72,7 +72,7 @@ class ProcessRunnerTest extends TestCase $this->output->expects($this->exactly(2))->method('isVeryVerbose')->with()->willReturn(false); $this->output->expects($this->once())->method('isDebug')->with()->willReturn(true); $this->output->expects($this->never())->method('write'); - $this->process->expects($this->once())->method('mustRun')->withAnyParameters()->willReturn($this->process); + $this->process->expects($this->once())->method('mustRun')->withAnyParameters()->willReturnSelf(); $this->process->expects($this->never())->method('isSuccessful'); $this->process->expects($this->never())->method('getCommandLine'); $this->helper->expects($this->once())->method('wrapCallback')->withAnyParameters()->willReturn( diff --git a/module/CLI/test/Util/ShlinkTableTest.php b/module/CLI/test/Util/ShlinkTableTest.php index ffe1f30d..a9a6f3f5 100644 --- a/module/CLI/test/Util/ShlinkTableTest.php +++ b/module/CLI/test/Util/ShlinkTableTest.php @@ -4,10 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Util; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use ReflectionObject; use Shlinkio\Shlink\CLI\Util\ShlinkTable; use Symfony\Component\Console\Helper\Table; @@ -16,15 +14,13 @@ use Symfony\Component\Console\Output\OutputInterface; class ShlinkTableTest extends TestCase { - use ProphecyTrait; - private ShlinkTable $shlinkTable; - private ObjectProphecy $baseTable; + private MockObject $baseTable; protected function setUp(): void { - $this->baseTable = $this->prophesize(Table::class); - $this->shlinkTable = ShlinkTable::fromBaseTable($this->baseTable->reveal()); + $this->baseTable = $this->createMock(Table::class); + $this->shlinkTable = ShlinkTable::fromBaseTable($this->baseTable); } /** @test */ @@ -35,29 +31,28 @@ class ShlinkTableTest extends TestCase $headerTitle = 'Header'; $footerTitle = 'Footer'; - $setStyle = $this->baseTable->setStyle(Argument::type(TableStyle::class))->willReturn( - $this->baseTable->reveal(), - ); - $setHeaders = $this->baseTable->setHeaders($headers)->willReturn($this->baseTable->reveal()); - $setRows = $this->baseTable->setRows($rows)->willReturn($this->baseTable->reveal()); - $setFooterTitle = $this->baseTable->setFooterTitle($footerTitle)->willReturn($this->baseTable->reveal()); - $setHeaderTitle = $this->baseTable->setHeaderTitle($headerTitle)->willReturn($this->baseTable->reveal()); - $render = $this->baseTable->render()->willReturn($this->baseTable->reveal()); + $this->baseTable->expects($this->once())->method('setStyle')->with( + $this->isInstanceOf(TableStyle::class) + )->willReturnSelf(); + $this->baseTable->expects($this->once())->method('setHeaders')->with( + $this->equalTo($headers), + )->willReturnSelf(); + $this->baseTable->expects($this->once())->method('setRows')->with($this->equalTo($rows))->willReturnSelf(); + $this->baseTable->expects($this->once())->method('setFooterTitle')->with( + $this->equalTo($footerTitle), + )->willReturnSelf(); + $this->baseTable->expects($this->once())->method('setHeaderTitle')->with( + $this->equalTo($headerTitle), + )->willReturnSelf(); + $this->baseTable->expects($this->once())->method('render')->with()->willReturnSelf(); $this->shlinkTable->render($headers, $rows, $footerTitle, $headerTitle); - - $setStyle->shouldHaveBeenCalledOnce(); - $setHeaders->shouldHaveBeenCalledOnce(); - $setRows->shouldHaveBeenCalledOnce(); - $setFooterTitle->shouldHaveBeenCalledOnce(); - $setHeaderTitle->shouldHaveBeenCalledOnce(); - $render->shouldHaveBeenCalledOnce(); } /** @test */ public function newTableIsCreatedForFactoryMethod(): void { - $instance = ShlinkTable::default($this->prophesize(OutputInterface::class)->reveal()); + $instance = ShlinkTable::default($this->createMock(OutputInterface::class)); $ref = new ReflectionObject($instance); $baseTable = $ref->getProperty('baseTable'); From 52366b9dd42d0b872869ce6e91063228136df24b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 14:41:22 +0200 Subject: [PATCH 058/182] Removed last reference to prophecytrait in CLI module --- module/CLI/test/CliTestUtilsTrait.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/module/CLI/test/CliTestUtilsTrait.php b/module/CLI/test/CliTestUtilsTrait.php index e69eb71e..00b493e3 100644 --- a/module/CLI/test/CliTestUtilsTrait.php +++ b/module/CLI/test/CliTestUtilsTrait.php @@ -6,7 +6,6 @@ namespace ShlinkioTest\Shlink\CLI; use PHPUnit\Framework\Assert; use PHPUnit\Framework\MockObject\MockObject; -use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputDefinition; @@ -14,8 +13,6 @@ use Symfony\Component\Console\Tester\CommandTester; trait CliTestUtilsTrait { - use ProphecyTrait; // TODO Remove - /** * @return MockObject & Command */ From b7671f70da096657191c45044c0ce9800f37c870 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 14:41:42 +0200 Subject: [PATCH 059/182] Fixed coding styles --- module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php | 2 +- module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php | 2 +- module/CLI/test/Util/ShlinkTableTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php index dfd1a028..b1356e11 100644 --- a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php @@ -73,7 +73,7 @@ class ListShortUrlsCommandTest extends TestCase } $this->shortUrlService->expects($this->once())->method('listShortUrls')->with( - $this->equalTo(ShortUrlsParams::emptyInstance()) + $this->equalTo(ShortUrlsParams::emptyInstance()), )->willReturn(new Paginator(new ArrayAdapter($data))); $this->commandTester->setInputs(['n']); diff --git a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php index 3b063ab4..609a921a 100644 --- a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php @@ -53,7 +53,7 @@ class ResolveUrlCommandTest extends TestCase $shortCode = $identifier->shortCode; $this->urlResolver->expects($this->once())->method('resolveShortUrl')->with( - $this->equalTo($identifier) + $this->equalTo($identifier), )->willThrowException(ShortUrlNotFoundException::fromNotFound($identifier)); $this->commandTester->execute(['shortCode' => $shortCode]); diff --git a/module/CLI/test/Util/ShlinkTableTest.php b/module/CLI/test/Util/ShlinkTableTest.php index a9a6f3f5..6b7e00a3 100644 --- a/module/CLI/test/Util/ShlinkTableTest.php +++ b/module/CLI/test/Util/ShlinkTableTest.php @@ -32,7 +32,7 @@ class ShlinkTableTest extends TestCase $footerTitle = 'Footer'; $this->baseTable->expects($this->once())->method('setStyle')->with( - $this->isInstanceOf(TableStyle::class) + $this->isInstanceOf(TableStyle::class), )->willReturnSelf(); $this->baseTable->expects($this->once())->method('setHeaders')->with( $this->equalTo($headers), From a15e9c29c8d5d5f2d2886ef80b329a218743050a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 18:49:43 +0200 Subject: [PATCH 060/182] Migrated NotifyNewShortUrlToRabbitMqTest to use PHPUnit mocks --- .../NotifyNewShortUrlToRabbitMqTest.php | 101 +++++++++--------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php b/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php index 477654bb..40ceb3aa 100644 --- a/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php +++ b/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Core\EventDispatcher\RabbitMq; use Doctrine\ORM\EntityManagerInterface; use DomainException; use Exception; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Log\LoggerInterface; use RuntimeException; use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface; @@ -25,48 +23,46 @@ use Throwable; class NotifyNewShortUrlToRabbitMqTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $helper; - private ObjectProphecy $updatesGenerator; - private ObjectProphecy $em; - private ObjectProphecy $logger; + private MockObject $helper; + private MockObject $updatesGenerator; + private MockObject $em; + private MockObject $logger; protected function setUp(): void { - $this->helper = $this->prophesize(PublishingHelperInterface::class); - $this->updatesGenerator = $this->prophesize(PublishingUpdatesGeneratorInterface::class); - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->logger = $this->prophesize(LoggerInterface::class); + $this->helper = $this->createMock(PublishingHelperInterface::class); + $this->updatesGenerator = $this->createMock(PublishingUpdatesGeneratorInterface::class); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); } /** @test */ public function doesNothingWhenTheFeatureIsNotEnabled(): void { - ($this->listener(false))(new ShortUrlCreated('123')); + $this->helper->expects($this->never())->method('publishUpdate'); + $this->em->expects($this->never())->method('find'); + $this->logger->expects($this->never())->method('warning'); + $this->logger->expects($this->never())->method('debug'); - $this->em->find(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->helper->publishUpdate(Argument::cetera())->shouldNotHaveBeenCalled(); + ($this->listener(false))(new ShortUrlCreated('123')); } /** @test */ public function notificationsAreNotSentWhenShortUrlCannotBeFound(): void { $shortUrlId = '123'; - $find = $this->em->find(ShortUrl::class, $shortUrlId)->willReturn(null); - $logWarning = $this->logger->warning( - 'Tried to notify {name} for new short URL with id "{shortUrlId}", but it does not exist.', - ['shortUrlId' => $shortUrlId, 'name' => 'RabbitMQ'], + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(ShortUrl::class), + $this->equalTo($shortUrlId), + )->willReturn(null); + $this->logger->expects($this->once())->method('warning')->with( + $this->equalTo('Tried to notify {name} for new short URL with id "{shortUrlId}", but it does not exist.'), + $this->equalTo(['shortUrlId' => $shortUrlId, 'name' => 'RabbitMQ']), ); + $this->logger->expects($this->never())->method('debug'); + $this->helper->expects($this->never())->method('publishUpdate'); ($this->listener())(new ShortUrlCreated($shortUrlId)); - - $find->shouldHaveBeenCalledOnce(); - $logWarning->shouldHaveBeenCalledOnce(); - $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->helper->publishUpdate(Argument::cetera())->shouldNotHaveBeenCalled(); } /** @test */ @@ -74,17 +70,17 @@ class NotifyNewShortUrlToRabbitMqTest extends TestCase { $shortUrlId = '123'; $update = Update::forTopicAndPayload(Topic::NEW_SHORT_URL->value, []); - $find = $this->em->find(ShortUrl::class, $shortUrlId)->willReturn(ShortUrl::withLongUrl('')); - $generateUpdate = $this->updatesGenerator->newShortUrlUpdate(Argument::type(ShortUrl::class))->willReturn( - $update, - ); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(ShortUrl::class), + $this->equalTo($shortUrlId), + )->willReturn(ShortUrl::withLongUrl('')); + $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with( + $this->isInstanceOf(ShortUrl::class), + )->willReturn($update); + $this->helper->expects($this->once())->method('publishUpdate')->with($this->equalTo($update)); + $this->logger->expects($this->never())->method('debug'); ($this->listener())(new ShortUrlCreated($shortUrlId)); - - $find->shouldHaveBeenCalledOnce(); - $generateUpdate->shouldHaveBeenCalledOnce(); - $this->helper->publishUpdate($update)->shouldHaveBeenCalledOnce(); - $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); } /** @@ -95,21 +91,22 @@ class NotifyNewShortUrlToRabbitMqTest extends TestCase { $shortUrlId = '123'; $update = Update::forTopicAndPayload(Topic::NEW_SHORT_URL->value, []); - $find = $this->em->find(ShortUrl::class, $shortUrlId)->willReturn(ShortUrl::withLongUrl('')); - $generateUpdate = $this->updatesGenerator->newShortUrlUpdate(Argument::type(ShortUrl::class))->willReturn( - $update, + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(ShortUrl::class), + $this->equalTo($shortUrlId), + )->willReturn(ShortUrl::withLongUrl('')); + $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with( + $this->isInstanceOf(ShortUrl::class), + )->willReturn($update); + $this->helper->expects($this->once())->method('publishUpdate')->with( + $this->equalTo($update), + )->willThrowException($e); + $this->logger->expects($this->once())->method('debug')->with( + $this->equalTo('Error while trying to notify {name} with new short URL. {e}'), + $this->equalTo(['e' => $e, 'name' => 'RabbitMQ']), ); - $publish = $this->helper->publishUpdate($update)->willThrow($e); ($this->listener())(new ShortUrlCreated($shortUrlId)); - - $this->logger->debug( - 'Error while trying to notify {name} with new short URL. {e}', - ['e' => $e, 'name' => 'RabbitMQ'], - )->shouldHaveBeenCalledOnce(); - $find->shouldHaveBeenCalledOnce(); - $generateUpdate->shouldHaveBeenCalledOnce(); - $publish->shouldHaveBeenCalledOnce(); } public function provideExceptions(): iterable @@ -122,10 +119,10 @@ class NotifyNewShortUrlToRabbitMqTest extends TestCase private function listener(bool $enabled = true): NotifyNewShortUrlToRabbitMq { return new NotifyNewShortUrlToRabbitMq( - $this->helper->reveal(), - $this->updatesGenerator->reveal(), - $this->em->reveal(), - $this->logger->reveal(), + $this->helper, + $this->updatesGenerator, + $this->em, + $this->logger, new RabbitMqOptions($enabled), ); } From 739433ba8b4a34e38e69caaaefe836470ecc9f7e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 19:05:34 +0200 Subject: [PATCH 061/182] Migrated NotifyVisitToRabbitMqTest to use PHPUnit mocks --- .../RabbitMq/NotifyVisitToRabbitMqTest.php | 158 +++++++++--------- 1 file changed, 77 insertions(+), 81 deletions(-) diff --git a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php index aef04cdf..94688dd3 100644 --- a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php +++ b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php @@ -8,10 +8,8 @@ use Doctrine\ORM\EntityManagerInterface; use DomainException; use Exception; use PHPUnit\Framework\Assert; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Log\LoggerInterface; use RuntimeException; use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface; @@ -33,48 +31,46 @@ use function Functional\noop; class NotifyVisitToRabbitMqTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $helper; - private ObjectProphecy $updatesGenerator; - private ObjectProphecy $em; - private ObjectProphecy $logger; + private MockObject $helper; + private MockObject $updatesGenerator; + private MockObject $em; + private MockObject $logger; protected function setUp(): void { - $this->helper = $this->prophesize(PublishingHelperInterface::class); - $this->updatesGenerator = $this->prophesize(PublishingUpdatesGeneratorInterface::class); - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->logger = $this->prophesize(LoggerInterface::class); + $this->helper = $this->createMock(PublishingHelperInterface::class); + $this->updatesGenerator = $this->createMock(PublishingUpdatesGeneratorInterface::class); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); } /** @test */ public function doesNothingWhenTheFeatureIsNotEnabled(): void { - ($this->listener(new RabbitMqOptions(enabled: false)))(new VisitLocated('123')); + $this->helper->expects($this->never())->method('publishUpdate'); + $this->em->expects($this->never())->method('find'); + $this->logger->expects($this->never())->method('warning'); + $this->logger->expects($this->never())->method('debug'); - $this->em->find(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->helper->publishUpdate(Argument::cetera())->shouldNotHaveBeenCalled(); + ($this->listener(new RabbitMqOptions(enabled: false)))(new VisitLocated('123')); } /** @test */ public function notificationsAreNotSentWhenVisitCannotBeFound(): void { $visitId = '123'; - $findVisit = $this->em->find(Visit::class, $visitId)->willReturn(null); - $logWarning = $this->logger->warning( - 'Tried to notify {name} for visit with id "{visitId}", but it does not exist.', - ['visitId' => $visitId, 'name' => 'RabbitMQ'], + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo($visitId), + )->willReturn(null); + $this->logger->expects($this->once())->method('warning')->with( + $this->equalTo('Tried to notify {name} for visit with id "{visitId}", but it does not exist.'), + $this->equalTo(['visitId' => $visitId, 'name' => 'RabbitMQ']), ); + $this->logger->expects($this->never())->method('debug'); + $this->helper->expects($this->never())->method('publishUpdate'); ($this->listener())(new VisitLocated($visitId)); - - $findVisit->shouldHaveBeenCalledOnce(); - $logWarning->shouldHaveBeenCalledOnce(); - $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->helper->publishUpdate(Argument::cetera())->shouldNotHaveBeenCalled(); } /** @@ -84,20 +80,21 @@ class NotifyVisitToRabbitMqTest extends TestCase public function expectedChannelsAreNotifiedBasedOnTheVisitType(Visit $visit, array $expectedChannels): void { $visitId = '123'; - $findVisit = $this->em->find(Visit::class, $visitId)->willReturn($visit); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo($visitId), + )->willReturn($visit); each($expectedChannels, function (string $method): void { - $this->updatesGenerator->{$method}(Argument::type(Visit::class))->willReturn( - Update::forTopicAndPayload('', []), - )->shouldBeCalledOnce(); + $this->updatesGenerator->expects($this->once())->method($method)->with( + $this->isInstanceOf(Visit::class), + )->willReturn(Update::forTopicAndPayload('', [])); }); + $this->helper->expects($this->exactly(count($expectedChannels)))->method('publishUpdate')->with( + $this->isInstanceOf(Update::class), + ); + $this->logger->expects($this->never())->method('debug'); ($this->listener())(new VisitLocated($visitId)); - - $findVisit->shouldHaveBeenCalledOnce(); - $this->helper->publishUpdate(Argument::type(Update::class))->shouldHaveBeenCalledTimes( - count($expectedChannels), - ); - $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); } public function provideVisits(): iterable @@ -124,21 +121,20 @@ class NotifyVisitToRabbitMqTest extends TestCase public function printsDebugMessageInCaseOfError(Throwable $e): void { $visitId = '123'; - $findVisit = $this->em->find(Visit::class, $visitId)->willReturn(Visit::forBasePath(Visitor::emptyInstance())); - $generateUpdate = $this->updatesGenerator->newOrphanVisitUpdate(Argument::type(Visit::class))->willReturn( - Update::forTopicAndPayload('', []), + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo($visitId), + )->willReturn(Visit::forBasePath(Visitor::emptyInstance())); + $this->updatesGenerator->expects($this->once())->method('newOrphanVisitUpdate')->with( + $this->isInstanceOf(Visit::class), + )->willReturn(Update::forTopicAndPayload('', [])); + $this->helper->expects($this->once())->method('publishUpdate')->withAnyParameters()->willThrowException($e); + $this->logger->expects($this->once())->method('debug')->with( + $this->equalTo('Error while trying to notify {name} with new visit. {e}'), + $this->equalTo(['e' => $e, 'name' => 'RabbitMQ']), ); - $publish = $this->helper->publishUpdate(Argument::cetera())->willThrow($e); ($this->listener())(new VisitLocated($visitId)); - - $this->logger->debug( - 'Error while trying to notify {name} with new visit. {e}', - ['e' => $e, 'name' => 'RabbitMQ'], - )->shouldHaveBeenCalledOnce(); - $findVisit->shouldHaveBeenCalledOnce(); - $generateUpdate->shouldHaveBeenCalledOnce(); - $publish->shouldHaveBeenCalledOnce(); } public function provideExceptions(): iterable @@ -155,17 +151,18 @@ class NotifyVisitToRabbitMqTest extends TestCase public function expectedPayloadIsPublishedDependingOnConfig( bool $legacy, Visit $visit, - callable $assert, callable $setup, + callable $expect, ): void { $visitId = '123'; - $findVisit = $this->em->find(Visit::class, $visitId)->willReturn($visit); + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo($visitId), + )->willReturn($visit); $setup($this->updatesGenerator); + $expect($this->helper, $this->updatesGenerator); ($this->listener(new RabbitMqOptions(true, $legacy)))(new VisitLocated($visitId)); - - $findVisit->shouldHaveBeenCalledOnce(); - $assert($this->helper, $this->updatesGenerator); } public function provideLegacyPayloads(): iterable @@ -173,8 +170,9 @@ class NotifyVisitToRabbitMqTest extends TestCase yield 'legacy non-orphan visit' => [ true, $visit = Visit::forValidShortUrl(ShortUrl::withLongUrl(''), Visitor::emptyInstance()), - function (ObjectProphecy|PublishingHelperInterface $helper) use ($visit): void { - $helper->publishUpdate(Argument::that(function (Update $update) use ($visit): bool { + noop(...), + function (MockObject & PublishingHelperInterface $helper) use ($visit): void { + $helper->method('publishUpdate')->with($this->callback(function (Update $update) use ($visit): bool { $payload = $update->payload; Assert::assertEquals($payload, $visit->jsonSerialize()); Assert::assertArrayNotHasKey('visitedUrl', $payload); @@ -185,13 +183,13 @@ class NotifyVisitToRabbitMqTest extends TestCase return true; })); }, - noop(...), ]; yield 'legacy orphan visit' => [ true, Visit::forBasePath(Visitor::emptyInstance()), - function (ObjectProphecy|PublishingHelperInterface $helper): void { - $helper->publishUpdate(Argument::that(function (Update $update): bool { + noop(...), + function (MockObject & PublishingHelperInterface $helper): void { + $helper->method('publishUpdate')->with($this->callback(function (Update $update): bool { $payload = $update->payload; Assert::assertArrayHasKey('visitedUrl', $payload); Assert::assertArrayHasKey('type', $payload); @@ -199,35 +197,33 @@ class NotifyVisitToRabbitMqTest extends TestCase return true; })); }, - noop(...), ]; yield 'non-legacy non-orphan visit' => [ false, Visit::forValidShortUrl(ShortUrl::withLongUrl(''), Visitor::emptyInstance()), - function (ObjectProphecy|PublishingHelperInterface $helper): void { - $helper->publishUpdate(Argument::type(Update::class))->shouldHaveBeenCalledTimes(2); - }, - function (ObjectProphecy|PublishingUpdatesGeneratorInterface $updatesGenerator): void { + function (MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator): void { $update = Update::forTopicAndPayload('', []); - $updatesGenerator->newOrphanVisitUpdate(Argument::cetera())->shouldNotBeCalled(); - $updatesGenerator->newVisitUpdate(Argument::cetera())->willReturn($update) - ->shouldBeCalledOnce(); - $updatesGenerator->newShortUrlVisitUpdate(Argument::cetera())->willReturn($update) - ->shouldBeCalledOnce(); + $updatesGenerator->expects($this->never())->method('newOrphanVisitUpdate'); + $updatesGenerator->expects($this->once())->method('newVisitUpdate')->withAnyParameters()->willReturn( + $update, + ); + $updatesGenerator->expects($this->once())->method('newShortUrlVisitUpdate')->willReturn($update); + }, + function (MockObject & PublishingHelperInterface $helper): void { + $helper->expects($this->exactly(2))->method('publishUpdate')->with($this->isInstanceOf(Update::class)); }, ]; yield 'non-legacy orphan visit' => [ false, Visit::forBasePath(Visitor::emptyInstance()), - function (ObjectProphecy|PublishingHelperInterface $helper): void { - $helper->publishUpdate(Argument::type(Update::class))->shouldHaveBeenCalledOnce(); - }, - function (ObjectProphecy|PublishingUpdatesGeneratorInterface $updatesGenerator): void { + function (MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator): void { $update = Update::forTopicAndPayload('', []); - $updatesGenerator->newOrphanVisitUpdate(Argument::cetera())->willReturn($update) - ->shouldBeCalledOnce(); - $updatesGenerator->newVisitUpdate(Argument::cetera())->shouldNotBeCalled(); - $updatesGenerator->newShortUrlVisitUpdate(Argument::cetera())->shouldNotBeCalled(); + $updatesGenerator->expects($this->once())->method('newOrphanVisitUpdate')->willReturn($update); + $updatesGenerator->expects($this->never())->method('newVisitUpdate'); + $updatesGenerator->expects($this->never())->method('newShortUrlVisitUpdate'); + }, + function (MockObject & PublishingHelperInterface $helper): void { + $helper->expects($this->once())->method('publishUpdate')->with($this->isInstanceOf(Update::class)); }, ]; } @@ -235,10 +231,10 @@ class NotifyVisitToRabbitMqTest extends TestCase private function listener(?RabbitMqOptions $options = null): NotifyVisitToRabbitMq { return new NotifyVisitToRabbitMq( - $this->helper->reveal(), - $this->updatesGenerator->reveal(), - $this->em->reveal(), - $this->logger->reveal(), + $this->helper, + $this->updatesGenerator, + $this->em, + $this->logger, new OrphanVisitDataTransformer(), $options ?? new RabbitMqOptions(enabled: true, legacyVisitsPublishing: false), ); From d0393799d2d7a1bf530a3b6d543f876240a3ba1d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 19:59:32 +0200 Subject: [PATCH 062/182] Migrated NotifyNewShortUrlToRedisTest to use PHPUnit mocks --- .../NotifyNewShortUrlToRedisTest.php | 65 ++++++++----------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/module/Core/test/EventDispatcher/RedisPubSub/NotifyNewShortUrlToRedisTest.php b/module/Core/test/EventDispatcher/RedisPubSub/NotifyNewShortUrlToRedisTest.php index ec3338d1..fd0e3904 100644 --- a/module/Core/test/EventDispatcher/RedisPubSub/NotifyNewShortUrlToRedisTest.php +++ b/module/Core/test/EventDispatcher/RedisPubSub/NotifyNewShortUrlToRedisTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Core\EventDispatcher\RedisPubSub; use Doctrine\ORM\EntityManagerInterface; use DomainException; use Exception; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Log\LoggerInterface; use RuntimeException; use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface; @@ -24,30 +22,28 @@ use Throwable; class NotifyNewShortUrlToRedisTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $helper; - private ObjectProphecy $updatesGenerator; - private ObjectProphecy $em; - private ObjectProphecy $logger; + private MockObject $helper; + private MockObject $updatesGenerator; + private MockObject $em; + private MockObject $logger; protected function setUp(): void { - $this->helper = $this->prophesize(PublishingHelperInterface::class); - $this->updatesGenerator = $this->prophesize(PublishingUpdatesGeneratorInterface::class); - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->logger = $this->prophesize(LoggerInterface::class); + $this->helper = $this->createMock(PublishingHelperInterface::class); + $this->updatesGenerator = $this->createMock(PublishingUpdatesGeneratorInterface::class); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); } /** @test */ public function doesNothingWhenTheFeatureIsNotEnabled(): void { - $this->createListener(false)(new ShortUrlCreated('123')); + $this->helper->expects($this->never())->method('publishUpdate'); + $this->em->expects($this->never())->method('find'); + $this->logger->expects($this->never())->method('warning'); + $this->logger->expects($this->never())->method('debug'); - $this->em->find(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->helper->publishUpdate(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->createListener(false)(new ShortUrlCreated('123')); } /** @@ -58,21 +54,22 @@ class NotifyNewShortUrlToRedisTest extends TestCase { $shortUrlId = '123'; $update = Update::forTopicAndPayload(Topic::NEW_SHORT_URL->value, []); - $find = $this->em->find(ShortUrl::class, $shortUrlId)->willReturn(ShortUrl::withLongUrl('')); - $generateUpdate = $this->updatesGenerator->newShortUrlUpdate(Argument::type(ShortUrl::class))->willReturn( - $update, + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(ShortUrl::class), + $this->equalTo($shortUrlId), + )->willReturn(ShortUrl::withLongUrl('')); + $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with( + $this->isInstanceOf(ShortUrl::class), + )->willReturn($update); + $this->helper->expects($this->once())->method('publishUpdate')->with( + $this->equalTo($update), + )->willThrowException($e); + $this->logger->expects($this->once())->method('debug')->with( + $this->equalTo('Error while trying to notify {name} with new short URL. {e}'), + $this->equalTo(['e' => $e, 'name' => 'Redis pub/sub']), ); - $publish = $this->helper->publishUpdate($update)->willThrow($e); $this->createListener()(new ShortUrlCreated($shortUrlId)); - - $this->logger->debug( - 'Error while trying to notify {name} with new short URL. {e}', - ['e' => $e, 'name' => 'Redis pub/sub'], - )->shouldHaveBeenCalledOnce(); - $find->shouldHaveBeenCalledOnce(); - $generateUpdate->shouldHaveBeenCalledOnce(); - $publish->shouldHaveBeenCalledOnce(); } public function provideExceptions(): iterable @@ -84,12 +81,6 @@ class NotifyNewShortUrlToRedisTest extends TestCase private function createListener(bool $enabled = true): NotifyNewShortUrlToRedis { - return new NotifyNewShortUrlToRedis( - $this->helper->reveal(), - $this->updatesGenerator->reveal(), - $this->em->reveal(), - $this->logger->reveal(), - $enabled, - ); + return new NotifyNewShortUrlToRedis($this->helper, $this->updatesGenerator, $this->em, $this->logger, $enabled); } } From 1706a869d9fc4a66a8549f6e820a105760cb0121 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 20:04:12 +0200 Subject: [PATCH 063/182] Migrated NotifyVisitToRedisTest to use PHPUnit mocks --- .../RedisPubSub/NotifyVisitToRedisTest.php | 63 ++++++++----------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/module/Core/test/EventDispatcher/RedisPubSub/NotifyVisitToRedisTest.php b/module/Core/test/EventDispatcher/RedisPubSub/NotifyVisitToRedisTest.php index 5c1e797b..c3188e5d 100644 --- a/module/Core/test/EventDispatcher/RedisPubSub/NotifyVisitToRedisTest.php +++ b/module/Core/test/EventDispatcher/RedisPubSub/NotifyVisitToRedisTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Core\EventDispatcher\RedisPubSub; use Doctrine\ORM\EntityManagerInterface; use DomainException; use Exception; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Log\LoggerInterface; use RuntimeException; use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface; @@ -24,30 +22,28 @@ use Throwable; class NotifyVisitToRedisTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $helper; - private ObjectProphecy $updatesGenerator; - private ObjectProphecy $em; - private ObjectProphecy $logger; + private MockObject $helper; + private MockObject $updatesGenerator; + private MockObject $em; + private MockObject $logger; protected function setUp(): void { - $this->helper = $this->prophesize(PublishingHelperInterface::class); - $this->updatesGenerator = $this->prophesize(PublishingUpdatesGeneratorInterface::class); - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->logger = $this->prophesize(LoggerInterface::class); + $this->helper = $this->createMock(PublishingHelperInterface::class); + $this->updatesGenerator = $this->createMock(PublishingUpdatesGeneratorInterface::class); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); } /** @test */ public function doesNothingWhenTheFeatureIsNotEnabled(): void { - $this->createListener(false)(new VisitLocated('123')); + $this->helper->expects($this->never())->method('publishUpdate'); + $this->em->expects($this->never())->method('find'); + $this->logger->expects($this->never())->method('warning'); + $this->logger->expects($this->never())->method('debug'); - $this->em->find(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->helper->publishUpdate(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->createListener(false)(new VisitLocated('123')); } /** @@ -57,21 +53,20 @@ class NotifyVisitToRedisTest extends TestCase public function printsDebugMessageInCaseOfError(Throwable $e): void { $visitId = '123'; - $findVisit = $this->em->find(Visit::class, $visitId)->willReturn(Visit::forBasePath(Visitor::emptyInstance())); - $generateUpdate = $this->updatesGenerator->newOrphanVisitUpdate(Argument::type(Visit::class))->willReturn( - Update::forTopicAndPayload('', []), + $this->em->expects($this->once())->method('find')->with( + $this->equalTo(Visit::class), + $this->equalTo($visitId), + )->willReturn(Visit::forBasePath(Visitor::emptyInstance())); + $this->updatesGenerator->expects($this->once())->method('newOrphanVisitUpdate')->with( + $this->isInstanceOf(Visit::class), + )->willReturn(Update::forTopicAndPayload('', [])); + $this->helper->expects($this->once())->method('publishUpdate')->withAnyParameters()->willThrowException($e); + $this->logger->expects($this->once())->method('debug')->with( + $this->equalTo('Error while trying to notify {name} with new visit. {e}'), + $this->equalTo(['e' => $e, 'name' => 'Redis pub/sub']), ); - $publish = $this->helper->publishUpdate(Argument::cetera())->willThrow($e); $this->createListener()(new VisitLocated($visitId)); - - $this->logger->debug( - 'Error while trying to notify {name} with new visit. {e}', - ['e' => $e, 'name' => 'Redis pub/sub'], - )->shouldHaveBeenCalledOnce(); - $findVisit->shouldHaveBeenCalledOnce(); - $generateUpdate->shouldHaveBeenCalledOnce(); - $publish->shouldHaveBeenCalledOnce(); } public function provideExceptions(): iterable @@ -83,12 +78,6 @@ class NotifyVisitToRedisTest extends TestCase private function createListener(bool $enabled = true): NotifyVisitToRedis { - return new NotifyVisitToRedis( - $this->helper->reveal(), - $this->updatesGenerator->reveal(), - $this->em->reveal(), - $this->logger->reveal(), - $enabled, - ); + return new NotifyVisitToRedis($this->helper, $this->updatesGenerator, $this->em, $this->logger, $enabled); } } From 10b0ec301b5b6d98a37c33b2a143f3585a9b2518 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 20:05:06 +0200 Subject: [PATCH 064/182] Migrated ValidationExceptionTest to use PHPUnit mocks --- module/Core/test/Exception/ValidationExceptionTest.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/module/Core/test/Exception/ValidationExceptionTest.php b/module/Core/test/Exception/ValidationExceptionTest.php index a0980738..b34badf3 100644 --- a/module/Core/test/Exception/ValidationExceptionTest.php +++ b/module/Core/test/Exception/ValidationExceptionTest.php @@ -8,7 +8,6 @@ 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; @@ -18,8 +17,6 @@ use function print_r; class ValidationExceptionTest extends TestCase { - use ProphecyTrait; - /** * @test * @dataProvider provideExceptions @@ -36,10 +33,10 @@ class ValidationExceptionTest extends TestCase 'something' => {$barValue} EOT; - $inputFilter = $this->prophesize(InputFilterInterface::class); - $getMessages = $inputFilter->getMessages()->willReturn($invalidData); + $inputFilter = $this->createMock(InputFilterInterface::class); + $inputFilter->expects($this->once())->method('getMessages')->with()->willReturn($invalidData); - $e = ValidationException::fromInputFilter($inputFilter->reveal(), $prev); + $e = ValidationException::fromInputFilter($inputFilter, $prev); self::assertEquals($invalidData, $e->getInvalidElements()); self::assertEquals(['invalidElements' => array_keys($invalidData)], $e->getAdditionalData()); @@ -47,7 +44,6 @@ class ValidationExceptionTest extends TestCase self::assertEquals(StatusCodeInterface::STATUS_BAD_REQUEST, $e->getCode()); self::assertEquals($prev, $e->getPrevious()); self::assertStringContainsString($expectedStringRepresentation, (string) $e); - $getMessages->shouldHaveBeenCalledOnce(); } public function provideExceptions(): iterable From 173420c608b9efb0f22bcf8eb0d431265784d8cc Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 20:39:55 +0200 Subject: [PATCH 065/182] Migrated ImportedLinksProcessorTest to use PHPUnit mocks --- .../Importer/ImportedLinksProcessorTest.php | 171 +++++++++--------- 1 file changed, 81 insertions(+), 90 deletions(-) diff --git a/module/Core/test/Importer/ImportedLinksProcessorTest.php b/module/Core/test/Importer/ImportedLinksProcessorTest.php index 2ce93647..926901a9 100644 --- a/module/Core/test/Importer/ImportedLinksProcessorTest.php +++ b/module/Core/test/Importer/ImportedLinksProcessorTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Core\Importer; use Cake\Chronos\Chronos; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use RuntimeException; use Shlinkio\Shlink\Core\Importer\ImportedLinksProcessor; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; @@ -23,6 +21,7 @@ use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit; use Shlinkio\Shlink\Importer\Params\ImportParams; use Shlinkio\Shlink\Importer\Sources\ImportSource; +use stdClass; use Symfony\Component\Console\Style\StyleInterface; use function count; @@ -32,32 +31,30 @@ use function str_contains; class ImportedLinksProcessorTest extends TestCase { - use ProphecyTrait; - private ImportedLinksProcessor $processor; - private ObjectProphecy $em; - private ObjectProphecy $shortCodeHelper; - private ObjectProphecy $repo; - private ObjectProphecy $io; + private MockObject $em; + private MockObject $shortCodeHelper; + private MockObject $repo; + private MockObject $io; protected function setUp(): void { - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->repo = $this->prophesize(ShortUrlRepositoryInterface::class); - $this->em->getRepository(ShortUrl::class)->willReturn($this->repo->reveal()); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->repo = $this->createMock(ShortUrlRepositoryInterface::class); + $this->em->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn($this->repo); - $this->shortCodeHelper = $this->prophesize(ShortCodeUniquenessHelperInterface::class); - $batchHelper = $this->prophesize(DoctrineBatchHelperInterface::class); - $batchHelper->wrapIterable(Argument::cetera())->willReturnArgument(0); + $this->shortCodeHelper = $this->createMock(ShortCodeUniquenessHelperInterface::class); + $batchHelper = $this->createMock(DoctrineBatchHelperInterface::class); + $batchHelper->method('wrapIterable')->willReturnArgument(0); $this->processor = new ImportedLinksProcessor( - $this->em->reveal(), + $this->em, new SimpleShortUrlRelationResolver(), - $this->shortCodeHelper->reveal(), - $batchHelper->reveal(), + $this->shortCodeHelper, + $batchHelper, ); - $this->io = $this->prophesize(StyleInterface::class); + $this->io = $this->createMock(StyleInterface::class); } /** @test */ @@ -70,16 +67,16 @@ class ImportedLinksProcessorTest extends TestCase ]; $expectedCalls = count($urls); - $importedUrlExists = $this->repo->findOneByImportedUrl(Argument::cetera())->willReturn(null); - $ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true); - $persist = $this->em->persist(Argument::type(ShortUrl::class)); + $this->repo->expects($this->exactly($expectedCalls))->method('findOneByImportedUrl')->willReturn(null); + $this->shortCodeHelper->expects($this->exactly($expectedCalls)) + ->method('ensureShortCodeUniqueness') + ->willReturn(true); + $this->em->expects($this->exactly($expectedCalls))->method('persist')->with( + $this->isInstanceOf(ShortUrl::class), + ); + $this->io->expects($this->exactly($expectedCalls))->method('text')->with($this->isType('string')); - $this->processor->process($this->io->reveal(), $urls, $this->buildParams()); - - $importedUrlExists->shouldHaveBeenCalledTimes($expectedCalls); - $ensureUniqueness->shouldHaveBeenCalledTimes($expectedCalls); - $persist->shouldHaveBeenCalledTimes($expectedCalls); - $this->io->text(Argument::type('string'))->shouldHaveBeenCalledTimes($expectedCalls); + $this->processor->process($this->io, $urls, $this->buildParams()); } /** @test */ @@ -91,26 +88,21 @@ class ImportedLinksProcessorTest extends TestCase new ImportedShlinkUrl(ImportSource::BITLY, 'baz', [], Chronos::now(), null, 'baz', null), ]; - $importedUrlExists = $this->repo->findOneByImportedUrl(Argument::cetera())->willReturn(null); - $ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true); - $persist = $this->em->persist(Argument::type(ShortUrl::class))->will(function (array $args): void { - /** @var ShortUrl $shortUrl */ - [$shortUrl] = $args; - + $this->repo->expects($this->exactly(3))->method('findOneByImportedUrl')->willReturn(null); + $this->shortCodeHelper->expects($this->exactly(3))->method('ensureShortCodeUniqueness')->willReturn(true); + $this->em->expects($this->exactly(3))->method('persist')->with( + $this->isInstanceOf(ShortUrl::class), + )->willReturnCallback(function (ShortUrl $shortUrl): void { if ($shortUrl->getShortCode() === 'baz') { throw new RuntimeException('Whatever error'); } }); + $textCalls = $this->setUpIoText('Skipped. Reason: Whatever error', 'Imported'); - $this->processor->process($this->io->reveal(), $urls, $this->buildParams()); + $this->processor->process($this->io, $urls, $this->buildParams()); - $importedUrlExists->shouldHaveBeenCalledTimes(3); - $ensureUniqueness->shouldHaveBeenCalledTimes(3); - $persist->shouldHaveBeenCalledTimes(3); - $this->io->text(Argument::containingString('Imported'))->shouldHaveBeenCalledTimes(2); - $this->io->text( - Argument::containingString('Skipped. Reason: Whatever error'), - )->shouldHaveBeenCalledOnce(); + self::assertEquals(2, $textCalls->importedCount); + self::assertEquals(1, $textCalls->skippedCount); } /** @test */ @@ -124,24 +116,18 @@ class ImportedLinksProcessorTest extends TestCase new ImportedShlinkUrl(ImportSource::BITLY, 'baz3', [], Chronos::now(), null, 'baz3', null), ]; - $importedUrlExists = $this->repo->findOneByImportedUrl(Argument::cetera())->will( - function (array $args): ?ShortUrl { - /** @var ImportedShlinkUrl $url */ - [$url] = $args; - - return contains(['foo', 'baz2', 'baz3'], $url->longUrl) ? ShortUrl::fromImport($url, true) : null; - }, + $this->repo->expects($this->exactly(count($urls)))->method('findOneByImportedUrl')->willReturnCallback( + fn (ImportedShlinkUrl $url): ?ShortUrl + => contains(['foo', 'baz2', 'baz3'], $url->longUrl) ? ShortUrl::fromImport($url, true) : null, ); - $ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true); - $persist = $this->em->persist(Argument::type(ShortUrl::class)); + $this->shortCodeHelper->expects($this->exactly(2))->method('ensureShortCodeUniqueness')->willReturn(true); + $this->em->expects($this->exactly(2))->method('persist')->with($this->isInstanceOf(ShortUrl::class)); + $textCalls = $this->setUpIoText(); - $this->processor->process($this->io->reveal(), $urls, $this->buildParams()); + $this->processor->process($this->io, $urls, $this->buildParams()); - $importedUrlExists->shouldHaveBeenCalledTimes(count($urls)); - $ensureUniqueness->shouldHaveBeenCalledTimes(2); - $persist->shouldHaveBeenCalledTimes(2); - $this->io->text(Argument::containingString('Skipped'))->shouldHaveBeenCalledTimes(3); - $this->io->text(Argument::containingString('Imported'))->shouldHaveBeenCalledTimes(2); + self::assertEquals(2, $textCalls->importedCount); + self::assertEquals(3, $textCalls->skippedCount); } /** @test */ @@ -155,32 +141,20 @@ class ImportedLinksProcessorTest extends TestCase new ImportedShlinkUrl(ImportSource::BITLY, 'baz3', [], Chronos::now(), null, 'baz3', 'bar'), ]; - $importedUrlExists = $this->repo->findOneByImportedUrl(Argument::cetera())->willReturn(null); - $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; - + $this->repo->expects($this->exactly(count($urls)))->method('findOneByImportedUrl')->willReturn(null); + $this->shortCodeHelper->expects($this->exactly(7))->method('ensureShortCodeUniqueness')->willReturnCallback( + fn ($_, bool $hasCustomSlug) => ! $hasCustomSlug, + ); + $this->em->expects($this->exactly(2))->method('persist')->with($this->isInstanceOf(ShortUrl::class)); + $this->io->expects($this->exactly(5))->method('choice')->willReturnCallback(function (string $question) { return some(['foo', 'baz2', 'baz3'], fn (string $item) => str_contains($question, $item)) ? 'Skip' : ''; }); - $persist = $this->em->persist(Argument::type(ShortUrl::class)); + $textCalls = $this->setUpIoText('Error'); - $this->processor->process($this->io->reveal(), $urls, $this->buildParams()); + $this->processor->process($this->io, $urls, $this->buildParams()); - $importedUrlExists->shouldHaveBeenCalledTimes(count($urls)); - $failingEnsureUniqueness->shouldHaveBeenCalledTimes(5); - $successEnsureUniqueness->shouldHaveBeenCalledTimes(2); - $choice->shouldHaveBeenCalledTimes(5); - $persist->shouldHaveBeenCalledTimes(2); - $this->io->text(Argument::containingString('Error'))->shouldHaveBeenCalledTimes(3); - $this->io->text(Argument::containingString('Imported'))->shouldHaveBeenCalledTimes(2); + self::assertEquals(2, $textCalls->importedCount); + self::assertEquals(3, $textCalls->skippedCount); } /** @@ -193,18 +167,16 @@ class ImportedLinksProcessorTest extends TestCase int $amountOfPersistedVisits, ?ShortUrl $foundShortUrl, ): void { - $findExisting = $this->repo->findOneByImportedUrl(Argument::cetera())->willReturn($foundShortUrl); - $ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true); - $persistUrl = $this->em->persist(Argument::type(ShortUrl::class)); - $persistVisits = $this->em->persist(Argument::type(Visit::class)); + $this->repo->expects($this->once())->method('findOneByImportedUrl')->willReturn($foundShortUrl); + $this->shortCodeHelper->expects($this->exactly($foundShortUrl === null ? 1 : 0)) + ->method('ensureShortCodeUniqueness') + ->willReturn(true); + $this->em->expects($this->exactly($amountOfPersistedVisits + ($foundShortUrl === null ? 1 : 0)))->method( + 'persist', + )->with($this->callback(fn (object $arg) => $arg instanceof ShortUrl || $arg instanceof Visit)); + $this->io->expects($this->once())->method('text')->with($this->stringContains($expectedOutput)); - $this->processor->process($this->io->reveal(), [$importedUrl], $this->buildParams()); - - $findExisting->shouldHaveBeenCalledOnce(); - $ensureUniqueness->shouldHaveBeenCalledTimes($foundShortUrl === null ? 1 : 0); - $persistUrl->shouldHaveBeenCalledTimes($foundShortUrl === null ? 1 : 0); - $persistVisits->shouldHaveBeenCalledTimes($amountOfPersistedVisits); - $this->io->text(Argument::containingString($expectedOutput))->shouldHaveBeenCalledOnce(); + $this->processor->process($this->io, [$importedUrl], $this->buildParams()); } public function provideUrlsWithVisits(): iterable @@ -251,4 +223,23 @@ class ImportedLinksProcessorTest extends TestCase { return ImportSource::BITLY->toParamsWithCallableMap(['import_short_codes' => static fn () => true]); } + + public function setUpIoText(string $skippedText = 'Skipped', string $importedText = 'Imported'): stdClass + { + $counts = new stdClass(); + $counts->importedCount = 0; + $counts->skippedCount = 0; + + $this->io->method('text')->willReturnCallback( + function (string $output) use ($counts, $skippedText, $importedText) { + if (str_contains($output, $skippedText)) { + $counts->skippedCount++; + } elseif (str_contains($output, $importedText)) { + $counts->importedCount++; + } + }, + ); + + return $counts; + } } From a78c59c11a78e47ffb3b70a6a86e50a7a406298c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2022 20:41:17 +0200 Subject: [PATCH 066/182] Fixed coding styles --- module/Core/test/Importer/ImportedLinksProcessorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/Core/test/Importer/ImportedLinksProcessorTest.php b/module/Core/test/Importer/ImportedLinksProcessorTest.php index 926901a9..183c54c1 100644 --- a/module/Core/test/Importer/ImportedLinksProcessorTest.php +++ b/module/Core/test/Importer/ImportedLinksProcessorTest.php @@ -231,7 +231,7 @@ class ImportedLinksProcessorTest extends TestCase $counts->skippedCount = 0; $this->io->method('text')->willReturnCallback( - function (string $output) use ($counts, $skippedText, $importedText) { + function (string $output) use ($counts, $skippedText, $importedText): void { if (str_contains($output, $skippedText)) { $counts->skippedCount++; } elseif (str_contains($output, $importedText)) { From 5ceb6fb7404fb35aa0e4017220fa42894a957008 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 11:00:50 +0200 Subject: [PATCH 067/182] No longer let pipelines pass on error with PHP 8.2 --- .github/workflows/ci-db-tests.yml | 1 - .github/workflows/ci-mutation-tests.yml | 1 - .github/workflows/ci-tests.yml | 1 - .github/workflows/ci.yml | 1 - 4 files changed, 4 deletions(-) diff --git a/.github/workflows/ci-db-tests.yml b/.github/workflows/ci-db-tests.yml index aa3a2bc3..db3efacf 100644 --- a/.github/workflows/ci-db-tests.yml +++ b/.github/workflows/ci-db-tests.yml @@ -14,7 +14,6 @@ jobs: strategy: matrix: php-version: ['8.1', '8.2'] - continue-on-error: ${{ matrix.php-version == '8.2' }} env: LC_ALL: C steps: diff --git a/.github/workflows/ci-mutation-tests.yml b/.github/workflows/ci-mutation-tests.yml index 5eeb9ac3..ac510c7d 100644 --- a/.github/workflows/ci-mutation-tests.yml +++ b/.github/workflows/ci-mutation-tests.yml @@ -14,7 +14,6 @@ jobs: strategy: matrix: php-version: ['8.1', '8.2'] - continue-on-error: ${{ matrix.php-version == '8.2' }} steps: - uses: actions/checkout@v3 - uses: './.github/actions/ci-setup' diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 66aa666b..f7e7b141 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -14,7 +14,6 @@ jobs: strategy: matrix: php-version: ['8.1', '8.2'] - continue-on-error: ${{ matrix.php-version == '8.2' }} steps: - uses: actions/checkout@v3 - name: Start postgres database server diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6986061..ba4c77dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,6 @@ jobs: strategy: matrix: php-version: ['8.1', '8.2'] - continue-on-error: ${{ matrix.php-version == '8.2' }} steps: - uses: actions/checkout@v3 - run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres From bd884e85d46cacc0a5e58b72d7644c62f0c71a01 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 11:03:47 +0200 Subject: [PATCH 068/182] Migrated DeleteShortUrlServiceTest to use PHPUnit mocks --- .../ShortUrl/DeleteShortUrlServiceTest.php | 47 ++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/module/Core/test/ShortUrl/DeleteShortUrlServiceTest.php b/module/Core/test/ShortUrl/DeleteShortUrlServiceTest.php index 39e332c5..4ce1568b 100644 --- a/module/Core/test/ShortUrl/DeleteShortUrlServiceTest.php +++ b/module/Core/test/ShortUrl/DeleteShortUrlServiceTest.php @@ -6,10 +6,8 @@ namespace ShlinkioTest\Shlink\Core\ShortUrl; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Exception\DeleteShortUrlException; use Shlinkio\Shlink\Core\Options\DeleteShortUrlsOptions; use Shlinkio\Shlink\Core\ShortUrl\DeleteShortUrlService; @@ -25,10 +23,8 @@ use function sprintf; class DeleteShortUrlServiceTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $em; - private ObjectProphecy $urlResolver; + private MockObject $em; + private MockObject $urlResolver; private string $shortCode; protected function setUp(): void @@ -38,10 +34,10 @@ class DeleteShortUrlServiceTest extends TestCase )); $this->shortCode = $shortUrl->getShortCode(); - $this->em = $this->prophesize(EntityManagerInterface::class); + $this->em = $this->createMock(EntityManagerInterface::class); - $this->urlResolver = $this->prophesize(ShortUrlResolverInterface::class); - $this->urlResolver->resolveShortUrl(Argument::cetera())->willReturn($shortUrl); + $this->urlResolver = $this->createMock(ShortUrlResolverInterface::class); + $this->urlResolver->method('resolveShortUrl')->willReturn($shortUrl); } /** @test */ @@ -63,13 +59,12 @@ class DeleteShortUrlServiceTest extends TestCase { $service = $this->createService(); - $remove = $this->em->remove(Argument::type(ShortUrl::class))->willReturn(null); - $flush = $this->em->flush()->willReturn(null); + $this->em->expects($this->once())->method('remove')->with($this->isInstanceOf(ShortUrl::class))->willReturn( + null, + ); + $this->em->expects($this->once())->method('flush')->with()->willReturn(null); $service->deleteByShortCode(ShortUrlIdentifier::fromShortCodeAndDomain($this->shortCode), true); - - $remove->shouldHaveBeenCalledOnce(); - $flush->shouldHaveBeenCalledOnce(); } /** @test */ @@ -77,13 +72,12 @@ class DeleteShortUrlServiceTest extends TestCase { $service = $this->createService(false); - $remove = $this->em->remove(Argument::type(ShortUrl::class))->willReturn(null); - $flush = $this->em->flush()->willReturn(null); + $this->em->expects($this->once())->method('remove')->with($this->isInstanceOf(ShortUrl::class))->willReturn( + null, + ); + $this->em->expects($this->once())->method('flush')->with()->willReturn(null); $service->deleteByShortCode(ShortUrlIdentifier::fromShortCodeAndDomain($this->shortCode)); - - $remove->shouldHaveBeenCalledOnce(); - $flush->shouldHaveBeenCalledOnce(); } /** @test */ @@ -91,20 +85,19 @@ class DeleteShortUrlServiceTest extends TestCase { $service = $this->createService(true, 100); - $remove = $this->em->remove(Argument::type(ShortUrl::class))->willReturn(null); - $flush = $this->em->flush()->willReturn(null); + $this->em->expects($this->once())->method('remove')->with($this->isInstanceOf(ShortUrl::class))->willReturn( + null, + ); + $this->em->expects($this->once())->method('flush')->with()->willReturn(null); $service->deleteByShortCode(ShortUrlIdentifier::fromShortCodeAndDomain($this->shortCode)); - - $remove->shouldHaveBeenCalledOnce(); - $flush->shouldHaveBeenCalledOnce(); } private function createService(bool $checkVisitsThreshold = true, int $visitsThreshold = 5): DeleteShortUrlService { - return new DeleteShortUrlService($this->em->reveal(), new DeleteShortUrlsOptions( + return new DeleteShortUrlService($this->em, new DeleteShortUrlsOptions( $visitsThreshold, $checkVisitsThreshold, - ), $this->urlResolver->reveal()); + ), $this->urlResolver); } } From ee8cab8455172eaebfb3a6501669156a9a9e6209 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 11:09:40 +0200 Subject: [PATCH 069/182] Migrated ShortUrlResolverTest to use PHPUnit mocks --- .../test/ShortUrl/ShortUrlResolverTest.php | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/module/Core/test/ShortUrl/ShortUrlResolverTest.php b/module/Core/test/ShortUrl/ShortUrlResolverTest.php index 5ba2d514..ef63a92e 100644 --- a/module/Core/test/ShortUrl/ShortUrlResolverTest.php +++ b/module/Core/test/ShortUrl/ShortUrlResolverTest.php @@ -7,9 +7,8 @@ namespace ShlinkioTest\Shlink\Core\ShortUrl; use Cake\Chronos\Chronos; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; @@ -27,15 +26,16 @@ use function range; class ShortUrlResolverTest extends TestCase { use ApiKeyHelpersTrait; - use ProphecyTrait; private ShortUrlResolver $urlResolver; - private ObjectProphecy $em; + private MockObject $em; + private MockObject $repo; protected function setUp(): void { - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->urlResolver = new ShortUrlResolver($this->em->reveal()); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->repo = $this->createMock(ShortUrlRepositoryInterface::class); + $this->urlResolver = new ShortUrlResolver($this->em); } /** @@ -48,15 +48,17 @@ class ShortUrlResolverTest extends TestCase $shortCode = $shortUrl->getShortCode(); $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); - $repo = $this->prophesize(ShortUrlRepositoryInterface::class); - $findOne = $repo->findOne($identifier, $apiKey?->spec())->willReturn($shortUrl); - $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + $this->repo->expects($this->once())->method('findOne')->with( + $this->equalTo($identifier), + $this->equalTo($apiKey?->spec()), + )->willReturn($shortUrl); + $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn( + $this->repo, + ); $result = $this->urlResolver->resolveShortUrl($identifier, $apiKey); self::assertSame($shortUrl, $result); - $findOne->shouldHaveBeenCalledOnce(); - $getRepo->shouldHaveBeenCalledOnce(); } /** @@ -68,13 +70,15 @@ class ShortUrlResolverTest extends TestCase $shortCode = 'abc123'; $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); - $repo = $this->prophesize(ShortUrlRepositoryInterface::class); - $findOne = $repo->findOne($identifier, $apiKey?->spec())->willReturn(null); - $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal(), $apiKey); + $this->repo->expects($this->once())->method('findOne')->with( + $this->equalTo($identifier), + $this->equalTo($apiKey?->spec()), + )->willReturn(null); + $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn( + $this->repo, + ); $this->expectException(ShortUrlNotFoundException::class); - $findOne->shouldBeCalledOnce(); - $getRepo->shouldBeCalledOnce(); $this->urlResolver->resolveShortUrl($identifier, $apiKey); } @@ -85,17 +89,16 @@ class ShortUrlResolverTest extends TestCase $shortUrl = ShortUrl::withLongUrl('expected_url'); $shortCode = $shortUrl->getShortCode(); - $repo = $this->prophesize(ShortUrlRepositoryInterface::class); - $findOneByShortCode = $repo->findOneWithDomainFallback( - ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), + $this->repo->expects($this->once())->method('findOneWithDomainFallback')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), )->willReturn($shortUrl); - $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn( + $this->repo, + ); $result = $this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)); self::assertSame($shortUrl, $result); - $findOneByShortCode->shouldHaveBeenCalledOnce(); - $getRepo->shouldHaveBeenCalledOnce(); } /** @@ -106,15 +109,14 @@ class ShortUrlResolverTest extends TestCase { $shortCode = $shortUrl->getShortCode(); - $repo = $this->prophesize(ShortUrlRepositoryInterface::class); - $findOneByShortCode = $repo->findOneWithDomainFallback( - ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), + $this->repo->expects($this->once())->method('findOneWithDomainFallback')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), )->willReturn($shortUrl); - $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn( + $this->repo, + ); $this->expectException(ShortUrlNotFoundException::class); - $findOneByShortCode->shouldBeCalledOnce(); - $getRepo->shouldBeCalledOnce(); $this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)); } From 36ab455a49768e49ff1296b07b38e192077d7eab Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 11:14:01 +0200 Subject: [PATCH 070/182] Migrated ShortUrlServiceTest to use PHPUnit mocks --- .../test/ShortUrl/ShortUrlServiceTest.php | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/module/Core/test/ShortUrl/ShortUrlServiceTest.php b/module/Core/test/ShortUrl/ShortUrlServiceTest.php index 9037be60..16afbe8c 100644 --- a/module/Core/test/ShortUrl/ShortUrlServiceTest.php +++ b/module/Core/test/ShortUrl/ShortUrlServiceTest.php @@ -6,10 +6,8 @@ namespace ShlinkioTest\Shlink\Core\ShortUrl; use Cake\Chronos\Chronos; use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlTitleResolutionHelperInterface; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlEdition; @@ -27,26 +25,25 @@ use function count; class ShortUrlServiceTest extends TestCase { use ApiKeyHelpersTrait; - use ProphecyTrait; private ShortUrlService $service; - private ObjectProphecy $em; - private ObjectProphecy $urlResolver; - private ObjectProphecy $titleResolutionHelper; + private MockObject $em; + private MockObject $urlResolver; + private MockObject $titleResolutionHelper; protected function setUp(): void { - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->em->persist(Argument::any())->willReturn(null); - $this->em->flush()->willReturn(null); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->em->method('persist')->willReturn(null); + $this->em->method('flush')->willReturn(null); - $this->urlResolver = $this->prophesize(ShortUrlResolverInterface::class); - $this->titleResolutionHelper = $this->prophesize(ShortUrlTitleResolutionHelperInterface::class); + $this->urlResolver = $this->createMock(ShortUrlResolverInterface::class); + $this->titleResolutionHelper = $this->createMock(ShortUrlTitleResolutionHelperInterface::class); $this->service = new ShortUrlService( - $this->em->reveal(), - $this->urlResolver->reveal(), - $this->titleResolutionHelper->reveal(), + $this->em, + $this->urlResolver, + $this->titleResolutionHelper, new SimpleShortUrlRelationResolver(), ); } @@ -64,10 +61,10 @@ class ShortUrlServiceTest extends TestCase ShortUrl::createEmpty(), ]; - $repo = $this->prophesize(ShortUrlRepository::class); - $repo->findList(Argument::cetera())->willReturn($list)->shouldBeCalledOnce(); - $repo->countList(Argument::cetera())->willReturn(count($list))->shouldBeCalledOnce(); - $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + $repo = $this->createMock(ShortUrlRepository::class); + $repo->expects($this->once())->method('findList')->willReturn($list); + $repo->expects($this->once())->method('countList')->willReturn(count($list)); + $this->em->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn($repo); $paginator = $this->service->listShortUrls(ShortUrlsParams::emptyInstance(), $apiKey); @@ -87,15 +84,15 @@ class ShortUrlServiceTest extends TestCase $originalLongUrl = 'originalLongUrl'; $shortUrl = ShortUrl::withLongUrl($originalLongUrl); - $findShortUrl = $this->urlResolver->resolveShortUrl( - ShortUrlIdentifier::fromShortCodeAndDomain('abc123'), - $apiKey, + $this->urlResolver->expects($this->once())->method('resolveShortUrl')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain('abc123')), + $this->equalTo($apiKey), )->willReturn($shortUrl); - $flush = $this->em->flush()->willReturn(null); - $processTitle = $this->titleResolutionHelper->processTitleAndValidateUrl($shortUrlEdit)->willReturn( - $shortUrlEdit, - ); + $this->titleResolutionHelper->expects($this->exactly($expectedValidateCalls)) + ->method('processTitleAndValidateUrl') + ->with($this->equalTo($shortUrlEdit)) + ->willReturn($shortUrlEdit); $result = $this->service->updateShortUrl( ShortUrlIdentifier::fromShortCodeAndDomain('abc123'), @@ -108,9 +105,6 @@ class ShortUrlServiceTest extends TestCase self::assertEquals($shortUrlEdit->validUntil(), $shortUrl->getValidUntil()); self::assertEquals($shortUrlEdit->maxVisits(), $shortUrl->getMaxVisits()); self::assertEquals($shortUrlEdit->longUrl() ?? $originalLongUrl, $shortUrl->getLongUrl()); - $findShortUrl->shouldHaveBeenCalled(); - $flush->shouldHaveBeenCalled(); - $processTitle->shouldHaveBeenCalledTimes($expectedValidateCalls); } public function provideShortUrlEdits(): iterable From d2f5be1d18ccade16f1ee55f22e3ad853af98480 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 11:32:13 +0200 Subject: [PATCH 071/182] Migrated UrlShortenerTest to use PHPUnit mocks --- .../Core/test/ShortUrl/UrlShortenerTest.php | 83 +++++++++---------- 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/module/Core/test/ShortUrl/UrlShortenerTest.php b/module/Core/test/ShortUrl/UrlShortenerTest.php index 9bf5a4d1..f1097669 100644 --- a/module/Core/test/ShortUrl/UrlShortenerTest.php +++ b/module/Core/test/ShortUrl/UrlShortenerTest.php @@ -5,11 +5,9 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\ShortUrl; use Cake\Chronos\Chronos; -use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityManager; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\EventDispatcher\EventDispatcherInterface; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; @@ -22,42 +20,29 @@ use Shlinkio\Shlink\Core\ShortUrl\UrlShortener; class UrlShortenerTest extends TestCase { - use ProphecyTrait; - private UrlShortener $urlShortener; - private ObjectProphecy $em; - private ObjectProphecy $titleResolutionHelper; - private ObjectProphecy $shortCodeHelper; + private MockObject $em; + private MockObject $titleResolutionHelper; + private MockObject $shortCodeHelper; protected function setUp(): void { - $this->titleResolutionHelper = $this->prophesize(ShortUrlTitleResolutionHelperInterface::class); - $this->titleResolutionHelper->processTitleAndValidateUrl(Argument::cetera())->willReturnArgument(); + $this->titleResolutionHelper = $this->createMock(ShortUrlTitleResolutionHelperInterface::class); + $this->shortCodeHelper = $this->createMock(ShortCodeUniquenessHelperInterface::class); - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->em->persist(Argument::any())->will(function ($arguments): void { - /** @var ShortUrl $shortUrl */ - [$shortUrl] = $arguments; - $shortUrl->setId('10'); - }); - $this->em->wrapInTransaction(Argument::type('callable'))->will(function (array $args) { - /** @var callable $callback */ - [$callback] = $args; - - return $callback(); - }); - $repo = $this->prophesize(ShortUrlRepository::class); - $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); - - $this->shortCodeHelper = $this->prophesize(ShortCodeUniquenessHelperInterface::class); - $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true); + // FIXME Should use the interface, but it doe snot define wrapInTransaction explicitly + $this->em = $this->createMock(EntityManager::class); + $this->em->method('persist')->willReturnCallback(fn (ShortUrl $shortUrl) => $shortUrl->setId('10')); + $this->em->method('wrapInTransaction')->with($this->isType('callable'))->willReturnCallback( + fn (callable $callback) => $callback(), + ); $this->urlShortener = new UrlShortener( - $this->titleResolutionHelper->reveal(), - $this->em->reveal(), + $this->titleResolutionHelper, + $this->em, new SimpleShortUrlRelationResolver(), - $this->shortCodeHelper->reveal(), - $this->prophesize(EventDispatcherInterface::class)->reveal(), + $this->shortCodeHelper, + $this->createMock(EventDispatcherInterface::class), ); } @@ -66,23 +51,31 @@ class UrlShortenerTest extends TestCase { $longUrl = 'http://foobar.com/12345/hello?foo=bar'; $meta = ShortUrlCreation::fromRawData(['longUrl' => $longUrl]); + $this->titleResolutionHelper->expects($this->once())->method('processTitleAndValidateUrl')->with( + $this->equalTo($meta), + )->willReturnArgument(0); + $this->shortCodeHelper->method('ensureShortCodeUniqueness')->willReturn(true); + $shortUrl = $this->urlShortener->shorten($meta); self::assertEquals($longUrl, $shortUrl->getLongUrl()); - $this->titleResolutionHelper->processTitleAndValidateUrl($meta)->shouldHaveBeenCalledOnce(); } /** @test */ public function exceptionIsThrownWhenNonUniqueSlugIsProvided(): void { - $ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(false); + $meta = ShortUrlCreation::fromRawData( + ['customSlug' => 'custom-slug', 'longUrl' => 'http://foobar.com/12345/hello?foo=bar'], + ); + + $this->shortCodeHelper->expects($this->once())->method('ensureShortCodeUniqueness')->willReturn(false); + $this->titleResolutionHelper->expects($this->once())->method('processTitleAndValidateUrl')->with( + $this->equalTo($meta), + )->willReturnArgument(0); - $ensureUniqueness->shouldBeCalledOnce(); $this->expectException(NonUniqueSlugException::class); - $this->urlShortener->shorten(ShortUrlCreation::fromRawData( - ['customSlug' => 'custom-slug', 'longUrl' => 'http://foobar.com/12345/hello?foo=bar'], - )); + $this->urlShortener->shorten($meta); } /** @@ -91,16 +84,16 @@ class UrlShortenerTest extends TestCase */ public function existingShortUrlIsReturnedWhenRequested(ShortUrlCreation $meta, ShortUrl $expected): void { - $repo = $this->prophesize(ShortUrlRepository::class); - $findExisting = $repo->findOneMatching(Argument::cetera())->willReturn($expected); - $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + $repo = $this->createMock(ShortUrlRepository::class); + $repo->expects($this->once())->method('findOneMatching')->willReturn($expected); + $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn( + $repo, + ); + $this->titleResolutionHelper->expects($this->never())->method('processTitleAndValidateUrl'); + $this->shortCodeHelper->method('ensureShortCodeUniqueness')->willReturn(true); $result = $this->urlShortener->shorten($meta); - $findExisting->shouldHaveBeenCalledOnce(); - $getRepo->shouldHaveBeenCalledOnce(); - $this->em->persist(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->titleResolutionHelper->processTitleAndValidateUrl(Argument::cetera())->shouldNotHaveBeenCalled(); self::assertSame($expected, $result); } From 5aaf50d68e01638877a5680deb0b301ce04b436e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 17:35:50 +0200 Subject: [PATCH 072/182] Migrated ShortCodeUniquenessHelperTest to use PHPUnit mocks --- .../Helper/ShortCodeUniquenessHelperTest.php | 55 +++++++++---------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/module/Core/test/ShortUrl/Helper/ShortCodeUniquenessHelperTest.php b/module/Core/test/ShortUrl/Helper/ShortCodeUniquenessHelperTest.php index 23f45506..0891c8ae 100644 --- a/module/Core/test/ShortUrl/Helper/ShortCodeUniquenessHelperTest.php +++ b/module/Core/test/ShortUrl/Helper/ShortCodeUniquenessHelperTest.php @@ -5,9 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\ShortUrl\Helper; use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Domain\Entity\Domain; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortCodeUniquenessHelper; @@ -16,19 +15,17 @@ use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepository; class ShortCodeUniquenessHelperTest extends TestCase { - use ProphecyTrait; - private ShortCodeUniquenessHelper $helper; - private ObjectProphecy $em; - private ObjectProphecy $shortUrl; + private MockObject $em; + private MockObject $shortUrl; protected function setUp(): void { - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->helper = new ShortCodeUniquenessHelper($this->em->reveal()); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->helper = new ShortCodeUniquenessHelper($this->em); - $this->shortUrl = $this->prophesize(ShortUrl::class); - $this->shortUrl->getShortCode()->willReturn('abc123'); + $this->shortUrl = $this->createMock(ShortUrl::class); + $this->shortUrl->method('getShortCode')->willReturn('abc123'); } /** @@ -39,22 +36,22 @@ class ShortCodeUniquenessHelperTest extends TestCase { $callIndex = 0; $expectedCalls = 3; - $repo = $this->prophesize(ShortUrlRepository::class); - $shortCodeIsInUse = $repo->shortCodeIsInUseWithLock( - ShortUrlIdentifier::fromShortCodeAndDomain('abc123', $expectedAuthority), - )->will(function () use (&$callIndex, $expectedCalls) { + $repo = $this->createMock(ShortUrlRepository::class); + $repo->expects($this->exactly($expectedCalls))->method('shortCodeIsInUseWithLock')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain('abc123', $expectedAuthority)), + )->willReturnCallback(function () use (&$callIndex, $expectedCalls) { $callIndex++; return $callIndex < $expectedCalls; }); - $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); - $this->shortUrl->getDomain()->willReturn($domain); + $this->em->expects($this->exactly($expectedCalls))->method('getRepository')->with( + $this->equalTo(ShortUrl::class), + )->willReturn($repo); + $this->shortUrl->method('getDomain')->willReturn($domain); + $this->shortUrl->expects($this->exactly($expectedCalls - 1))->method('regenerateShortCode')->with(); - $result = $this->helper->ensureShortCodeUniqueness($this->shortUrl->reveal(), false); + $result = $this->helper->ensureShortCodeUniqueness($this->shortUrl, false); self::assertTrue($result); - $this->shortUrl->regenerateShortCode()->shouldHaveBeenCalledTimes($expectedCalls - 1); - $getRepo->shouldBeCalledTimes($expectedCalls); - $shortCodeIsInUse->shouldBeCalledTimes($expectedCalls); } public function provideDomains(): iterable @@ -66,18 +63,18 @@ class ShortCodeUniquenessHelperTest extends TestCase /** @test */ public function inUseSlugReturnsError(): void { - $repo = $this->prophesize(ShortUrlRepository::class); - $shortCodeIsInUse = $repo->shortCodeIsInUseWithLock( - ShortUrlIdentifier::fromShortCodeAndDomain('abc123'), + $repo = $this->createMock(ShortUrlRepository::class); + $repo->expects($this->once())->method('shortCodeIsInUseWithLock')->with( + $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain('abc123')), )->willReturn(true); - $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); - $this->shortUrl->getDomain()->willReturn(null); + $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn( + $repo, + ); + $this->shortUrl->method('getDomain')->willReturn(null); + $this->shortUrl->expects($this->never())->method('regenerateShortCode'); - $result = $this->helper->ensureShortCodeUniqueness($this->shortUrl->reveal(), true); + $result = $this->helper->ensureShortCodeUniqueness($this->shortUrl, true); self::assertFalse($result); - $this->shortUrl->regenerateShortCode()->shouldNotHaveBeenCalled(); - $getRepo->shouldBeCalledOnce(); - $shortCodeIsInUse->shouldBeCalledOnce(); } } From 162e913cc4dbf7bb48ad1197cddd1e2fca4df6d7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 17:38:04 +0200 Subject: [PATCH 073/182] Migrated ShortUrlTitleResolutionHelperTest to use PHPUnit mocks --- .../ShortUrlTitleResolutionHelperTest.php | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/module/Core/test/ShortUrl/Helper/ShortUrlTitleResolutionHelperTest.php b/module/Core/test/ShortUrl/Helper/ShortUrlTitleResolutionHelperTest.php index b2cbfd85..61618eda 100644 --- a/module/Core/test/ShortUrl/Helper/ShortUrlTitleResolutionHelperTest.php +++ b/module/Core/test/ShortUrl/Helper/ShortUrlTitleResolutionHelperTest.php @@ -4,24 +4,21 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\ShortUrl\Helper; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlTitleResolutionHelper; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; use Shlinkio\Shlink\Core\Util\UrlValidatorInterface; class ShortUrlTitleResolutionHelperTest extends TestCase { - use ProphecyTrait; - private ShortUrlTitleResolutionHelper $helper; - private ObjectProphecy $urlValidator; + private MockObject $urlValidator; protected function setUp(): void { - $this->urlValidator = $this->prophesize(UrlValidatorInterface::class); - $this->helper = new ShortUrlTitleResolutionHelper($this->urlValidator->reveal()); + $this->urlValidator = $this->createMock(UrlValidatorInterface::class); + $this->helper = new ShortUrlTitleResolutionHelper($this->urlValidator); } /** @@ -31,14 +28,18 @@ class ShortUrlTitleResolutionHelperTest extends TestCase public function urlIsProperlyShortened(?string $title, int $validateWithTitleCallsNum, int $validateCallsNum): void { $longUrl = 'http://foobar.com/12345/hello?foo=bar'; + $this->urlValidator->expects($this->exactly($validateWithTitleCallsNum))->method('validateUrlWithTitle')->with( + $this->equalTo($longUrl), + $this->isFalse(), + ); + $this->urlValidator->expects($this->exactly($validateCallsNum))->method('validateUrl')->with( + $this->equalTo($longUrl), + $this->isFalse(), + ); + $this->helper->processTitleAndValidateUrl( ShortUrlCreation::fromRawData(['longUrl' => $longUrl, 'title' => $title]), ); - - $this->urlValidator->validateUrlWithTitle($longUrl, false)->shouldHaveBeenCalledTimes( - $validateWithTitleCallsNum, - ); - $this->urlValidator->validateUrl($longUrl, false)->shouldHaveBeenCalledTimes($validateCallsNum); } public function provideTitles(): iterable From 168c839cf1183291963bea60ce492e288a804491 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 17:39:57 +0200 Subject: [PATCH 074/182] Migrated TrimTrailingSlashMiddlewareTest to use PHPUnit mocks --- .../TrimTrailingSlashMiddlewareTest.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/module/Core/test/ShortUrl/Middleware/TrimTrailingSlashMiddlewareTest.php b/module/Core/test/ShortUrl/Middleware/TrimTrailingSlashMiddlewareTest.php index 6a46f8e9..ca971b00 100644 --- a/module/Core/test/ShortUrl/Middleware/TrimTrailingSlashMiddlewareTest.php +++ b/module/Core/test/ShortUrl/Middleware/TrimTrailingSlashMiddlewareTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Core\ShortUrl\Middleware; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequestFactory; use PHPUnit\Framework\Assert; +use PHPUnit\Framework\MockObject\MockObject; 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; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; @@ -21,13 +19,11 @@ use function Functional\const_function; class TrimTrailingSlashMiddlewareTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $requestHandler; + private MockObject $requestHandler; protected function setUp(): void { - $this->requestHandler = $this->prophesize(RequestHandlerInterface::class); + $this->requestHandler = $this->createMock(RequestHandlerInterface::class); } /** @@ -40,9 +36,11 @@ class TrimTrailingSlashMiddlewareTest extends TestCase callable $assertions, ): void { $arg = compose($assertions, const_function(true)); + $this->requestHandler->expects($this->once())->method('handle')->with($this->callback($arg))->willReturn( + new Response(), + ); - $this->requestHandler->handle(Argument::that($arg))->willReturn(new Response()); - $this->middleware($trailingSlashEnabled)->process($inputRequest, $this->requestHandler->reveal()); + $this->middleware($trailingSlashEnabled)->process($inputRequest, $this->requestHandler); } public function provideRequests(): iterable From 1fbcea7a06d42c17f4ed92cb1b0c3aa0c6cc113b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 17:53:09 +0200 Subject: [PATCH 075/182] Migrated ExtraPathRedirectMiddlewareTest to use PHPUnit mocks --- .../ExtraPathRedirectMiddlewareTest.php | 111 ++++++++---------- 1 file changed, 51 insertions(+), 60 deletions(-) diff --git a/module/Core/test/ShortUrl/Middleware/ExtraPathRedirectMiddlewareTest.php b/module/Core/test/ShortUrl/Middleware/ExtraPathRedirectMiddlewareTest.php index 3367e4b2..80491132 100644 --- a/module/Core/test/ShortUrl/Middleware/ExtraPathRedirectMiddlewareTest.php +++ b/module/Core/test/ShortUrl/Middleware/ExtraPathRedirectMiddlewareTest.php @@ -9,10 +9,8 @@ use Laminas\Diactoros\ServerRequestFactory; use Laminas\Diactoros\Uri; use Mezzio\Router\Route; use Mezzio\Router\RouteResult; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -32,22 +30,20 @@ use function str_starts_with; class ExtraPathRedirectMiddlewareTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $resolver; - private ObjectProphecy $requestTracker; - private ObjectProphecy $redirectionBuilder; - private ObjectProphecy $redirectResponseHelper; - private ObjectProphecy $handler; + private MockObject $resolver; + private MockObject $requestTracker; + private MockObject $redirectionBuilder; + private MockObject $redirectResponseHelper; + private MockObject $handler; protected function setUp(): void { - $this->resolver = $this->prophesize(ShortUrlResolverInterface::class); - $this->requestTracker = $this->prophesize(RequestTrackerInterface::class); - $this->redirectionBuilder = $this->prophesize(ShortUrlRedirectionBuilderInterface::class); - $this->redirectResponseHelper = $this->prophesize(RedirectResponseHelperInterface::class); - $this->handler = $this->prophesize(RequestHandlerInterface::class); - $this->handler->handle(Argument::cetera())->willReturn(new RedirectResponse('')); + $this->resolver = $this->createMock(ShortUrlResolverInterface::class); + $this->requestTracker = $this->createMock(RequestTrackerInterface::class); + $this->redirectionBuilder = $this->createMock(ShortUrlRedirectionBuilderInterface::class); + $this->redirectResponseHelper = $this->createMock(RedirectResponseHelperInterface::class); + $this->handler = $this->createMock(RequestHandlerInterface::class); + $this->handler->method('handle')->willReturn(new RedirectResponse('')); } /** @@ -63,14 +59,13 @@ class ExtraPathRedirectMiddlewareTest extends TestCase appendExtraPath: $appendExtraPath, multiSegmentSlugsEnabled: $multiSegmentEnabled, ); + $this->resolver->expects($this->never())->method('resolveEnabledShortUrl'); + $this->requestTracker->expects($this->never())->method('trackIfApplicable'); + $this->redirectionBuilder->expects($this->never())->method('buildShortUrlRedirect'); + $this->redirectResponseHelper->expects($this->never())->method('buildRedirectResponse'); + $this->handler->expects($this->once())->method('handle'); - $this->middleware($options)->process($request, $this->handler->reveal()); - - $this->handler->handle($request)->shouldHaveBeenCalledOnce(); - $this->resolver->resolveEnabledShortUrl(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->requestTracker->trackIfApplicable(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->redirectionBuilder->buildShortUrlRedirect(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->redirectResponseHelper->buildRedirectResponse(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->middleware($options)->process($request, $this->handler); } public function provideNonRedirectingRequests(): iterable @@ -89,7 +84,7 @@ class ExtraPathRedirectMiddlewareTest extends TestCase RouteResult::class, RouteResult::fromRoute(new Route( '/foo', - $this->prophesize(MiddlewareInterface::class)->reveal(), + $this->createMock(MiddlewareInterface::class), ['GET'], RedirectAction::class, )), @@ -115,22 +110,20 @@ class ExtraPathRedirectMiddlewareTest extends TestCase ): void { $options = new UrlShortenerOptions(appendExtraPath: true, multiSegmentSlugsEnabled: $multiSegmentEnabled); - $type = $this->prophesize(NotFoundType::class); - $type->isRegularNotFound()->willReturn(true); - $type->isInvalidShortUrl()->willReturn(true); - $request = ServerRequestFactory::fromGlobals()->withAttribute(NotFoundType::class, $type->reveal()) + $type = $this->createMock(NotFoundType::class); + $type->method('isRegularNotFound')->willReturn(true); + $type->method('isInvalidShortUrl')->willReturn(true); + $request = ServerRequestFactory::fromGlobals()->withAttribute(NotFoundType::class, $type) ->withUri(new Uri('/shortCode/bar/baz')); - $resolve = $this->resolver->resolveEnabledShortUrl( - Argument::that(fn (ShortUrlIdentifier $identifier) => str_starts_with($identifier->shortCode, 'shortCode')), - )->willThrow(ShortUrlNotFoundException::class); + $this->resolver->expects($this->exactly($expectedResolveCalls))->method('resolveEnabledShortUrl')->with( + $this->callback(fn (ShortUrlIdentifier $id) => str_starts_with($id->shortCode, 'shortCode')), + )->willThrowException(ShortUrlNotFoundException::fromNotFound(ShortUrlIdentifier::fromShortCodeAndDomain(''))); + $this->requestTracker->expects($this->never())->method('trackIfApplicable'); + $this->redirectionBuilder->expects($this->never())->method('buildShortUrlRedirect'); + $this->redirectResponseHelper->expects($this->never())->method('buildRedirectResponse'); - $this->middleware($options)->process($request, $this->handler->reveal()); - - $resolve->shouldHaveBeenCalledTimes($expectedResolveCalls); - $this->requestTracker->trackIfApplicable(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->redirectionBuilder->buildShortUrlRedirect(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->redirectResponseHelper->buildRedirectResponse(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->middleware($options)->process($request, $this->handler); } /** @@ -144,18 +137,17 @@ class ExtraPathRedirectMiddlewareTest extends TestCase ): void { $options = new UrlShortenerOptions(appendExtraPath: true, multiSegmentSlugsEnabled: $multiSegmentEnabled); - $type = $this->prophesize(NotFoundType::class); - $type->isRegularNotFound()->willReturn(true); - $type->isInvalidShortUrl()->willReturn(true); - $request = ServerRequestFactory::fromGlobals()->withAttribute(NotFoundType::class, $type->reveal()) + $type = $this->createMock(NotFoundType::class); + $type->method('isRegularNotFound')->willReturn(true); + $type->method('isInvalidShortUrl')->willReturn(true); + $request = ServerRequestFactory::fromGlobals()->withAttribute(NotFoundType::class, $type) ->withUri(new Uri('https://doma.in/shortCode/bar/baz')); $shortUrl = ShortUrl::withLongUrl(''); - $identifier = Argument::that( - fn (ShortUrlIdentifier $identifier) => str_starts_with($identifier->shortCode, 'shortCode'), - ); $currentIteration = 1; - $resolve = $this->resolver->resolveEnabledShortUrl($identifier)->will( + $this->resolver->expects($this->exactly($expectedResolveCalls))->method('resolveEnabledShortUrl')->with( + $this->callback(fn (ShortUrlIdentifier $id) => str_starts_with($id->shortCode, 'shortCode')), + )->willReturnCallback( function () use ($shortUrl, &$currentIteration, $expectedResolveCalls): ShortUrl { if ($expectedResolveCalls === $currentIteration) { return $shortUrl; @@ -165,18 +157,17 @@ class ExtraPathRedirectMiddlewareTest extends TestCase throw ShortUrlNotFoundException::fromNotFound(ShortUrlIdentifier::fromShortUrl($shortUrl)); }, ); - $buildLongUrl = $this->redirectionBuilder->buildShortUrlRedirect($shortUrl, [], $expectedExtraPath) - ->willReturn('the_built_long_url'); - $buildResp = $this->redirectResponseHelper->buildRedirectResponse('the_built_long_url')->willReturn( - new RedirectResponse(''), - ); + $this->redirectionBuilder->expects($this->once())->method('buildShortUrlRedirect')->with( + $shortUrl, + [], + $expectedExtraPath, + )->willReturn('the_built_long_url'); + $this->redirectResponseHelper->expects($this->once())->method('buildRedirectResponse')->with( + 'the_built_long_url', + )->willReturn(new RedirectResponse('')); + $this->requestTracker->expects($this->once())->method('trackIfApplicable')->with($shortUrl, $request); - $this->middleware($options)->process($request, $this->handler->reveal()); - - $resolve->shouldHaveBeenCalledTimes($expectedResolveCalls); - $buildLongUrl->shouldHaveBeenCalledOnce(); - $buildResp->shouldHaveBeenCalledOnce(); - $this->requestTracker->trackIfApplicable($shortUrl, $request)->shouldHaveBeenCalledOnce(); + $this->middleware($options)->process($request, $this->handler); } public function provideResolves(): iterable @@ -188,10 +179,10 @@ class ExtraPathRedirectMiddlewareTest extends TestCase private function middleware(?UrlShortenerOptions $options = null): ExtraPathRedirectMiddleware { return new ExtraPathRedirectMiddleware( - $this->resolver->reveal(), - $this->requestTracker->reveal(), - $this->redirectionBuilder->reveal(), - $this->redirectResponseHelper->reveal(), + $this->resolver, + $this->requestTracker, + $this->redirectionBuilder, + $this->redirectResponseHelper, $options ?? new UrlShortenerOptions(appendExtraPath: true), ); } From 6a2227efc54784bf237fadb21e22d6790b2bdd7a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 18:15:57 +0200 Subject: [PATCH 076/182] Removed all uinnecessary usages of equalsTo param constraint --- module/CLI/test/ApiKey/RoleResolverTest.php | 8 +- .../Command/Api/DisableKeyCommandTest.php | 8 +- .../test/Command/Api/ListKeysCommandTest.php | 4 +- .../Command/Db/CreateDatabaseCommandTest.php | 17 ++-- .../Command/Db/MigrateDatabaseCommandTest.php | 15 ++-- .../Domain/DomainRedirectsCommandTest.php | 50 ++++++------ .../Domain/GetDomainVisitsCommandTest.php | 4 +- .../ShortUrl/CreateShortUrlCommandTest.php | 4 +- .../ShortUrl/DeleteShortUrlCommandTest.php | 8 +- .../ShortUrl/GetShortUrlVisitsCommandTest.php | 14 ++-- .../ShortUrl/ListShortUrlsCommandTest.php | 54 ++++++------- .../ShortUrl/ResolveUrlCommandTest.php | 8 +- .../Command/Tag/DeleteTagsCommandTest.php | 2 +- .../Command/Tag/GetTagVisitsCommandTest.php | 9 +-- .../test/Command/Tag/RenameTagCommandTest.php | 4 +- .../Visit/GetNonOrphanVisitsCommandTest.php | 4 +- module/CLI/test/Util/ProcessRunnerTest.php | 2 +- module/CLI/test/Util/ShlinkTableTest.php | 14 +--- module/Core/test/Action/PixelActionTest.php | 2 +- module/Core/test/Action/QrCodeActionTest.php | 10 +-- .../Core/test/Action/RedirectActionTest.php | 6 +- .../Config/NotFoundRedirectResolverTest.php | 6 +- .../Core/test/Crawling/CrawlingHelperTest.php | 4 +- module/Core/test/Domain/DomainServiceTest.php | 46 +++-------- .../NotFoundRedirectHandlerTest.php | 8 +- .../NotFoundTrackerMiddlewareTest.php | 6 +- ...DbConnectionEventListenerDelegatorTest.php | 2 +- .../LocateUnlocatedVisitsTest.php | 8 +- .../test/EventDispatcher/LocateVisitTest.php | 78 +++++++------------ .../NotifyNewShortUrlToMercureTest.php | 38 ++++----- .../Mercure/NotifyVisitToMercureTest.php | 62 ++++++--------- .../NotifyVisitToWebHooksTest.php | 25 +++--- .../NotifyNewShortUrlToRabbitMqTest.php | 33 ++++---- .../RabbitMq/NotifyVisitToRabbitMqTest.php | 30 +++---- .../NotifyNewShortUrlToRedisTest.php | 15 ++-- .../RedisPubSub/NotifyVisitToRedisTest.php | 11 ++- .../EventDispatcher/UpdateGeoLiteDbTest.php | 10 +-- .../Importer/ImportedLinksProcessorTest.php | 2 +- .../Helper/ShortCodeUniquenessHelperTest.php | 14 ++-- .../ShortUrlTitleResolutionHelperTest.php | 4 +- .../test/ShortUrl/ShortUrlResolverTest.php | 30 +++---- .../test/ShortUrl/ShortUrlServiceTest.php | 8 +- .../Core/test/ShortUrl/UrlShortenerTest.php | 8 +- 43 files changed, 272 insertions(+), 423 deletions(-) diff --git a/module/CLI/test/ApiKey/RoleResolverTest.php b/module/CLI/test/ApiKey/RoleResolverTest.php index 245a7106..5195d46d 100644 --- a/module/CLI/test/ApiKey/RoleResolverTest.php +++ b/module/CLI/test/ApiKey/RoleResolverTest.php @@ -36,11 +36,9 @@ class RoleResolverTest extends TestCase array $expectedRoles, int $expectedDomainCalls, ): void { - $this->domainService - ->expects($this->exactly($expectedDomainCalls)) - ->method('getOrCreate') - ->with($this->equalTo('example.com')) - ->willReturn(Domain::withAuthority('example.com')->setId('1')); + $this->domainService->expects($this->exactly($expectedDomainCalls))->method('getOrCreate')->with( + 'example.com', + )->willReturn(Domain::withAuthority('example.com')->setId('1')); $result = $this->resolver->determineRoles($input); diff --git a/module/CLI/test/Command/Api/DisableKeyCommandTest.php b/module/CLI/test/Command/Api/DisableKeyCommandTest.php index 78f5bfa6..864ef0a4 100644 --- a/module/CLI/test/Command/Api/DisableKeyCommandTest.php +++ b/module/CLI/test/Command/Api/DisableKeyCommandTest.php @@ -29,7 +29,7 @@ class DisableKeyCommandTest extends TestCase public function providedApiKeyIsDisabled(): void { $apiKey = 'abcd1234'; - $this->apiKeyService->expects($this->once())->method('disable')->with($this->equalTo($apiKey)); + $this->apiKeyService->expects($this->once())->method('disable')->with($apiKey); $this->commandTester->execute([ 'apiKey' => $apiKey, @@ -44,9 +44,9 @@ class DisableKeyCommandTest extends TestCase { $apiKey = 'abcd1234'; $expectedMessage = 'API key "abcd1234" does not exist.'; - $this->apiKeyService->expects($this->once())->method('disable')->with( - $this->equalTo($apiKey), - )->willThrowException(new InvalidArgumentException($expectedMessage)); + $this->apiKeyService->expects($this->once())->method('disable')->with($apiKey)->willThrowException( + new InvalidArgumentException($expectedMessage), + ); $this->commandTester->execute([ 'apiKey' => $apiKey, diff --git a/module/CLI/test/Command/Api/ListKeysCommandTest.php b/module/CLI/test/Command/Api/ListKeysCommandTest.php index 39da39a6..2983367b 100644 --- a/module/CLI/test/Command/Api/ListKeysCommandTest.php +++ b/module/CLI/test/Command/Api/ListKeysCommandTest.php @@ -35,9 +35,7 @@ class ListKeysCommandTest extends TestCase */ public function returnsExpectedOutput(array $keys, bool $enabledOnly, string $expected): void { - $this->apiKeyService->expects($this->once())->method('listKeys')->with( - $this->equalTo($enabledOnly), - )->willReturn($keys); + $this->apiKeyService->expects($this->once())->method('listKeys')->with($enabledOnly)->willReturn($keys); $this->commandTester->execute(['--enabled-only' => $enabledOnly]); $output = $this->commandTester->getDisplay(); diff --git a/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php b/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php index b335e19e..4462c949 100644 --- a/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php +++ b/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php @@ -87,7 +87,7 @@ class CreateDatabaseCommandTest extends TestCase $shlinkDatabase = 'shlink_database'; $this->regularConn->expects($this->once())->method('getParams')->willReturn(['dbname' => $shlinkDatabase]); $this->schemaManager->expects($this->once())->method('listDatabases')->willReturn(['foo', 'bar']); - $this->schemaManager->expects($this->once())->method('createDatabase')->with($this->equalTo($shlinkDatabase)); + $this->schemaManager->expects($this->once())->method('createDatabase')->with($shlinkDatabase); $this->schemaManager->expects($this->once())->method('listTableNames')->willReturn( ['foo_table', 'bar_table', MIGRATIONS_TABLE], ); @@ -109,15 +109,12 @@ class CreateDatabaseCommandTest extends TestCase ); $this->schemaManager->expects($this->never())->method('createDatabase'); $this->schemaManager->expects($this->once())->method('listTableNames')->willReturn($tables); - $this->processHelper->expects($this->once())->method('run')->with( - $this->isInstanceOf(OutputInterface::class), - $this->equalTo([ - '/usr/local/bin/php', - CreateDatabaseCommand::DOCTRINE_SCRIPT, - CreateDatabaseCommand::DOCTRINE_CREATE_SCHEMA_COMMAND, - '--no-interaction', - ]), - ); + $this->processHelper->expects($this->once())->method('run')->with($this->isInstanceOf(OutputInterface::class), [ + '/usr/local/bin/php', + CreateDatabaseCommand::DOCTRINE_SCRIPT, + CreateDatabaseCommand::DOCTRINE_CREATE_SCHEMA_COMMAND, + '--no-interaction', + ]); $this->driver->method('getDatabasePlatform')->willReturn($this->createMock(AbstractPlatform::class)); $this->commandTester->execute([]); diff --git a/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php b/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php index 6390d044..5132e892 100644 --- a/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php +++ b/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php @@ -41,15 +41,12 @@ class MigrateDatabaseCommandTest extends TestCase /** @test */ public function migrationsCommandIsRunWithProperVerbosity(): void { - $this->processHelper->expects($this->once())->method('run')->with( - $this->isInstanceOf(OutputInterface::class), - $this->equalTo([ - '/usr/local/bin/php', - MigrateDatabaseCommand::DOCTRINE_MIGRATIONS_SCRIPT, - MigrateDatabaseCommand::DOCTRINE_MIGRATE_COMMAND, - '--no-interaction', - ]), - ); + $this->processHelper->expects($this->once())->method('run')->with($this->isInstanceOf(OutputInterface::class), [ + '/usr/local/bin/php', + MigrateDatabaseCommand::DOCTRINE_MIGRATIONS_SCRIPT, + MigrateDatabaseCommand::DOCTRINE_MIGRATE_COMMAND, + '--no-interaction', + ]); $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); diff --git a/module/CLI/test/Command/Domain/DomainRedirectsCommandTest.php b/module/CLI/test/Command/Domain/DomainRedirectsCommandTest.php index 5d9f5bd4..ee3320a3 100644 --- a/module/CLI/test/Command/Domain/DomainRedirectsCommandTest.php +++ b/module/CLI/test/Command/Domain/DomainRedirectsCommandTest.php @@ -37,12 +37,12 @@ class DomainRedirectsCommandTest extends TestCase public function onlyPlainQuestionsAreAskedForNewDomainsAndDomainsWithNoRedirects(?Domain $domain): void { $domainAuthority = 'my-domain.com'; - $this->domainService->expects($this->once())->method('findByAuthority')->with( - $this->equalTo($domainAuthority), - )->willReturn($domain); + $this->domainService->expects($this->once())->method('findByAuthority')->with($domainAuthority)->willReturn( + $domain, + ); $this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with( - $this->equalTo($domainAuthority), - $this->equalTo(NotFoundRedirects::withRedirects('foo.com', null, 'baz.com')), + $domainAuthority, + NotFoundRedirects::withRedirects('foo.com', null, 'baz.com'), )->willReturn(Domain::withAuthority('')); $this->domainService->expects($this->never())->method('listDomains'); @@ -73,12 +73,12 @@ class DomainRedirectsCommandTest extends TestCase $domain = Domain::withAuthority($domainAuthority); $domain->configureNotFoundRedirects(NotFoundRedirects::withRedirects('foo.com', 'bar.com', 'baz.com')); - $this->domainService->expects($this->once())->method('findByAuthority')->with( - $this->equalTo($domainAuthority), - )->willReturn($domain); + $this->domainService->expects($this->once())->method('findByAuthority')->with($domainAuthority)->willReturn( + $domain, + ); $this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with( - $this->equalTo($domainAuthority), - $this->equalTo(NotFoundRedirects::withRedirects(null, 'edited.com', 'baz.com')), + $domainAuthority, + NotFoundRedirects::withRedirects(null, 'edited.com', 'baz.com'), )->willReturn($domain); $this->domainService->expects($this->never())->method('listDomains'); @@ -102,12 +102,12 @@ class DomainRedirectsCommandTest extends TestCase $domain = Domain::withAuthority($domainAuthority); $this->domainService->expects($this->once())->method('listDomains')->with()->willReturn([]); - $this->domainService->expects($this->once())->method('findByAuthority')->with( - $this->equalTo($domainAuthority), - )->willReturn($domain); + $this->domainService->expects($this->once())->method('findByAuthority')->with($domainAuthority)->willReturn( + $domain, + ); $this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with( - $this->equalTo($domainAuthority), - $this->equalTo(NotFoundRedirects::withoutRedirects()), + $domainAuthority, + NotFoundRedirects::withoutRedirects(), )->willReturn($domain); $this->commandTester->setInputs([$domainAuthority, '', '', '']); @@ -128,12 +128,12 @@ class DomainRedirectsCommandTest extends TestCase DomainItem::forNonDefaultDomain(Domain::withAuthority('existing-one.com')), DomainItem::forNonDefaultDomain(Domain::withAuthority($domainAuthority)), ]); - $this->domainService->expects($this->once())->method('findByAuthority')->with( - $this->equalTo($domainAuthority), - )->willReturn($domain); + $this->domainService->expects($this->once())->method('findByAuthority')->with($domainAuthority)->willReturn( + $domain, + ); $this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with( - $this->equalTo($domainAuthority), - $this->equalTo(NotFoundRedirects::withoutRedirects()), + $domainAuthority, + NotFoundRedirects::withoutRedirects(), )->willReturn($domain); $this->commandTester->setInputs(['1', '', '', '']); @@ -157,12 +157,12 @@ class DomainRedirectsCommandTest extends TestCase DomainItem::forNonDefaultDomain(Domain::withAuthority('existing-one.com')), DomainItem::forNonDefaultDomain(Domain::withAuthority('existing-two.com')), ]); - $this->domainService->expects($this->once())->method('findByAuthority')->with( - $this->equalTo($domainAuthority), - )->willReturn($domain); + $this->domainService->expects($this->once())->method('findByAuthority')->with($domainAuthority)->willReturn( + $domain, + ); $this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with( - $this->equalTo($domainAuthority), - $this->equalTo(NotFoundRedirects::withoutRedirects()), + $domainAuthority, + NotFoundRedirects::withoutRedirects(), )->willReturn($domain); $this->commandTester->setInputs(['2', $domainAuthority, '', '', '']); diff --git a/module/CLI/test/Command/Domain/GetDomainVisitsCommandTest.php b/module/CLI/test/Command/Domain/GetDomainVisitsCommandTest.php index a50138b6..7fffbcc4 100644 --- a/module/CLI/test/Command/Domain/GetDomainVisitsCommandTest.php +++ b/module/CLI/test/Command/Domain/GetDomainVisitsCommandTest.php @@ -46,10 +46,10 @@ class GetDomainVisitsCommandTest extends TestCase ); $domain = 'doma.in'; $this->visitsHelper->expects($this->once())->method('visitsForDomain')->with( - $this->equalTo($domain), + $domain, $this->anything(), )->willReturn(new Paginator(new ArrayAdapter([$visit]))); - $this->stringifier->expects($this->once())->method('stringify')->with($this->equalTo($shortUrl))->willReturn( + $this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn( 'the_short_url', ); diff --git a/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php index b1bbad77..79ce3b5f 100644 --- a/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php @@ -47,7 +47,7 @@ class CreateShortUrlCommandTest extends TestCase { $shortUrl = ShortUrl::createEmpty(); $this->urlShortener->expects($this->once())->method('shorten')->withAnyParameters()->willReturn($shortUrl); - $this->stringifier->expects($this->once())->method('stringify')->with($this->equalTo($shortUrl))->willReturn( + $this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn( 'stringified_short_url', ); @@ -103,7 +103,7 @@ class CreateShortUrlCommandTest extends TestCase return true; }), )->willReturn($shortUrl); - $this->stringifier->expects($this->once())->method('stringify')->with($this->equalTo($shortUrl))->willReturn( + $this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn( 'stringified_short_url', ); diff --git a/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php index fce9197a..537ed51b 100644 --- a/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php @@ -35,7 +35,7 @@ class DeleteShortUrlCommandTest extends TestCase { $shortCode = 'abc123'; $this->service->expects($this->once())->method('deleteByShortCode')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), $this->isFalse(), ); @@ -54,7 +54,7 @@ class DeleteShortUrlCommandTest extends TestCase $shortCode = 'abc123'; $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); $this->service->expects($this->once())->method('deleteByShortCode')->with( - $this->equalTo($identifier), + $identifier, $this->isFalse(), )->willThrowException(Exception\ShortUrlNotFoundException::fromNotFound($identifier)); @@ -76,7 +76,7 @@ class DeleteShortUrlCommandTest extends TestCase $shortCode = 'abc123'; $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); $this->service->expects($this->exactly($expectedDeleteCalls))->method('deleteByShortCode')->with( - $this->equalTo($identifier), + $identifier, $this->isType('bool'), )->willReturnCallback(function ($_, bool $ignoreThreshold) use ($shortCode): void { if (!$ignoreThreshold) { @@ -110,7 +110,7 @@ class DeleteShortUrlCommandTest extends TestCase { $shortCode = 'abc123'; $this->service->expects($this->once())->method('deleteByShortCode')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), $this->isFalse(), )->willThrowException(Exception\DeleteShortUrlException::fromVisitsThreshold( 10, diff --git a/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php b/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php index f271e26b..dd2f4ddc 100644 --- a/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php @@ -44,8 +44,8 @@ class GetShortUrlVisitsCommandTest extends TestCase { $shortCode = 'abc123'; $this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), - $this->equalTo(new VisitsParams(DateRange::allTime())), + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), + new VisitsParams(DateRange::allTime()), )->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->execute(['shortCode' => $shortCode]); @@ -58,8 +58,8 @@ class GetShortUrlVisitsCommandTest extends TestCase $startDate = '2016-01-01'; $endDate = '2016-02-01'; $this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), - $this->equalTo(new VisitsParams(buildDateRange(Chronos::parse($startDate), Chronos::parse($endDate)))), + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), + new VisitsParams(buildDateRange(Chronos::parse($startDate), Chronos::parse($endDate))), )->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->execute([ @@ -75,8 +75,8 @@ class GetShortUrlVisitsCommandTest extends TestCase $shortCode = 'abc123'; $startDate = 'foo'; $this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), - $this->equalTo(new VisitsParams(DateRange::allTime())), + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), + new VisitsParams(DateRange::allTime()), )->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->execute([ @@ -99,7 +99,7 @@ class GetShortUrlVisitsCommandTest extends TestCase ); $shortCode = 'abc123'; $this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), $this->anything(), )->willReturn(new Paginator(new ArrayAdapter([$visit]))); diff --git a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php index b1356e11..208e595a 100644 --- a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php @@ -73,7 +73,7 @@ class ListShortUrlsCommandTest extends TestCase } $this->shortUrlService->expects($this->once())->method('listShortUrls')->with( - $this->equalTo(ShortUrlsParams::emptyInstance()), + ShortUrlsParams::emptyInstance(), )->willReturn(new Paginator(new ArrayAdapter($data))); $this->commandTester->setInputs(['n']); @@ -94,7 +94,7 @@ class ListShortUrlsCommandTest extends TestCase { $page = 5; $this->shortUrlService->expects($this->once())->method('listShortUrls')->with( - $this->equalTo(ShortUrlsParams::fromRawData(['page' => $page])), + ShortUrlsParams::fromRawData(['page' => $page]), )->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->setInputs(['y']); @@ -112,7 +112,7 @@ class ListShortUrlsCommandTest extends TestCase ApiKey $apiKey, ): void { $this->shortUrlService->expects($this->once())->method('listShortUrls')->with( - $this->equalTo(ShortUrlsParams::emptyInstance()), + ShortUrlsParams::emptyInstance(), )->willReturn(new Paginator(new ArrayAdapter([ ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo.com', @@ -187,16 +187,14 @@ class ListShortUrlsCommandTest extends TestCase ?string $startDate = null, ?string $endDate = null, ): void { - $this->shortUrlService->expects($this->once())->method('listShortUrls')->with( - $this->equalTo(ShortUrlsParams::fromRawData([ - 'page' => $page, - 'searchTerm' => $searchTerm, - 'tags' => $tags, - 'tagsMode' => $tagsMode, - 'startDate' => $startDate !== null ? Chronos::parse($startDate)->toAtomString() : null, - 'endDate' => $endDate !== null ? Chronos::parse($endDate)->toAtomString() : null, - ])), - )->willReturn(new Paginator(new ArrayAdapter([]))); + $this->shortUrlService->expects($this->once())->method('listShortUrls')->with(ShortUrlsParams::fromRawData([ + 'page' => $page, + 'searchTerm' => $searchTerm, + 'tags' => $tags, + 'tagsMode' => $tagsMode, + 'startDate' => $startDate !== null ? Chronos::parse($startDate)->toAtomString() : null, + 'endDate' => $endDate !== null ? Chronos::parse($endDate)->toAtomString() : null, + ]))->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->setInputs(['n']); $this->commandTester->execute($commandArgs); @@ -249,11 +247,9 @@ class ListShortUrlsCommandTest extends TestCase */ public function orderByIsProperlyComputed(array $commandArgs, ?string $expectedOrderBy): void { - $this->shortUrlService->expects($this->once())->method('listShortUrls')->with( - $this->equalTo(ShortUrlsParams::fromRawData([ - 'orderBy' => $expectedOrderBy, - ])), - )->willReturn(new Paginator(new ArrayAdapter([]))); + $this->shortUrlService->expects($this->once())->method('listShortUrls')->with(ShortUrlsParams::fromRawData([ + 'orderBy' => $expectedOrderBy, + ]))->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->setInputs(['n']); $this->commandTester->execute($commandArgs); @@ -271,18 +267,16 @@ class ListShortUrlsCommandTest extends TestCase /** @test */ public function requestingAllElementsWillSetItemsPerPage(): void { - $this->shortUrlService->expects($this->once())->method('listShortUrls')->with( - $this->equalTo(ShortUrlsParams::fromRawData([ - 'page' => 1, - 'searchTerm' => null, - 'tags' => [], - 'tagsMode' => TagsMode::ANY->value, - 'startDate' => null, - 'endDate' => null, - 'orderBy' => null, - 'itemsPerPage' => Paginator::ALL_ITEMS, - ])), - )->willReturn(new Paginator(new ArrayAdapter([]))); + $this->shortUrlService->expects($this->once())->method('listShortUrls')->with(ShortUrlsParams::fromRawData([ + 'page' => 1, + 'searchTerm' => null, + 'tags' => [], + 'tagsMode' => TagsMode::ANY->value, + 'startDate' => null, + 'endDate' => null, + 'orderBy' => null, + 'itemsPerPage' => Paginator::ALL_ITEMS, + ]))->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->execute(['--all' => true]); } diff --git a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php index 609a921a..87962355 100644 --- a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php @@ -38,7 +38,7 @@ class ResolveUrlCommandTest extends TestCase $expectedUrl = 'http://domain.com/foo/bar'; $shortUrl = ShortUrl::withLongUrl($expectedUrl); $this->urlResolver->expects($this->once())->method('resolveShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), )->willReturn($shortUrl); $this->commandTester->execute(['shortCode' => $shortCode]); @@ -52,9 +52,9 @@ class ResolveUrlCommandTest extends TestCase $identifier = ShortUrlIdentifier::fromShortCodeAndDomain('abc123'); $shortCode = $identifier->shortCode; - $this->urlResolver->expects($this->once())->method('resolveShortUrl')->with( - $this->equalTo($identifier), - )->willThrowException(ShortUrlNotFoundException::fromNotFound($identifier)); + $this->urlResolver->expects($this->once())->method('resolveShortUrl')->with($identifier)->willThrowException( + ShortUrlNotFoundException::fromNotFound($identifier), + ); $this->commandTester->execute(['shortCode' => $shortCode]); $output = $this->commandTester->getDisplay(); diff --git a/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php b/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php index fbaa248f..74c02dde 100644 --- a/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php +++ b/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php @@ -37,7 +37,7 @@ class DeleteTagsCommandTest extends TestCase public function serviceIsInvokedOnSuccess(): void { $tagNames = ['foo', 'bar']; - $this->tagService->expects($this->once())->method('deleteTags')->with($this->equalTo($tagNames)); + $this->tagService->expects($this->once())->method('deleteTags')->with($tagNames); $this->commandTester->execute([ '--name' => $tagNames, diff --git a/module/CLI/test/Command/Tag/GetTagVisitsCommandTest.php b/module/CLI/test/Command/Tag/GetTagVisitsCommandTest.php index f2832541..c10c9d8d 100644 --- a/module/CLI/test/Command/Tag/GetTagVisitsCommandTest.php +++ b/module/CLI/test/Command/Tag/GetTagVisitsCommandTest.php @@ -45,13 +45,10 @@ class GetTagVisitsCommandTest extends TestCase VisitLocation::fromGeolocation(new Location('', 'Spain', '', 'Madrid', 0, 0, '')), ); $tag = 'abc123'; - $this->visitsHelper->expects($this->once())->method('visitsForTag')->with( - $this->equalTo($tag), - $this->anything(), - )->willReturn(new Paginator(new ArrayAdapter([$visit]))); - $this->stringifier->expects($this->once())->method('stringify')->with($this->equalTo($shortUrl))->willReturn( - 'the_short_url', + $this->visitsHelper->expects($this->once())->method('visitsForTag')->with($tag, $this->anything())->willReturn( + new Paginator(new ArrayAdapter([$visit])), ); + $this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn('the_short_url'); $this->commandTester->execute(['tag' => $tag]); $output = $this->commandTester->getDisplay(); diff --git a/module/CLI/test/Command/Tag/RenameTagCommandTest.php b/module/CLI/test/Command/Tag/RenameTagCommandTest.php index a3429e40..4a752de9 100644 --- a/module/CLI/test/Command/Tag/RenameTagCommandTest.php +++ b/module/CLI/test/Command/Tag/RenameTagCommandTest.php @@ -33,7 +33,7 @@ class RenameTagCommandTest extends TestCase $oldName = 'foo'; $newName = 'bar'; $this->tagService->expects($this->once())->method('renameTag')->with( - $this->equalTo(TagRenaming::fromNames($oldName, $newName)), + TagRenaming::fromNames($oldName, $newName), )->willThrowException(TagNotFoundException::fromTag('foo')); $this->commandTester->execute([ @@ -51,7 +51,7 @@ class RenameTagCommandTest extends TestCase $oldName = 'foo'; $newName = 'bar'; $this->tagService->expects($this->once())->method('renameTag')->with( - $this->equalTo(TagRenaming::fromNames($oldName, $newName)), + TagRenaming::fromNames($oldName, $newName), )->willReturn(new Tag($newName)); $this->commandTester->execute([ diff --git a/module/CLI/test/Command/Visit/GetNonOrphanVisitsCommandTest.php b/module/CLI/test/Command/Visit/GetNonOrphanVisitsCommandTest.php index 23f20da9..71008d16 100644 --- a/module/CLI/test/Command/Visit/GetNonOrphanVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/GetNonOrphanVisitsCommandTest.php @@ -47,9 +47,7 @@ class GetNonOrphanVisitsCommandTest extends TestCase $this->visitsHelper->expects($this->once())->method('nonOrphanVisits')->withAnyParameters()->willReturn( new Paginator(new ArrayAdapter([$visit])), ); - $this->stringifier->expects($this->once())->method('stringify')->with($this->equalTo($shortUrl))->willReturn( - 'the_short_url', - ); + $this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn('the_short_url'); $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); diff --git a/module/CLI/test/Util/ProcessRunnerTest.php b/module/CLI/test/Util/ProcessRunnerTest.php index 3e2e5ed0..28ff21af 100644 --- a/module/CLI/test/Util/ProcessRunnerTest.php +++ b/module/CLI/test/Util/ProcessRunnerTest.php @@ -26,7 +26,7 @@ class ProcessRunnerTest extends TestCase $this->helper = $this->createMock(ProcessHelper::class); $this->formatter = $this->createMock(DebugFormatterHelper::class); $helperSet = $this->createMock(HelperSet::class); - $helperSet->method('get')->with($this->equalTo('debug_formatter'))->willReturn($this->formatter); + $helperSet->method('get')->with('debug_formatter')->willReturn($this->formatter); $this->helper->method('getHelperSet')->with()->willReturn($helperSet); $this->process = $this->createMock(Process::class); $this->output = $this->createMock(OutputInterface::class); diff --git a/module/CLI/test/Util/ShlinkTableTest.php b/module/CLI/test/Util/ShlinkTableTest.php index 6b7e00a3..01e1be1f 100644 --- a/module/CLI/test/Util/ShlinkTableTest.php +++ b/module/CLI/test/Util/ShlinkTableTest.php @@ -34,16 +34,10 @@ class ShlinkTableTest extends TestCase $this->baseTable->expects($this->once())->method('setStyle')->with( $this->isInstanceOf(TableStyle::class), )->willReturnSelf(); - $this->baseTable->expects($this->once())->method('setHeaders')->with( - $this->equalTo($headers), - )->willReturnSelf(); - $this->baseTable->expects($this->once())->method('setRows')->with($this->equalTo($rows))->willReturnSelf(); - $this->baseTable->expects($this->once())->method('setFooterTitle')->with( - $this->equalTo($footerTitle), - )->willReturnSelf(); - $this->baseTable->expects($this->once())->method('setHeaderTitle')->with( - $this->equalTo($headerTitle), - )->willReturnSelf(); + $this->baseTable->expects($this->once())->method('setHeaders')->with($headers)->willReturnSelf(); + $this->baseTable->expects($this->once())->method('setRows')->with($rows)->willReturnSelf(); + $this->baseTable->expects($this->once())->method('setFooterTitle')->with($footerTitle)->willReturnSelf(); + $this->baseTable->expects($this->once())->method('setHeaderTitle')->with($headerTitle)->willReturnSelf(); $this->baseTable->expects($this->once())->method('render')->with()->willReturnSelf(); $this->shlinkTable->render($headers, $rows, $footerTitle, $headerTitle); diff --git a/module/Core/test/Action/PixelActionTest.php b/module/Core/test/Action/PixelActionTest.php index 7a150970..e2e75144 100644 --- a/module/Core/test/Action/PixelActionTest.php +++ b/module/Core/test/Action/PixelActionTest.php @@ -34,7 +34,7 @@ class PixelActionTest extends TestCase { $shortCode = 'abc123'; $this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')), + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''), )->willReturn(ShortUrl::withLongUrl('http://domain.com/foo/bar')); $this->requestTracker->expects($this->once())->method('trackIfApplicable')->withAnyParameters(); diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index 06df1ce8..06b07ecf 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -42,7 +42,7 @@ class QrCodeActionTest extends TestCase { $shortCode = 'abc123'; $this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')), + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''), )->willThrowException(ShortUrlNotFoundException::fromNotFound(ShortUrlIdentifier::fromShortCodeAndDomain(''))); $delegate = $this->createMock(RequestHandlerInterface::class); $delegate->expects($this->once())->method('handle')->withAnyParameters()->willReturn(new Response()); @@ -55,7 +55,7 @@ class QrCodeActionTest extends TestCase { $shortCode = 'abc123'; $this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')), + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''), )->willReturn(ShortUrl::createEmpty()); $delegate = $this->createMock(RequestHandlerInterface::class); $delegate->expects($this->never())->method('handle'); @@ -77,7 +77,7 @@ class QrCodeActionTest extends TestCase ): void { $code = 'abc123'; $this->urlResolver->method('resolveEnabledShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($code, '')), + ShortUrlIdentifier::fromShortCodeAndDomain($code, ''), )->willReturn(ShortUrl::createEmpty()); $delegate = $this->createMock(RequestHandlerInterface::class); $req = (new ServerRequest())->withAttribute('shortCode', $code)->withQueryParams($query); @@ -110,7 +110,7 @@ class QrCodeActionTest extends TestCase ): void { $code = 'abc123'; $this->urlResolver->method('resolveEnabledShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($code, '')), + ShortUrlIdentifier::fromShortCodeAndDomain($code, ''), )->willReturn(ShortUrl::createEmpty()); $delegate = $this->createMock(RequestHandlerInterface::class); @@ -201,7 +201,7 @@ class QrCodeActionTest extends TestCase ->withAttribute('shortCode', $code); $this->urlResolver->method('resolveEnabledShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($code, '')), + ShortUrlIdentifier::fromShortCodeAndDomain($code, ''), )->willReturn(ShortUrl::withLongUrl('https://shlink.io')); $delegate = $this->createMock(RequestHandlerInterface::class); diff --git a/module/Core/test/Action/RedirectActionTest.php b/module/Core/test/Action/RedirectActionTest.php index 06b7ac40..de572d2c 100644 --- a/module/Core/test/Action/RedirectActionTest.php +++ b/module/Core/test/Action/RedirectActionTest.php @@ -50,12 +50,12 @@ class RedirectActionTest extends TestCase $shortCode = 'abc123'; $shortUrl = ShortUrl::withLongUrl(self::LONG_URL); $this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')), + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''), )->willReturn($shortUrl); $this->requestTracker->expects($this->once())->method('trackIfApplicable'); $expectedResp = new Response\RedirectResponse(self::LONG_URL); $this->redirectRespHelper->expects($this->once())->method('buildRedirectResponse')->with( - $this->equalTo(self::LONG_URL), + self::LONG_URL, )->willReturn($expectedResp); $request = (new ServerRequest())->withAttribute('shortCode', $shortCode); @@ -69,7 +69,7 @@ class RedirectActionTest extends TestCase { $shortCode = 'abc123'; $this->urlResolver->expects($this->once())->method('resolveEnabledShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, '')), + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''), )->willThrowException(ShortUrlNotFoundException::fromNotFound(ShortUrlIdentifier::fromShortCodeAndDomain(''))); $this->requestTracker->expects($this->never())->method('trackIfApplicable'); diff --git a/module/Core/test/Config/NotFoundRedirectResolverTest.php b/module/Core/test/Config/NotFoundRedirectResolverTest.php index 3cc9f691..7ca2bb82 100644 --- a/module/Core/test/Config/NotFoundRedirectResolverTest.php +++ b/module/Core/test/Config/NotFoundRedirectResolverTest.php @@ -43,9 +43,9 @@ class NotFoundRedirectResolverTest extends TestCase string $expectedRedirectTo, ): void { $expectedResp = new Response(); - $this->helper->expects($this->once())->method('buildRedirectResponse')->with( - $this->equalTo($expectedRedirectTo), - )->willReturn($expectedResp); + $this->helper->expects($this->once())->method('buildRedirectResponse')->with($expectedRedirectTo)->willReturn( + $expectedResp, + ); $resp = $this->resolver->resolveRedirectResponse($notFoundType, $redirectConfig, $uri); diff --git a/module/Core/test/Crawling/CrawlingHelperTest.php b/module/Core/test/Crawling/CrawlingHelperTest.php index 7667fb93..8af3396a 100644 --- a/module/Core/test/Crawling/CrawlingHelperTest.php +++ b/module/Core/test/Crawling/CrawlingHelperTest.php @@ -27,9 +27,7 @@ class CrawlingHelperTest extends TestCase { $repo = $this->createMock(ShortUrlRepositoryInterface::class); $repo->expects($this->once())->method('findCrawlableShortCodes')->willReturn([]); - $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn( - $repo, - ); + $this->em->expects($this->once())->method('getRepository')->with(ShortUrl::class)->willReturn($repo); $result = $this->helper->listCrawlableShortCodes(); foreach ($result as $shortCode) { diff --git a/module/Core/test/Domain/DomainServiceTest.php b/module/Core/test/Domain/DomainServiceTest.php index 307fbcb3..38d925f2 100644 --- a/module/Core/test/Domain/DomainServiceTest.php +++ b/module/Core/test/Domain/DomainServiceTest.php @@ -36,10 +36,8 @@ class DomainServiceTest extends TestCase public function listDomainsDelegatesIntoRepository(array $domains, array $expectedResult, ?ApiKey $apiKey): void { $repo = $this->createMock(DomainRepositoryInterface::class); - $repo->expects($this->once())->method('findDomains')->with($this->equalTo($apiKey))->willReturn($domains); - $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(Domain::class))->willReturn( - $repo, - ); + $repo->expects($this->once())->method('findDomains')->with($apiKey)->willReturn($domains); + $this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo); $result = $this->domainService->listDomains($apiKey); @@ -105,10 +103,7 @@ class DomainServiceTest extends TestCase /** @test */ public function getDomainThrowsExceptionWhenDomainIsNotFound(): void { - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Domain::class), - $this->equalTo('123'), - )->willReturn(null); + $this->em->expects($this->once())->method('find')->with(Domain::class, '123')->willReturn(null); $this->expectException(DomainNotFoundException::class); @@ -119,10 +114,7 @@ class DomainServiceTest extends TestCase public function getDomainReturnsEntityWhenFound(): void { $domain = Domain::withAuthority(''); - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Domain::class), - $this->equalTo('123'), - )->willReturn($domain); + $this->em->expects($this->once())->method('find')->with(Domain::class, '123')->willReturn($domain); $result = $this->domainService->getDomain('123'); @@ -137,15 +129,11 @@ class DomainServiceTest extends TestCase { $authority = 'example.com'; $repo = $this->createMock(DomainRepositoryInterface::class); - $repo->method('findOneByAuthority')->with($this->equalTo($authority), $this->equalTo($apiKey))->willReturn( + $repo->method('findOneByAuthority')->with($authority, $apiKey)->willReturn( $foundDomain, ); - $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(Domain::class))->willReturn( - $repo, - ); - $this->em->expects($this->once())->method('persist')->with( - $foundDomain !== null ? $this->equalTo($foundDomain) : $this->isInstanceOf(Domain::class), - ); + $this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo); + $this->em->expects($this->once())->method('persist')->with($foundDomain ?? $this->isInstanceOf(Domain::class)); $this->em->expects($this->once())->method('flush'); $result = $this->domainService->getOrCreate($authority, $apiKey); @@ -162,12 +150,8 @@ class DomainServiceTest extends TestCase $domain = Domain::withAuthority($authority)->setId('1'); $apiKey = ApiKey::fromMeta(ApiKeyMeta::withRoles(RoleDefinition::forDomain($domain))); $repo = $this->createMock(DomainRepositoryInterface::class); - $repo->method('findOneByAuthority')->with($this->equalTo($authority), $this->equalTo($apiKey))->willReturn( - null, - ); - $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(Domain::class))->willReturn( - $repo, - ); + $repo->method('findOneByAuthority')->with($authority, $apiKey)->willReturn(null); + $this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo); $this->em->expects($this->never())->method('persist'); $this->em->expects($this->never())->method('flush'); @@ -184,15 +168,9 @@ class DomainServiceTest extends TestCase { $authority = 'example.com'; $repo = $this->createMock(DomainRepositoryInterface::class); - $repo->method('findOneByAuthority')->with($this->equalTo($authority), $this->equalTo($apiKey))->willReturn( - $foundDomain, - ); - $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(Domain::class))->willReturn( - $repo, - ); - $this->em->expects($this->once())->method('persist')->with( - $foundDomain !== null ? $this->equalTo($foundDomain) : $this->isInstanceOf(Domain::class), - ); + $repo->method('findOneByAuthority')->with($authority, $apiKey)->willReturn($foundDomain); + $this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo); + $this->em->expects($this->once())->method('persist')->with($foundDomain ?? $this->isInstanceOf(Domain::class)); $this->em->expects($this->once())->method('flush'); $result = $this->domainService->configureNotFoundRedirects($authority, NotFoundRedirects::withRedirects( diff --git a/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php b/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php index 964f5da4..d9e5615e 100644 --- a/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php +++ b/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php @@ -51,9 +51,7 @@ class NotFoundRedirectHandlerTest extends TestCase $expectedResp = new Response(); $setUp($this->domainService, $this->resolver); - $this->next->expects($this->once())->method('handle')->with($this->equalTo($this->req))->willReturn( - $expectedResp, - ); + $this->next->expects($this->once())->method('handle')->with($this->req)->willReturn($expectedResp); $result = $this->middleware->process($this->req, $this->next); @@ -105,7 +103,7 @@ class NotFoundRedirectHandlerTest extends TestCase $this->domainService->expects($this->once())->method('findByAuthority')->withAnyParameters()->willReturn(null); $this->resolver->expects($this->once())->method('resolveRedirectResponse')->with( $this->isInstanceOf(NotFoundType::class), - $this->equalTo($this->redirectOptions), + $this->redirectOptions, $this->isInstanceOf(UriInterface::class), )->willReturn($expectedResp); $this->next->expects($this->never())->method('handle'); @@ -126,7 +124,7 @@ class NotFoundRedirectHandlerTest extends TestCase ); $this->resolver->expects($this->once())->method('resolveRedirectResponse')->with( $this->isInstanceOf(NotFoundType::class), - $this->equalTo($domain), + $domain, $this->isInstanceOf(UriInterface::class), )->willReturn($expectedResp); $this->next->expects($this->never())->method('handle'); diff --git a/module/Core/test/ErrorHandler/NotFoundTrackerMiddlewareTest.php b/module/Core/test/ErrorHandler/NotFoundTrackerMiddlewareTest.php index 823a7546..f475d317 100644 --- a/module/Core/test/ErrorHandler/NotFoundTrackerMiddlewareTest.php +++ b/module/Core/test/ErrorHandler/NotFoundTrackerMiddlewareTest.php @@ -35,10 +35,8 @@ class NotFoundTrackerMiddlewareTest extends TestCase /** @test */ public function delegatesIntoRequestTracker(): void { - $this->handler->expects($this->once())->method('handle')->with($this->equalTo($this->request)); - $this->requestTracker->expects($this->once())->method('trackNotFoundIfApplicable')->with( - $this->equalTo($this->request), - ); + $this->handler->expects($this->once())->method('handle')->with($this->request); + $this->requestTracker->expects($this->once())->method('trackNotFoundIfApplicable')->with($this->request); $this->middleware->process($this->request, $this->handler); } diff --git a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php index 7123aa29..ebdaebdf 100644 --- a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php +++ b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php @@ -32,7 +32,7 @@ class CloseDbConnectionEventListenerDelegatorTest extends TestCase }; }; - $this->container->expects($this->once())->method('get')->with($this->equalTo('em'))->willReturn( + $this->container->expects($this->once())->method('get')->with('em')->willReturn( $this->createMock(ReopeningEntityManagerInterface::class), ); diff --git a/module/Core/test/EventDispatcher/LocateUnlocatedVisitsTest.php b/module/Core/test/EventDispatcher/LocateUnlocatedVisitsTest.php index b3063374..8b10821c 100644 --- a/module/Core/test/EventDispatcher/LocateUnlocatedVisitsTest.php +++ b/module/Core/test/EventDispatcher/LocateUnlocatedVisitsTest.php @@ -31,7 +31,7 @@ class LocateUnlocatedVisitsTest extends TestCase /** @test */ public function locatorIsCalledWhenInvoked(): void { - $this->locator->expects($this->once())->method('locateUnlocatedVisits')->with($this->equalTo($this->listener)); + $this->locator->expects($this->once())->method('locateUnlocatedVisits')->with($this->listener); ($this->listener)(new GeoLiteDbCreated()); } @@ -41,9 +41,9 @@ class LocateUnlocatedVisitsTest extends TestCase $visit = Visit::forBasePath(Visitor::emptyInstance()); $location = Location::emptyInstance(); - $this->visitToLocation->expects($this->once())->method('resolveVisitLocation')->with( - $this->equalTo($visit), - )->willReturn($location); + $this->visitToLocation->expects($this->once())->method('resolveVisitLocation')->with($visit)->willReturn( + $location, + ); $result = $this->listener->geolocateVisit($visit); diff --git a/module/Core/test/EventDispatcher/LocateVisitTest.php b/module/Core/test/EventDispatcher/LocateVisitTest.php index b90b5b7f..ca862105 100644 --- a/module/Core/test/EventDispatcher/LocateVisitTest.php +++ b/module/Core/test/EventDispatcher/LocateVisitTest.php @@ -53,18 +53,13 @@ class LocateVisitTest extends TestCase public function invalidVisitLogsWarning(): void { $event = new UrlVisited('123'); - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo('123'), - )->willReturn(null); + $this->em->expects($this->once())->method('find')->with(Visit::class, '123')->willReturn(null); $this->em->expects($this->never())->method('flush'); $this->logger->expects($this->once())->method('warning')->with( - $this->equalTo('Tried to locate visit with id "{visitId}", but it does not exist.'), - $this->equalTo(['visitId' => 123]), - ); - $this->eventDispatcher->expects($this->never())->method('dispatch')->with( - $this->equalTo(new VisitLocated('123')), + 'Tried to locate visit with id "{visitId}", but it does not exist.', + ['visitId' => 123], ); + $this->eventDispatcher->expects($this->never())->method('dispatch')->with(new VisitLocated('123')); $this->ipLocationResolver->expects($this->never())->method('resolveIpLocation'); ($this->locateVisit)($event); @@ -74,19 +69,16 @@ class LocateVisitTest extends TestCase public function nonExistingGeoLiteDbLogsWarning(): void { $event = new UrlVisited('123'); - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo('123'), - )->willReturn(Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', ''))); + $this->em->expects($this->once())->method('find')->with(Visit::class, '123')->willReturn( + Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', '')), + ); $this->em->expects($this->never())->method('flush'); $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(false); $this->logger->expects($this->once())->method('warning')->with( - $this->equalTo('Tried to locate visit with id "{visitId}", but a GeoLite2 db was not found.'), - $this->equalTo(['visitId' => 123]), - ); - $this->eventDispatcher->expects($this->once())->method('dispatch')->with( - $this->equalTo(new VisitLocated('123')), + 'Tried to locate visit with id "{visitId}", but a GeoLite2 db was not found.', + ['visitId' => 123], ); + $this->eventDispatcher->expects($this->once())->method('dispatch')->with(new VisitLocated('123')); $this->ipLocationResolver->expects($this->never())->method('resolveIpLocation'); ($this->locateVisit)($event); @@ -96,22 +88,19 @@ class LocateVisitTest extends TestCase public function invalidAddressLogsWarning(): void { $event = new UrlVisited('123'); - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo('123'), - )->willReturn(Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', ''))); + $this->em->expects($this->once())->method('find')->with(Visit::class, '123')->willReturn( + Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', '')), + ); $this->em->expects($this->never())->method('flush'); $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(true); $this->ipLocationResolver->expects( $this->once(), )->method('resolveIpLocation')->withAnyParameters()->willThrowException(WrongIpException::fromIpAddress('')); $this->logger->expects($this->once())->method('warning')->with( - $this->equalTo('Tried to locate visit with id "{visitId}", but its address seems to be wrong. {e}'), + 'Tried to locate visit with id "{visitId}", but its address seems to be wrong. {e}', $this->isType('array'), ); - $this->eventDispatcher->expects($this->once())->method('dispatch')->with( - $this->equalTo(new VisitLocated('123')), - ); + $this->eventDispatcher->expects($this->once())->method('dispatch')->with(new VisitLocated('123')); ($this->locateVisit)($event); } @@ -120,22 +109,19 @@ class LocateVisitTest extends TestCase public function unhandledExceptionLogsError(): void { $event = new UrlVisited('123'); - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo('123'), - )->willReturn(Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', ''))); + $this->em->expects($this->once())->method('find')->with(Visit::class, '123')->willReturn( + Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', '')), + ); $this->em->expects($this->never())->method('flush'); $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(true); $this->ipLocationResolver->expects( $this->once(), )->method('resolveIpLocation')->withAnyParameters()->willThrowException(new OutOfRangeException()); $this->logger->expects($this->once())->method('error')->with( - $this->equalTo('An unexpected error occurred while trying to locate visit with id "{visitId}". {e}'), + 'An unexpected error occurred while trying to locate visit with id "{visitId}". {e}', $this->isType('array'), ); - $this->eventDispatcher->expects($this->once())->method('dispatch')->with( - $this->equalTo(new VisitLocated('123')), - ); + $this->eventDispatcher->expects($this->once())->method('dispatch')->with(new VisitLocated('123')); ($this->locateVisit)($event); } @@ -147,17 +133,12 @@ class LocateVisitTest extends TestCase public function nonLocatableVisitsResolveToEmptyLocations(Visit $visit): void { $event = new UrlVisited('123'); - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo('123'), - )->willReturn($visit); + $this->em->expects($this->once())->method('find')->with(Visit::class, '123')->willReturn($visit); $this->em->expects($this->once())->method('flush'); $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(true); $this->ipLocationResolver->expects($this->never())->method('resolveIpLocation'); - $this->eventDispatcher->expects($this->once())->method('dispatch')->with( - $this->equalTo(new VisitLocated('123')), - ); + $this->eventDispatcher->expects($this->once())->method('dispatch')->with(new VisitLocated('123')); $this->logger->expects($this->never())->method('warning'); ($this->locateVisit)($event); @@ -184,19 +165,14 @@ class LocateVisitTest extends TestCase $location = new Location('', '', '', '', 0.0, 0.0, ''); $event = UrlVisited::withOriginalIpAddress('123', $originalIpAddress); - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo('123'), - )->willReturn($visit); + $this->em->expects($this->once())->method('find')->with(Visit::class, '123')->willReturn($visit); $this->em->expects($this->once())->method('flush'); $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(true); - $this->ipLocationResolver->expects($this->once())->method('resolveIpLocation')->with( - $this->equalTo($ipAddr), - )->willReturn($location); - - $this->eventDispatcher->expects($this->once())->method('dispatch')->with( - $this->equalTo(new VisitLocated('123')), + $this->ipLocationResolver->expects($this->once())->method('resolveIpLocation')->with($ipAddr)->willReturn( + $location, ); + + $this->eventDispatcher->expects($this->once())->method('dispatch')->with(new VisitLocated('123')); $this->logger->expects($this->never())->method('warning'); ($this->locateVisit)($event); diff --git a/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php b/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php index 6ae76f8c..7e87fc44 100644 --- a/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php +++ b/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php @@ -42,15 +42,12 @@ class NotifyNewShortUrlToMercureTest extends TestCase /** @test */ public function messageIsLoggedWhenShortUrlIsNotFound(): void { - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(ShortUrl::class), - $this->equalTo('123'), - )->willReturn(null); + $this->em->expects($this->once())->method('find')->with(ShortUrl::class, '123')->willReturn(null); $this->helper->expects($this->never())->method('publishUpdate'); $this->updatesGenerator->expects($this->never())->method('newShortUrlUpdate'); $this->logger->expects($this->once())->method('warning')->with( - $this->equalTo('Tried to notify {name} for new short URL with id "{shortUrlId}", but it does not exist.'), - $this->equalTo(['shortUrlId' => '123', 'name' => 'Mercure']), + 'Tried to notify {name} for new short URL with id "{shortUrlId}", but it does not exist.', + ['shortUrlId' => '123', 'name' => 'Mercure'], ); $this->logger->expects($this->never())->method('debug'); @@ -63,14 +60,11 @@ class NotifyNewShortUrlToMercureTest extends TestCase $shortUrl = ShortUrl::withLongUrl(''); $update = Update::forTopicAndPayload('', []); - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(ShortUrl::class), - $this->equalTo('123'), - )->willReturn($shortUrl); - $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with( - $this->equalTo($shortUrl), - )->willReturn($update); - $this->helper->expects($this->once())->method('publishUpdate')->with($this->equalTo($update)); + $this->em->expects($this->once())->method('find')->with(ShortUrl::class, '123')->willReturn($shortUrl); + $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with($shortUrl)->willReturn( + $update, + ); + $this->helper->expects($this->once())->method('publishUpdate')->with($update); $this->logger->expects($this->never())->method('warning'); $this->logger->expects($this->never())->method('debug'); @@ -85,19 +79,15 @@ class NotifyNewShortUrlToMercureTest extends TestCase $e = new Exception('Error'); $this->em->expects($this->once())->method('find')->with( - $this->equalTo(ShortUrl::class), - $this->equalTo('123'), + ShortUrl::class, + '123', )->willReturn($shortUrl); - $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with( - $this->equalTo($shortUrl), - )->willReturn($update); - $this->helper->expects($this->once())->method('publishUpdate')->with( - $this->equalTo($update), - )->willThrowException($e); + $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with($shortUrl)->willReturn($update); + $this->helper->expects($this->once())->method('publishUpdate')->with($update)->willThrowException($e); $this->logger->expects($this->never())->method('warning'); $this->logger->expects($this->once())->method('debug')->with( - $this->equalTo('Error while trying to notify {name} with new short URL. {e}'), - $this->equalTo(['e' => $e, 'name' => 'Mercure']), + 'Error while trying to notify {name} with new short URL. {e}', + ['e' => $e, 'name' => 'Mercure'], ); ($this->listener)(new ShortUrlCreated('123')); diff --git a/module/Core/test/EventDispatcher/Mercure/NotifyVisitToMercureTest.php b/module/Core/test/EventDispatcher/Mercure/NotifyVisitToMercureTest.php index 193adac3..00d521f5 100644 --- a/module/Core/test/EventDispatcher/Mercure/NotifyVisitToMercureTest.php +++ b/module/Core/test/EventDispatcher/Mercure/NotifyVisitToMercureTest.php @@ -41,13 +41,10 @@ class NotifyVisitToMercureTest extends TestCase public function notificationsAreNotSentWhenVisitCannotBeFound(): void { $visitId = '123'; - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo($visitId), - )->willReturn(null); + $this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn(null); $this->logger->expects($this->once())->method('warning')->with( - $this->equalTo('Tried to notify {name} for visit with id "{visitId}", but it does not exist.'), - $this->equalTo(['visitId' => $visitId, 'name' => 'Mercure']), + 'Tried to notify {name} for visit with id "{visitId}", but it does not exist.', + ['visitId' => $visitId, 'name' => 'Mercure'], ); $this->logger->expects($this->never())->method('debug'); $this->updatesGenerator->expects($this->never())->method('newShortUrlVisitUpdate'); @@ -65,20 +62,15 @@ class NotifyVisitToMercureTest extends TestCase $visit = Visit::forValidShortUrl(ShortUrl::createEmpty(), Visitor::emptyInstance()); $update = Update::forTopicAndPayload('', []); - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo($visitId), - )->willReturn($visit); + $this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn($visit); $this->logger->expects($this->never())->method('warning'); $this->logger->expects($this->never())->method('debug'); - $this->updatesGenerator->expects($this->once())->method('newShortUrlVisitUpdate')->with( - $this->equalTo($visit), - )->willReturn($update); + $this->updatesGenerator->expects($this->once())->method('newShortUrlVisitUpdate')->with($visit)->willReturn( + $update, + ); $this->updatesGenerator->expects($this->never())->method('newOrphanVisitUpdate'); - $this->updatesGenerator->expects($this->once())->method('newVisitUpdate')->with( - $this->equalTo($visit), - )->willReturn($update); - $this->helper->expects($this->exactly(2))->method('publishUpdate')->with($this->equalTo($update)); + $this->updatesGenerator->expects($this->once())->method('newVisitUpdate')->with($visit)->willReturn($update); + $this->helper->expects($this->exactly(2))->method('publishUpdate')->with($update); ($this->listener)(new VisitLocated($visitId)); } @@ -91,25 +83,18 @@ class NotifyVisitToMercureTest extends TestCase $update = Update::forTopicAndPayload('', []); $e = new RuntimeException('Error'); - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo($visitId), - )->willReturn($visit); + $this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn($visit); $this->logger->expects($this->never())->method('warning'); $this->logger->expects($this->once())->method('debug')->with( - $this->equalTo('Error while trying to notify {name} with new visit. {e}'), - $this->equalTo(['e' => $e, 'name' => 'Mercure']), + 'Error while trying to notify {name} with new visit. {e}', + ['e' => $e, 'name' => 'Mercure'], + ); + $this->updatesGenerator->expects($this->once())->method('newShortUrlVisitUpdate')->with($visit)->willReturn( + $update, ); - $this->updatesGenerator->expects($this->once())->method('newShortUrlVisitUpdate')->with( - $this->equalTo($visit), - )->willReturn($update); $this->updatesGenerator->expects($this->never())->method('newOrphanVisitUpdate'); - $this->updatesGenerator->expects($this->once())->method('newVisitUpdate')->with( - $this->equalTo($visit), - )->willReturn($update); - $this->helper->expects($this->once())->method('publishUpdate')->with( - $this->equalTo($update), - )->willThrowException($e); + $this->updatesGenerator->expects($this->once())->method('newVisitUpdate')->with($visit)->willReturn($update); + $this->helper->expects($this->once())->method('publishUpdate')->with($update)->willThrowException($e); ($this->listener)(new VisitLocated($visitId)); } @@ -123,18 +108,15 @@ class NotifyVisitToMercureTest extends TestCase $visitId = '123'; $update = Update::forTopicAndPayload('', []); - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo($visitId), - )->willReturn($visit); + $this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn($visit); $this->logger->expects($this->never())->method('warning'); $this->logger->expects($this->never())->method('debug'); $this->updatesGenerator->expects($this->never())->method('newShortUrlVisitUpdate'); - $this->updatesGenerator->expects($this->once())->method('newOrphanVisitUpdate')->with( - $this->equalTo($visit), - )->willReturn($update); + $this->updatesGenerator->expects($this->once())->method('newOrphanVisitUpdate')->with($visit)->willReturn( + $update, + ); $this->updatesGenerator->expects($this->never())->method('newVisitUpdate'); - $this->helper->expects($this->once())->method('publishUpdate')->with($this->equalTo($update)); + $this->helper->expects($this->once())->method('publishUpdate')->with($update); ($this->listener)(new VisitLocated($visitId)); } diff --git a/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php b/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php index 5148748a..be4741d6 100644 --- a/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php +++ b/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php @@ -52,14 +52,11 @@ class NotifyVisitToWebHooksTest extends TestCase /** @test */ public function invalidVisitDoesNotPerformAnyRequest(): void { - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo('1'), - )->willReturn(null); + $this->em->expects($this->once())->method('find')->with(Visit::class, '1')->willReturn(null); $this->httpClient->expects($this->never())->method('requestAsync'); $this->logger->expects($this->once())->method('warning')->with( - $this->equalTo('Tried to notify webhooks for visit with id "{visitId}", but it does not exist.'), - $this->equalTo(['visitId' => '1']), + 'Tried to notify webhooks for visit with id "{visitId}", but it does not exist.', + ['visitId' => '1'], ); $this->createListener(['foo', 'bar'])(new VisitLocated('1')); @@ -68,10 +65,9 @@ class NotifyVisitToWebHooksTest extends TestCase /** @test */ public function orphanVisitDoesNotPerformAnyRequestWhenDisabled(): void { - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo('1'), - )->willReturn(Visit::forBasePath(Visitor::emptyInstance())); + $this->em->expects($this->once())->method('find')->with(Visit::class, '1')->willReturn( + Visit::forBasePath(Visitor::emptyInstance()), + ); $this->httpClient->expects($this->never())->method('requestAsync'); $this->logger->expects($this->never())->method('warning'); @@ -87,12 +83,9 @@ class NotifyVisitToWebHooksTest extends TestCase $webhooks = ['foo', 'invalid', 'bar', 'baz']; $invalidWebhooks = ['invalid', 'baz']; - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo('1'), - )->willReturn($visit); + $this->em->expects($this->once())->method('find')->with(Visit::class, '1')->willReturn($visit); $this->httpClient->expects($this->exactly(count($webhooks)))->method('requestAsync')->with( - $this->equalTo(RequestMethodInterface::METHOD_POST), + RequestMethodInterface::METHOD_POST, $this->istype('string'), $this->callback(function (array $requestOptions) use ($expectedResponseKeys) { Assert::assertArrayHasKey(RequestOptions::HEADERS, $requestOptions); @@ -114,7 +107,7 @@ class NotifyVisitToWebHooksTest extends TestCase return $shouldReject ? new RejectedPromise(new Exception('')) : new FulfilledPromise(''); }); $this->logger->expects($this->exactly(count($invalidWebhooks)))->method('warning')->with( - $this->equalTo('Failed to notify visit with id "{visitId}" to webhook "{webhook}". {e}'), + 'Failed to notify visit with id "{visitId}" to webhook "{webhook}". {e}', $this->callback(function (array $extra): bool { Assert::assertArrayHasKey('webhook', $extra); Assert::assertArrayHasKey('visitId', $extra); diff --git a/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php b/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php index 40ceb3aa..ffa1b505 100644 --- a/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php +++ b/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php @@ -51,13 +51,10 @@ class NotifyNewShortUrlToRabbitMqTest extends TestCase public function notificationsAreNotSentWhenShortUrlCannotBeFound(): void { $shortUrlId = '123'; - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(ShortUrl::class), - $this->equalTo($shortUrlId), - )->willReturn(null); + $this->em->expects($this->once())->method('find')->with(ShortUrl::class, $shortUrlId)->willReturn(null); $this->logger->expects($this->once())->method('warning')->with( - $this->equalTo('Tried to notify {name} for new short URL with id "{shortUrlId}", but it does not exist.'), - $this->equalTo(['shortUrlId' => $shortUrlId, 'name' => 'RabbitMQ']), + 'Tried to notify {name} for new short URL with id "{shortUrlId}", but it does not exist.', + ['shortUrlId' => $shortUrlId, 'name' => 'RabbitMQ'], ); $this->logger->expects($this->never())->method('debug'); $this->helper->expects($this->never())->method('publishUpdate'); @@ -70,14 +67,13 @@ class NotifyNewShortUrlToRabbitMqTest extends TestCase { $shortUrlId = '123'; $update = Update::forTopicAndPayload(Topic::NEW_SHORT_URL->value, []); - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(ShortUrl::class), - $this->equalTo($shortUrlId), - )->willReturn(ShortUrl::withLongUrl('')); + $this->em->expects($this->once())->method('find')->with(ShortUrl::class, $shortUrlId)->willReturn( + ShortUrl::withLongUrl(''), + ); $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with( $this->isInstanceOf(ShortUrl::class), )->willReturn($update); - $this->helper->expects($this->once())->method('publishUpdate')->with($this->equalTo($update)); + $this->helper->expects($this->once())->method('publishUpdate')->with($update); $this->logger->expects($this->never())->method('debug'); ($this->listener())(new ShortUrlCreated($shortUrlId)); @@ -91,19 +87,16 @@ class NotifyNewShortUrlToRabbitMqTest extends TestCase { $shortUrlId = '123'; $update = Update::forTopicAndPayload(Topic::NEW_SHORT_URL->value, []); - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(ShortUrl::class), - $this->equalTo($shortUrlId), - )->willReturn(ShortUrl::withLongUrl('')); + $this->em->expects($this->once())->method('find')->with(ShortUrl::class, $shortUrlId)->willReturn( + ShortUrl::withLongUrl(''), + ); $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with( $this->isInstanceOf(ShortUrl::class), )->willReturn($update); - $this->helper->expects($this->once())->method('publishUpdate')->with( - $this->equalTo($update), - )->willThrowException($e); + $this->helper->expects($this->once())->method('publishUpdate')->with($update)->willThrowException($e); $this->logger->expects($this->once())->method('debug')->with( - $this->equalTo('Error while trying to notify {name} with new short URL. {e}'), - $this->equalTo(['e' => $e, 'name' => 'RabbitMQ']), + 'Error while trying to notify {name} with new short URL. {e}', + ['e' => $e, 'name' => 'RabbitMQ'], ); ($this->listener())(new ShortUrlCreated($shortUrlId)); diff --git a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php index 94688dd3..5abb15f0 100644 --- a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php +++ b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php @@ -59,13 +59,10 @@ class NotifyVisitToRabbitMqTest extends TestCase public function notificationsAreNotSentWhenVisitCannotBeFound(): void { $visitId = '123'; - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo($visitId), - )->willReturn(null); + $this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn(null); $this->logger->expects($this->once())->method('warning')->with( - $this->equalTo('Tried to notify {name} for visit with id "{visitId}", but it does not exist.'), - $this->equalTo(['visitId' => $visitId, 'name' => 'RabbitMQ']), + 'Tried to notify {name} for visit with id "{visitId}", but it does not exist.', + ['visitId' => $visitId, 'name' => 'RabbitMQ'], ); $this->logger->expects($this->never())->method('debug'); $this->helper->expects($this->never())->method('publishUpdate'); @@ -80,10 +77,7 @@ class NotifyVisitToRabbitMqTest extends TestCase public function expectedChannelsAreNotifiedBasedOnTheVisitType(Visit $visit, array $expectedChannels): void { $visitId = '123'; - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo($visitId), - )->willReturn($visit); + $this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn($visit); each($expectedChannels, function (string $method): void { $this->updatesGenerator->expects($this->once())->method($method)->with( $this->isInstanceOf(Visit::class), @@ -121,17 +115,16 @@ class NotifyVisitToRabbitMqTest extends TestCase public function printsDebugMessageInCaseOfError(Throwable $e): void { $visitId = '123'; - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo($visitId), - )->willReturn(Visit::forBasePath(Visitor::emptyInstance())); + $this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn( + Visit::forBasePath(Visitor::emptyInstance()), + ); $this->updatesGenerator->expects($this->once())->method('newOrphanVisitUpdate')->with( $this->isInstanceOf(Visit::class), )->willReturn(Update::forTopicAndPayload('', [])); $this->helper->expects($this->once())->method('publishUpdate')->withAnyParameters()->willThrowException($e); $this->logger->expects($this->once())->method('debug')->with( - $this->equalTo('Error while trying to notify {name} with new visit. {e}'), - $this->equalTo(['e' => $e, 'name' => 'RabbitMQ']), + 'Error while trying to notify {name} with new visit. {e}', + ['e' => $e, 'name' => 'RabbitMQ'], ); ($this->listener())(new VisitLocated($visitId)); @@ -155,10 +148,7 @@ class NotifyVisitToRabbitMqTest extends TestCase callable $expect, ): void { $visitId = '123'; - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo($visitId), - )->willReturn($visit); + $this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn($visit); $setup($this->updatesGenerator); $expect($this->helper, $this->updatesGenerator); diff --git a/module/Core/test/EventDispatcher/RedisPubSub/NotifyNewShortUrlToRedisTest.php b/module/Core/test/EventDispatcher/RedisPubSub/NotifyNewShortUrlToRedisTest.php index fd0e3904..347d5029 100644 --- a/module/Core/test/EventDispatcher/RedisPubSub/NotifyNewShortUrlToRedisTest.php +++ b/module/Core/test/EventDispatcher/RedisPubSub/NotifyNewShortUrlToRedisTest.php @@ -54,19 +54,16 @@ class NotifyNewShortUrlToRedisTest extends TestCase { $shortUrlId = '123'; $update = Update::forTopicAndPayload(Topic::NEW_SHORT_URL->value, []); - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(ShortUrl::class), - $this->equalTo($shortUrlId), - )->willReturn(ShortUrl::withLongUrl('')); + $this->em->expects($this->once())->method('find')->with(ShortUrl::class, $shortUrlId)->willReturn( + ShortUrl::withLongUrl(''), + ); $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with( $this->isInstanceOf(ShortUrl::class), )->willReturn($update); - $this->helper->expects($this->once())->method('publishUpdate')->with( - $this->equalTo($update), - )->willThrowException($e); + $this->helper->expects($this->once())->method('publishUpdate')->with($update)->willThrowException($e); $this->logger->expects($this->once())->method('debug')->with( - $this->equalTo('Error while trying to notify {name} with new short URL. {e}'), - $this->equalTo(['e' => $e, 'name' => 'Redis pub/sub']), + 'Error while trying to notify {name} with new short URL. {e}', + ['e' => $e, 'name' => 'Redis pub/sub'], ); $this->createListener()(new ShortUrlCreated($shortUrlId)); diff --git a/module/Core/test/EventDispatcher/RedisPubSub/NotifyVisitToRedisTest.php b/module/Core/test/EventDispatcher/RedisPubSub/NotifyVisitToRedisTest.php index c3188e5d..92bf4583 100644 --- a/module/Core/test/EventDispatcher/RedisPubSub/NotifyVisitToRedisTest.php +++ b/module/Core/test/EventDispatcher/RedisPubSub/NotifyVisitToRedisTest.php @@ -53,17 +53,16 @@ class NotifyVisitToRedisTest extends TestCase public function printsDebugMessageInCaseOfError(Throwable $e): void { $visitId = '123'; - $this->em->expects($this->once())->method('find')->with( - $this->equalTo(Visit::class), - $this->equalTo($visitId), - )->willReturn(Visit::forBasePath(Visitor::emptyInstance())); + $this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn( + Visit::forBasePath(Visitor::emptyInstance()), + ); $this->updatesGenerator->expects($this->once())->method('newOrphanVisitUpdate')->with( $this->isInstanceOf(Visit::class), )->willReturn(Update::forTopicAndPayload('', [])); $this->helper->expects($this->once())->method('publishUpdate')->withAnyParameters()->willThrowException($e); $this->logger->expects($this->once())->method('debug')->with( - $this->equalTo('Error while trying to notify {name} with new visit. {e}'), - $this->equalTo(['e' => $e, 'name' => 'Redis pub/sub']), + 'Error while trying to notify {name} with new visit. {e}', + ['e' => $e, 'name' => 'Redis pub/sub'], ); $this->createListener()(new VisitLocated($visitId)); diff --git a/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php b/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php index 93cde57c..e74508d3 100644 --- a/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php +++ b/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php @@ -39,8 +39,8 @@ class UpdateGeoLiteDbTest extends TestCase $this->dbUpdater->expects($this->once())->method('checkDbUpdate')->withAnyParameters()->willThrowException($e); $this->logger->expects($this->once())->method('error')->with( - $this->equalTo('GeoLite2 database download failed. {e}'), - $this->equalTo(['e' => $e]), + 'GeoLite2 database download failed. {e}', + ['e' => $e], ); $this->logger->expects($this->never())->method('notice'); $this->eventDispatcher->expects($this->never())->method('dispatch'); @@ -60,7 +60,7 @@ class UpdateGeoLiteDbTest extends TestCase return GeolocationResult::DB_IS_UP_TO_DATE; }, ); - $this->logger->expects($this->once())->method('notice')->with($this->equalTo($expectedMessage)); + $this->logger->expects($this->once())->method('notice')->with($expectedMessage); $this->logger->expects($this->never())->method('error'); $this->eventDispatcher->expects($this->never())->method('dispatch'); @@ -94,7 +94,7 @@ class UpdateGeoLiteDbTest extends TestCase }, ); $logNoticeExpectation = $expectedMessage !== null ? $this->once() : $this->never(); - $this->logger->expects($logNoticeExpectation)->method('notice')->with($this->equalTo($expectedMessage)); + $this->logger->expects($logNoticeExpectation)->method('notice')->with($expectedMessage); $this->logger->expects($this->never())->method('error'); $this->eventDispatcher->expects($this->never())->method('dispatch'); @@ -123,7 +123,7 @@ class UpdateGeoLiteDbTest extends TestCase ): void { $this->dbUpdater->expects($this->once())->method('checkDbUpdate')->withAnyParameters()->willReturn($result); $this->eventDispatcher->expects($this->exactly($expectedDispatches))->method('dispatch')->with( - $this->equalTo(new GeoLiteDbCreated()), + new GeoLiteDbCreated(), ); ($this->listener)(); diff --git a/module/Core/test/Importer/ImportedLinksProcessorTest.php b/module/Core/test/Importer/ImportedLinksProcessorTest.php index 183c54c1..3f5fa495 100644 --- a/module/Core/test/Importer/ImportedLinksProcessorTest.php +++ b/module/Core/test/Importer/ImportedLinksProcessorTest.php @@ -41,7 +41,7 @@ class ImportedLinksProcessorTest extends TestCase { $this->em = $this->createMock(EntityManagerInterface::class); $this->repo = $this->createMock(ShortUrlRepositoryInterface::class); - $this->em->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn($this->repo); + $this->em->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); $this->shortCodeHelper = $this->createMock(ShortCodeUniquenessHelperInterface::class); $batchHelper = $this->createMock(DoctrineBatchHelperInterface::class); diff --git a/module/Core/test/ShortUrl/Helper/ShortCodeUniquenessHelperTest.php b/module/Core/test/ShortUrl/Helper/ShortCodeUniquenessHelperTest.php index 0891c8ae..2dfd4433 100644 --- a/module/Core/test/ShortUrl/Helper/ShortCodeUniquenessHelperTest.php +++ b/module/Core/test/ShortUrl/Helper/ShortCodeUniquenessHelperTest.php @@ -38,14 +38,14 @@ class ShortCodeUniquenessHelperTest extends TestCase $expectedCalls = 3; $repo = $this->createMock(ShortUrlRepository::class); $repo->expects($this->exactly($expectedCalls))->method('shortCodeIsInUseWithLock')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain('abc123', $expectedAuthority)), + ShortUrlIdentifier::fromShortCodeAndDomain('abc123', $expectedAuthority), )->willReturnCallback(function () use (&$callIndex, $expectedCalls) { $callIndex++; return $callIndex < $expectedCalls; }); - $this->em->expects($this->exactly($expectedCalls))->method('getRepository')->with( - $this->equalTo(ShortUrl::class), - )->willReturn($repo); + $this->em->expects($this->exactly($expectedCalls))->method('getRepository')->with(ShortUrl::class)->willReturn( + $repo, + ); $this->shortUrl->method('getDomain')->willReturn($domain); $this->shortUrl->expects($this->exactly($expectedCalls - 1))->method('regenerateShortCode')->with(); @@ -65,11 +65,9 @@ class ShortCodeUniquenessHelperTest extends TestCase { $repo = $this->createMock(ShortUrlRepository::class); $repo->expects($this->once())->method('shortCodeIsInUseWithLock')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain('abc123')), + ShortUrlIdentifier::fromShortCodeAndDomain('abc123'), )->willReturn(true); - $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn( - $repo, - ); + $this->em->expects($this->once())->method('getRepository')->with(ShortUrl::class)->willReturn($repo); $this->shortUrl->method('getDomain')->willReturn(null); $this->shortUrl->expects($this->never())->method('regenerateShortCode'); diff --git a/module/Core/test/ShortUrl/Helper/ShortUrlTitleResolutionHelperTest.php b/module/Core/test/ShortUrl/Helper/ShortUrlTitleResolutionHelperTest.php index 61618eda..52255be7 100644 --- a/module/Core/test/ShortUrl/Helper/ShortUrlTitleResolutionHelperTest.php +++ b/module/Core/test/ShortUrl/Helper/ShortUrlTitleResolutionHelperTest.php @@ -29,11 +29,11 @@ class ShortUrlTitleResolutionHelperTest extends TestCase { $longUrl = 'http://foobar.com/12345/hello?foo=bar'; $this->urlValidator->expects($this->exactly($validateWithTitleCallsNum))->method('validateUrlWithTitle')->with( - $this->equalTo($longUrl), + $longUrl, $this->isFalse(), ); $this->urlValidator->expects($this->exactly($validateCallsNum))->method('validateUrl')->with( - $this->equalTo($longUrl), + $longUrl, $this->isFalse(), ); diff --git a/module/Core/test/ShortUrl/ShortUrlResolverTest.php b/module/Core/test/ShortUrl/ShortUrlResolverTest.php index ef63a92e..e15bb124 100644 --- a/module/Core/test/ShortUrl/ShortUrlResolverTest.php +++ b/module/Core/test/ShortUrl/ShortUrlResolverTest.php @@ -48,13 +48,10 @@ class ShortUrlResolverTest extends TestCase $shortCode = $shortUrl->getShortCode(); $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); - $this->repo->expects($this->once())->method('findOne')->with( - $this->equalTo($identifier), - $this->equalTo($apiKey?->spec()), - )->willReturn($shortUrl); - $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn( - $this->repo, + $this->repo->expects($this->once())->method('findOne')->with($identifier, $apiKey?->spec())->willReturn( + $shortUrl, ); + $this->em->expects($this->once())->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); $result = $this->urlResolver->resolveShortUrl($identifier, $apiKey); @@ -70,13 +67,8 @@ class ShortUrlResolverTest extends TestCase $shortCode = 'abc123'; $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); - $this->repo->expects($this->once())->method('findOne')->with( - $this->equalTo($identifier), - $this->equalTo($apiKey?->spec()), - )->willReturn(null); - $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn( - $this->repo, - ); + $this->repo->expects($this->once())->method('findOne')->with($identifier, $apiKey?->spec())->willReturn(null); + $this->em->expects($this->once())->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); $this->expectException(ShortUrlNotFoundException::class); @@ -90,11 +82,9 @@ class ShortUrlResolverTest extends TestCase $shortCode = $shortUrl->getShortCode(); $this->repo->expects($this->once())->method('findOneWithDomainFallback')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), )->willReturn($shortUrl); - $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn( - $this->repo, - ); + $this->em->expects($this->once())->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); $result = $this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)); @@ -110,11 +100,9 @@ class ShortUrlResolverTest extends TestCase $shortCode = $shortUrl->getShortCode(); $this->repo->expects($this->once())->method('findOneWithDomainFallback')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode)), + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), )->willReturn($shortUrl); - $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn( - $this->repo, - ); + $this->em->expects($this->once())->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); $this->expectException(ShortUrlNotFoundException::class); diff --git a/module/Core/test/ShortUrl/ShortUrlServiceTest.php b/module/Core/test/ShortUrl/ShortUrlServiceTest.php index 16afbe8c..9513b6dc 100644 --- a/module/Core/test/ShortUrl/ShortUrlServiceTest.php +++ b/module/Core/test/ShortUrl/ShortUrlServiceTest.php @@ -64,7 +64,7 @@ class ShortUrlServiceTest extends TestCase $repo = $this->createMock(ShortUrlRepository::class); $repo->expects($this->once())->method('findList')->willReturn($list); $repo->expects($this->once())->method('countList')->willReturn(count($list)); - $this->em->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn($repo); + $this->em->method('getRepository')->with(ShortUrl::class)->willReturn($repo); $paginator = $this->service->listShortUrls(ShortUrlsParams::emptyInstance(), $apiKey); @@ -85,13 +85,13 @@ class ShortUrlServiceTest extends TestCase $shortUrl = ShortUrl::withLongUrl($originalLongUrl); $this->urlResolver->expects($this->once())->method('resolveShortUrl')->with( - $this->equalTo(ShortUrlIdentifier::fromShortCodeAndDomain('abc123')), - $this->equalTo($apiKey), + ShortUrlIdentifier::fromShortCodeAndDomain('abc123'), + $apiKey, )->willReturn($shortUrl); $this->titleResolutionHelper->expects($this->exactly($expectedValidateCalls)) ->method('processTitleAndValidateUrl') - ->with($this->equalTo($shortUrlEdit)) + ->with($shortUrlEdit) ->willReturn($shortUrlEdit); $result = $this->service->updateShortUrl( diff --git a/module/Core/test/ShortUrl/UrlShortenerTest.php b/module/Core/test/ShortUrl/UrlShortenerTest.php index f1097669..e5fe7eca 100644 --- a/module/Core/test/ShortUrl/UrlShortenerTest.php +++ b/module/Core/test/ShortUrl/UrlShortenerTest.php @@ -52,7 +52,7 @@ class UrlShortenerTest extends TestCase $longUrl = 'http://foobar.com/12345/hello?foo=bar'; $meta = ShortUrlCreation::fromRawData(['longUrl' => $longUrl]); $this->titleResolutionHelper->expects($this->once())->method('processTitleAndValidateUrl')->with( - $this->equalTo($meta), + $meta, )->willReturnArgument(0); $this->shortCodeHelper->method('ensureShortCodeUniqueness')->willReturn(true); @@ -70,7 +70,7 @@ class UrlShortenerTest extends TestCase $this->shortCodeHelper->expects($this->once())->method('ensureShortCodeUniqueness')->willReturn(false); $this->titleResolutionHelper->expects($this->once())->method('processTitleAndValidateUrl')->with( - $this->equalTo($meta), + $meta, )->willReturnArgument(0); $this->expectException(NonUniqueSlugException::class); @@ -86,9 +86,7 @@ class UrlShortenerTest extends TestCase { $repo = $this->createMock(ShortUrlRepository::class); $repo->expects($this->once())->method('findOneMatching')->willReturn($expected); - $this->em->expects($this->once())->method('getRepository')->with($this->equalTo(ShortUrl::class))->willReturn( - $repo, - ); + $this->em->expects($this->once())->method('getRepository')->with(ShortUrl::class)->willReturn($repo); $this->titleResolutionHelper->expects($this->never())->method('processTitleAndValidateUrl'); $this->shortCodeHelper->method('ensureShortCodeUniqueness')->willReturn(true); From 8753e3a77f4b10120b6d1f01ab15423ca64f403e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 18:17:29 +0200 Subject: [PATCH 077/182] Migrated ShortUrlRepositoryAdapterTest to use PHPUnit mocks --- .../Adapter/ShortUrlRepositoryAdapterTest.php | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php b/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php index 31a8f4f1..1df421bb 100644 --- a/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php +++ b/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php @@ -5,9 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\ShortUrl\Paginator\Adapter; use Cake\Chronos\Chronos; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode; use Shlinkio\Shlink\Core\ShortUrl\Paginator\Adapter\ShortUrlRepositoryAdapter; @@ -18,13 +17,11 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ShortUrlRepositoryAdapterTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $repo; + private MockObject $repo; protected function setUp(): void { - $this->repo = $this->prophesize(ShortUrlRepositoryInterface::class); + $this->repo = $this->createMock(ShortUrlRepositoryInterface::class); } /** @@ -45,13 +42,14 @@ class ShortUrlRepositoryAdapterTest extends TestCase 'endDate' => $endDate, 'orderBy' => $orderBy, ]); - $adapter = new ShortUrlRepositoryAdapter($this->repo->reveal(), $params, null); + $adapter = new ShortUrlRepositoryAdapter($this->repo, $params, null); $orderBy = $params->orderBy(); $dateRange = $params->dateRange(); - $this->repo->findList( + $this->repo->expects($this->once())->method('findList')->with( new ShortUrlsListFiltering(10, 5, $orderBy, $searchTerm, $tags, TagsMode::ANY, $dateRange), - )->shouldBeCalledOnce(); + ); + $adapter->getSlice(5, 10); } @@ -72,12 +70,12 @@ class ShortUrlRepositoryAdapterTest extends TestCase 'endDate' => $endDate, ]); $apiKey = ApiKey::create(); - $adapter = new ShortUrlRepositoryAdapter($this->repo->reveal(), $params, $apiKey); + $adapter = new ShortUrlRepositoryAdapter($this->repo, $params, $apiKey); $dateRange = $params->dateRange(); - $this->repo->countList( + $this->repo->expects($this->once())->method('countList')->with( new ShortUrlsCountFiltering($searchTerm, $tags, TagsMode::ANY, $dateRange, $apiKey), - )->shouldBeCalledOnce(); + ); $adapter->getNbResults(); } From 6e836b5fd9a6ca83bf11856e16b86fcad75fe032 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 18:28:28 +0200 Subject: [PATCH 078/182] Migrated PersistenceShortUrlRelationResolverTest to use PHPUnit mocks --- .../PersistenceShortUrlRelationResolver.php | 5 +- ...ersistenceShortUrlRelationResolverTest.php | 66 ++++++++----------- 2 files changed, 29 insertions(+), 42 deletions(-) diff --git a/module/Core/src/ShortUrl/Resolver/PersistenceShortUrlRelationResolver.php b/module/Core/src/ShortUrl/Resolver/PersistenceShortUrlRelationResolver.php index 6a58cf54..971ef932 100644 --- a/module/Core/src/ShortUrl/Resolver/PersistenceShortUrlRelationResolver.php +++ b/module/Core/src/ShortUrl/Resolver/PersistenceShortUrlRelationResolver.php @@ -21,8 +21,9 @@ class PersistenceShortUrlRelationResolver implements ShortUrlRelationResolverInt /** @var array */ private array $memoizedNewTags = []; - public function __construct(private EntityManagerInterface $em) + public function __construct(private readonly EntityManagerInterface $em) { + // Registering this as an event listener will make the postFlush method to be called automatically $this->em->getEventManager()->addEventListener(Events::postFlush, $this); } @@ -61,7 +62,7 @@ class PersistenceShortUrlRelationResolver implements ShortUrlRelationResolverInt return new Collections\ArrayCollection(map($tags, function (string $tagName) use ($repo): Tag { // Memoize only new tags, and let doctrine handle objects hydrated from persistence - $tag = $repo->findOneBy(['name' => $tagName]) ?? $this->memoizeNewTag($tagName); + $tag = $repo->findOneBy(['name' => $tagName]) ?? $this->memoizeNewTag($tagName); $this->em->persist($tag); return $tag; diff --git a/module/Core/test/ShortUrl/Resolver/PersistenceShortUrlRelationResolverTest.php b/module/Core/test/ShortUrl/Resolver/PersistenceShortUrlRelationResolverTest.php index 997178c5..995c61cd 100644 --- a/module/Core/test/ShortUrl/Resolver/PersistenceShortUrlRelationResolverTest.php +++ b/module/Core/test/ShortUrl/Resolver/PersistenceShortUrlRelationResolverTest.php @@ -6,10 +6,8 @@ namespace ShlinkioTest\Shlink\Core\ShortUrl\Resolver; use Doctrine\Common\EventManager; use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Domain\Entity\Domain; use Shlinkio\Shlink\Core\Domain\Repository\DomainRepositoryInterface; use Shlinkio\Shlink\Core\ShortUrl\Resolver\PersistenceShortUrlRelationResolver; @@ -20,26 +18,22 @@ use function count; class PersistenceShortUrlRelationResolverTest extends TestCase { - use ProphecyTrait; - private PersistenceShortUrlRelationResolver $resolver; - private ObjectProphecy $em; + private MockObject $em; protected function setUp(): void { - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->em->getEventManager()->willReturn(new EventManager()); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->em->method('getEventManager')->willReturn(new EventManager()); - $this->resolver = new PersistenceShortUrlRelationResolver($this->em->reveal()); + $this->resolver = new PersistenceShortUrlRelationResolver($this->em); } /** @test */ public function returnsEmptyWhenNoDomainIsProvided(): void { - $getRepository = $this->em->getRepository(Domain::class); - + $this->em->expects($this->never())->method('getRepository')->with(Domain::class); self::assertNull($this->resolver->resolveDomain(null)); - $getRepository->shouldNotHaveBeenCalled(); } /** @@ -48,9 +42,9 @@ class PersistenceShortUrlRelationResolverTest extends TestCase */ public function findsOrCreatesDomainWhenValueIsProvided(?Domain $foundDomain, string $authority): void { - $repo = $this->prophesize(DomainRepositoryInterface::class); - $findDomain = $repo->findOneBy(['authority' => $authority])->willReturn($foundDomain); - $getRepository = $this->em->getRepository(Domain::class)->willReturn($repo->reveal()); + $repo = $this->createMock(DomainRepositoryInterface::class); + $repo->expects($this->once())->method('findOneBy')->with(['authority' => $authority])->willReturn($foundDomain); + $this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo); $result = $this->resolver->resolveDomain($authority); @@ -59,8 +53,6 @@ class PersistenceShortUrlRelationResolverTest extends TestCase } self::assertInstanceOf(Domain::class, $result); self::assertEquals($authority, $result->getAuthority()); - $findDomain->shouldHaveBeenCalledOnce(); - $getRepository->shouldHaveBeenCalledOnce(); } public function provideFoundDomains(): iterable @@ -79,21 +71,22 @@ class PersistenceShortUrlRelationResolverTest extends TestCase { $expectedPersistedTags = count($expectedTags); - $tagRepo = $this->prophesize(TagRepositoryInterface::class); - $findTag = $tagRepo->findOneBy(Argument::type('array'))->will(function (array $args): ?Tag { - ['name' => $name] = $args[0]; + $tagRepo = $this->createMock(TagRepositoryInterface::class); + $tagRepo->expects($this->exactly($expectedPersistedTags))->method('findOneBy')->with( + $this->isType('array'), + )->willReturnCallback(function (array $criteria): ?Tag { + ['name' => $name] = $criteria; return $name === 'foo' ? new Tag($name) : null; }); - $getRepo = $this->em->getRepository(Tag::class)->willReturn($tagRepo->reveal()); - $persist = $this->em->persist(Argument::type(Tag::class)); + $this->em->expects($this->once())->method('getRepository')->with(Tag::class)->willReturn($tagRepo); + $this->em->expects($this->exactly($expectedPersistedTags))->method('persist')->with( + $this->isInstanceOf(Tag::class), + ); $result = $this->resolver->resolveTags($tags); self::assertCount($expectedPersistedTags, $result); self::assertEquals($expectedTags, $result->toArray()); - $findTag->shouldHaveBeenCalledTimes($expectedPersistedTags); - $getRepo->shouldHaveBeenCalledOnce(); - $persist->shouldHaveBeenCalledTimes($expectedPersistedTags); } public function provideTags(): iterable @@ -105,25 +98,20 @@ class PersistenceShortUrlRelationResolverTest extends TestCase /** @test */ public function returnsEmptyCollectionWhenProvidingEmptyListOfTags(): void { - $tagRepo = $this->prophesize(TagRepositoryInterface::class); - $findTag = $tagRepo->findOneBy(Argument::type('array'))->willReturn(null); - $getRepo = $this->em->getRepository(Tag::class)->willReturn($tagRepo->reveal()); - $persist = $this->em->persist(Argument::type(Tag::class)); + $this->em->expects($this->never())->method('getRepository')->with(Tag::class); + $this->em->expects($this->never())->method('persist'); $result = $this->resolver->resolveTags([]); self::assertEmpty($result); - $findTag->shouldNotHaveBeenCalled(); - $getRepo->shouldNotHaveBeenCalled(); - $persist->shouldNotHaveBeenCalled(); } /** @test */ public function newDomainsAreMemoizedUntilStateIsCleared(): void { - $repo = $this->prophesize(DomainRepositoryInterface::class); - $repo->findOneBy(Argument::type('array'))->willReturn(null); - $this->em->getRepository(Domain::class)->willReturn($repo->reveal()); + $repo = $this->createMock(DomainRepositoryInterface::class); + $repo->expects($this->exactly(3))->method('findOneBy')->with($this->isType('array'))->willReturn(null); + $this->em->method('getRepository')->with(Domain::class)->willReturn($repo); $authority = 'foo.com'; $domain1 = $this->resolver->resolveDomain($authority); @@ -140,11 +128,9 @@ class PersistenceShortUrlRelationResolverTest extends TestCase /** @test */ public function newTagsAreMemoizedUntilStateIsCleared(): void { - $tagRepo = $this->prophesize(TagRepositoryInterface::class); - $tagRepo->findOneBy(Argument::type('array'))->willReturn(null); - $this->em->getRepository(Tag::class)->willReturn($tagRepo->reveal()); - $this->em->persist(Argument::type(Tag::class))->will(function (): void { - }); + $tagRepo = $this->createMock(TagRepositoryInterface::class); + $tagRepo->expects($this->exactly(6))->method('findOneBy')->with($this->isType('array'))->willReturn(null); + $this->em->method('getRepository')->with(Tag::class)->willReturn($tagRepo); $tags = ['foo', 'bar']; [$foo1, $bar1] = $this->resolver->resolveTags($tags); From 5e02cfe3753ce2e74da317f6a7acd23b9af3e5ad Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 18:29:32 +0200 Subject: [PATCH 079/182] Fixed coding styles --- .../Mercure/NotifyNewShortUrlToMercureTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php b/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php index 7e87fc44..40ba0a2a 100644 --- a/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php +++ b/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php @@ -82,7 +82,9 @@ class NotifyNewShortUrlToMercureTest extends TestCase ShortUrl::class, '123', )->willReturn($shortUrl); - $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with($shortUrl)->willReturn($update); + $this->updatesGenerator->expects($this->once())->method('newShortUrlUpdate')->with($shortUrl)->willReturn( + $update, + ); $this->helper->expects($this->once())->method('publishUpdate')->with($update)->willThrowException($e); $this->logger->expects($this->never())->method('warning'); $this->logger->expects($this->once())->method('debug')->with( From c81ae9c40d122d43e718a0aa8055b633bfe17040 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 20:26:44 +0200 Subject: [PATCH 080/182] Migrated TagServiceTest to use PHPUnit mocks --- module/Core/test/Tag/TagServiceTest.php | 64 +++++++++---------------- 1 file changed, 23 insertions(+), 41 deletions(-) diff --git a/module/Core/test/Tag/TagServiceTest.php b/module/Core/test/Tag/TagServiceTest.php index 59d22bba..9cba4875 100644 --- a/module/Core/test/Tag/TagServiceTest.php +++ b/module/Core/test/Tag/TagServiceTest.php @@ -5,10 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Tag; use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Exception\ForbiddenTagOperationException; use Shlinkio\Shlink\Core\Exception\TagConflictException; use Shlinkio\Shlink\Core\Exception\TagNotFoundException; @@ -27,19 +25,18 @@ use ShlinkioTest\Shlink\Core\Util\ApiKeyHelpersTrait; class TagServiceTest extends TestCase { use ApiKeyHelpersTrait; - use ProphecyTrait; private TagService $service; - private ObjectProphecy $em; - private ObjectProphecy $repo; + private MockObject $em; + private MockObject $repo; protected function setUp(): void { - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->repo = $this->prophesize(TagRepository::class); - $this->em->getRepository(Tag::class)->willReturn($this->repo->reveal()); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->repo = $this->createMock(TagRepository::class); + $this->em->method('getRepository')->with(Tag::class)->willReturn($this->repo); - $this->service = new TagService($this->em->reveal()); + $this->service = new TagService($this->em); } /** @test */ @@ -47,14 +44,12 @@ class TagServiceTest extends TestCase { $expected = [new Tag('foo'), new Tag('bar')]; - $match = $this->repo->match(Argument::cetera())->willReturn($expected); - $count = $this->repo->matchSingleScalarResult(Argument::cetera())->willReturn(2); + $this->repo->expects($this->once())->method('match')->willReturn($expected); + $this->repo->expects($this->once())->method('matchSingleScalarResult')->willReturn(2); $result = $this->service->listTags(TagsParams::fromRawData([])); self::assertEquals($expected, $result->getCurrentPageResults()); - $match->shouldHaveBeenCalled(); - $count->shouldHaveBeenCalled(); } /** @@ -69,14 +64,14 @@ class TagServiceTest extends TestCase ): void { $expected = [new TagInfo('foo', 1, 1), new TagInfo('bar', 3, 10)]; - $find = $this->repo->findTagsWithInfo($expectedFiltering)->willReturn($expected); - $count = $this->repo->matchSingleScalarResult(Argument::cetera())->willReturn(2); + $this->repo->expects($this->once())->method('findTagsWithInfo')->with($expectedFiltering)->willReturn( + $expected, + ); + $this->repo->expects($this->exactly($countCalls))->method('matchSingleScalarResult')->willReturn(2); $result = $this->service->tagsInfo($params, $apiKey); self::assertEquals($expected, $result->getCurrentPageResults()); - $find->shouldHaveBeenCalledOnce(); - $count->shouldHaveBeenCalledTimes($countCalls); } public function provideApiKeysAndSearchTerm(): iterable @@ -113,21 +108,17 @@ class TagServiceTest extends TestCase */ public function deleteTagsDelegatesOnRepository(?ApiKey $apiKey): void { - $delete = $this->repo->deleteByName(['foo', 'bar'])->willReturn(4); - + $this->repo->expects($this->once())->method('deleteByName')->with(['foo', 'bar'])->willReturn(4); $this->service->deleteTags(['foo', 'bar'], $apiKey); - - $delete->shouldHaveBeenCalled(); } /** @test */ public function deleteTagsThrowsExceptionWhenProvidedApiKeyIsNotAdmin(): void { - $delete = $this->repo->deleteByName(['foo', 'bar']); + $this->repo->expects($this->never())->method('deleteByName'); $this->expectException(ForbiddenTagOperationException::class); $this->expectExceptionMessage('You are not allowed to delete tags'); - $delete->shouldNotBeCalled(); $this->service->deleteTags( ['foo', 'bar'], @@ -141,9 +132,7 @@ class TagServiceTest extends TestCase */ public function renameInvalidTagThrowsException(?ApiKey $apiKey): void { - $find = $this->repo->findOneBy(Argument::cetera())->willReturn(null); - - $find->shouldBeCalled(); + $this->repo->expects($this->once())->method('findOneBy')->willReturn(null); $this->expectException(TagNotFoundException::class); $this->service->renameTag(TagRenaming::fromNames('foo', 'bar'), $apiKey); @@ -157,17 +146,14 @@ class TagServiceTest extends TestCase { $expected = new Tag('foo'); - $find = $this->repo->findOneBy(Argument::cetera())->willReturn($expected); - $countTags = $this->repo->count(Argument::cetera())->willReturn($count); - $flush = $this->em->flush()->willReturn(null); + $this->repo->expects($this->once())->method('findOneBy')->willReturn($expected); + $this->repo->expects($this->exactly($count > 0 ? 0 : 1))->method('count')->willReturn($count); + $this->em->expects($this->once())->method('flush'); $tag = $this->service->renameTag(TagRenaming::fromNames($oldName, $newName)); self::assertSame($expected, $tag); self::assertEquals($newName, (string) $tag); - $find->shouldHaveBeenCalled(); - $flush->shouldHaveBeenCalled(); - $countTags->shouldHaveBeenCalledTimes($count > 0 ? 0 : 1); } public function provideValidRenames(): iterable @@ -182,13 +168,10 @@ class TagServiceTest extends TestCase */ public function renameTagToAnExistingNameThrowsException(?ApiKey $apiKey): void { - $find = $this->repo->findOneBy(Argument::cetera())->willReturn(new Tag('foo')); - $countTags = $this->repo->count(Argument::cetera())->willReturn(1); - $flush = $this->em->flush(Argument::any())->willReturn(null); + $this->repo->expects($this->once())->method('findOneBy')->willReturn(new Tag('foo')); + $this->repo->expects($this->once())->method('count')->willReturn(1); + $this->em->expects($this->never())->method('flush'); - $find->shouldBeCalled(); - $countTags->shouldBeCalled(); - $flush->shouldNotBeCalled(); $this->expectException(TagConflictException::class); $this->service->renameTag(TagRenaming::fromNames('foo', 'bar'), $apiKey); @@ -197,11 +180,10 @@ class TagServiceTest extends TestCase /** @test */ public function renamingTagThrowsExceptionWhenProvidedApiKeyIsNotAdmin(): void { - $getRepo = $this->em->getRepository(Tag::class); + $this->em->expects($this->never())->method('getRepository')->with(Tag::class); $this->expectExceptionMessage(ForbiddenTagOperationException::class); $this->expectExceptionMessage('You are not allowed to rename tags'); - $getRepo->shouldNotBeCalled(); $this->service->renameTag( TagRenaming::fromNames('foo', 'bar'), From 3b20f955ff31d8f11183441bc9c9b0bfafa901f1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 20:27:51 +0200 Subject: [PATCH 081/182] Migrated TagsInfoPaginatorAdapterTest to use PHPUnit mocks --- .../Adapter/TagsInfoPaginatorAdapterTest.php | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/module/Core/test/Tag/Paginator/Adapter/TagsInfoPaginatorAdapterTest.php b/module/Core/test/Tag/Paginator/Adapter/TagsInfoPaginatorAdapterTest.php index f2f573a4..99ebddf1 100644 --- a/module/Core/test/Tag/Paginator/Adapter/TagsInfoPaginatorAdapterTest.php +++ b/module/Core/test/Tag/Paginator/Adapter/TagsInfoPaginatorAdapterTest.php @@ -4,45 +4,37 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Tag\Paginator\Adapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Tag\Model\TagsParams; use Shlinkio\Shlink\Core\Tag\Paginator\Adapter\TagsInfoPaginatorAdapter; use Shlinkio\Shlink\Core\Tag\Repository\TagRepositoryInterface; class TagsInfoPaginatorAdapterTest extends TestCase { - use ProphecyTrait; - private TagsInfoPaginatorAdapter $adapter; - private ObjectProphecy $repo; + private MockObject $repo; protected function setUp(): void { - $this->repo = $this->prophesize(TagRepositoryInterface::class); - $this->adapter = new TagsInfoPaginatorAdapter($this->repo->reveal(), TagsParams::fromRawData([]), null); + $this->repo = $this->createMock(TagRepositoryInterface::class); + $this->adapter = new TagsInfoPaginatorAdapter($this->repo, TagsParams::fromRawData([]), null); } /** @test */ public function getSliceIsDelegatedToRepository(): void { - $findTags = $this->repo->findTagsWithInfo(Argument::cetera())->willReturn([]); - + $this->repo->expects($this->once())->method('findTagsWithInfo')->willReturn([]); $this->adapter->getSlice(1, 1); - - $findTags->shouldHaveBeenCalledOnce(); } /** @test */ public function getNbResultsIsDelegatedToRepository(): void { - $match = $this->repo->matchSingleScalarResult(Argument::cetera())->willReturn(3); + $this->repo->expects($this->once())->method('matchSingleScalarResult')->willReturn(3); $result = $this->adapter->getNbResults(); self::assertEquals(3, $result); - $match->shouldHaveBeenCalledOnce(); } } From 3b25fb27fef199b421f83f4a5e8a8b173e9468c4 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 20:28:45 +0200 Subject: [PATCH 082/182] Migrated TagsPaginatorAdapterTest to use PHPUnit mocks --- .../Adapter/TagsPaginatorAdapterTest.php | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/module/Core/test/Tag/Paginator/Adapter/TagsPaginatorAdapterTest.php b/module/Core/test/Tag/Paginator/Adapter/TagsPaginatorAdapterTest.php index ccad1ec1..bce7d9cc 100644 --- a/module/Core/test/Tag/Paginator/Adapter/TagsPaginatorAdapterTest.php +++ b/module/Core/test/Tag/Paginator/Adapter/TagsPaginatorAdapterTest.php @@ -4,34 +4,27 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Tag\Paginator\Adapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Tag\Model\TagsParams; use Shlinkio\Shlink\Core\Tag\Paginator\Adapter\TagsPaginatorAdapter; use Shlinkio\Shlink\Core\Tag\Repository\TagRepositoryInterface; class TagsPaginatorAdapterTest extends TestCase { - use ProphecyTrait; - private TagsPaginatorAdapter $adapter; - private ObjectProphecy $repo; + private MockObject $repo; protected function setUp(): void { - $this->repo = $this->prophesize(TagRepositoryInterface::class); - $this->adapter = new TagsPaginatorAdapter($this->repo->reveal(), TagsParams::fromRawData([]), null); + $this->repo = $this->createMock(TagRepositoryInterface::class); + $this->adapter = new TagsPaginatorAdapter($this->repo, TagsParams::fromRawData([]), null); } /** @test */ public function getSliceDelegatesToRepository(): void { - $match = $this->repo->match(Argument::cetera())->willReturn([]); - + $this->repo->expects($this->once())->method('match')->willReturn([]); $this->adapter->getSlice(1, 1); - - $match->shouldHaveBeenCalledOnce(); } } From 796543d194eb99bcfb4721bf4fd2d23400634df6 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 20:32:13 +0200 Subject: [PATCH 083/182] Migrated DoctrineBatchHelperTest to use PHPUnit mocks --- .../test/Util/DoctrineBatchHelperTest.php | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/module/Core/test/Util/DoctrineBatchHelperTest.php b/module/Core/test/Util/DoctrineBatchHelperTest.php index f6f9981d..aec4f0b6 100644 --- a/module/Core/test/Util/DoctrineBatchHelperTest.php +++ b/module/Core/test/Util/DoctrineBatchHelperTest.php @@ -5,23 +5,20 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Util; use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; 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; + private MockObject $em; protected function setUp(): void { - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->helper = new DoctrineBatchHelper($this->em->reveal()); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->helper = new DoctrineBatchHelper($this->em); } /** @@ -33,17 +30,17 @@ class DoctrineBatchHelperTest extends TestCase int $batchSize, int $expectedCalls, ): void { + $this->em->expects($this->once())->method('beginTransaction'); + $this->em->expects($this->once())->method('commit'); + $this->em->expects($this->never())->method('rollback'); + $this->em->expects($this->exactly($expectedCalls))->method('flush'); + $this->em->expects($this->exactly($expectedCalls))->method('clear'); + $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 @@ -56,15 +53,14 @@ class DoctrineBatchHelperTest extends TestCase /** @test */ public function transactionIsRolledBackWhenAnErrorOccurs(): void { - $flush = $this->em->flush()->willThrow(RuntimeException::class); + $this->em->expects($this->once())->method('flush')->willThrowException(new RuntimeException()); + $this->em->expects($this->once())->method('beginTransaction'); + $this->em->expects($this->never())->method('commit'); + $this->em->expects($this->once())->method('rollback'); $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 a2f34e02addb8e26307416db59d843c3edbe2edb Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 20:39:06 +0200 Subject: [PATCH 084/182] Migrated UrlValidatorTest to use PHPUnit mocks --- module/Core/test/Util/UrlValidatorTest.php | 80 +++++++++++----------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/module/Core/test/Util/UrlValidatorTest.php b/module/Core/test/Util/UrlValidatorTest.php index cc13bd2c..2610b7fd 100644 --- a/module/Core/test/Util/UrlValidatorTest.php +++ b/module/Core/test/Util/UrlValidatorTest.php @@ -7,35 +7,30 @@ namespace ShlinkioTest\Shlink\Core\Util; use Fig\Http\Message\RequestMethodInterface; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Psr7\Request; use GuzzleHttp\RequestOptions; use Laminas\Diactoros\Response; use Laminas\Diactoros\Stream; use PHPUnit\Framework\Assert; +use PHPUnit\Framework\MockObject\MockObject; 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; use Shlinkio\Shlink\Core\Util\UrlValidator; class UrlValidatorTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $httpClient; + private MockObject $httpClient; protected function setUp(): void { - $this->httpClient = $this->prophesize(ClientInterface::class); + $this->httpClient = $this->createMock(ClientInterface::class); } /** @test */ public function exceptionIsThrownWhenUrlIsInvalid(): void { - $request = $this->httpClient->request(Argument::cetera())->willThrow(ClientException::class); - - $request->shouldBeCalledOnce(); + $this->httpClient->expects($this->once())->method('request')->willThrowException($this->clientException()); $this->expectException(InvalidUrlException::class); $this->urlValidator()->validateUrl('http://foobar.com/12345/hello?foo=bar', true); @@ -46,10 +41,10 @@ class UrlValidatorTest extends TestCase { $expectedUrl = 'http://foobar.com'; - $request = $this->httpClient->request( + $this->httpClient->expects($this->once())->method('request')->with( RequestMethodInterface::METHOD_GET, $expectedUrl, - Argument::that(function (array $options) { + $this->callback(function (array $options) { Assert::assertArrayHasKey(RequestOptions::ALLOW_REDIRECTS, $options); Assert::assertEquals(['max' => 15], $options[RequestOptions::ALLOW_REDIRECTS]); Assert::assertArrayHasKey(RequestOptions::IDN_CONVERSION, $options); @@ -62,92 +57,91 @@ class UrlValidatorTest extends TestCase )->willReturn(new Response()); $this->urlValidator()->validateUrl($expectedUrl, true); - - $request->shouldHaveBeenCalledOnce(); } /** @test */ public function noCheckIsPerformedWhenUrlValidationIsDisabled(): void { - $request = $this->httpClient->request(Argument::cetera())->willReturn(new Response()); - + $this->httpClient->expects($this->never())->method('request'); $this->urlValidator()->validateUrl('', false); - - $request->shouldNotHaveBeenCalled(); } /** @test */ public function validateUrlWithTitleReturnsNullWhenRequestFailsAndValidationIsDisabled(): void { - $request = $this->httpClient->request(Argument::cetera())->willThrow(ClientException::class); + $this->httpClient->expects($this->once())->method('request')->willThrowException($this->clientException()); $result = $this->urlValidator(true)->validateUrlWithTitle('http://foobar.com/12345/hello?foo=bar', false); self::assertNull($result); - $request->shouldHaveBeenCalledOnce(); } /** @test */ public function validateUrlWithTitleReturnsNullWhenAutoResolutionIsDisabled(): void { - $request = $this->httpClient->request(Argument::cetera())->willReturn($this->respWithTitle()); + $this->httpClient->expects($this->never())->method('request'); $result = $this->urlValidator()->validateUrlWithTitle('http://foobar.com/12345/hello?foo=bar', false); self::assertNull($result); - $request->shouldNotHaveBeenCalled(); } /** @test */ public function validateUrlWithTitleReturnsNullWhenAutoResolutionIsDisabledAndValidationIsEnabled(): void { - $request = $this->httpClient->request(RequestMethodInterface::METHOD_HEAD, Argument::cetera())->willReturn( - $this->respWithTitle(), - ); + $this->httpClient->expects($this->once())->method('request')->with( + RequestMethodInterface::METHOD_HEAD, + $this->anything(), + $this->anything(), + )->willReturn($this->respWithTitle()); $result = $this->urlValidator()->validateUrlWithTitle('http://foobar.com/12345/hello?foo=bar', true); self::assertNull($result); - $request->shouldHaveBeenCalledOnce(); } /** @test */ public function validateUrlWithTitleResolvesTitleWhenAutoResolutionIsEnabled(): void { - $request = $this->httpClient->request(RequestMethodInterface::METHOD_GET, Argument::cetera())->willReturn( - $this->respWithTitle(), - ); + $this->httpClient->expects($this->once())->method('request')->with( + RequestMethodInterface::METHOD_GET, + $this->anything(), + $this->anything(), + )->willReturn($this->respWithTitle()); $result = $this->urlValidator(true)->validateUrlWithTitle('http://foobar.com/12345/hello?foo=bar', true); self::assertEquals('Resolved "title"', $result); - $request->shouldHaveBeenCalledOnce(); } /** @test */ public function validateUrlWithTitleReturnsNullWhenAutoResolutionIsEnabledAndReturnedContentTypeIsInvalid(): void { - $request = $this->httpClient->request(RequestMethodInterface::METHOD_GET, Argument::cetera())->willReturn( - new Response('php://memory', 200, ['Content-Type' => 'application/octet-stream']), - ); + $this->httpClient->expects($this->once())->method('request')->with( + RequestMethodInterface::METHOD_GET, + $this->anything(), + $this->anything(), + )->willReturn(new Response('php://memory', 200, ['Content-Type' => 'application/octet-stream'])); $result = $this->urlValidator(true)->validateUrlWithTitle('http://foobar.com/12345/hello?foo=bar', true); self::assertNull($result); - $request->shouldHaveBeenCalledOnce(); } /** @test */ public function validateUrlWithTitleReturnsNullWhenAutoResolutionIsEnabledAndBodyDoesNotContainTitle(): void { - $request = $this->httpClient->request(RequestMethodInterface::METHOD_GET, Argument::cetera())->willReturn( + $this->httpClient->expects($this->once())->method('request')->with( + RequestMethodInterface::METHOD_GET, + $this->anything(), + $this->anything(), + )->willReturn( new Response($this->createStreamWithContent('No title'), 200, ['Content-Type' => 'text/html']), ); $result = $this->urlValidator(true)->validateUrlWithTitle('http://foobar.com/12345/hello?foo=bar', true); self::assertNull($result); - $request->shouldHaveBeenCalledOnce(); } private function respWithTitle(): Response @@ -165,11 +159,17 @@ class UrlValidatorTest extends TestCase return $body; } - public function urlValidator(bool $autoResolveTitles = false): UrlValidator + private function clientException(): ClientException { - return new UrlValidator( - $this->httpClient->reveal(), - new UrlShortenerOptions(autoResolveTitles: $autoResolveTitles), + return new ClientException( + '', + new Request(RequestMethodInterface::METHOD_GET, ''), + new Response(), ); } + + public function urlValidator(bool $autoResolveTitles = false): UrlValidator + { + return new UrlValidator($this->httpClient, new UrlShortenerOptions(autoResolveTitles: $autoResolveTitles)); + } } From cff65737675dca2f3ea741336e1112e628f57baa Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 20:45:56 +0200 Subject: [PATCH 085/182] Migrated RequestTrackerTest to use PHPUnit mocks --- module/Core/test/Visit/RequestTrackerTest.php | 92 +++++++++---------- 1 file changed, 41 insertions(+), 51 deletions(-) diff --git a/module/Core/test/Visit/RequestTrackerTest.php b/module/Core/test/Visit/RequestTrackerTest.php index 81b70151..495963fb 100644 --- a/module/Core/test/Visit/RequestTrackerTest.php +++ b/module/Core/test/Visit/RequestTrackerTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Core\Visit; use Fig\Http\Message\RequestMethodInterface; use Laminas\Diactoros\ServerRequestFactory; use Mezzio\Router\Middleware\ImplicitHeadMiddleware; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Common\Middleware\IpAddressMiddlewareFactory; use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType; @@ -22,31 +20,28 @@ use Shlinkio\Shlink\Core\Visit\VisitsTrackerInterface; class RequestTrackerTest extends TestCase { - use ProphecyTrait; - private const LONG_URL = 'https://domain.com/foo/bar?some=thing'; private RequestTracker $requestTracker; - private ObjectProphecy $visitsTracker; - private ObjectProphecy $notFoundType; + private MockObject $notFoundType; + private MockObject $visitsTracker; private ServerRequestInterface $request; protected function setUp(): void { - $this->notFoundType = $this->prophesize(NotFoundType::class); - $this->visitsTracker = $this->prophesize(VisitsTrackerInterface::class); - + $this->visitsTracker = $this->createMock(VisitsTrackerInterface::class); $this->requestTracker = new RequestTracker( - $this->visitsTracker->reveal(), + $this->visitsTracker, new TrackingOptions( disableTrackParam: 'foobar', disableTrackingFrom: ['80.90.100.110', '192.168.10.0/24', '1.2.*.*'], ), ); + $this->notFoundType = $this->createMock(NotFoundType::class); $this->request = ServerRequestFactory::fromGlobals()->withAttribute( NotFoundType::class, - $this->notFoundType->reveal(), + $this->notFoundType, ); } @@ -56,11 +51,10 @@ class RequestTrackerTest extends TestCase */ public function trackingIsDisabledWhenRequestDoesNotMeetConditions(ServerRequestInterface $request): void { + $this->visitsTracker->expects($this->never())->method('track'); + $shortUrl = ShortUrl::withLongUrl(self::LONG_URL); - $this->requestTracker->trackIfApplicable($shortUrl, $request); - - $this->visitsTracker->track(Argument::cetera())->shouldNotHaveBeenCalled(); } public function provideNonTrackingRequests(): iterable @@ -91,61 +85,57 @@ class RequestTrackerTest extends TestCase public function trackingHappensOverShortUrlsWhenRequestMeetsConditions(): void { $shortUrl = ShortUrl::withLongUrl(self::LONG_URL); + $this->visitsTracker->expects($this->once())->method('track')->with( + $shortUrl, + $this->isInstanceOf(Visitor::class), + ); $this->requestTracker->trackIfApplicable($shortUrl, $this->request); - - $this->visitsTracker->track($shortUrl, Argument::type(Visitor::class))->shouldHaveBeenCalledOnce(); } /** @test */ public function baseUrlErrorIsTracked(): void { - $isBaseUrl = $this->notFoundType->isBaseUrl()->willReturn(true); - $isRegularNotFound = $this->notFoundType->isRegularNotFound()->willReturn(false); - $isInvalidShortUrl = $this->notFoundType->isInvalidShortUrl()->willReturn(false); + $this->notFoundType->expects($this->once())->method('isBaseUrl')->willReturn(true); + $this->notFoundType->expects($this->never())->method('isRegularNotFound'); + $this->notFoundType->expects($this->never())->method('isInvalidShortUrl'); + $this->visitsTracker->expects($this->once())->method('trackBaseUrlVisit')->with( + $this->isInstanceOf(Visitor::class), + ); + $this->visitsTracker->expects($this->never())->method('trackRegularNotFoundVisit'); + $this->visitsTracker->expects($this->never())->method('trackInvalidShortUrlVisit'); $this->requestTracker->trackNotFoundIfApplicable($this->request); - - $isBaseUrl->shouldHaveBeenCalledOnce(); - $isRegularNotFound->shouldNotHaveBeenCalled(); - $isInvalidShortUrl->shouldNotHaveBeenCalled(); - $this->visitsTracker->trackBaseUrlVisit(Argument::type(Visitor::class))->shouldHaveBeenCalledOnce(); - $this->visitsTracker->trackRegularNotFoundVisit(Argument::type(Visitor::class))->shouldNotHaveBeenCalled(); - $this->visitsTracker->trackInvalidShortUrlVisit(Argument::type(Visitor::class))->shouldNotHaveBeenCalled(); } /** @test */ public function regularNotFoundErrorIsTracked(): void { - $isBaseUrl = $this->notFoundType->isBaseUrl()->willReturn(false); - $isRegularNotFound = $this->notFoundType->isRegularNotFound()->willReturn(true); - $isInvalidShortUrl = $this->notFoundType->isInvalidShortUrl()->willReturn(false); + $this->notFoundType->expects($this->once())->method('isBaseUrl')->willReturn(false); + $this->notFoundType->expects($this->once())->method('isRegularNotFound')->willReturn(true); + $this->notFoundType->expects($this->never())->method('isInvalidShortUrl'); + $this->visitsTracker->expects($this->never())->method('trackBaseUrlVisit'); + $this->visitsTracker->expects($this->once())->method('trackRegularNotFoundVisit')->with( + $this->isInstanceOf(Visitor::class), + ); + $this->visitsTracker->expects($this->never())->method('trackInvalidShortUrlVisit'); $this->requestTracker->trackNotFoundIfApplicable($this->request); - - $isBaseUrl->shouldHaveBeenCalledOnce(); - $isRegularNotFound->shouldHaveBeenCalledOnce(); - $isInvalidShortUrl->shouldNotHaveBeenCalled(); - $this->visitsTracker->trackBaseUrlVisit(Argument::type(Visitor::class))->shouldNotHaveBeenCalled(); - $this->visitsTracker->trackRegularNotFoundVisit(Argument::type(Visitor::class))->shouldHaveBeenCalledOnce(); - $this->visitsTracker->trackInvalidShortUrlVisit(Argument::type(Visitor::class))->shouldNotHaveBeenCalled(); } /** @test */ public function invalidShortUrlErrorIsTracked(): void { - $isBaseUrl = $this->notFoundType->isBaseUrl()->willReturn(false); - $isRegularNotFound = $this->notFoundType->isRegularNotFound()->willReturn(false); - $isInvalidShortUrl = $this->notFoundType->isInvalidShortUrl()->willReturn(true); + $this->notFoundType->expects($this->once())->method('isBaseUrl')->willReturn(false); + $this->notFoundType->expects($this->once())->method('isRegularNotFound')->willReturn(false); + $this->notFoundType->expects($this->once())->method('isInvalidShortUrl')->willReturn(true); + $this->visitsTracker->expects($this->never())->method('trackBaseUrlVisit'); + $this->visitsTracker->expects($this->never())->method('trackRegularNotFoundVisit'); + $this->visitsTracker->expects($this->once())->method('trackInvalidShortUrlVisit')->with( + $this->isInstanceOf(Visitor::class), + ); $this->requestTracker->trackNotFoundIfApplicable($this->request); - - $isBaseUrl->shouldHaveBeenCalledOnce(); - $isRegularNotFound->shouldHaveBeenCalledOnce(); - $isInvalidShortUrl->shouldHaveBeenCalledOnce(); - $this->visitsTracker->trackBaseUrlVisit(Argument::type(Visitor::class))->shouldNotHaveBeenCalled(); - $this->visitsTracker->trackRegularNotFoundVisit(Argument::type(Visitor::class))->shouldNotHaveBeenCalled(); - $this->visitsTracker->trackInvalidShortUrlVisit(Argument::type(Visitor::class))->shouldHaveBeenCalledOnce(); } /** @@ -154,10 +144,10 @@ class RequestTrackerTest extends TestCase */ public function notFoundIsNotTrackedIfRequestDoesNotMeetConditions(ServerRequestInterface $request): void { - $this->requestTracker->trackNotFoundIfApplicable($request); + $this->visitsTracker->expects($this->never())->method('trackBaseUrlVisit'); + $this->visitsTracker->expects($this->never())->method('trackRegularNotFoundVisit'); + $this->visitsTracker->expects($this->never())->method('trackInvalidShortUrlVisit'); - $this->visitsTracker->trackBaseUrlVisit(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->visitsTracker->trackRegularNotFoundVisit(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->visitsTracker->trackInvalidShortUrlVisit(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->requestTracker->trackNotFoundIfApplicable($request); } } From 37b1306eb3db8e8f71439b11fb7179fbf0f513c3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 21:05:13 +0200 Subject: [PATCH 086/182] Migrated VisitsStatsHelperTest to use PHPUnit mocks --- .../Core/test/Visit/VisitsStatsHelperTest.php | 180 ++++++++++-------- 1 file changed, 96 insertions(+), 84 deletions(-) diff --git a/module/Core/test/Visit/VisitsStatsHelperTest.php b/module/Core/test/Visit/VisitsStatsHelperTest.php index c255dd6a..b6ab393e 100644 --- a/module/Core/test/Visit/VisitsStatsHelperTest.php +++ b/module/Core/test/Visit/VisitsStatsHelperTest.php @@ -6,10 +6,8 @@ namespace ShlinkioTest\Shlink\Core\Visit; use Doctrine\ORM\EntityManagerInterface; use Laminas\Stdlib\ArrayUtils; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Domain\Entity\Domain; use Shlinkio\Shlink\Core\Domain\Repository\DomainRepository; use Shlinkio\Shlink\Core\Exception\DomainNotFoundException; @@ -38,15 +36,14 @@ use function range; class VisitsStatsHelperTest extends TestCase { use ApiKeyHelpersTrait; - use ProphecyTrait; private VisitsStatsHelper $helper; - private ObjectProphecy $em; + private MockObject $em; protected function setUp(): void { - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->helper = new VisitsStatsHelper($this->em->reveal()); + $this->em = $this->createMock(EntityManagerInterface::class); + $this->helper = new VisitsStatsHelper($this->em); } /** @@ -55,19 +52,18 @@ class VisitsStatsHelperTest extends TestCase */ public function returnsExpectedVisitsStats(int $expectedCount): void { - $repo = $this->prophesize(VisitRepository::class); - $count = $repo->countNonOrphanVisits(new VisitsCountFiltering())->willReturn($expectedCount * 3); - $countOrphan = $repo->countOrphanVisits(Argument::type(VisitsCountFiltering::class))->willReturn( - $expectedCount, + $repo = $this->createMock(VisitRepository::class); + $repo->expects($this->once())->method('countNonOrphanVisits')->with(new VisitsCountFiltering())->willReturn( + $expectedCount * 3, ); - $getRepo = $this->em->getRepository(Visit::class)->willReturn($repo->reveal()); + $repo->expects($this->once())->method('countOrphanVisits')->with( + $this->isInstanceOf(VisitsCountFiltering::class) + )->willReturn($expectedCount); + $this->em->expects($this->once())->method('getRepository')->with(Visit::class)->willReturn($repo); $stats = $this->helper->getVisitsStats(); self::assertEquals(new VisitsStats($expectedCount * 3, $expectedCount), $stats); - $count->shouldHaveBeenCalledOnce(); - $countOrphan->shouldHaveBeenCalledOnce(); - $getRepo->shouldHaveBeenCalledOnce(); } public function provideCounts(): iterable @@ -85,22 +81,28 @@ class VisitsStatsHelperTest extends TestCase $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); $spec = $apiKey?->spec(); - $repo = $this->prophesize(ShortUrlRepositoryInterface::class); - $count = $repo->shortCodeIsInUse($identifier, $spec)->willReturn( - true, - ); - $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal())->shouldBeCalledOnce(); + $repo = $this->createMock(ShortUrlRepositoryInterface::class); + $repo->expects($this->once())->method('shortCodeIsInUse')->with($identifier, $spec)->willReturn(true); $list = map(range(0, 1), fn () => Visit::forValidShortUrl(ShortUrl::createEmpty(), Visitor::emptyInstance())); - $repo2 = $this->prophesize(VisitRepository::class); - $repo2->findVisitsByShortCode($identifier, Argument::type(VisitsListFiltering::class))->willReturn($list); - $repo2->countVisitsByShortCode($identifier, Argument::type(VisitsCountFiltering::class))->willReturn(1); - $this->em->getRepository(Visit::class)->willReturn($repo2->reveal())->shouldBeCalledOnce(); + $repo2 = $this->createMock(VisitRepository::class); + $repo2->method('findVisitsByShortCode')->with( + $identifier, + $this->isInstanceOf(VisitsListFiltering::class), + )->willReturn($list); + $repo2->method('countVisitsByShortCode')->with( + $identifier, + $this->isInstanceOf(VisitsCountFiltering::class) + )->willReturn(1); + + $this->em->expects($this->exactly(2))->method('getRepository')->willReturnMap([ + [ShortUrl::class, $repo], + [Visit::class, $repo2], + ]); $paginator = $this->helper->visitsForShortUrl($identifier, new VisitsParams(), $apiKey); self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults())); - $count->shouldHaveBeenCalledOnce(); } /** @test */ @@ -109,14 +111,11 @@ class VisitsStatsHelperTest extends TestCase $shortCode = '123ABC'; $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); - $repo = $this->prophesize(ShortUrlRepositoryInterface::class); - $count = $repo->shortCodeIsInUse($identifier, null)->willReturn( - false, - ); - $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal())->shouldBeCalledOnce(); + $repo = $this->createMock(ShortUrlRepositoryInterface::class); + $repo->expects($this->once())->method('shortCodeIsInUse')->with($identifier, null)->willReturn(false); + $this->em->expects($this->once())->method('getRepository')->with(ShortUrl::class)->willReturn($repo); $this->expectException(ShortUrlNotFoundException::class); - $count->shouldBeCalledOnce(); $this->helper->visitsForShortUrl($identifier, new VisitsParams()); } @@ -126,13 +125,11 @@ class VisitsStatsHelperTest extends TestCase { $tag = 'foo'; $apiKey = ApiKey::create(); - $repo = $this->prophesize(TagRepository::class); - $tagExists = $repo->tagExists($tag, $apiKey)->willReturn(false); - $getRepo = $this->em->getRepository(Tag::class)->willReturn($repo->reveal()); + $repo = $this->createMock(TagRepository::class); + $repo->expects($this->once())->method('tagExists')->with($tag, $apiKey)->willReturn(false); + $this->em->expects($this->once())->method('getRepository')->with(Tag::class)->willReturn($repo); $this->expectException(TagNotFoundException::class); - $tagExists->shouldBeCalledOnce(); - $getRepo->shouldBeCalledOnce(); $this->helper->visitsForTag($tag, new VisitsParams(), $apiKey); } @@ -144,21 +141,24 @@ class VisitsStatsHelperTest extends TestCase public function visitsForTagAreReturnedAsExpected(?ApiKey $apiKey): void { $tag = 'foo'; - $repo = $this->prophesize(TagRepository::class); - $tagExists = $repo->tagExists($tag, $apiKey)->willReturn(true); - $getRepo = $this->em->getRepository(Tag::class)->willReturn($repo->reveal()); + $repo = $this->createMock(TagRepository::class); + $repo->expects($this->once())->method('tagExists')->with($tag, $apiKey)->willReturn(true); $list = map(range(0, 1), fn () => Visit::forValidShortUrl(ShortUrl::createEmpty(), Visitor::emptyInstance())); - $repo2 = $this->prophesize(VisitRepository::class); - $repo2->findVisitsByTag($tag, Argument::type(VisitsListFiltering::class))->willReturn($list); - $repo2->countVisitsByTag($tag, Argument::type(VisitsCountFiltering::class))->willReturn(1); - $this->em->getRepository(Visit::class)->willReturn($repo2->reveal())->shouldBeCalledOnce(); + $repo2 = $this->createMock(VisitRepository::class); + $repo2->method('findVisitsByTag')->with($tag, $this->isInstanceOf(VisitsListFiltering::class))->willReturn( + $list, + ); + $repo2->method('countVisitsByTag')->with($tag, $this->isInstanceOf(VisitsCountFiltering::class))->willReturn(1); + + $this->em->expects($this->exactly(2))->method('getRepository')->willReturnMap([ + [Tag::class, $repo], + [Visit::class, $repo2], + ]); $paginator = $this->helper->visitsForTag($tag, new VisitsParams(), $apiKey); self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults())); - $tagExists->shouldHaveBeenCalledOnce(); - $getRepo->shouldHaveBeenCalledOnce(); } /** @test */ @@ -166,13 +166,11 @@ class VisitsStatsHelperTest extends TestCase { $domain = 'foo.com'; $apiKey = ApiKey::create(); - $repo = $this->prophesize(DomainRepository::class); - $domainExists = $repo->domainExists($domain, $apiKey)->willReturn(false); - $getRepo = $this->em->getRepository(Domain::class)->willReturn($repo->reveal()); + $repo = $this->createMock(DomainRepository::class); + $repo->expects($this->once())->method('domainExists')->with($domain, $apiKey)->willReturn(false); + $this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo); $this->expectException(DomainNotFoundException::class); - $domainExists->shouldBeCalledOnce(); - $getRepo->shouldBeCalledOnce(); $this->helper->visitsForDomain($domain, new VisitsParams(), $apiKey); } @@ -184,21 +182,28 @@ class VisitsStatsHelperTest extends TestCase public function visitsForNonDefaultDomainAreReturnedAsExpected(?ApiKey $apiKey): void { $domain = 'foo.com'; - $repo = $this->prophesize(DomainRepository::class); - $domainExists = $repo->domainExists($domain, $apiKey)->willReturn(true); - $getRepo = $this->em->getRepository(Domain::class)->willReturn($repo->reveal()); + $repo = $this->createMock(DomainRepository::class); + $repo->expects($this->once())->method('domainExists')->with($domain, $apiKey)->willReturn(true); $list = map(range(0, 1), fn () => Visit::forValidShortUrl(ShortUrl::createEmpty(), Visitor::emptyInstance())); - $repo2 = $this->prophesize(VisitRepository::class); - $repo2->findVisitsByDomain($domain, Argument::type(VisitsListFiltering::class))->willReturn($list); - $repo2->countVisitsByDomain($domain, Argument::type(VisitsCountFiltering::class))->willReturn(1); - $this->em->getRepository(Visit::class)->willReturn($repo2->reveal())->shouldBeCalledOnce(); + $repo2 = $this->createMock(VisitRepository::class); + $repo2->method('findVisitsByDomain')->with( + $domain, + $this->isInstanceOf(VisitsListFiltering::class), + )->willReturn($list); + $repo2->method('countVisitsByDomain')->with( + $domain, + $this->isInstanceOf(VisitsCountFiltering::class), + )->willReturn(1); + + $this->em->expects($this->exactly(2))->method('getRepository')->willReturnMap([ + [Domain::class, $repo], + [Visit::class, $repo2], + ]); $paginator = $this->helper->visitsForDomain($domain, new VisitsParams(), $apiKey); self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults())); - $domainExists->shouldHaveBeenCalledOnce(); - $getRepo->shouldHaveBeenCalledOnce(); } /** @@ -207,56 +212,63 @@ class VisitsStatsHelperTest extends TestCase */ public function visitsForDefaultDomainAreReturnedAsExpected(?ApiKey $apiKey): void { - $repo = $this->prophesize(DomainRepository::class); - $domainExists = $repo->domainExists(Argument::cetera()); - $getRepo = $this->em->getRepository(Domain::class)->willReturn($repo->reveal()); + $repo = $this->createMock(DomainRepository::class); + $repo->expects($this->never())->method('domainExists'); $list = map(range(0, 1), fn () => Visit::forValidShortUrl(ShortUrl::createEmpty(), Visitor::emptyInstance())); - $repo2 = $this->prophesize(VisitRepository::class); - $repo2->findVisitsByDomain('DEFAULT', Argument::type(VisitsListFiltering::class))->willReturn($list); - $repo2->countVisitsByDomain('DEFAULT', Argument::type(VisitsCountFiltering::class))->willReturn(1); - $this->em->getRepository(Visit::class)->willReturn($repo2->reveal())->shouldBeCalledOnce(); + $repo2 = $this->createMock(VisitRepository::class); + $repo2->method('findVisitsByDomain')->with( + 'DEFAULT', + $this->isInstanceOf(VisitsListFiltering::class), + )->willReturn($list); + $repo2->method('countVisitsByDomain')->with( + 'DEFAULT', + $this->isInstanceOf(VisitsCountFiltering::class), + )->willReturn(1); + + $this->em->expects($this->exactly(2))->method('getRepository')->willReturnMap([ + [Domain::class, $repo], + [Visit::class, $repo2], + ]); $paginator = $this->helper->visitsForDomain('DEFAULT', new VisitsParams(), $apiKey); self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults())); - $domainExists->shouldNotHaveBeenCalled(); - $getRepo->shouldHaveBeenCalledOnce(); } /** @test */ public function orphanVisitsAreReturnedAsExpected(): void { $list = map(range(0, 3), fn () => Visit::forBasePath(Visitor::emptyInstance())); - $repo = $this->prophesize(VisitRepository::class); - $countVisits = $repo->countOrphanVisits(Argument::type(VisitsCountFiltering::class))->willReturn(count($list)); - $listVisits = $repo->findOrphanVisits(Argument::type(VisitsListFiltering::class))->willReturn($list); - $getRepo = $this->em->getRepository(Visit::class)->willReturn($repo->reveal()); + $repo = $this->createMock(VisitRepository::class); + $repo->expects($this->once())->method('countOrphanVisits')->with( + $this->isInstanceOf(VisitsCountFiltering::class), + )->willReturn(count($list)); + $repo->expects($this->once())->method('findOrphanVisits')->with( + $this->isInstanceOf(VisitsListFiltering::class), + )->willReturn($list); + $this->em->expects($this->once())->method('getRepository')->with(Visit::class)->willReturn($repo); $paginator = $this->helper->orphanVisits(new VisitsParams()); self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults())); - $listVisits->shouldHaveBeenCalledOnce(); - $countVisits->shouldHaveBeenCalledOnce(); - $getRepo->shouldHaveBeenCalledOnce(); } /** @test */ public function nonOrphanVisitsAreReturnedAsExpected(): void { $list = map(range(0, 3), fn () => Visit::forValidShortUrl(ShortUrl::createEmpty(), Visitor::emptyInstance())); - $repo = $this->prophesize(VisitRepository::class); - $countVisits = $repo->countNonOrphanVisits(Argument::type(VisitsCountFiltering::class))->willReturn( - count($list), - ); - $listVisits = $repo->findNonOrphanVisits(Argument::type(VisitsListFiltering::class))->willReturn($list); - $getRepo = $this->em->getRepository(Visit::class)->willReturn($repo->reveal()); + $repo = $this->createMock(VisitRepository::class); + $repo->expects($this->once())->method('countNonOrphanVisits')->with( + $this->isInstanceOf(VisitsCountFiltering::class), + )->willReturn(count($list)); + $repo->expects($this->once())->method('findNonOrphanVisits')->with( + $this->isInstanceOf(VisitsListFiltering::class), + )->willReturn($list); + $this->em->expects($this->once())->method('getRepository')->with(Visit::class)->willReturn($repo); $paginator = $this->helper->nonOrphanVisits(new VisitsParams()); self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults())); - $listVisits->shouldHaveBeenCalledOnce(); - $countVisits->shouldHaveBeenCalledOnce(); - $getRepo->shouldHaveBeenCalledOnce(); } } From a45d6e6b443f40f304b5c684ccd31a57698ae1a0 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 21:08:58 +0200 Subject: [PATCH 087/182] Migrated VisitsTrackerTest to use PHPUnit mocks --- module/Core/test/Visit/VisitsTrackerTest.php | 49 +++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/module/Core/test/Visit/VisitsTrackerTest.php b/module/Core/test/Visit/VisitsTrackerTest.php index c10d57b1..2653fd42 100644 --- a/module/Core/test/Visit/VisitsTrackerTest.php +++ b/module/Core/test/Visit/VisitsTrackerTest.php @@ -5,10 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Visit; use Doctrine\ORM\EntityManager; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\EventDispatcher\EventDispatcherInterface; use Shlinkio\Shlink\Core\EventDispatcher\Event\UrlVisited; use Shlinkio\Shlink\Core\Options\TrackingOptions; @@ -19,16 +17,13 @@ use Shlinkio\Shlink\Core\Visit\VisitsTracker; class VisitsTrackerTest extends TestCase { - use ProphecyTrait; - - private VisitsTracker $visitsTracker; - private ObjectProphecy $em; - private ObjectProphecy $eventDispatcher; + private MockObject $em; + private MockObject $eventDispatcher; protected function setUp(): void { - $this->em = $this->prophesize(EntityManager::class); - $this->eventDispatcher = $this->prophesize(EventDispatcherInterface::class); + $this->em = $this->createMock(EntityManager::class); + $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); } /** @@ -37,14 +32,16 @@ class VisitsTrackerTest extends TestCase */ public function trackPersistsVisitAndDispatchesEvent(string $method, array $args): void { - $persist = $this->em->persist(Argument::that(fn (Visit $visit) => $visit->setId('1')))->will(function (): void { - }); + $this->em->expects($this->once())->method('persist')->with( + $this->callback(fn (Visit $visit) => $visit->setId('1') !== null) + ); + $this->em->expects($this->once())->method('flush'); + $this->eventDispatcher->expects($this->once())->method('dispatch')->with( + $this->isInstanceOf(UrlVisited::class), + ); $this->visitsTracker()->{$method}(...$args); - $persist->shouldHaveBeenCalledOnce(); - $this->em->flush()->shouldHaveBeenCalledOnce(); - $this->eventDispatcher->dispatch(Argument::type(UrlVisited::class))->shouldHaveBeenCalled(); } /** @@ -53,11 +50,11 @@ class VisitsTrackerTest extends TestCase */ public function trackingIsSkippedCompletelyWhenDisabledFromOptions(string $method, array $args): void { - $this->visitsTracker(new TrackingOptions(disableTracking: true))->{$method}(...$args); + $this->em->expects($this->never())->method('persist'); + $this->em->expects($this->never())->method('flush'); + $this->eventDispatcher->expects($this->never())->method('dispatch'); - $this->eventDispatcher->dispatch(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->em->persist(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->em->flush()->shouldNotHaveBeenCalled(); + $this->visitsTracker(new TrackingOptions(disableTracking: true))->{$method}(...$args); } public function provideTrackingMethodNames(): iterable @@ -74,11 +71,11 @@ class VisitsTrackerTest extends TestCase */ public function orphanVisitsAreNotTrackedWhenDisabled(string $method): void { - $this->visitsTracker(new TrackingOptions(trackOrphanVisits: false))->{$method}(Visitor::emptyInstance()); + $this->em->expects($this->never())->method('persist'); + $this->em->expects($this->never())->method('flush'); + $this->eventDispatcher->expects($this->never())->method('dispatch'); - $this->eventDispatcher->dispatch(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->em->persist(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->em->flush()->shouldNotHaveBeenCalled(); + $this->visitsTracker(new TrackingOptions(trackOrphanVisits: false))->{$method}(Visitor::emptyInstance()); } public function provideOrphanTrackingMethodNames(): iterable @@ -90,10 +87,6 @@ class VisitsTrackerTest extends TestCase private function visitsTracker(?TrackingOptions $options = null): VisitsTracker { - return new VisitsTracker( - $this->em->reveal(), - $this->eventDispatcher->reveal(), - $options ?? new TrackingOptions(), - ); + return new VisitsTracker($this->em, $this->eventDispatcher, $options ?? new TrackingOptions()); } } From 04419a72425d272037846a2f8956d54e82da8b1b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 21:21:23 +0200 Subject: [PATCH 088/182] Migrated VisitLocatorTest to use PHPUnit mocks --- .../Visit/Geolocation/VisitLocatorTest.php | 60 ++++++------------- 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/module/Core/test/Visit/Geolocation/VisitLocatorTest.php b/module/Core/test/Visit/Geolocation/VisitLocatorTest.php index 6cdd421d..4141f7c5 100644 --- a/module/Core/test/Visit/Geolocation/VisitLocatorTest.php +++ b/module/Core/test/Visit/Geolocation/VisitLocatorTest.php @@ -7,11 +7,8 @@ namespace ShlinkioTest\Shlink\Core\Visit\Geolocation; use Doctrine\ORM\EntityManager; use Exception; use PHPUnit\Framework\Assert; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\MethodProphecy; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\Visit\Entity\Visit; @@ -32,19 +29,17 @@ use function sprintf; class VisitLocatorTest extends TestCase { - use ProphecyTrait; - private VisitLocator $visitService; - private ObjectProphecy $em; - private ObjectProphecy $repo; + private MockObject $em; + private MockObject $repo; protected function setUp(): void { - $this->em = $this->prophesize(EntityManager::class); - $this->repo = $this->prophesize(VisitRepositoryInterface::class); - $this->em->getRepository(Visit::class)->willReturn($this->repo->reveal()); + $this->em = $this->createMock(EntityManager::class); + $this->repo = $this->createMock(VisitRepositoryInterface::class); + $this->em->method('getRepository')->with(Visit::class)->willReturn($this->repo); - $this->visitService = new VisitLocator($this->em->reveal()); + $this->visitService = new VisitLocator($this->em); } /** @@ -61,14 +56,13 @@ class VisitLocatorTest extends TestCase Visit::forValidShortUrl(ShortUrl::withLongUrl(sprintf('short_code_%s', $i)), Visitor::emptyInstance()), ); - $findVisits = $this->mockRepoMethod($expectedRepoMethodName)->willReturn($unlocatedVisits); + $this->repo->expects($this->once())->method($expectedRepoMethodName)->willReturn($unlocatedVisits); - $persist = $this->em->persist(Argument::type(Visit::class))->will(function (): void { - }); - $flush = $this->em->flush()->will(function (): void { - }); - $clear = $this->em->clear()->will(function (): void { - }); + $this->em->expects($this->exactly(count($unlocatedVisits)))->method('persist')->with( + $this->isInstanceOf(Visit::class), + ); + $this->em->expects($this->exactly((int) floor(count($unlocatedVisits) / 200) + 1))->method('flush'); + $this->em->expects($this->exactly((int) floor(count($unlocatedVisits) / 200) + 1))->method('clear'); $this->visitService->{$serviceMethodName}(new class implements VisitGeolocationHelperInterface { public function geolocateVisit(Visit $visit): Location @@ -84,11 +78,6 @@ class VisitLocatorTest extends TestCase Assert::assertInstanceOf(Visit::class, array_shift($args)); } }); - - $findVisits->shouldHaveBeenCalledOnce(); - $persist->shouldHaveBeenCalledTimes(count($unlocatedVisits)); - $flush->shouldHaveBeenCalledTimes(floor(count($unlocatedVisits) / 200) + 1); - $clear->shouldHaveBeenCalledTimes(floor(count($unlocatedVisits) / 200) + 1); } public function provideMethodNames(): iterable @@ -111,14 +100,13 @@ class VisitLocatorTest extends TestCase Visit::forValidShortUrl(ShortUrl::withLongUrl('foo'), Visitor::emptyInstance()), ]; - $findVisits = $this->mockRepoMethod($expectedRepoMethodName)->willReturn($unlocatedVisits); + $this->repo->expects($this->once())->method($expectedRepoMethodName)->willReturn($unlocatedVisits); - $persist = $this->em->persist(Argument::type(Visit::class))->will(function (): void { - }); - $flush = $this->em->flush()->will(function (): void { - }); - $clear = $this->em->clear()->will(function (): void { - }); + $this->em->expects($this->exactly($isNonLocatableAddress ? 1 : 0))->method('persist')->with( + $this->isInstanceOf(Visit::class), + ); + $this->em->expects($this->once())->method('flush'); + $this->em->expects($this->once())->method('clear'); $this->visitService->{$serviceMethodName}( new class ($isNonLocatableAddress) implements VisitGeolocationHelperInterface { @@ -138,11 +126,6 @@ class VisitLocatorTest extends TestCase } }, ); - - $findVisits->shouldHaveBeenCalledOnce(); - $persist->shouldHaveBeenCalledTimes($isNonLocatableAddress ? 1 : 0); - $flush->shouldHaveBeenCalledOnce(); - $clear->shouldHaveBeenCalledOnce(); } public function provideIsNonLocatableAddress(): iterable @@ -162,9 +145,4 @@ class VisitLocatorTest extends TestCase yield 'locateAllVisits - locatable address' => ['locateAllVisits', 'findAllVisits', false]; yield 'locateAllVisits - non-locatable address' => ['locateAllVisits', 'findAllVisits', true]; } - - private function mockRepoMethod(string $methodName): MethodProphecy - { - return (new MethodProphecy($this->repo, $methodName, new Argument\ArgumentsWildcard([]))); - } } From d3af51f68415e3a07bbbbe6a0b94fef61bf1e7da Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 21:24:30 +0200 Subject: [PATCH 089/182] Migrated VisitToLocationHelperTest to use PHPUnit mocks --- .../Geolocation/VisitToLocationHelperTest.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/module/Core/test/Visit/Geolocation/VisitToLocationHelperTest.php b/module/Core/test/Visit/Geolocation/VisitToLocationHelperTest.php index 505a24dd..b32dc037 100644 --- a/module/Core/test/Visit/Geolocation/VisitToLocationHelperTest.php +++ b/module/Core/test/Visit/Geolocation/VisitToLocationHelperTest.php @@ -4,10 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Visit\Geolocation; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Util\IpAddress; use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException; use Shlinkio\Shlink\Core\Visit\Entity\Visit; @@ -18,15 +16,13 @@ use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface; class VisitToLocationHelperTest extends TestCase { - use ProphecyTrait; - private VisitToLocationHelper $helper; - private ObjectProphecy $ipLocationResolver; + private MockObject $ipLocationResolver; protected function setUp(): void { - $this->ipLocationResolver = $this->prophesize(IpLocationResolverInterface::class); - $this->helper = new VisitToLocationHelper($this->ipLocationResolver->reveal()); + $this->ipLocationResolver = $this->createMock(IpLocationResolverInterface::class); + $this->helper = new VisitToLocationHelper($this->ipLocationResolver); } /** @@ -38,7 +34,7 @@ class VisitToLocationHelperTest extends TestCase IpCannotBeLocatedException $expectedException, ): void { $this->expectExceptionObject($expectedException); - $this->ipLocationResolver->resolveIpLocation(Argument::cetera())->shouldNotBeCalled(); + $this->ipLocationResolver->expects($this->never())->method('resolveIpLocation'); $this->helper->resolveVisitLocation($visit); } @@ -58,8 +54,7 @@ class VisitToLocationHelperTest extends TestCase $e = new WrongIpException(''); $this->expectExceptionObject(IpCannotBeLocatedException::forError($e)); - $this->ipLocationResolver->resolveIpLocation(Argument::cetera())->willThrow($e) - ->shouldBeCalledOnce(); + $this->ipLocationResolver->expects($this->once())->method('resolveIpLocation')->willThrowException($e); $this->helper->resolveVisitLocation(Visit::forBasePath(new Visitor('foo', 'bar', '1.2.3.4', ''))); } From 7442905873cac1de789afbca12ce878233647b24 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 21:55:06 +0200 Subject: [PATCH 090/182] Migrated NonOrphanVisitsPaginatorAdapterTest to use PHPUnit mocks --- .../NonOrphanVisitsPaginatorAdapterTest.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/module/Core/test/Visit/Paginator/Adapter/NonOrphanVisitsPaginatorAdapterTest.php b/module/Core/test/Visit/Paginator/Adapter/NonOrphanVisitsPaginatorAdapterTest.php index 0787cbba..1bd03109 100644 --- a/module/Core/test/Visit/Paginator/Adapter/NonOrphanVisitsPaginatorAdapterTest.php +++ b/module/Core/test/Visit/Paginator/Adapter/NonOrphanVisitsPaginatorAdapterTest.php @@ -4,9 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Visit\Paginator\Adapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Core\Visit\Model\Visitor; use Shlinkio\Shlink\Core\Visit\Model\VisitsParams; @@ -18,34 +17,31 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class NonOrphanVisitsPaginatorAdapterTest extends TestCase { - use ProphecyTrait; - private NonOrphanVisitsPaginatorAdapter $adapter; - private ObjectProphecy $repo; + private MockObject $repo; private VisitsParams $params; private ApiKey $apiKey; protected function setUp(): void { - $this->repo = $this->prophesize(VisitRepositoryInterface::class); + $this->repo = $this->createMock(VisitRepositoryInterface::class); $this->params = VisitsParams::fromRawData([]); $this->apiKey = ApiKey::create(); - $this->adapter = new NonOrphanVisitsPaginatorAdapter($this->repo->reveal(), $this->params, $this->apiKey); + $this->adapter = new NonOrphanVisitsPaginatorAdapter($this->repo, $this->params, $this->apiKey); } /** @test */ public function countDelegatesToRepository(): void { $expectedCount = 5; - $repoCount = $this->repo->countNonOrphanVisits( + $this->repo->expects($this->once())->method('countNonOrphanVisits')->with( new VisitsCountFiltering($this->params->dateRange, $this->params->excludeBots, $this->apiKey), )->willReturn($expectedCount); $result = $this->adapter->getNbResults(); self::assertEquals($expectedCount, $result); - $repoCount->shouldHaveBeenCalledOnce(); } /** @@ -56,7 +52,7 @@ class NonOrphanVisitsPaginatorAdapterTest extends TestCase { $visitor = Visitor::emptyInstance(); $list = [Visit::forRegularNotFound($visitor), Visit::forInvalidShortUrl($visitor)]; - $repoFind = $this->repo->findNonOrphanVisits(new VisitsListFiltering( + $this->repo->expects($this->once())->method('findNonOrphanVisits')->with(new VisitsListFiltering( $this->params->dateRange, $this->params->excludeBots, $this->apiKey, @@ -67,7 +63,6 @@ class NonOrphanVisitsPaginatorAdapterTest extends TestCase $result = $this->adapter->getSlice($offset, $limit); self::assertEquals($list, $result); - $repoFind->shouldHaveBeenCalledOnce(); } public function provideLimitAndOffset(): iterable From a4373aee91089d51c8db763bd98edc10520b8783 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 21:56:34 +0200 Subject: [PATCH 091/182] Migrated OrphanVisitsPaginatorAdapterTest to use PHPUnit mocks --- .../OrphanVisitsPaginatorAdapterTest.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/module/Core/test/Visit/Paginator/Adapter/OrphanVisitsPaginatorAdapterTest.php b/module/Core/test/Visit/Paginator/Adapter/OrphanVisitsPaginatorAdapterTest.php index 9e85b5fa..adad4322 100644 --- a/module/Core/test/Visit/Paginator/Adapter/OrphanVisitsPaginatorAdapterTest.php +++ b/module/Core/test/Visit/Paginator/Adapter/OrphanVisitsPaginatorAdapterTest.php @@ -4,9 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Visit\Paginator\Adapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Core\Visit\Model\Visitor; use Shlinkio\Shlink\Core\Visit\Model\VisitsParams; @@ -17,31 +16,28 @@ use Shlinkio\Shlink\Core\Visit\Repository\VisitRepositoryInterface; class OrphanVisitsPaginatorAdapterTest extends TestCase { - use ProphecyTrait; - private OrphanVisitsPaginatorAdapter $adapter; - private ObjectProphecy $repo; + private MockObject $repo; private VisitsParams $params; protected function setUp(): void { - $this->repo = $this->prophesize(VisitRepositoryInterface::class); + $this->repo = $this->createMock(VisitRepositoryInterface::class); $this->params = VisitsParams::fromRawData([]); - $this->adapter = new OrphanVisitsPaginatorAdapter($this->repo->reveal(), $this->params); + $this->adapter = new OrphanVisitsPaginatorAdapter($this->repo, $this->params); } /** @test */ public function countDelegatesToRepository(): void { $expectedCount = 5; - $repoCount = $this->repo->countOrphanVisits( + $this->repo->expects($this->once())->method('countOrphanVisits')->with( new VisitsCountFiltering($this->params->dateRange), )->willReturn($expectedCount); $result = $this->adapter->getNbResults(); self::assertEquals($expectedCount, $result); - $repoCount->shouldHaveBeenCalledOnce(); } /** @@ -52,14 +48,13 @@ class OrphanVisitsPaginatorAdapterTest extends TestCase { $visitor = Visitor::emptyInstance(); $list = [Visit::forRegularNotFound($visitor), Visit::forInvalidShortUrl($visitor)]; - $repoFind = $this->repo->findOrphanVisits( + $this->repo->expects($this->once())->method('findOrphanVisits')->with( new VisitsListFiltering($this->params->dateRange, $this->params->excludeBots, null, $limit, $offset), )->willReturn($list); $result = $this->adapter->getSlice($offset, $limit); self::assertEquals($list, $result); - $repoFind->shouldHaveBeenCalledOnce(); } public function provideLimitAndOffset(): iterable From 257134cd808f4bd1bda13ed28655240a3f034e51 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 21:59:18 +0200 Subject: [PATCH 092/182] Migrated VisitsForTagPaginatorAdapterTest to use PHPUnit mocks --- .../ShortUrlVisitsPaginatorAdapterTest.php | 19 +++++---------- .../VisitsForTagPaginatorAdapterTest.php | 24 +++++-------------- 2 files changed, 12 insertions(+), 31 deletions(-) diff --git a/module/Core/test/Visit/Paginator/Adapter/ShortUrlVisitsPaginatorAdapterTest.php b/module/Core/test/Visit/Paginator/Adapter/ShortUrlVisitsPaginatorAdapterTest.php index 91f2fc32..3e6c3737 100644 --- a/module/Core/test/Visit/Paginator/Adapter/ShortUrlVisitsPaginatorAdapterTest.php +++ b/module/Core/test/Visit/Paginator/Adapter/ShortUrlVisitsPaginatorAdapterTest.php @@ -4,9 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Visit\Paginator\Adapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\Visit\Model\VisitsParams; @@ -18,13 +17,11 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ShortUrlVisitsPaginatorAdapterTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $repo; + private MockObject $repo; protected function setUp(): void { - $this->repo = $this->prophesize(VisitRepositoryInterface::class); + $this->repo = $this->createMock(VisitRepositoryInterface::class); } /** @test */ @@ -34,7 +31,7 @@ class ShortUrlVisitsPaginatorAdapterTest extends TestCase $limit = 1; $offset = 5; $adapter = $this->createAdapter(null); - $findVisits = $this->repo->findVisitsByShortCode( + $this->repo->expects($this->exactly($count))->method('findVisitsByShortCode')->with( ShortUrlIdentifier::fromShortCodeAndDomain(''), new VisitsListFiltering(DateRange::allTime(), false, null, $limit, $offset), )->willReturn([]); @@ -42,8 +39,6 @@ class ShortUrlVisitsPaginatorAdapterTest extends TestCase for ($i = 0; $i < $count; $i++) { $adapter->getSlice($offset, $limit); } - - $findVisits->shouldHaveBeenCalledTimes($count); } /** @test */ @@ -52,7 +47,7 @@ class ShortUrlVisitsPaginatorAdapterTest extends TestCase $count = 3; $apiKey = ApiKey::create(); $adapter = $this->createAdapter($apiKey); - $countVisits = $this->repo->countVisitsByShortCode( + $this->repo->expects($this->once())->method('countVisitsByShortCode')->with( ShortUrlIdentifier::fromShortCodeAndDomain(''), new VisitsCountFiltering(DateRange::allTime(), false, $apiKey), )->willReturn(3); @@ -60,14 +55,12 @@ class ShortUrlVisitsPaginatorAdapterTest extends TestCase for ($i = 0; $i < $count; $i++) { $adapter->getNbResults(); } - - $countVisits->shouldHaveBeenCalledOnce(); } private function createAdapter(?ApiKey $apiKey): ShortUrlVisitsPaginatorAdapter { return new ShortUrlVisitsPaginatorAdapter( - $this->repo->reveal(), + $this->repo, ShortUrlIdentifier::fromShortCodeAndDomain(''), VisitsParams::fromRawData([]), $apiKey, diff --git a/module/Core/test/Visit/Paginator/Adapter/VisitsForTagPaginatorAdapterTest.php b/module/Core/test/Visit/Paginator/Adapter/VisitsForTagPaginatorAdapterTest.php index 59743637..3a92c8d3 100644 --- a/module/Core/test/Visit/Paginator/Adapter/VisitsForTagPaginatorAdapterTest.php +++ b/module/Core/test/Visit/Paginator/Adapter/VisitsForTagPaginatorAdapterTest.php @@ -4,9 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Visit\Paginator\Adapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Visit\Model\VisitsParams; use Shlinkio\Shlink\Core\Visit\Paginator\Adapter\TagVisitsPaginatorAdapter; @@ -17,13 +16,11 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class VisitsForTagPaginatorAdapterTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $repo; + private MockObject $repo; protected function setUp(): void { - $this->repo = $this->prophesize(VisitRepositoryInterface::class); + $this->repo = $this->createMock(VisitRepositoryInterface::class); } /** @test */ @@ -33,7 +30,7 @@ class VisitsForTagPaginatorAdapterTest extends TestCase $limit = 1; $offset = 5; $adapter = $this->createAdapter(null); - $findVisits = $this->repo->findVisitsByTag( + $this->repo->expects($this->exactly($count))->method('findVisitsByTag')->with( 'foo', new VisitsListFiltering(DateRange::allTime(), false, null, $limit, $offset), )->willReturn([]); @@ -41,8 +38,6 @@ class VisitsForTagPaginatorAdapterTest extends TestCase for ($i = 0; $i < $count; $i++) { $adapter->getSlice($offset, $limit); } - - $findVisits->shouldHaveBeenCalledTimes($count); } /** @test */ @@ -51,7 +46,7 @@ class VisitsForTagPaginatorAdapterTest extends TestCase $count = 3; $apiKey = ApiKey::create(); $adapter = $this->createAdapter($apiKey); - $countVisits = $this->repo->countVisitsByTag( + $this->repo->expects($this->once())->method('countVisitsByTag')->with( 'foo', new VisitsCountFiltering(DateRange::allTime(), false, $apiKey), )->willReturn(3); @@ -59,17 +54,10 @@ class VisitsForTagPaginatorAdapterTest extends TestCase for ($i = 0; $i < $count; $i++) { $adapter->getNbResults(); } - - $countVisits->shouldHaveBeenCalledOnce(); } private function createAdapter(?ApiKey $apiKey): TagVisitsPaginatorAdapter { - return new TagVisitsPaginatorAdapter( - $this->repo->reveal(), - 'foo', - VisitsParams::fromRawData([]), - $apiKey, - ); + return new TagVisitsPaginatorAdapter($this->repo, 'foo', VisitsParams::fromRawData([]), $apiKey); } } From 66ed152358c6eed62c9090831acf715283a96d31 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:02:31 +0200 Subject: [PATCH 093/182] Migrated HealthActionTest to use PHPUnit mocks --- module/Rest/test/Action/HealthActionTest.php | 29 ++++++++------------ 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/module/Rest/test/Action/HealthActionTest.php b/module/Rest/test/Action/HealthActionTest.php index 461152a4..48289290 100644 --- a/module/Rest/test/Action/HealthActionTest.php +++ b/module/Rest/test/Action/HealthActionTest.php @@ -11,37 +11,34 @@ use Doctrine\ORM\EntityManagerInterface; use Exception; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequest; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -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; + private MockObject $conn; protected function setUp(): void { - $this->conn = $this->prophesize(Connection::class); - $this->conn->executeQuery(Argument::cetera())->willReturn($this->prophesize(Result::class)->reveal()); - $dbPlatform = $this->prophesize(AbstractPlatform::class); - $dbPlatform->getDummySelectSQL()->willReturn(''); - $this->conn->getDatabasePlatform()->willReturn($dbPlatform->reveal()); + $this->conn = $this->createMock(Connection::class); + $dbPlatform = $this->createMock(AbstractPlatform::class); + $dbPlatform->method('getDummySelectSQL')->willReturn(''); + $this->conn->method('getDatabasePlatform')->willReturn($dbPlatform); - $em = $this->prophesize(EntityManagerInterface::class); - $em->getConnection()->willReturn($this->conn->reveal()); + $em = $this->createMock(EntityManagerInterface::class); + $em->method('getConnection')->willReturn($this->conn); - $this->action = new HealthAction($em->reveal(), new AppOptions(version: '1.2.3')); + $this->action = new HealthAction($em, new AppOptions(version: '1.2.3')); } /** @test */ public function passResponseIsReturnedWhenDummyQuerySucceeds(): void { + $this->conn->expects($this->once())->method('executeQuery')->willReturn($this->createMock(Result::class)); + /** @var JsonResponse $resp */ $resp = $this->action->handle(new ServerRequest()); $payload = $resp->getPayload(); @@ -54,13 +51,12 @@ class HealthActionTest extends TestCase 'project' => 'https://github.com/shlinkio/shlink', ], $payload['links']); self::assertEquals('application/health+json', $resp->getHeaderLine('Content-type')); - $this->conn->executeQuery(Argument::cetera())->shouldHaveBeenCalledOnce(); } /** @test */ public function failResponseIsReturnedWhenDummyQueryThrowsException(): void { - $executeQuery = $this->conn->executeQuery(Argument::cetera())->willThrow(Exception::class); + $this->conn->expects($this->once())->method('executeQuery')->willThrowException(new Exception()); /** @var JsonResponse $resp */ $resp = $this->action->handle(new ServerRequest()); @@ -74,6 +70,5 @@ class HealthActionTest extends TestCase 'project' => 'https://github.com/shlinkio/shlink', ], $payload['links']); self::assertEquals('application/health+json', $resp->getHeaderLine('Content-type')); - $executeQuery->shouldHaveBeenCalledOnce(); } } From 896b7f2d7339cfe236585deb664ff1e5848b566b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:04:00 +0200 Subject: [PATCH 094/182] Migrated MercureInfoActionTest to use PHPUnit mocks --- .../test/Action/MercureInfoActionTest.php | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/module/Rest/test/Action/MercureInfoActionTest.php b/module/Rest/test/Action/MercureInfoActionTest.php index e586a641..f99a8a37 100644 --- a/module/Rest/test/Action/MercureInfoActionTest.php +++ b/module/Rest/test/Action/MercureInfoActionTest.php @@ -7,23 +7,19 @@ namespace ShlinkioTest\Shlink\Rest\Action; use Cake\Chronos\Chronos; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequestFactory; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Mercure\JwtProviderInterface; use Shlinkio\Shlink\Rest\Action\MercureInfoAction; use Shlinkio\Shlink\Rest\Exception\MercureException; class MercureInfoActionTest extends TestCase { - use ProphecyTrait; - - private ObjectProphecy $provider; + private MockObject $provider; protected function setUp(): void { - $this->provider = $this->prophesize(JwtProviderInterface::class); + $this->provider = $this->createMock(JwtProviderInterface::class); } /** @@ -32,12 +28,11 @@ class MercureInfoActionTest extends TestCase */ public function throwsExceptionWhenConfigDoesNotHavePublicHost(array $mercureConfig): void { - $buildToken = $this->provider->buildSubscriptionToken(Argument::any())->willReturn('abc.123'); + $this->provider->expects($this->never())->method('buildSubscriptionToken'); - $action = new MercureInfoAction($this->provider->reveal(), $mercureConfig); + $action = new MercureInfoAction($this->provider, $mercureConfig); $this->expectException(MercureException::class); - $buildToken->shouldNotBeCalled(); $action->handle(ServerRequestFactory::fromGlobals()); } @@ -60,9 +55,9 @@ class MercureInfoActionTest extends TestCase */ public function returnsExpectedInfoWhenEverythingIsOk(?int $days): void { - $buildToken = $this->provider->buildSubscriptionToken(Argument::any())->willReturn('abc.123'); + $this->provider->expects($this->once())->method('buildSubscriptionToken')->willReturn('abc.123'); - $action = new MercureInfoAction($this->provider->reveal(), [ + $action = new MercureInfoAction($this->provider, [ 'public_hub_url' => 'http://foobar.com', 'jwt_days_duration' => $days, ]); @@ -79,7 +74,6 @@ class MercureInfoActionTest extends TestCase Chronos::now()->addDays($days ?? 1)->startOfDay(), Chronos::parse($payload['jwtExpiration'])->startOfDay(), ); - $buildToken->shouldHaveBeenCalledOnce(); } public function provideDays(): iterable From 91e21441f73de2162eccafbd7475631514f53ca9 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:05:51 +0200 Subject: [PATCH 095/182] Migrated DomainRedirectsActionTest to use PHPUnit mocks --- .../Domain/DomainRedirectsActionTest.php | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/module/Rest/test/Action/Domain/DomainRedirectsActionTest.php b/module/Rest/test/Action/Domain/DomainRedirectsActionTest.php index 19e34ccf..a620ee01 100644 --- a/module/Rest/test/Action/Domain/DomainRedirectsActionTest.php +++ b/module/Rest/test/Action/Domain/DomainRedirectsActionTest.php @@ -6,10 +6,8 @@ namespace ShlinkioTest\Shlink\Rest\Action\Domain; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequestFactory; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Config\NotFoundRedirects; use Shlinkio\Shlink\Core\Domain\DomainServiceInterface; use Shlinkio\Shlink\Core\Domain\Entity\Domain; @@ -22,15 +20,13 @@ use function array_key_exists; class DomainRedirectsActionTest extends TestCase { - use ProphecyTrait; - private DomainRedirectsAction $action; - private ObjectProphecy $domainService; + private MockObject $domainService; protected function setUp(): void { - $this->domainService = $this->prophesize(DomainServiceInterface::class); - $this->action = new DomainRedirectsAction($this->domainService->reveal()); + $this->domainService = $this->createMock(DomainServiceInterface::class); + $this->action = new DomainRedirectsAction($this->domainService); } /** @@ -42,8 +38,8 @@ class DomainRedirectsActionTest extends TestCase $request = ServerRequestFactory::fromGlobals()->withParsedBody($body); $this->expectException(ValidationException::class); - $this->domainService->getOrCreate(Argument::cetera())->shouldNotBeCalled(); - $this->domainService->configureNotFoundRedirects(Argument::cetera())->shouldNotBeCalled(); + $this->domainService->expects($this->never())->method('getOrCreate'); + $this->domainService->expects($this->never())->method('configureNotFoundRedirects'); $this->action->handle($request); } @@ -70,19 +66,19 @@ class DomainRedirectsActionTest extends TestCase $request = ServerRequestFactory::fromGlobals()->withParsedBody($redirects) ->withAttribute(ApiKey::class, $apiKey); - $getOrCreate = $this->domainService->getOrCreate($authority)->willReturn($domain); - $configureNotFoundRedirects = $this->domainService->configureNotFoundRedirects( + $this->domainService->expects($this->once())->method('getOrCreate')->with($authority)->willReturn($domain); + $this->domainService->expects($this->once())->method('configureNotFoundRedirects')->with( $authority, NotFoundRedirects::withRedirects( array_key_exists(DomainRedirectsInputFilter::BASE_URL_REDIRECT, $redirects) ? $redirects[DomainRedirectsInputFilter::BASE_URL_REDIRECT] - : $domain?->baseUrlRedirect(), + : $domain->baseUrlRedirect(), array_key_exists(DomainRedirectsInputFilter::REGULAR_404_REDIRECT, $redirects) ? $redirects[DomainRedirectsInputFilter::REGULAR_404_REDIRECT] - : $domain?->regular404Redirect(), + : $domain->regular404Redirect(), array_key_exists(DomainRedirectsInputFilter::INVALID_SHORT_URL_REDIRECT, $redirects) ? $redirects[DomainRedirectsInputFilter::INVALID_SHORT_URL_REDIRECT] - : $domain?->invalidShortUrlRedirect(), + : $domain->invalidShortUrlRedirect(), ), $apiKey, ); @@ -93,8 +89,6 @@ class DomainRedirectsActionTest extends TestCase $payload = $response->getPayload(); self::assertEquals($expectedResult, $payload->jsonSerialize()); - $getOrCreate->shouldHaveBeenCalledOnce(); - $configureNotFoundRedirects->shouldHaveBeenCalledOnce(); } public function provideDomainsAndRedirects(): iterable From 74176c298fd2d06702d57d97eb297931195ed7db Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:06:48 +0200 Subject: [PATCH 096/182] Migrated ListDomainsActionTest to use PHPUnit mocks --- .../test/Action/Domain/ListDomainsActionTest.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/module/Rest/test/Action/Domain/ListDomainsActionTest.php b/module/Rest/test/Action/Domain/ListDomainsActionTest.php index b04c8d0e..8c7fcc94 100644 --- a/module/Rest/test/Action/Domain/ListDomainsActionTest.php +++ b/module/Rest/test/Action/Domain/ListDomainsActionTest.php @@ -6,9 +6,8 @@ namespace ShlinkioTest\Shlink\Rest\Action\Domain; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequestFactory; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Config\NotFoundRedirects; use Shlinkio\Shlink\Core\Domain\DomainServiceInterface; use Shlinkio\Shlink\Core\Domain\Entity\Domain; @@ -19,17 +18,15 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ListDomainsActionTest extends TestCase { - use ProphecyTrait; - private ListDomainsAction $action; - private ObjectProphecy $domainService; + private MockObject $domainService; private NotFoundRedirectOptions $options; protected function setUp(): void { - $this->domainService = $this->prophesize(DomainServiceInterface::class); + $this->domainService = $this->createMock(DomainServiceInterface::class); $this->options = new NotFoundRedirectOptions(); - $this->action = new ListDomainsAction($this->domainService->reveal(), $this->options); + $this->action = new ListDomainsAction($this->domainService, $this->options); } /** @test */ @@ -40,7 +37,7 @@ class ListDomainsActionTest extends TestCase DomainItem::forDefaultDomain('bar.com', new NotFoundRedirectOptions()), DomainItem::forNonDefaultDomain(Domain::withAuthority('baz.com')), ]; - $listDomains = $this->domainService->listDomains($apiKey)->willReturn($domains); + $this->domainService->expects($this->once())->method('listDomains')->with($apiKey)->willReturn($domains); /** @var JsonResponse $resp */ $resp = $this->action->handle(ServerRequestFactory::fromGlobals()->withAttribute(ApiKey::class, $apiKey)); @@ -52,6 +49,5 @@ class ListDomainsActionTest extends TestCase 'defaultRedirects' => NotFoundRedirects::fromConfig($this->options), ], ], $payload); - $listDomains->shouldHaveBeenCalledOnce(); } } From a84b642ba57f0a9e460c24cb1554aa96af967ff3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:09:37 +0200 Subject: [PATCH 097/182] Migrated CreateShortUrlActionTest to use PHPUnit mocks --- .../ShortUrl/CreateShortUrlActionTest.php | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php index 40284969..140f0230 100644 --- a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php @@ -8,10 +8,8 @@ use Cake\Chronos\Chronos; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\ServerRequestFactory; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; @@ -23,23 +21,16 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class CreateShortUrlActionTest extends TestCase { - use ProphecyTrait; - private CreateShortUrlAction $action; - private ObjectProphecy $urlShortener; - private ObjectProphecy $transformer; + private MockObject $urlShortener; + private MockObject $transformer; protected function setUp(): void { - $this->urlShortener = $this->prophesize(UrlShortener::class); - $this->transformer = $this->prophesize(DataTransformerInterface::class); - $this->transformer->transform(Argument::type(ShortUrl::class))->willReturn([]); + $this->urlShortener = $this->createMock(UrlShortener::class); + $this->transformer = $this->createMock(DataTransformerInterface::class); - $this->action = new CreateShortUrlAction( - $this->urlShortener->reveal(), - $this->transformer->reveal(), - new UrlShortenerOptions(), - ); + $this->action = new CreateShortUrlAction($this->urlShortener, $this->transformer, new UrlShortenerOptions()); } /** @test */ @@ -58,8 +49,12 @@ class CreateShortUrlActionTest extends TestCase ]; $expectedMeta['apiKey'] = $apiKey; - $shorten = $this->urlShortener->shorten(ShortUrlCreation::fromRawData($expectedMeta))->willReturn($shortUrl); - $transform = $this->transformer->transform($shortUrl)->willReturn(['shortUrl' => 'stringified_short_url']); + $this->urlShortener->expects($this->once())->method('shorten')->with( + ShortUrlCreation::fromRawData($expectedMeta), + )->willReturn($shortUrl); + $this->transformer->expects($this->once())->method('transform')->with($shortUrl)->willReturn( + ['shortUrl' => 'stringified_short_url'], + ); $request = ServerRequestFactory::fromGlobals()->withParsedBody($body)->withAttribute(ApiKey::class, $apiKey); @@ -69,8 +64,6 @@ class CreateShortUrlActionTest extends TestCase self::assertEquals(200, $response->getStatusCode()); self::assertEquals('stringified_short_url', $payload['shortUrl']); - $shorten->shouldHaveBeenCalledOnce(); - $transform->shouldHaveBeenCalledOnce(); } /** @@ -79,8 +72,8 @@ class CreateShortUrlActionTest extends TestCase */ public function anInvalidDomainReturnsError(string $domain): void { - $shortUrl = ShortUrl::createEmpty(); - $urlToShortCode = $this->urlShortener->shorten(Argument::cetera())->willReturn($shortUrl); + $this->urlShortener->expects($this->never())->method('shorten'); + $this->transformer->expects($this->never())->method('transform'); $request = (new ServerRequest())->withParsedBody([ 'longUrl' => 'http://www.domain.com/foo/bar', @@ -88,7 +81,6 @@ class CreateShortUrlActionTest extends TestCase ])->withAttribute(ApiKey::class, ApiKey::create()); $this->expectException(ValidationException::class); - $urlToShortCode->shouldNotBeCalled(); $this->action->handle($request); } From d20253858161a7386f4af0298897473546138f97 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:10:41 +0200 Subject: [PATCH 098/182] Migrated DeleteShortUrlActionTest to use PHPUnit mocks --- .../ShortUrl/DeleteShortUrlActionTest.php | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php index bc073cf6..30a8e647 100644 --- a/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php @@ -5,39 +5,31 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; use Laminas\Diactoros\ServerRequestFactory; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\ShortUrl\DeleteShortUrlServiceInterface; use Shlinkio\Shlink\Rest\Action\ShortUrl\DeleteShortUrlAction; use Shlinkio\Shlink\Rest\Entity\ApiKey; class DeleteShortUrlActionTest extends TestCase { - use ProphecyTrait; - private DeleteShortUrlAction $action; - private ObjectProphecy $service; + private MockObject $service; protected function setUp(): void { - $this->service = $this->prophesize(DeleteShortUrlServiceInterface::class); - $this->action = new DeleteShortUrlAction($this->service->reveal()); + $this->service = $this->createMock(DeleteShortUrlServiceInterface::class); + $this->action = new DeleteShortUrlAction($this->service); } /** @test */ public function emptyResponseIsReturnedIfProperlyDeleted(): void { $apiKey = ApiKey::create(); - $deleteByShortCode = $this->service->deleteByShortCode(Argument::any(), false, $apiKey)->will( - function (): void { - }, - ); + $this->service->expects($this->once())->method('deleteByShortCode'); $resp = $this->action->handle(ServerRequestFactory::fromGlobals()->withAttribute(ApiKey::class, $apiKey)); self::assertEquals(204, $resp->getStatusCode()); - $deleteByShortCode->shouldHaveBeenCalledOnce(); } } From 9c02ea87993326151d6e7e5f775415b70b3088b1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:12:27 +0200 Subject: [PATCH 099/182] Migrated EditShortUrlActionTest to use PHPUnit mocks --- .../Action/ShortUrl/EditShortUrlActionTest.php | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php index 19cb27e9..94f3dbae 100644 --- a/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php @@ -5,10 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; use Laminas\Diactoros\ServerRequest; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier; @@ -19,15 +17,13 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class EditShortUrlActionTest extends TestCase { - use ProphecyTrait; - private EditShortUrlAction $action; - private ObjectProphecy $shortUrlService; + private MockObject $shortUrlService; protected function setUp(): void { - $this->shortUrlService = $this->prophesize(ShortUrlServiceInterface::class); - $this->action = new EditShortUrlAction($this->shortUrlService->reveal(), new ShortUrlDataTransformer( + $this->shortUrlService = $this->createMock(ShortUrlServiceInterface::class); + $this->action = new EditShortUrlAction($this->shortUrlService, new ShortUrlDataTransformer( new ShortUrlStringifier([]), )); } @@ -38,6 +34,7 @@ class EditShortUrlActionTest extends TestCase $request = (new ServerRequest())->withParsedBody([ 'maxVisits' => 'invalid', ]); + $this->shortUrlService->expects($this->never())->method('updateShortUrl'); $this->expectException(ValidationException::class); @@ -52,13 +49,10 @@ class EditShortUrlActionTest extends TestCase ->withParsedBody([ 'maxVisits' => 5, ]); - $updateMeta = $this->shortUrlService->updateShortUrl(Argument::cetera())->willReturn( - ShortUrl::createEmpty(), - ); + $this->shortUrlService->expects($this->once())->method('updateShortUrl')->willReturn(ShortUrl::createEmpty()); $resp = $this->action->handle($request); self::assertEquals(200, $resp->getStatusCode()); - $updateMeta->shouldHaveBeenCalled(); } } From 01829c82ee60bc2124b19010fd0d70c18b407080 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:13:27 +0200 Subject: [PATCH 100/182] Migrated ListShortUrlsActionTest to use PHPUnit mocks --- .../Action/ShortUrl/ListShortUrlsActionTest.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php b/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php index 65e6ebbf..068677d3 100644 --- a/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php +++ b/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php @@ -8,9 +8,8 @@ use Cake\Chronos\Chronos; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequestFactory; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlsParams; @@ -21,16 +20,14 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ListShortUrlsActionTest extends TestCase { - use ProphecyTrait; - private ListShortUrlsAction $action; - private ObjectProphecy $service; + private MockObject $service; protected function setUp(): void { - $this->service = $this->prophesize(ShortUrlService::class); + $this->service = $this->createMock(ShortUrlService::class); - $this->action = new ListShortUrlsAction($this->service->reveal(), new ShortUrlDataTransformer( + $this->action = new ListShortUrlsAction($this->service, new ShortUrlDataTransformer( new ShortUrlStringifier([ 'hostname' => 'doma.in', 'schema' => 'https', @@ -54,7 +51,7 @@ class ListShortUrlsActionTest extends TestCase $apiKey = ApiKey::create(); $request = ServerRequestFactory::fromGlobals()->withQueryParams($query) ->withAttribute(ApiKey::class, $apiKey); - $listShortUrls = $this->service->listShortUrls(ShortUrlsParams::fromRawData([ + $this->service->expects($this->once())->method('listShortUrls')->with(ShortUrlsParams::fromRawData([ 'page' => $expectedPage, 'searchTerm' => $expectedSearchTerm, 'tags' => $expectedTags, @@ -71,7 +68,6 @@ class ListShortUrlsActionTest extends TestCase self::assertArrayHasKey('data', $payload['shortUrls']); self::assertEquals([], $payload['shortUrls']['data']); self::assertEquals(200, $response->getStatusCode()); - $listShortUrls->shouldHaveBeenCalledOnce(); } public function provideFilteringData(): iterable From acc9cb94b5dd5c568160bf855a363538dbb8b10b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:14:28 +0200 Subject: [PATCH 101/182] Migrated ResolveShortUrlActionTest to use PHPUnit mocks --- .../ShortUrl/ResolveShortUrlActionTest.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php index 1b805c5f..0e91290f 100644 --- a/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php @@ -5,9 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; use Laminas\Diactoros\ServerRequest; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; @@ -18,15 +17,13 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ResolveShortUrlActionTest extends TestCase { - use ProphecyTrait; - private ResolveShortUrlAction $action; - private ObjectProphecy $urlResolver; + private MockObject $urlResolver; protected function setUp(): void { - $this->urlResolver = $this->prophesize(ShortUrlResolverInterface::class); - $this->action = new ResolveShortUrlAction($this->urlResolver->reveal(), new ShortUrlDataTransformer( + $this->urlResolver = $this->createMock(ShortUrlResolverInterface::class); + $this->action = new ResolveShortUrlAction($this->urlResolver, new ShortUrlDataTransformer( new ShortUrlStringifier([]), )); } @@ -36,11 +33,10 @@ class ResolveShortUrlActionTest extends TestCase { $shortCode = 'abc123'; $apiKey = ApiKey::create(); - $this->urlResolver->resolveShortUrl( + $this->urlResolver->expects($this->once())->method('resolveShortUrl')->with( ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), $apiKey, - )->willReturn(ShortUrl::withLongUrl('http://domain.com/foo/bar')) - ->shouldBeCalledOnce(); + )->willReturn(ShortUrl::withLongUrl('http://domain.com/foo/bar')); $request = (new ServerRequest())->withAttribute('shortCode', $shortCode)->withAttribute(ApiKey::class, $apiKey); $response = $this->action->handle($request); From 9ac6a50e66c3576d4fbe202d576a04973d30c89b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:16:19 +0200 Subject: [PATCH 102/182] Migrated SingleStepCreateShortUrlActionTest to use PHPUnit mocks --- .../SingleStepCreateShortUrlActionTest.php | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php index 3fd29c2f..5e0867e7 100644 --- a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php @@ -5,10 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; use Laminas\Diactoros\ServerRequest; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; @@ -19,21 +17,18 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class SingleStepCreateShortUrlActionTest extends TestCase { - use ProphecyTrait; - private SingleStepCreateShortUrlAction $action; - private ObjectProphecy $urlShortener; - private ObjectProphecy $transformer; + private MockObject $urlShortener; protected function setUp(): void { - $this->urlShortener = $this->prophesize(UrlShortenerInterface::class); - $this->transformer = $this->prophesize(DataTransformerInterface::class); - $this->transformer->transform(Argument::type(ShortUrl::class))->willReturn([]); + $this->urlShortener = $this->createMock(UrlShortenerInterface::class); + $transformer = $this->createMock(DataTransformerInterface::class); + $transformer->method('transform')->willReturn([]); $this->action = new SingleStepCreateShortUrlAction( - $this->urlShortener->reveal(), - $this->transformer->reveal(), + $this->urlShortener, + $transformer, new UrlShortenerOptions(), ); } @@ -46,13 +41,12 @@ class SingleStepCreateShortUrlActionTest extends TestCase $request = (new ServerRequest())->withQueryParams([ 'longUrl' => 'http://foobar.com', ])->withAttribute(ApiKey::class, $apiKey); - $generateShortCode = $this->urlShortener->shorten( + $this->urlShortener->expects($this->once())->method('shorten')->with( ShortUrlCreation::fromRawData(['apiKey' => $apiKey, 'longUrl' => 'http://foobar.com']), )->willReturn(ShortUrl::createEmpty()); $resp = $this->action->handle($request); self::assertEquals(200, $resp->getStatusCode()); - $generateShortCode->shouldHaveBeenCalled(); } } From ab9ea887d2ef96f097c4253e85be567f743ffd35 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:17:35 +0200 Subject: [PATCH 103/182] Migrated DeleteTagsActionTest to use PHPUnit mocks --- .../test/Action/Tag/DeleteTagsActionTest.php | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/module/Rest/test/Action/Tag/DeleteTagsActionTest.php b/module/Rest/test/Action/Tag/DeleteTagsActionTest.php index 457507e8..cca532a4 100644 --- a/module/Rest/test/Action/Tag/DeleteTagsActionTest.php +++ b/module/Rest/test/Action/Tag/DeleteTagsActionTest.php @@ -5,25 +5,21 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\Tag; use Laminas\Diactoros\ServerRequest; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Tag\TagServiceInterface; use Shlinkio\Shlink\Rest\Action\Tag\DeleteTagsAction; use Shlinkio\Shlink\Rest\Entity\ApiKey; class DeleteTagsActionTest extends TestCase { - use ProphecyTrait; - private DeleteTagsAction $action; - private ObjectProphecy $tagService; + private MockObject $tagService; protected function setUp(): void { - $this->tagService = $this->prophesize(TagServiceInterface::class); - $this->action = new DeleteTagsAction($this->tagService->reveal()); + $this->tagService = $this->createMock(TagServiceInterface::class); + $this->action = new DeleteTagsAction($this->tagService); } /** @@ -35,12 +31,14 @@ class DeleteTagsActionTest extends TestCase $request = (new ServerRequest()) ->withQueryParams(['tags' => $tags]) ->withAttribute(ApiKey::class, ApiKey::create()); - $deleteTags = $this->tagService->deleteTags($tags ?: [], Argument::type(ApiKey::class)); + $this->tagService->expects($this->once())->method('deleteTags')->with( + $tags ?? [], + $this->isInstanceOf(ApiKey::class), + ); $response = $this->action->handle($request); self::assertEquals(204, $response->getStatusCode()); - $deleteTags->shouldHaveBeenCalled(); } public function provideTags(): iterable From 656083cb6f40358400c2aae7a078d37eb0f92497 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:19:14 +0200 Subject: [PATCH 104/182] Migrated ListTagsActionTest to use PHPUnit mocks --- .../test/Action/Tag/ListTagsActionTest.php | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/module/Rest/test/Action/Tag/ListTagsActionTest.php b/module/Rest/test/Action/Tag/ListTagsActionTest.php index e362aca9..d340c82f 100644 --- a/module/Rest/test/Action/Tag/ListTagsActionTest.php +++ b/module/Rest/test/Action/Tag/ListTagsActionTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Rest\Action\Tag; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequestFactory; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\Tag\Entity\Tag; @@ -23,15 +21,13 @@ use function count; class ListTagsActionTest extends TestCase { - use ProphecyTrait; - private ListTagsAction $action; - private ObjectProphecy $tagService; + private MockObject $tagService; protected function setUp(): void { - $this->tagService = $this->prophesize(TagServiceInterface::class); - $this->action = new ListTagsAction($this->tagService->reveal()); + $this->tagService = $this->createMock(TagServiceInterface::class); + $this->action = new ListTagsAction($this->tagService); } /** @@ -42,9 +38,10 @@ class ListTagsActionTest extends TestCase { $tags = [new Tag('foo'), new Tag('bar')]; $tagsCount = count($tags); - $listTags = $this->tagService->listTags(Argument::any(), Argument::type(ApiKey::class))->willReturn( - new Paginator(new ArrayAdapter($tags)), - ); + $this->tagService->expects($this->once())->method('listTags')->with( + $this->anything(), + $this->isInstanceOf(ApiKey::class), + )->willReturn(new Paginator(new ArrayAdapter($tags))); /** @var JsonResponse $resp */ $resp = $this->action->handle($this->requestWithApiKey()->withQueryParams($query)); @@ -62,7 +59,6 @@ class ListTagsActionTest extends TestCase ], ], ], $payload); - $listTags->shouldHaveBeenCalled(); } public function provideNoStatsQueries(): iterable @@ -80,9 +76,10 @@ class ListTagsActionTest extends TestCase new TagInfo('bar', 3, 10), ]; $itemsCount = count($stats); - $tagsInfo = $this->tagService->tagsInfo(Argument::any(), Argument::type(ApiKey::class))->willReturn( - new Paginator(new ArrayAdapter($stats)), - ); + $this->tagService->expects($this->once())->method('tagsInfo')->with( + $this->anything(), + $this->isInstanceOf(ApiKey::class), + )->willReturn(new Paginator(new ArrayAdapter($stats))); $req = $this->requestWithApiKey()->withQueryParams(['withStats' => 'true']); /** @var JsonResponse $resp */ @@ -102,7 +99,6 @@ class ListTagsActionTest extends TestCase ], ], ], $payload); - $tagsInfo->shouldHaveBeenCalled(); } private function requestWithApiKey(): ServerRequestInterface From 69e994c0677db46e56f6335f625f3e06d420dcc1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:20:21 +0200 Subject: [PATCH 105/182] Migrated TagsStatsActionTest to use PHPUnit mocks --- .../test/Action/Tag/TagsStatsActionTest.php | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/module/Rest/test/Action/Tag/TagsStatsActionTest.php b/module/Rest/test/Action/Tag/TagsStatsActionTest.php index 44e6afb0..8e0e8e74 100644 --- a/module/Rest/test/Action/Tag/TagsStatsActionTest.php +++ b/module/Rest/test/Action/Tag/TagsStatsActionTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Rest\Action\Tag; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequestFactory; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\Tag\Model\TagInfo; @@ -22,15 +20,13 @@ use function count; class TagsStatsActionTest extends TestCase { - use ProphecyTrait; - private TagsStatsAction $action; - private ObjectProphecy $tagService; + private MockObject $tagService; protected function setUp(): void { - $this->tagService = $this->prophesize(TagServiceInterface::class); - $this->action = new TagsStatsAction($this->tagService->reveal()); + $this->tagService = $this->createMock(TagServiceInterface::class); + $this->action = new TagsStatsAction($this->tagService); } /** @test */ @@ -41,9 +37,10 @@ class TagsStatsActionTest extends TestCase new TagInfo('bar', 3, 10), ]; $itemsCount = count($stats); - $tagsInfo = $this->tagService->tagsInfo(Argument::any(), Argument::type(ApiKey::class))->willReturn( - new Paginator(new ArrayAdapter($stats)), - ); + $this->tagService->expects($this->once())->method('tagsInfo')->with( + $this->anything(), + $this->isInstanceOf(ApiKey::class), + )->willReturn(new Paginator(new ArrayAdapter($stats))); $req = $this->requestWithApiKey()->withQueryParams(['withStats' => 'true']); /** @var JsonResponse $resp */ @@ -62,7 +59,6 @@ class TagsStatsActionTest extends TestCase ], ], ], $payload); - $tagsInfo->shouldHaveBeenCalled(); } private function requestWithApiKey(): ServerRequestInterface From 28f26920dd1cab2399060e2f5843086498513419 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:21:23 +0200 Subject: [PATCH 106/182] Migrated UpdateTagActionTest to use PHPUnit mocks --- .../test/Action/Tag/UpdateTagActionTest.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/module/Rest/test/Action/Tag/UpdateTagActionTest.php b/module/Rest/test/Action/Tag/UpdateTagActionTest.php index aea611ac..d1424849 100644 --- a/module/Rest/test/Action/Tag/UpdateTagActionTest.php +++ b/module/Rest/test/Action/Tag/UpdateTagActionTest.php @@ -5,10 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\Tag; use Laminas\Diactoros\ServerRequestFactory; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Tag\Entity\Tag; @@ -19,15 +17,13 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class UpdateTagActionTest extends TestCase { - use ProphecyTrait; - private UpdateTagAction $action; - private ObjectProphecy $tagService; + private MockObject $tagService; protected function setUp(): void { - $this->tagService = $this->prophesize(TagServiceInterface::class); - $this->action = new UpdateTagAction($this->tagService->reveal()); + $this->tagService = $this->createMock(TagServiceInterface::class); + $this->action = new UpdateTagAction($this->tagService); } /** @@ -57,15 +53,14 @@ class UpdateTagActionTest extends TestCase 'oldName' => 'foo', 'newName' => 'bar', ]); - $rename = $this->tagService->renameTag( + $this->tagService->expects($this->once())->method('renameTag')->with( TagRenaming::fromNames('foo', 'bar'), - Argument::type(ApiKey::class), + $this->isInstanceOf(ApiKey::class), )->willReturn(new Tag('bar')); $resp = $this->action->handle($request); self::assertEquals(204, $resp->getStatusCode()); - $rename->shouldHaveBeenCalled(); } private function requestWithApiKey(): ServerRequestInterface From f493baaf2b550fe4473f4c4ac7d44c8e72028aba Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:22:14 +0200 Subject: [PATCH 107/182] Migrated DomainVisitsActionTest to use PHPUnit mocks --- .../Action/Visit/DomainVisitsActionTest.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/module/Rest/test/Action/Visit/DomainVisitsActionTest.php b/module/Rest/test/Action/Visit/DomainVisitsActionTest.php index 6e3f2fe8..b56f557b 100644 --- a/module/Rest/test/Action/Visit/DomainVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/DomainVisitsActionTest.php @@ -6,10 +6,8 @@ namespace ShlinkioTest\Shlink\Rest\Action\Visit; use Laminas\Diactoros\ServerRequestFactory; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\Visit\Model\VisitsParams; use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface; @@ -18,15 +16,13 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class DomainVisitsActionTest extends TestCase { - use ProphecyTrait; - private DomainVisitsAction $action; - private ObjectProphecy $visitsHelper; + private MockObject $visitsHelper; protected function setUp(): void { - $this->visitsHelper = $this->prophesize(VisitsStatsHelperInterface::class); - $this->action = new DomainVisitsAction($this->visitsHelper->reveal(), 'the_default.com'); + $this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class); + $this->action = new DomainVisitsAction($this->visitsHelper, 'the_default.com'); } /** @@ -36,9 +32,9 @@ class DomainVisitsActionTest extends TestCase public function providingCorrectDomainReturnsVisits(string $providedDomain, string $expectedDomain): void { $apiKey = ApiKey::create(); - $getVisits = $this->visitsHelper->visitsForDomain( + $this->visitsHelper->expects($this->once())->method('visitsForDomain')->with( $expectedDomain, - Argument::type(VisitsParams::class), + $this->isInstanceOf(VisitsParams::class), $apiKey, )->willReturn(new Paginator(new ArrayAdapter([]))); @@ -48,7 +44,6 @@ class DomainVisitsActionTest extends TestCase ); self::assertEquals(200, $response->getStatusCode()); - $getVisits->shouldHaveBeenCalledOnce(); } public function provideDomainAuthorities(): iterable From e980a8d1215e552ce6498cbae49a0ad1793e13d4 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:23:08 +0200 Subject: [PATCH 108/182] Migrated GlobalVisitsActionTest to use PHPUnit mocks --- .../test/Action/Visit/GlobalVisitsActionTest.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php b/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php index d5f94250..87830cb5 100644 --- a/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php @@ -6,9 +6,8 @@ namespace ShlinkioTest\Shlink\Rest\Action\Visit; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequestFactory; +use PHPUnit\Framework\MockObject\MockObject; 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; use Shlinkio\Shlink\Rest\Action\Visit\GlobalVisitsAction; @@ -16,15 +15,13 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class GlobalVisitsActionTest extends TestCase { - use ProphecyTrait; - private GlobalVisitsAction $action; - private ObjectProphecy $helper; + private MockObject $helper; protected function setUp(): void { - $this->helper = $this->prophesize(VisitsStatsHelperInterface::class); - $this->action = new GlobalVisitsAction($this->helper->reveal()); + $this->helper = $this->createMock(VisitsStatsHelperInterface::class); + $this->action = new GlobalVisitsAction($this->helper); } /** @test */ @@ -32,13 +29,12 @@ class GlobalVisitsActionTest extends TestCase { $apiKey = ApiKey::create(); $stats = new VisitsStats(5, 3); - $getStats = $this->helper->getVisitsStats($apiKey)->willReturn($stats); + $this->helper->expects($this->once())->method('getVisitsStats')->with($apiKey)->willReturn($stats); /** @var JsonResponse $resp */ $resp = $this->action->handle(ServerRequestFactory::fromGlobals()->withAttribute(ApiKey::class, $apiKey)); $payload = $resp->getPayload(); self::assertEquals($payload, ['visits' => $stats]); - $getStats->shouldHaveBeenCalledOnce(); } } From bb444a02fe5129ce300bf412f705e6e2f9692989 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:24:06 +0200 Subject: [PATCH 109/182] Migrated NonOrphanVisitsActionTest to use PHPUnit mocks --- .../Visit/NonOrphanVisitsActionTest.php | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/module/Rest/test/Action/Visit/NonOrphanVisitsActionTest.php b/module/Rest/test/Action/Visit/NonOrphanVisitsActionTest.php index 4ecf5b88..4cf6d8a2 100644 --- a/module/Rest/test/Action/Visit/NonOrphanVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/NonOrphanVisitsActionTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Rest\Action\Visit; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequestFactory; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\Visit\Model\VisitsParams; use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface; @@ -19,24 +17,23 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class NonOrphanVisitsActionTest extends TestCase { - use ProphecyTrait; - private NonOrphanVisitsAction $action; - private ObjectProphecy $visitsHelper; + private MockObject $visitsHelper; protected function setUp(): void { - $this->visitsHelper = $this->prophesize(VisitsStatsHelperInterface::class); - $this->action = new NonOrphanVisitsAction($this->visitsHelper->reveal()); + $this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class); + $this->action = new NonOrphanVisitsAction($this->visitsHelper); } /** @test */ public function requestIsHandled(): void { $apiKey = ApiKey::create(); - $getVisits = $this->visitsHelper->nonOrphanVisits(Argument::type(VisitsParams::class), $apiKey)->willReturn( - new Paginator(new ArrayAdapter([])), - ); + $this->visitsHelper->expects($this->once())->method('nonOrphanVisits')->with( + $this->isInstanceOf(VisitsParams::class), + $apiKey, + )->willReturn(new Paginator(new ArrayAdapter([]))); /** @var JsonResponse $response */ $response = $this->action->handle(ServerRequestFactory::fromGlobals()->withAttribute(ApiKey::class, $apiKey)); @@ -44,6 +41,5 @@ class NonOrphanVisitsActionTest extends TestCase self::assertEquals(200, $response->getStatusCode()); self::assertArrayHasKey('visits', $payload); - $getVisits->shouldHaveBeenCalledOnce(); } } From d4684fd01f10c36c7ae85f61acb02c38809e9b0a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:25:54 +0200 Subject: [PATCH 110/182] Migrated OrphanVisitsActionTest to use PHPUnit mocks --- .../Action/Visit/OrphanVisitsActionTest.php | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/module/Rest/test/Action/Visit/OrphanVisitsActionTest.php b/module/Rest/test/Action/Visit/OrphanVisitsActionTest.php index ca1ae4e5..7ad77425 100644 --- a/module/Rest/test/Action/Visit/OrphanVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/OrphanVisitsActionTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Rest\Action\Visit; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequestFactory; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Core\Visit\Entity\Visit; @@ -23,18 +21,16 @@ use function count; class OrphanVisitsActionTest extends TestCase { - use ProphecyTrait; - private OrphanVisitsAction $action; - private ObjectProphecy $visitsHelper; - private ObjectProphecy $orphanVisitTransformer; + private MockObject $visitsHelper; + private MockObject $orphanVisitTransformer; protected function setUp(): void { - $this->visitsHelper = $this->prophesize(VisitsStatsHelperInterface::class); - $this->orphanVisitTransformer = $this->prophesize(DataTransformerInterface::class); + $this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class); + $this->orphanVisitTransformer = $this->createMock(DataTransformerInterface::class); - $this->action = new OrphanVisitsAction($this->visitsHelper->reveal(), $this->orphanVisitTransformer->reveal()); + $this->action = new OrphanVisitsAction($this->visitsHelper, $this->orphanVisitTransformer); } /** @test */ @@ -42,11 +38,13 @@ class OrphanVisitsActionTest extends TestCase { $visitor = Visitor::emptyInstance(); $visits = [Visit::forInvalidShortUrl($visitor), Visit::forRegularNotFound($visitor)]; - $orphanVisits = $this->visitsHelper->orphanVisits(Argument::type(VisitsParams::class))->willReturn( - new Paginator(new ArrayAdapter($visits)), - ); + $this->visitsHelper->expects($this->once())->method('orphanVisits')->with( + $this->isInstanceOf(VisitsParams::class), + )->willReturn(new Paginator(new ArrayAdapter($visits))); $visitsAmount = count($visits); - $transform = $this->orphanVisitTransformer->transform(Argument::type(Visit::class))->willReturn([]); + $this->orphanVisitTransformer->expects($this->exactly($visitsAmount))->method('transform')->with( + $this->isInstanceOf(Visit::class), + )->willReturn([]); /** @var JsonResponse $response */ $response = $this->action->handle(ServerRequestFactory::fromGlobals()); @@ -54,7 +52,5 @@ class OrphanVisitsActionTest extends TestCase self::assertCount($visitsAmount, $payload['visits']['data']); self::assertEquals(200, $response->getStatusCode()); - $orphanVisits->shouldHaveBeenCalledOnce(); - $transform->shouldHaveBeenCalledTimes($visitsAmount); } } From d414496a3ca5e6f94b2758b1cb2fded59b863de0 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:27:27 +0200 Subject: [PATCH 111/182] Migrated ShortUrlVisitsActionTest to use PHPUnit mocks --- .../Action/Visit/ShortUrlVisitsActionTest.php | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php b/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php index be1a88e8..8302dd27 100644 --- a/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Rest\Action\Visit; use Cake\Chronos\Chronos; use Laminas\Diactoros\ServerRequestFactory; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Common\Util\DateRange; @@ -22,27 +20,24 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ShortUrlVisitsActionTest extends TestCase { - use ProphecyTrait; - private ShortUrlVisitsAction $action; - private ObjectProphecy $visitsHelper; + private MockObject $visitsHelper; protected function setUp(): void { - $this->visitsHelper = $this->prophesize(VisitsStatsHelperInterface::class); - $this->action = new ShortUrlVisitsAction($this->visitsHelper->reveal()); + $this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class); + $this->action = new ShortUrlVisitsAction($this->visitsHelper); } /** @test */ public function providingCorrectShortCodeReturnsVisits(): void { $shortCode = 'abc123'; - $this->visitsHelper->visitsForShortUrl( + $this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with( ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), - Argument::type(VisitsParams::class), - Argument::type(ApiKey::class), - )->willReturn(new Paginator(new ArrayAdapter([]))) - ->shouldBeCalledOnce(); + $this->isInstanceOf(VisitsParams::class), + $this->isInstanceOf(ApiKey::class), + )->willReturn(new Paginator(new ArrayAdapter([]))); $response = $this->action->handle($this->requestWithApiKey()->withAttribute('shortCode', $shortCode)); self::assertEquals(200, $response->getStatusCode()); @@ -52,13 +47,15 @@ class ShortUrlVisitsActionTest extends TestCase public function paramsAreReadFromQuery(): void { $shortCode = 'abc123'; - $this->visitsHelper->visitsForShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), new VisitsParams( - DateRange::until(Chronos::parse('2016-01-01 00:00:00')), - 3, - 10, - ), Argument::type(ApiKey::class)) - ->willReturn(new Paginator(new ArrayAdapter([]))) - ->shouldBeCalledOnce(); + $this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with( + ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), + new VisitsParams( + DateRange::until(Chronos::parse('2016-01-01 00:00:00')), + 3, + 10, + ), + $this->isInstanceOf(ApiKey::class), + )->willReturn(new Paginator(new ArrayAdapter([]))); $response = $this->action->handle( $this->requestWithApiKey()->withAttribute('shortCode', $shortCode) From 7aa6afeb3056ced38fe8e9c08f263edde8571069 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:28:29 +0200 Subject: [PATCH 112/182] Migrated TagVisitsActionTest to use PHPUnit mocks --- .../test/Action/Visit/TagVisitsActionTest.php | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/module/Rest/test/Action/Visit/TagVisitsActionTest.php b/module/Rest/test/Action/Visit/TagVisitsActionTest.php index f413a9eb..fd924d17 100644 --- a/module/Rest/test/Action/Visit/TagVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/TagVisitsActionTest.php @@ -6,10 +6,8 @@ namespace ShlinkioTest\Shlink\Rest\Action\Visit; use Laminas\Diactoros\ServerRequestFactory; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\Visit\Model\VisitsParams; use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface; @@ -18,15 +16,13 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class TagVisitsActionTest extends TestCase { - use ProphecyTrait; - private TagVisitsAction $action; - private ObjectProphecy $visitsHelper; + private MockObject $visitsHelper; protected function setUp(): void { - $this->visitsHelper = $this->prophesize(VisitsStatsHelperInterface::class); - $this->action = new TagVisitsAction($this->visitsHelper->reveal()); + $this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class); + $this->action = new TagVisitsAction($this->visitsHelper); } /** @test */ @@ -34,15 +30,16 @@ class TagVisitsActionTest extends TestCase { $tag = 'foo'; $apiKey = ApiKey::create(); - $getVisits = $this->visitsHelper->visitsForTag($tag, Argument::type(VisitsParams::class), $apiKey)->willReturn( - new Paginator(new ArrayAdapter([])), - ); + $this->visitsHelper->expects($this->once())->method('visitsForTag')->with( + $tag, + $this->isInstanceOf(VisitsParams::class), + $apiKey, + )->willReturn(new Paginator(new ArrayAdapter([]))); $response = $this->action->handle( ServerRequestFactory::fromGlobals()->withAttribute('tag', $tag)->withAttribute(ApiKey::class, $apiKey), ); self::assertEquals(200, $response->getStatusCode()); - $getVisits->shouldHaveBeenCalledOnce(); } } From b1f814e118d7c7a8db080157c5602a5559881d67 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:36:16 +0200 Subject: [PATCH 113/182] Migrated InitialApiKeyDelegatorTest to use PHPUnit mocks --- .../ApiKey/InitialApiKeyDelegatorTest.php | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/module/Rest/test/ApiKey/InitialApiKeyDelegatorTest.php b/module/Rest/test/ApiKey/InitialApiKeyDelegatorTest.php index 1db53b80..a44700a6 100644 --- a/module/Rest/test/ApiKey/InitialApiKeyDelegatorTest.php +++ b/module/Rest/test/ApiKey/InitialApiKeyDelegatorTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Rest\ApiKey; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use Mezzio\Application; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Container\ContainerInterface; use Shlinkio\Shlink\Rest\ApiKey\InitialApiKeyDelegator; use Shlinkio\Shlink\Rest\ApiKey\Repository\ApiKeyRepositoryInterface; @@ -18,15 +16,13 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class InitialApiKeyDelegatorTest extends TestCase { - use ProphecyTrait; - private InitialApiKeyDelegator $delegator; - private ObjectProphecy $container; + private MockObject $container; protected function setUp(): void { $this->delegator = new InitialApiKeyDelegator(); - $this->container = $this->prophesize(ContainerInterface::class); + $this->container = $this->createMock(ContainerInterface::class); } /** @@ -35,21 +31,21 @@ class InitialApiKeyDelegatorTest extends TestCase */ public function apiKeyIsInitializedWhenAppropriate(array $config, int $expectedCalls): void { - $app = $this->prophesize(Application::class)->reveal(); - $apiKeyRepo = $this->prophesize(ApiKeyRepositoryInterface::class); - $em = $this->prophesize(EntityManagerInterface::class); + $app = $this->createMock(Application::class); + $apiKeyRepo = $this->createMock(ApiKeyRepositoryInterface::class); + $apiKeyRepo->expects($this->exactly($expectedCalls))->method('createInitialApiKey'); + $em = $this->createMock(EntityManagerInterface::class); + $em->expects($this->exactly($expectedCalls))->method('getRepository')->with(ApiKey::class)->willReturn( + $apiKeyRepo, + ); + $this->container->expects($this->exactly($expectedCalls + 1))->method('get')->willReturnMap([ + ['config', $config], + [EntityManager::class, $em], + ]); - $getConfig = $this->container->get('config')->willReturn($config); - $getRepo = $em->getRepository(ApiKey::class)->willReturn($apiKeyRepo->reveal()); - $getEm = $this->container->get(EntityManager::class)->willReturn($em->reveal()); - - $result = ($this->delegator)($this->container->reveal(), '', fn () => $app); + $result = ($this->delegator)($this->container, '', fn () => $app); self::assertSame($result, $app); - $getConfig->shouldHaveBeenCalledOnce(); - $getRepo->shouldHaveBeenCalledTimes($expectedCalls); - $getEm->shouldHaveBeenCalledTimes($expectedCalls); - $apiKeyRepo->createInitialApiKey(Argument::any())->shouldHaveBeenCalledTimes($expectedCalls); } public function provideConfigs(): iterable From 34338995776dcba674a00d109ff4d7c682f4222f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:40:14 +0200 Subject: [PATCH 114/182] Migrated AuthenticationMiddlewareTest to use PHPUnit mocks --- .../AuthenticationMiddlewareTest.php | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php b/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php index eef78ab7..03379921 100644 --- a/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php +++ b/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php @@ -10,10 +10,8 @@ use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\ServerRequestFactory; use Mezzio\Router\Route; use Mezzio\Router\RouteResult; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -29,21 +27,19 @@ use function Laminas\Stratigility\middleware; class AuthenticationMiddlewareTest extends TestCase { - use ProphecyTrait; - private AuthenticationMiddleware $middleware; - private ObjectProphecy $apiKeyService; - private ObjectProphecy $handler; + private MockObject $apiKeyService; + private MockObject $handler; protected function setUp(): void { - $this->apiKeyService = $this->prophesize(ApiKeyServiceInterface::class); + $this->apiKeyService = $this->createMock(ApiKeyServiceInterface::class); $this->middleware = new AuthenticationMiddleware( - $this->apiKeyService->reveal(), + $this->apiKeyService, [HealthAction::class], ['with_query_api_key'], ); - $this->handler = $this->prophesize(RequestHandlerInterface::class); + $this->handler = $this->createMock(RequestHandlerInterface::class); } /** @@ -52,13 +48,10 @@ class AuthenticationMiddlewareTest extends TestCase */ public function someSituationsFallbackToNextMiddleware(ServerRequestInterface $request): void { - $handle = $this->handler->handle($request)->willReturn(new Response()); - $checkApiKey = $this->apiKeyService->check(Argument::any()); + $this->handler->expects($this->once())->method('handle')->with($request)->willReturn(new Response()); + $this->apiKeyService->expects($this->never())->method('check'); - $this->middleware->process($request, $this->handler->reveal()); - - $handle->shouldHaveBeenCalledOnce(); - $checkApiKey->shouldNotHaveBeenCalled(); + $this->middleware->process($request, $this->handler); } public function provideRequestsWithoutAuth(): iterable @@ -90,12 +83,12 @@ class AuthenticationMiddlewareTest extends TestCase ServerRequestInterface $request, string $expectedMessage, ): void { - $this->apiKeyService->check(Argument::any())->shouldNotBeCalled(); - $this->handler->handle($request)->shouldNotBeCalled(); + $this->apiKeyService->expects($this->never())->method('check'); + $this->handler->expects($this->never())->method('handle'); $this->expectException(MissingAuthenticationException::class); $this->expectExceptionMessage($expectedMessage); - $this->middleware->process($request, $this->handler->reveal()); + $this->middleware->process($request, $this->handler); } public function provideRequestsWithoutApiKey(): iterable @@ -127,12 +120,14 @@ class AuthenticationMiddlewareTest extends TestCase ) ->withHeader('X-Api-Key', $apiKey); - $this->apiKeyService->check($apiKey)->willReturn(new ApiKeyCheckResult())->shouldBeCalledOnce(); - $this->handler->handle($request)->shouldNotBeCalled(); + $this->apiKeyService->expects($this->once())->method('check')->with($apiKey)->willReturn( + new ApiKeyCheckResult(), + ); + $this->handler->expects($this->never())->method('handle'); $this->expectException(VerifyAuthenticationException::class); $this->expectExceptionMessage('Provided API key does not exist or is invalid'); - $this->middleware->process($request, $this->handler->reveal()); + $this->middleware->process($request, $this->handler); } /** @test */ @@ -147,13 +142,14 @@ class AuthenticationMiddlewareTest extends TestCase ) ->withHeader('X-Api-Key', $key); - $handle = $this->handler->handle($request->withAttribute(ApiKey::class, $apiKey))->willReturn(new Response()); - $checkApiKey = $this->apiKeyService->check($key)->willReturn(new ApiKeyCheckResult($apiKey)); + $this->handler->expects($this->once())->method('handle')->with( + $request->withAttribute(ApiKey::class, $apiKey), + )->willReturn(new Response()); + $this->apiKeyService->expects($this->once())->method('check')->with($key)->willReturn( + new ApiKeyCheckResult($apiKey), + ); - $this->middleware->process($request, $this->handler->reveal()); - - $handle->shouldHaveBeenCalledOnce(); - $checkApiKey->shouldHaveBeenCalledOnce(); + $this->middleware->process($request, $this->handler); } private function getDummyMiddleware(): MiddlewareInterface From b2b424a4ed308793a6572ff112e91b5aa4c774d8 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:45:23 +0200 Subject: [PATCH 115/182] Migrated BodyParserMiddlewareTest to use PHPUnit mocks --- .../Middleware/BodyParserMiddlewareTest.php | 53 +++++++------------ 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/module/Rest/test/Middleware/BodyParserMiddlewareTest.php b/module/Rest/test/Middleware/BodyParserMiddlewareTest.php index f254197e..63354a76 100644 --- a/module/Rest/test/Middleware/BodyParserMiddlewareTest.php +++ b/module/Rest/test/Middleware/BodyParserMiddlewareTest.php @@ -7,20 +7,14 @@ namespace ShlinkioTest\Shlink\Rest\Middleware; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\Stream; +use PHPUnit\Framework\MockObject\MockObject; 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; use Shlinkio\Shlink\Rest\Middleware\BodyParserMiddleware; -use function array_shift; - class BodyParserMiddlewareTest extends TestCase { - use ProphecyTrait; - private BodyParserMiddleware $middleware; protected function setUp(): void @@ -34,11 +28,11 @@ class BodyParserMiddlewareTest extends TestCase */ public function requestsFromOtherMethodsJustFallbackToNextMiddleware(string $method): void { - $request = $this->prophesize(ServerRequestInterface::class); - $request->getMethod()->willReturn($method); - $request->getParsedBody()->willReturn([]); + $request = $this->createMock(ServerRequestInterface::class); + $request->method('getMethod')->willReturn($method); + $request->method('getParsedBody')->willReturn([]); - self::assertHandlingRequestJustFallsBackToNext($request); + $this->assertHandlingRequestJustFallsBackToNext($request); } public function provideIgnoredRequestMethods(): iterable @@ -51,25 +45,21 @@ class BodyParserMiddlewareTest extends TestCase /** @test */ public function requestsWithNonEmptyBodyJustFallbackToNextMiddleware(): void { - $request = $this->prophesize(ServerRequestInterface::class); - $request->getMethod()->willReturn('POST'); - $request->getParsedBody()->willReturn(['foo' => 'bar']); + $request = $this->createMock(ServerRequestInterface::class); + $request->method('getMethod')->willReturn('POST'); + $request->method('getParsedBody')->willReturn(['foo' => 'bar']); - self::assertHandlingRequestJustFallsBackToNext($request); + $this->assertHandlingRequestJustFallsBackToNext($request); } - private function assertHandlingRequestJustFallsBackToNext(ProphecyInterface $requestMock): void + private function assertHandlingRequestJustFallsBackToNext(MockObject & ServerRequestInterface $request): void { - $getContentType = $requestMock->getHeaderLine('Content-type')->willReturn(''); - $request = $requestMock->reveal(); + $request->expects($this->never())->method('getHeaderLine'); - $nextHandler = $this->prophesize(RequestHandlerInterface::class); - $handle = $nextHandler->handle($request)->willReturn(new Response()); + $nextHandler = $this->createMock(RequestHandlerInterface::class); + $nextHandler->expects($this->once())->method('handle')->with($request)->willReturn(new Response()); - $this->middleware->process($request, $nextHandler->reveal()); - - $handle->shouldHaveBeenCalledOnce(); - $getContentType->shouldNotHaveBeenCalled(); + $this->middleware->process($request, $nextHandler); } /** @test */ @@ -80,12 +70,11 @@ class BodyParserMiddlewareTest extends TestCase $body->write('{"foo": "bar", "bar": ["one", 5]}'); $request = (new ServerRequest())->withMethod('PUT') ->withBody($body); - $delegate = $this->prophesize(RequestHandlerInterface::class); - $process = $delegate->handle(Argument::type(ServerRequestInterface::class))->will( - function (array $args) use ($test) { - /** @var ServerRequestInterface $req */ - $req = array_shift($args); - + $handler = $this->createMock(RequestHandlerInterface::class); + $handler->expects($this->once())->method('handle')->with( + $this->isInstanceOf(ServerRequestInterface::class), + )->willReturnCallback( + function (ServerRequestInterface $req) use ($test) { $test->assertEquals([ 'foo' => 'bar', 'bar' => ['one', 5], @@ -95,8 +84,6 @@ class BodyParserMiddlewareTest extends TestCase }, ); - $this->middleware->process($request, $delegate->reveal()); - - $process->shouldHaveBeenCalledOnce(); + $this->middleware->process($request, $handler); } } From dfc8e8d74e17af0608890d1119487d8f2d04bb79 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:47:34 +0200 Subject: [PATCH 116/182] Migrated CrossDomainMiddlewareTest to use PHPUnit mocks --- .../Middleware/CrossDomainMiddlewareTest.php | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php index 286652bf..e9b294da 100644 --- a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php +++ b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php @@ -6,33 +6,29 @@ namespace ShlinkioTest\Shlink\Rest\Middleware; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware; class CrossDomainMiddlewareTest extends TestCase { - use ProphecyTrait; - private CrossDomainMiddleware $middleware; - private ObjectProphecy $handler; + private MockObject $handler; protected function setUp(): void { $this->middleware = new CrossDomainMiddleware(['max_age' => 1000]); - $this->handler = $this->prophesize(RequestHandlerInterface::class); + $this->handler = $this->createMock(RequestHandlerInterface::class); } /** @test */ public function nonCrossDomainRequestsAreNotAffected(): void { $originalResponse = (new Response())->withStatus(404); - $this->handler->handle(Argument::any())->willReturn($originalResponse)->shouldBeCalledOnce(); + $this->handler->expects($this->once())->method('handle')->willReturn($originalResponse); - $response = $this->middleware->process(new ServerRequest(), $this->handler->reveal()); + $response = $this->middleware->process(new ServerRequest(), $this->handler); $headers = $response->getHeaders(); self::assertSame($originalResponse, $response); @@ -47,12 +43,9 @@ class CrossDomainMiddlewareTest extends TestCase public function anyRequestIncludesTheAllowAccessHeader(): void { $originalResponse = new Response(); - $this->handler->handle(Argument::any())->willReturn($originalResponse)->shouldBeCalledOnce(); + $this->handler->expects($this->once())->method('handle')->willReturn($originalResponse); - $response = $this->middleware->process( - (new ServerRequest())->withHeader('Origin', 'local'), - $this->handler->reveal(), - ); + $response = $this->middleware->process((new ServerRequest())->withHeader('Origin', 'local'), $this->handler); self::assertNotSame($originalResponse, $response); $headers = $response->getHeaders(); @@ -71,9 +64,9 @@ class CrossDomainMiddlewareTest extends TestCase ->withMethod('OPTIONS') ->withHeader('Origin', 'local') ->withHeader('Access-Control-Request-Headers', 'foo, bar, baz'); - $this->handler->handle(Argument::any())->willReturn($originalResponse)->shouldBeCalledOnce(); + $this->handler->expects($this->once())->method('handle')->willReturn($originalResponse); - $response = $this->middleware->process($request, $this->handler->reveal()); + $response = $this->middleware->process($request, $this->handler); self::assertNotSame($originalResponse, $response); $headers = $response->getHeaders(); @@ -99,9 +92,9 @@ class CrossDomainMiddlewareTest extends TestCase } $request = (new ServerRequest())->withHeader('Origin', 'local') ->withMethod('OPTIONS'); - $this->handler->handle(Argument::any())->willReturn($originalResponse)->shouldBeCalledOnce(); + $this->handler->expects($this->once())->method('handle')->willReturn($originalResponse); - $response = $this->middleware->process($request, $this->handler->reveal()); + $response = $this->middleware->process($request, $this->handler); self::assertEquals($response->getHeaderLine('Access-Control-Allow-Methods'), $expectedAllowedMethods); self::assertEquals(204, $response->getStatusCode()); @@ -126,9 +119,9 @@ class CrossDomainMiddlewareTest extends TestCase $originalResponse = (new Response())->withStatus($status); $request = (new ServerRequest())->withMethod($method) ->withHeader('Origin', 'local'); - $this->handler->handle(Argument::any())->willReturn($originalResponse)->shouldBeCalledOnce(); + $this->handler->expects($this->once())->method('handle')->willReturn($originalResponse); - $response = $this->middleware->process($request, $this->handler->reveal()); + $response = $this->middleware->process($request, $this->handler); self::assertEquals($expectedStatus, $response->getStatusCode()); } From db85915c2f04ec68cd8cbb724a7fd340a8a30702 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:48:30 +0200 Subject: [PATCH 117/182] Migrated BackwardsCompatibleProblemDetailsHandlerTest to use PHPUnit mocks --- .../BackwardsCompatibleProblemDetailsHandlerTest.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/module/Rest/test/Middleware/ErrorHandler/BackwardsCompatibleProblemDetailsHandlerTest.php b/module/Rest/test/Middleware/ErrorHandler/BackwardsCompatibleProblemDetailsHandlerTest.php index 00dddb2f..ab468803 100644 --- a/module/Rest/test/Middleware/ErrorHandler/BackwardsCompatibleProblemDetailsHandlerTest.php +++ b/module/Rest/test/Middleware/ErrorHandler/BackwardsCompatibleProblemDetailsHandlerTest.php @@ -6,7 +6,6 @@ namespace ShlinkioTest\Shlink\Rest\Middleware\ErrorHandler; use Laminas\Diactoros\ServerRequestFactory; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Core\Exception\ValidationException; @@ -16,8 +15,6 @@ use Throwable; class BackwardsCompatibleProblemDetailsHandlerTest extends TestCase { - use ProphecyTrait; - private BackwardsCompatibleProblemDetailsHandler $handler; protected function setUp(): void @@ -34,13 +31,12 @@ class BackwardsCompatibleProblemDetailsHandlerTest extends TestCase Throwable $thrownException, string $expectedException, ): void { - $handler = $this->prophesize(RequestHandlerInterface::class); - $handle = $handler->handle($request)->willThrow($thrownException); + $handler = $this->createMock(RequestHandlerInterface::class); + $handler->expects($this->once())->method('handle')->with($request)->willThrowException($thrownException); $this->expectException($expectedException); - $handle->shouldBeCalledOnce(); - $this->handler->process($request, $handler->reveal()); + $this->handler->process($request, $handler); } public function provideExceptions(): iterable From 674a4416cf930fd3285f553b67e0292830105335 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:51:29 +0200 Subject: [PATCH 118/182] Migrated NotConfiguredMercureErrorHandlerTest to use PHPUnit mocks --- .../NotConfiguredMercureErrorHandlerTest.php | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/module/Rest/test/Middleware/Mercure/NotConfiguredMercureErrorHandlerTest.php b/module/Rest/test/Middleware/Mercure/NotConfiguredMercureErrorHandlerTest.php index 138c01f0..9fe595ec 100644 --- a/module/Rest/test/Middleware/Mercure/NotConfiguredMercureErrorHandlerTest.php +++ b/module/Rest/test/Middleware/Mercure/NotConfiguredMercureErrorHandlerTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Rest\Middleware\Mercure; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequestFactory; use Mezzio\ProblemDetails\ProblemDetailsResponseFactory; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Server\RequestHandlerInterface; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Rest\Exception\MercureException; @@ -18,45 +16,40 @@ use Shlinkio\Shlink\Rest\Middleware\Mercure\NotConfiguredMercureErrorHandler; class NotConfiguredMercureErrorHandlerTest extends TestCase { - use ProphecyTrait; - private NotConfiguredMercureErrorHandler $middleware; - private ObjectProphecy $respFactory; - private ObjectProphecy $logger; - private ObjectProphecy $handler; + private MockObject $respFactory; + private MockObject $logger; + private MockObject $handler; protected function setUp(): void { - $this->respFactory = $this->prophesize(ProblemDetailsResponseFactory::class); - $this->logger = $this->prophesize(LoggerInterface::class); - $this->middleware = new NotConfiguredMercureErrorHandler($this->respFactory->reveal(), $this->logger->reveal()); - $this->handler = $this->prophesize(RequestHandlerInterface::class); + $this->respFactory = $this->createMock(ProblemDetailsResponseFactory::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->middleware = new NotConfiguredMercureErrorHandler($this->respFactory, $this->logger); + $this->handler = $this->createMock(RequestHandlerInterface::class); } /** @test */ public function requestHandlerIsInvokedWhenNotErrorOccurs(): void { $req = ServerRequestFactory::fromGlobals(); - $handle = $this->handler->handle($req)->willReturn(new Response()); + $this->handler->expects($this->once())->method('handle')->with($req)->willReturn(new Response()); + $this->respFactory->expects($this->never())->method('createResponseFromThrowable'); + $this->logger->expects($this->never())->method('warning'); - $this->middleware->process($req, $this->handler->reveal()); - - $handle->shouldHaveBeenCalledOnce(); - $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->respFactory->createResponseFromThrowable(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->middleware->process($req, $this->handler); } /** @test */ public function exceptionIsParsedToResponse(): void { $req = ServerRequestFactory::fromGlobals(); - $handle = $this->handler->handle($req)->willThrow(MercureException::mercureNotConfigured()); - $createResp = $this->respFactory->createResponseFromThrowable(Argument::cetera())->willReturn(new Response()); + $this->handler->expects($this->once())->method('handle')->with($req)->willThrowException( + MercureException::mercureNotConfigured(), + ); + $this->respFactory->expects($this->once())->method('createResponseFromThrowable')->willReturn(new Response()); + $this->logger->expects($this->once())->method('warning'); - $this->middleware->process($req, $this->handler->reveal()); - - $handle->shouldHaveBeenCalledOnce(); - $createResp->shouldHaveBeenCalledOnce(); - $this->logger->warning(Argument::cetera())->shouldHaveBeenCalledOnce(); + $this->middleware->process($req, $this->handler); } } From 23aa7a015c6211eb2fd86eebecc5fdb8e5b09045 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:53:48 +0200 Subject: [PATCH 119/182] Migrated CreateShortUrlContentNegotiationMiddlewareTest to use PHPUnit mocks --- ...ortUrlContentNegotiationMiddlewareTest.php | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php index b77d79a9..1e56d690 100644 --- a/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php +++ b/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php @@ -7,34 +7,32 @@ namespace ShlinkioTest\Shlink\Rest\Middleware\ShortUrl; use Laminas\Diactoros\Response; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\ServerRequest; +use PHPUnit\Framework\MockObject\MockObject; 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; use Shlinkio\Shlink\Rest\Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware; class CreateShortUrlContentNegotiationMiddlewareTest extends TestCase { - use ProphecyTrait; - private CreateShortUrlContentNegotiationMiddleware $middleware; - private ObjectProphecy $requestHandler; + private MockObject $requestHandler; protected function setUp(): void { $this->middleware = new CreateShortUrlContentNegotiationMiddleware(); - $this->requestHandler = $this->prophesize(RequestHandlerInterface::class); + $this->requestHandler = $this->createMock(RequestHandlerInterface::class); } /** @test */ public function whenNoJsonResponseIsReturnedNoFurtherOperationsArePerformed(): void { $expectedResp = new Response(); - $this->requestHandler->handle(Argument::type(ServerRequestInterface::class))->willReturn($expectedResp); + $this->requestHandler->method('handle')->with($this->isInstanceOf(ServerRequestInterface::class))->willReturn( + $expectedResp, + ); - $resp = $this->middleware->process(new ServerRequest(), $this->requestHandler->reveal()); + $resp = $this->middleware->process(new ServerRequest(), $this->requestHandler); self::assertSame($expectedResp, $resp); } @@ -51,14 +49,13 @@ class CreateShortUrlContentNegotiationMiddlewareTest extends TestCase $request = $request->withHeader('Accept', $accept); } - $handle = $this->requestHandler->handle(Argument::type(ServerRequestInterface::class))->willReturn( - new JsonResponse(['shortUrl' => 'http://doma.in/foo']), - ); + $this->requestHandler->expects($this->once())->method('handle')->with( + $this->isInstanceOf(ServerRequestInterface::class), + )->willReturn(new JsonResponse(['shortUrl' => 'http://doma.in/foo'])); - $response = $this->middleware->process($request, $this->requestHandler->reveal()); + $response = $this->middleware->process($request, $this->requestHandler); self::assertEquals($expectedContentType, $response->getHeaderLine('Content-type')); - $handle->shouldHaveBeenCalled(); } public function provideData(): iterable @@ -82,14 +79,13 @@ class CreateShortUrlContentNegotiationMiddlewareTest extends TestCase { $request = (new ServerRequest())->withQueryParams(['format' => 'txt']); - $handle = $this->requestHandler->handle(Argument::type(ServerRequestInterface::class))->willReturn( - new JsonResponse($json), - ); + $this->requestHandler->expects($this->once())->method('handle')->with( + $this->isInstanceOf(ServerRequestInterface::class), + )->willReturn(new JsonResponse($json)); - $response = $this->middleware->process($request, $this->requestHandler->reveal()); + $response = $this->middleware->process($request, $this->requestHandler); self::assertEquals($expectedBody, (string) $response->getBody()); - $handle->shouldHaveBeenCalled(); } public function provideTextBodies(): iterable From 8f76c3e2021776f2c0cc0a3d6cf969e043cef867 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:55:11 +0200 Subject: [PATCH 120/182] Migrated DefaultShortCodesLengthMiddlewareTest to use PHPUnit mocks --- .../DefaultShortCodesLengthMiddlewareTest.php | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/module/Rest/test/Middleware/ShortUrl/DefaultShortCodesLengthMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/DefaultShortCodesLengthMiddlewareTest.php index 7f61830c..f7a647b1 100644 --- a/module/Rest/test/Middleware/ShortUrl/DefaultShortCodesLengthMiddlewareTest.php +++ b/module/Rest/test/Middleware/ShortUrl/DefaultShortCodesLengthMiddlewareTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Rest\Middleware\ShortUrl; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequestFactory; use PHPUnit\Framework\Assert; +use PHPUnit\Framework\MockObject\MockObject; 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; use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter; @@ -18,14 +16,12 @@ use Shlinkio\Shlink\Rest\Middleware\ShortUrl\DefaultShortCodesLengthMiddleware; class DefaultShortCodesLengthMiddlewareTest extends TestCase { - use ProphecyTrait; - private DefaultShortCodesLengthMiddleware $middleware; - private ObjectProphecy $handler; + private MockObject $handler; protected function setUp(): void { - $this->handler = $this->prophesize(RequestHandlerInterface::class); + $this->handler = $this->createMock(RequestHandlerInterface::class); $this->middleware = new DefaultShortCodesLengthMiddleware(8); } @@ -36,17 +32,17 @@ class DefaultShortCodesLengthMiddlewareTest extends TestCase public function defaultValueIsInjectedInBodyWhenNotProvided(array $body, int $expectedLength): void { $request = ServerRequestFactory::fromGlobals()->withParsedBody($body); - $handle = $this->handler->handle(Argument::that(function (ServerRequestInterface $req) use ($expectedLength) { - $parsedBody = $req->getParsedBody(); - Assert::assertArrayHasKey(ShortUrlInputFilter::SHORT_CODE_LENGTH, $parsedBody); - Assert::assertEquals($expectedLength, $parsedBody[ShortUrlInputFilter::SHORT_CODE_LENGTH]); + $this->handler->expects($this->once())->method('handle')->with($this->callback( + function (ServerRequestInterface $req) use ($expectedLength) { + $parsedBody = $req->getParsedBody(); + Assert::assertArrayHasKey(ShortUrlInputFilter::SHORT_CODE_LENGTH, $parsedBody); + Assert::assertEquals($expectedLength, $parsedBody[ShortUrlInputFilter::SHORT_CODE_LENGTH]); - return $req; - }))->willReturn(new Response()); + return true; + }, + ))->willReturn(new Response()); - $this->middleware->process($request, $this->handler->reveal()); - - $handle->shouldHaveBeenCalledOnce(); + $this->middleware->process($request, $this->handler); } public function provideBodies(): iterable From d61c79da84a6f2bb2a0c26dbc834a6705b5b7edd Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 22:56:12 +0200 Subject: [PATCH 121/182] Migrated DropDefaultDomainFromRequestMiddlewareTest to use PHPUnit mocks --- ...DefaultDomainFromRequestMiddlewareTest.php | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/module/Rest/test/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddlewareTest.php index 9418a16a..eb018ad3 100644 --- a/module/Rest/test/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddlewareTest.php +++ b/module/Rest/test/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddlewareTest.php @@ -7,24 +7,20 @@ namespace ShlinkioTest\Shlink\Rest\Middleware\ShortUrl; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequestFactory; use PHPUnit\Framework\Assert; +use PHPUnit\Framework\MockObject\MockObject; 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; use Shlinkio\Shlink\Rest\Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware; class DropDefaultDomainFromRequestMiddlewareTest extends TestCase { - use ProphecyTrait; - private DropDefaultDomainFromRequestMiddleware $middleware; - private ObjectProphecy $next; + private MockObject $next; protected function setUp(): void { - $this->next = $this->prophesize(RequestHandlerInterface::class); + $this->next = $this->createMock(RequestHandlerInterface::class); $this->middleware = new DropDefaultDomainFromRequestMiddleware('doma.in'); } @@ -36,15 +32,15 @@ class DropDefaultDomainFromRequestMiddlewareTest extends TestCase { $req = ServerRequestFactory::fromGlobals()->withQueryParams($providedPayload)->withParsedBody($providedPayload); - $handle = $this->next->handle(Argument::that(function (ServerRequestInterface $request) use ($expectedPayload) { - Assert::assertEquals($expectedPayload, $request->getQueryParams()); - Assert::assertEquals($expectedPayload, $request->getParsedBody()); - return $request; - }))->willReturn(new Response()); + $this->next->expects($this->once())->method('handle')->with($this->callback( + function (ServerRequestInterface $request) use ($expectedPayload) { + Assert::assertEquals($expectedPayload, $request->getQueryParams()); + Assert::assertEquals($expectedPayload, $request->getParsedBody()); + return true; + }, + ))->willReturn(new Response()); - $this->middleware->process($req, $this->next->reveal()); - - $handle->shouldHaveBeenCalledOnce(); + $this->middleware->process($req, $this->next); } public function provideQueryParams(): iterable From 083ccd36b7486f86a2b0c9d8282f5f70b1eb3d41 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 23:00:49 +0200 Subject: [PATCH 122/182] Migrated OverrideDomainMiddlewareTest to use PHPUnit mocks --- .../ShortUrl/OverrideDomainMiddlewareTest.php | 67 ++++++++----------- 1 file changed, 27 insertions(+), 40 deletions(-) diff --git a/module/Rest/test/Middleware/ShortUrl/OverrideDomainMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/OverrideDomainMiddlewareTest.php index 4bf469a4..f4f0b4d1 100644 --- a/module/Rest/test/Middleware/ShortUrl/OverrideDomainMiddlewareTest.php +++ b/module/Rest/test/Middleware/ShortUrl/OverrideDomainMiddlewareTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Rest\Middleware\ShortUrl; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequestFactory; use PHPUnit\Framework\Assert; +use PHPUnit\Framework\MockObject\MockObject; 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; use Shlinkio\Shlink\Core\Domain\DomainServiceInterface; @@ -22,20 +20,18 @@ use Shlinkio\Shlink\Rest\Middleware\ShortUrl\OverrideDomainMiddleware; class OverrideDomainMiddlewareTest extends TestCase { - use ProphecyTrait; - private OverrideDomainMiddleware $middleware; - private ObjectProphecy $domainService; - private ObjectProphecy $apiKey; - private ObjectProphecy $handler; + private MockObject $domainService; + private MockObject $apiKey; + private MockObject $handler; protected function setUp(): void { - $this->apiKey = $this->prophesize(ApiKey::class); - $this->handler = $this->prophesize(RequestHandlerInterface::class); + $this->apiKey = $this->createMock(ApiKey::class); + $this->handler = $this->createMock(RequestHandlerInterface::class); - $this->domainService = $this->prophesize(DomainServiceInterface::class); - $this->middleware = new OverrideDomainMiddleware($this->domainService->reveal()); + $this->domainService = $this->createMock(DomainServiceInterface::class); + $this->middleware = new OverrideDomainMiddleware($this->domainService); } /** @test */ @@ -43,16 +39,13 @@ class OverrideDomainMiddlewareTest extends TestCase { $request = $this->requestWithApiKey(); $response = new Response(); - $hasRole = $this->apiKey->hasRole(Role::DOMAIN_SPECIFIC)->willReturn(false); - $handle = $this->handler->handle($request)->willReturn($response); - $getDomain = $this->domainService->getDomain(Argument::cetera()); + $this->apiKey->expects($this->once())->method('hasRole')->with(Role::DOMAIN_SPECIFIC)->willReturn(false); + $this->handler->expects($this->once())->method('handle')->with($request)->willReturn($response); + $this->domainService->expects($this->never())->method('getDomain'); - $result = $this->middleware->process($request, $this->handler->reveal()); + $result = $this->middleware->process($request, $this->handler); self::assertSame($response, $result); - $hasRole->shouldHaveBeenCalledOnce(); - $handle->shouldHaveBeenCalledOnce(); - $getDomain->shouldNotHaveBeenCalled(); } /** @@ -62,22 +55,19 @@ class OverrideDomainMiddlewareTest extends TestCase public function overwritesRequestBodyWhenMethodIsPost(Domain $domain, array $body, array $expectedBody): void { $request = $this->requestWithApiKey()->withMethod('POST')->withParsedBody($body); - $hasRole = $this->apiKey->hasRole(Role::DOMAIN_SPECIFIC)->willReturn(true); - $getRoleMeta = $this->apiKey->getRoleMeta(Role::DOMAIN_SPECIFIC)->willReturn(['domain_id' => '123']); - $getDomain = $this->domainService->getDomain('123')->willReturn($domain); - $handle = $this->handler->handle(Argument::that( + $this->apiKey->expects($this->once())->method('hasRole')->with(Role::DOMAIN_SPECIFIC)->willReturn(true); + $this->apiKey->expects($this->once())->method('getRoleMeta')->with(Role::DOMAIN_SPECIFIC)->willReturn( + ['domain_id' => '123'], + ); + $this->domainService->expects($this->once())->method('getDomain')->with('123')->willReturn($domain); + $this->handler->expects($this->once())->method('handle')->with($this->callback( function (ServerRequestInterface $req) use ($expectedBody): bool { Assert::assertEquals($req->getParsedBody(), $expectedBody); return true; }, ))->willReturn(new Response()); - $this->middleware->process($request, $this->handler->reveal()); - - $hasRole->shouldHaveBeenCalledOnce(); - $getRoleMeta->shouldHaveBeenCalledOnce(); - $getDomain->shouldHaveBeenCalledOnce(); - $handle->shouldHaveBeenCalledOnce(); + $this->middleware->process($request, $this->handler); } public function provideBodies(): iterable @@ -112,22 +102,19 @@ class OverrideDomainMiddlewareTest extends TestCase { $domain = Domain::withAuthority('something.com'); $request = $this->requestWithApiKey()->withMethod($method); - $hasRole = $this->apiKey->hasRole(Role::DOMAIN_SPECIFIC)->willReturn(true); - $getRoleMeta = $this->apiKey->getRoleMeta(Role::DOMAIN_SPECIFIC)->willReturn(['domain_id' => '123']); - $getDomain = $this->domainService->getDomain('123')->willReturn($domain); - $handle = $this->handler->handle(Argument::that( + $this->apiKey->expects($this->once())->method('hasRole')->with(Role::DOMAIN_SPECIFIC)->willReturn(true); + $this->apiKey->expects($this->once())->method('getRoleMeta')->with(Role::DOMAIN_SPECIFIC)->willReturn( + ['domain_id' => '123'], + ); + $this->domainService->expects($this->once())->method('getDomain')->with('123')->willReturn($domain); + $this->handler->expects($this->once())->method('handle')->with($this->callback( function (ServerRequestInterface $req): bool { Assert::assertEquals($req->getAttribute(ShortUrlInputFilter::DOMAIN), 'something.com'); return true; }, ))->willReturn(new Response()); - $this->middleware->process($request, $this->handler->reveal()); - - $hasRole->shouldHaveBeenCalledOnce(); - $getRoleMeta->shouldHaveBeenCalledOnce(); - $getDomain->shouldHaveBeenCalledOnce(); - $handle->shouldHaveBeenCalledOnce(); + $this->middleware->process($request, $this->handler); } public function provideMethods(): iterable @@ -140,6 +127,6 @@ class OverrideDomainMiddlewareTest extends TestCase private function requestWithApiKey(): ServerRequestInterface { - return ServerRequestFactory::fromGlobals()->withAttribute(ApiKey::class, $this->apiKey->reveal()); + return ServerRequestFactory::fromGlobals()->withAttribute(ApiKey::class, $this->apiKey); } } From ef82158368c15bab15e7241e5d868d1e47a779cb Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 23:07:17 +0200 Subject: [PATCH 123/182] Migrated ApiKeyServiceTest to use PHPUnit mocks --- composer.json | 1 - .../Rest/test/Service/ApiKeyServiceTest.php | 57 +++++++------------ 2 files changed, 21 insertions(+), 37 deletions(-) diff --git a/composer.json b/composer.json index 5f72b1db..cdf2c42e 100644 --- a/composer.json +++ b/composer.json @@ -65,7 +65,6 @@ "dms/phpunit-arraysubset-asserts": "^0.4.0", "infection/infection": "^0.26.15", "openswoole/ide-helper": "~4.11.5", - "phpspec/prophecy-phpunit": "^2.0", "phpstan/phpstan": "^1.8", "phpstan/phpstan-doctrine": "^1.3", "phpstan/phpstan-symfony": "^1.2", diff --git a/module/Rest/test/Service/ApiKeyServiceTest.php b/module/Rest/test/Service/ApiKeyServiceTest.php index 66bd4c28..9336da9e 100644 --- a/module/Rest/test/Service/ApiKeyServiceTest.php +++ b/module/Rest/test/Service/ApiKeyServiceTest.php @@ -7,10 +7,8 @@ namespace ShlinkioTest\Shlink\Rest\Service; use Cake\Chronos\Chronos; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Core\Domain\Entity\Domain; use Shlinkio\Shlink\Rest\ApiKey\Model\ApiKeyMeta; @@ -20,15 +18,15 @@ use Shlinkio\Shlink\Rest\Service\ApiKeyService; class ApiKeyServiceTest extends TestCase { - use ProphecyTrait; - private ApiKeyService $service; - private ObjectProphecy $em; + private MockObject $em; + private MockObject $repo; protected function setUp(): void { - $this->em = $this->prophesize(EntityManager::class); - $this->service = new ApiKeyService($this->em->reveal()); + $this->em = $this->createMock(EntityManager::class); + $this->repo = $this->createMock(EntityRepository::class); + $this->service = new ApiKeyService($this->em); } /** @@ -38,8 +36,8 @@ class ApiKeyServiceTest extends TestCase */ public function apiKeyIsProperlyCreated(?Chronos $date, ?string $name, array $roles): void { - $this->em->flush()->shouldBeCalledOnce(); - $this->em->persist(Argument::type(ApiKey::class))->shouldBeCalledOnce(); + $this->em->expects($this->once())->method('flush'); + $this->em->expects($this->once())->method('persist')->with($this->isInstanceOf(ApiKey::class)); $key = $this->service->create($date, $name, ...$roles); @@ -69,10 +67,8 @@ class ApiKeyServiceTest extends TestCase */ public function checkReturnsFalseForInvalidApiKeys(?ApiKey $invalidKey): void { - $repo = $this->prophesize(EntityRepository::class); - $repo->findOneBy(['key' => '12345'])->willReturn($invalidKey) - ->shouldBeCalledOnce(); - $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); + $this->repo->expects($this->once())->method('findOneBy')->with(['key' => '12345'])->willReturn($invalidKey); + $this->em->method('getRepository')->with(ApiKey::class)->willReturn($this->repo); $result = $this->service->check('12345'); @@ -92,10 +88,8 @@ class ApiKeyServiceTest extends TestCase { $apiKey = ApiKey::create(); - $repo = $this->prophesize(EntityRepository::class); - $repo->findOneBy(['key' => '12345'])->willReturn($apiKey) - ->shouldBeCalledOnce(); - $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); + $this->repo->expects($this->once())->method('findOneBy')->with(['key' => '12345'])->willReturn($apiKey); + $this->em->method('getRepository')->with(ApiKey::class)->willReturn($this->repo); $result = $this->service->check('12345'); @@ -106,10 +100,8 @@ class ApiKeyServiceTest extends TestCase /** @test */ public function disableThrowsExceptionWhenNoApiKeyIsFound(): void { - $repo = $this->prophesize(EntityRepository::class); - $repo->findOneBy(['key' => '12345'])->willReturn(null) - ->shouldBeCalledOnce(); - $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); + $this->repo->expects($this->once())->method('findOneBy')->with(['key' => '12345'])->willReturn(null); + $this->em->method('getRepository')->with(ApiKey::class)->willReturn($this->repo); $this->expectException(InvalidArgumentException::class); @@ -120,12 +112,9 @@ class ApiKeyServiceTest extends TestCase public function disableReturnsDisabledApiKeyWhenFound(): void { $key = ApiKey::create(); - $repo = $this->prophesize(EntityRepository::class); - $repo->findOneBy(['key' => '12345'])->willReturn($key) - ->shouldBeCalledOnce(); - $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); - - $this->em->flush()->shouldBeCalledOnce(); + $this->repo->expects($this->once())->method('findOneBy')->with(['key' => '12345'])->willReturn($key); + $this->em->method('getRepository')->with(ApiKey::class)->willReturn($this->repo); + $this->em->expects($this->once())->method('flush'); self::assertTrue($key->isEnabled()); $returnedKey = $this->service->disable('12345'); @@ -138,10 +127,8 @@ class ApiKeyServiceTest extends TestCase { $expectedApiKeys = [ApiKey::create(), ApiKey::create(), ApiKey::create()]; - $repo = $this->prophesize(EntityRepository::class); - $repo->findBy([])->willReturn($expectedApiKeys) - ->shouldBeCalledOnce(); - $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); + $this->repo->expects($this->once())->method('findBy')->with([])->willReturn($expectedApiKeys); + $this->em->method('getRepository')->with(ApiKey::class)->willReturn($this->repo); $result = $this->service->listKeys(); @@ -153,10 +140,8 @@ class ApiKeyServiceTest extends TestCase { $expectedApiKeys = [ApiKey::create(), ApiKey::create(), ApiKey::create()]; - $repo = $this->prophesize(EntityRepository::class); - $repo->findBy(['enabled' => true])->willReturn($expectedApiKeys) - ->shouldBeCalledOnce(); - $this->em->getRepository(ApiKey::class)->willReturn($repo->reveal()); + $this->repo->expects($this->once())->method('findBy')->with(['enabled' => true])->willReturn($expectedApiKeys); + $this->em->method('getRepository')->with(ApiKey::class)->willReturn($this->repo); $result = $this->service->listKeys(true); From da658185c329896f9425f0f5d0affa930c9d48cd Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 23:07:50 +0200 Subject: [PATCH 124/182] Fixed coding styles --- module/Core/test/Visit/VisitsStatsHelperTest.php | 4 ++-- module/Core/test/Visit/VisitsTrackerTest.php | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/module/Core/test/Visit/VisitsStatsHelperTest.php b/module/Core/test/Visit/VisitsStatsHelperTest.php index b6ab393e..4f3ffee2 100644 --- a/module/Core/test/Visit/VisitsStatsHelperTest.php +++ b/module/Core/test/Visit/VisitsStatsHelperTest.php @@ -57,7 +57,7 @@ class VisitsStatsHelperTest extends TestCase $expectedCount * 3, ); $repo->expects($this->once())->method('countOrphanVisits')->with( - $this->isInstanceOf(VisitsCountFiltering::class) + $this->isInstanceOf(VisitsCountFiltering::class), )->willReturn($expectedCount); $this->em->expects($this->once())->method('getRepository')->with(Visit::class)->willReturn($repo); @@ -92,7 +92,7 @@ class VisitsStatsHelperTest extends TestCase )->willReturn($list); $repo2->method('countVisitsByShortCode')->with( $identifier, - $this->isInstanceOf(VisitsCountFiltering::class) + $this->isInstanceOf(VisitsCountFiltering::class), )->willReturn(1); $this->em->expects($this->exactly(2))->method('getRepository')->willReturnMap([ diff --git a/module/Core/test/Visit/VisitsTrackerTest.php b/module/Core/test/Visit/VisitsTrackerTest.php index 2653fd42..abdf8fb3 100644 --- a/module/Core/test/Visit/VisitsTrackerTest.php +++ b/module/Core/test/Visit/VisitsTrackerTest.php @@ -33,7 +33,7 @@ class VisitsTrackerTest extends TestCase public function trackPersistsVisitAndDispatchesEvent(string $method, array $args): void { $this->em->expects($this->once())->method('persist')->with( - $this->callback(fn (Visit $visit) => $visit->setId('1') !== null) + $this->callback(fn (Visit $visit) => $visit->setId('1') !== null), ); $this->em->expects($this->once())->method('flush'); $this->eventDispatcher->expects($this->once())->method('dispatch')->with( @@ -41,7 +41,6 @@ class VisitsTrackerTest extends TestCase ); $this->visitsTracker()->{$method}(...$args); - } /** From 142417dda1722fa2af432fc240d6d5ccf0026fda Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 23:08:54 +0200 Subject: [PATCH 125/182] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80a6dc66..6d6c7aa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ### Changed * [#1563](https://github.com/shlinkio/shlink/issues/1563) Moved logic to reuse command options to option classes instead of base abstract command classes. +* [#1569](https://github.com/shlinkio/shlink/issues/1569) Migrated test doubles from phpspec/prophecy to PHPUnit mocks. ### Deprecated * *Nothing* From 51f243995af4bef40fe5db9599ea6f684c2bb505 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 24 Oct 2022 19:53:13 +0200 Subject: [PATCH 126/182] Added stricter types for mocks --- composer.json | 3 ++- module/CLI/test/ApiKey/RoleResolverTest.php | 2 +- module/CLI/test/CliTestUtilsTrait.php | 5 +---- .../CLI/test/Command/Api/DisableKeyCommandTest.php | 2 +- .../CLI/test/Command/Api/GenerateKeyCommandTest.php | 2 +- module/CLI/test/Command/Api/ListKeysCommandTest.php | 2 +- .../test/Command/Db/CreateDatabaseCommandTest.php | 8 ++++---- .../test/Command/Db/MigrateDatabaseCommandTest.php | 2 +- .../Command/Domain/DomainRedirectsCommandTest.php | 2 +- .../test/Command/Domain/ListDomainsCommandTest.php | 2 +- .../Command/ShortUrl/CreateShortUrlCommandTest.php | 8 ++++---- .../Command/ShortUrl/DeleteShortUrlCommandTest.php | 2 +- .../Command/ShortUrl/ListShortUrlsCommandTest.php | 2 +- .../test/Command/ShortUrl/ResolveUrlCommandTest.php | 2 +- .../CLI/test/Command/Tag/DeleteTagsCommandTest.php | 2 +- module/CLI/test/Command/Tag/ListTagsCommandTest.php | 2 +- .../CLI/test/Command/Tag/RenameTagCommandTest.php | 2 +- .../Command/Visit/DownloadGeoLiteDbCommandTest.php | 2 +- .../test/Command/Visit/LocateVisitsCommandTest.php | 13 +++++++------ module/CLI/test/Util/ProcessRunnerTest.php | 8 ++++---- module/CLI/test/Util/ShlinkTableTest.php | 2 +- module/Core/test/Action/PixelActionTest.php | 4 ++-- module/Core/test/Action/QrCodeActionTest.php | 2 +- module/Core/test/Action/RedirectActionTest.php | 6 +++--- module/Core/test/Action/RobotsActionTest.php | 2 +- .../test/Config/NotFoundRedirectResolverTest.php | 2 +- module/Core/test/Crawling/CrawlingHelperTest.php | 2 +- module/Core/test/Domain/DomainServiceTest.php | 2 +- .../ErrorHandler/NotFoundRedirectHandlerTest.php | 6 +++--- .../ErrorHandler/NotFoundTrackerMiddlewareTest.php | 4 ++-- .../NotFoundTypeResolverMiddlewareTest.php | 2 +- .../CloseDbConnectionEventListenerDelegatorTest.php | 2 +- .../CloseDbConnectionEventListenerTest.php | 2 +- .../Core/test/EventDispatcher/LocateVisitTest.php | 10 +++++----- .../Mercure/NotifyNewShortUrlToMercureTest.php | 8 ++++---- .../Mercure/NotifyVisitToMercureTest.php | 8 ++++---- .../EventDispatcher/NotifyVisitToWebHooksTest.php | 6 +++--- .../RabbitMq/NotifyNewShortUrlToRabbitMqTest.php | 8 ++++---- .../RabbitMq/NotifyVisitToRabbitMqTest.php | 8 ++++---- .../RedisPubSub/NotifyNewShortUrlToRedisTest.php | 8 ++++---- .../test/EventDispatcher/UpdateGeoLiteDbTest.php | 6 +++--- .../test/Importer/ImportedLinksProcessorTest.php | 8 ++++---- .../Helper/ShortUrlTitleResolutionHelperTest.php | 2 +- .../Middleware/ExtraPathRedirectMiddlewareTest.php | 10 +++++----- .../Adapter/ShortUrlRepositoryAdapterTest.php | 2 +- .../PersistenceShortUrlRelationResolverTest.php | 2 +- .../Adapter/TagsInfoPaginatorAdapterTest.php | 2 +- .../Paginator/Adapter/TagsPaginatorAdapterTest.php | 2 +- module/Core/test/Tag/TagServiceTest.php | 4 ++-- module/Core/test/Util/DoctrineBatchHelperTest.php | 2 +- module/Core/test/Util/UrlValidatorTest.php | 2 +- .../test/Visit/Geolocation/VisitLocatorTest.php | 4 ++-- .../Adapter/NonOrphanVisitsPaginatorAdapterTest.php | 2 +- .../Adapter/OrphanVisitsPaginatorAdapterTest.php | 2 +- .../Adapter/ShortUrlVisitsPaginatorAdapterTest.php | 2 +- .../Adapter/VisitsForTagPaginatorAdapterTest.php | 2 +- module/Core/test/Visit/RequestTrackerTest.php | 4 ++-- module/Core/test/Visit/VisitsStatsHelperTest.php | 2 +- module/Core/test/Visit/VisitsTrackerTest.php | 4 ++-- .../Action/Domain/DomainRedirectsActionTest.php | 2 +- .../test/Action/Domain/ListDomainsActionTest.php | 2 +- module/Rest/test/Action/HealthActionTest.php | 2 +- module/Rest/test/Action/MercureInfoActionTest.php | 2 +- .../Action/ShortUrl/CreateShortUrlActionTest.php | 4 ++-- .../Action/ShortUrl/DeleteShortUrlActionTest.php | 2 +- .../test/Action/ShortUrl/EditShortUrlActionTest.php | 2 +- .../Action/ShortUrl/ListShortUrlsActionTest.php | 2 +- .../Action/ShortUrl/ResolveShortUrlActionTest.php | 2 +- .../ShortUrl/SingleStepCreateShortUrlActionTest.php | 2 +- .../Rest/test/Action/Tag/DeleteTagsActionTest.php | 2 +- module/Rest/test/Action/Tag/ListTagsActionTest.php | 2 +- module/Rest/test/Action/Tag/TagsStatsActionTest.php | 2 +- module/Rest/test/Action/Tag/UpdateTagActionTest.php | 2 +- .../test/Action/Visit/DomainVisitsActionTest.php | 2 +- .../test/Action/Visit/GlobalVisitsActionTest.php | 2 +- .../test/Action/Visit/NonOrphanVisitsActionTest.php | 2 +- .../test/Action/Visit/OrphanVisitsActionTest.php | 4 ++-- .../test/Action/Visit/ShortUrlVisitsActionTest.php | 2 +- .../Rest/test/Action/Visit/TagVisitsActionTest.php | 2 +- .../test/Middleware/CrossDomainMiddlewareTest.php | 2 +- .../NotConfiguredMercureErrorHandlerTest.php | 6 +++--- ...eateShortUrlContentNegotiationMiddlewareTest.php | 2 +- .../DefaultShortCodesLengthMiddlewareTest.php | 4 ++-- .../DropDefaultDomainFromRequestMiddlewareTest.php | 2 +- .../ShortUrl/OverrideDomainMiddlewareTest.php | 6 +++--- module/Rest/test/Service/ApiKeyServiceTest.php | 9 ++++++--- phpstan.neon | 2 ++ 87 files changed, 156 insertions(+), 152 deletions(-) diff --git a/composer.json b/composer.json index cdf2c42e..f052e27c 100644 --- a/composer.json +++ b/composer.json @@ -67,6 +67,7 @@ "openswoole/ide-helper": "~4.11.5", "phpstan/phpstan": "^1.8", "phpstan/phpstan-doctrine": "^1.3", + "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-symfony": "^1.2", "phpunit/php-code-coverage": "^9.2", "phpunit/phpunit": "^9.5", @@ -108,7 +109,7 @@ ], "cs": "phpcs", "cs:fix": "phpcbf", - "stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/config config docker/config data/migrations --level=8", + "stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/test module/*/config config docker/config data/migrations --level=8", "test": [ "@parallel test:unit test:db", "@parallel test:api test:cli" diff --git a/module/CLI/test/ApiKey/RoleResolverTest.php b/module/CLI/test/ApiKey/RoleResolverTest.php index 5195d46d..af50e17e 100644 --- a/module/CLI/test/ApiKey/RoleResolverTest.php +++ b/module/CLI/test/ApiKey/RoleResolverTest.php @@ -19,7 +19,7 @@ use function Functional\map; class RoleResolverTest extends TestCase { private RoleResolver $resolver; - private MockObject $domainService; + private MockObject & DomainServiceInterface $domainService; protected function setUp(): void { diff --git a/module/CLI/test/CliTestUtilsTrait.php b/module/CLI/test/CliTestUtilsTrait.php index 00b493e3..761567ae 100644 --- a/module/CLI/test/CliTestUtilsTrait.php +++ b/module/CLI/test/CliTestUtilsTrait.php @@ -13,10 +13,7 @@ use Symfony\Component\Console\Tester\CommandTester; trait CliTestUtilsTrait { - /** - * @return MockObject & Command - */ - private function createCommandMock(string $name): MockObject + private function createCommandMock(string $name): MockObject & Command { $command = $this->createMock(Command::class); $command->method('getName')->willReturn($name); diff --git a/module/CLI/test/Command/Api/DisableKeyCommandTest.php b/module/CLI/test/Command/Api/DisableKeyCommandTest.php index 864ef0a4..3a3c2def 100644 --- a/module/CLI/test/Command/Api/DisableKeyCommandTest.php +++ b/module/CLI/test/Command/Api/DisableKeyCommandTest.php @@ -17,7 +17,7 @@ class DisableKeyCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $apiKeyService; + private MockObject & ApiKeyServiceInterface $apiKeyService; protected function setUp(): void { diff --git a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php index 02e704ee..631a01c8 100644 --- a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php +++ b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php @@ -20,7 +20,7 @@ class GenerateKeyCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $apiKeyService; + private MockObject & ApiKeyServiceInterface $apiKeyService; protected function setUp(): void { diff --git a/module/CLI/test/Command/Api/ListKeysCommandTest.php b/module/CLI/test/Command/Api/ListKeysCommandTest.php index 2983367b..a0e7d833 100644 --- a/module/CLI/test/Command/Api/ListKeysCommandTest.php +++ b/module/CLI/test/Command/Api/ListKeysCommandTest.php @@ -21,7 +21,7 @@ class ListKeysCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $apiKeyService; + private MockObject & ApiKeyServiceInterface $apiKeyService; protected function setUp(): void { diff --git a/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php b/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php index 4462c949..bf1eac98 100644 --- a/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php +++ b/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php @@ -27,10 +27,10 @@ class CreateDatabaseCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $processHelper; - private MockObject $regularConn; - private MockObject $schemaManager; - private MockObject $driver; + private MockObject & ProcessRunnerInterface $processHelper; + private MockObject & Connection $regularConn; + private MockObject & AbstractSchemaManager $schemaManager; + private MockObject & Driver $driver; protected function setUp(): void { diff --git a/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php b/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php index 5132e892..7027ca21 100644 --- a/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php +++ b/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php @@ -20,7 +20,7 @@ class MigrateDatabaseCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $processHelper; + private MockObject & ProcessRunnerInterface $processHelper; protected function setUp(): void { diff --git a/module/CLI/test/Command/Domain/DomainRedirectsCommandTest.php b/module/CLI/test/Command/Domain/DomainRedirectsCommandTest.php index ee3320a3..1bf5cec3 100644 --- a/module/CLI/test/Command/Domain/DomainRedirectsCommandTest.php +++ b/module/CLI/test/Command/Domain/DomainRedirectsCommandTest.php @@ -22,7 +22,7 @@ class DomainRedirectsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $domainService; + private MockObject & DomainServiceInterface $domainService; protected function setUp(): void { diff --git a/module/CLI/test/Command/Domain/ListDomainsCommandTest.php b/module/CLI/test/Command/Domain/ListDomainsCommandTest.php index ef8b276c..0275ba87 100644 --- a/module/CLI/test/Command/Domain/ListDomainsCommandTest.php +++ b/module/CLI/test/Command/Domain/ListDomainsCommandTest.php @@ -21,7 +21,7 @@ class ListDomainsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $domainService; + private MockObject & DomainServiceInterface $domainService; protected function setUp(): void { diff --git a/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php index 79ce3b5f..16b85290 100644 --- a/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php @@ -15,7 +15,7 @@ use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; -use Shlinkio\Shlink\Core\ShortUrl\UrlShortener; +use Shlinkio\Shlink\Core\ShortUrl\UrlShortenerInterface; use ShlinkioTest\Shlink\CLI\CliTestUtilsTrait; use Symfony\Component\Console\Tester\CommandTester; @@ -26,12 +26,12 @@ class CreateShortUrlCommandTest extends TestCase private const DEFAULT_DOMAIN = 'default.com'; private CommandTester $commandTester; - private MockObject $urlShortener; - private MockObject $stringifier; + private MockObject & UrlShortenerInterface $urlShortener; + private MockObject & ShortUrlStringifierInterface $stringifier; protected function setUp(): void { - $this->urlShortener = $this->createMock(UrlShortener::class); + $this->urlShortener = $this->createMock(UrlShortenerInterface::class); $this->stringifier = $this->createMock(ShortUrlStringifierInterface::class); $command = new CreateShortUrlCommand( diff --git a/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php index 537ed51b..09d48d12 100644 --- a/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php @@ -22,7 +22,7 @@ class DeleteShortUrlCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $service; + private MockObject & DeleteShortUrlServiceInterface $service; protected function setUp(): void { diff --git a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php index 208e595a..3b186bd1 100644 --- a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php @@ -30,7 +30,7 @@ class ListShortUrlsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $shortUrlService; + private MockObject & ShortUrlServiceInterface $shortUrlService; protected function setUp(): void { diff --git a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php index 87962355..89614e6f 100644 --- a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php @@ -23,7 +23,7 @@ class ResolveUrlCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $urlResolver; + private MockObject & ShortUrlResolverInterface $urlResolver; protected function setUp(): void { diff --git a/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php b/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php index 74c02dde..0528af24 100644 --- a/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php +++ b/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php @@ -16,7 +16,7 @@ class DeleteTagsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $tagService; + private MockObject & TagServiceInterface $tagService; protected function setUp(): void { diff --git a/module/CLI/test/Command/Tag/ListTagsCommandTest.php b/module/CLI/test/Command/Tag/ListTagsCommandTest.php index e3802a1a..6ac53f8a 100644 --- a/module/CLI/test/Command/Tag/ListTagsCommandTest.php +++ b/module/CLI/test/Command/Tag/ListTagsCommandTest.php @@ -19,7 +19,7 @@ class ListTagsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $tagService; + private MockObject & TagServiceInterface $tagService; protected function setUp(): void { diff --git a/module/CLI/test/Command/Tag/RenameTagCommandTest.php b/module/CLI/test/Command/Tag/RenameTagCommandTest.php index 4a752de9..95a1e85d 100644 --- a/module/CLI/test/Command/Tag/RenameTagCommandTest.php +++ b/module/CLI/test/Command/Tag/RenameTagCommandTest.php @@ -19,7 +19,7 @@ class RenameTagCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $tagService; + private MockObject & TagServiceInterface $tagService; protected function setUp(): void { diff --git a/module/CLI/test/Command/Visit/DownloadGeoLiteDbCommandTest.php b/module/CLI/test/Command/Visit/DownloadGeoLiteDbCommandTest.php index b5197dde..742fa31b 100644 --- a/module/CLI/test/Command/Visit/DownloadGeoLiteDbCommandTest.php +++ b/module/CLI/test/Command/Visit/DownloadGeoLiteDbCommandTest.php @@ -21,7 +21,7 @@ class DownloadGeoLiteDbCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $dbUpdater; + private MockObject & GeolocationDbUpdaterInterface $dbUpdater; protected function setUp(): void { diff --git a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php index 418e3af6..518d9f45 100644 --- a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php @@ -14,12 +14,13 @@ use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation; use Shlinkio\Shlink\Core\Visit\Geolocation\VisitGeolocationHelperInterface; -use Shlinkio\Shlink\Core\Visit\Geolocation\VisitLocator; +use Shlinkio\Shlink\Core\Visit\Geolocation\VisitLocatorInterface; use Shlinkio\Shlink\Core\Visit\Geolocation\VisitToLocationHelperInterface; use Shlinkio\Shlink\Core\Visit\Model\Visitor; use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException; use Shlinkio\Shlink\IpGeolocation\Model\Location; use ShlinkioTest\Shlink\CLI\CliTestUtilsTrait; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Tester\CommandTester; @@ -34,14 +35,14 @@ class LocateVisitsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $visitService; - private MockObject $visitToLocation; - private MockObject $lock; - private MockObject $downloadDbCommand; + private MockObject & VisitLocatorInterface $visitService; + private MockObject & VisitToLocationHelperInterface $visitToLocation; + private MockObject & Lock\LockInterface $lock; + private MockObject & Command $downloadDbCommand; protected function setUp(): void { - $this->visitService = $this->createMock(VisitLocator::class); + $this->visitService = $this->createMock(VisitLocatorInterface::class); $this->visitToLocation = $this->createMock(VisitToLocationHelperInterface::class); $locker = $this->createMock(Lock\LockFactory::class); diff --git a/module/CLI/test/Util/ProcessRunnerTest.php b/module/CLI/test/Util/ProcessRunnerTest.php index 28ff21af..a23d1b48 100644 --- a/module/CLI/test/Util/ProcessRunnerTest.php +++ b/module/CLI/test/Util/ProcessRunnerTest.php @@ -16,10 +16,10 @@ use Symfony\Component\Process\Process; class ProcessRunnerTest extends TestCase { private ProcessRunner $runner; - private MockObject $helper; - private MockObject $formatter; - private MockObject $process; - private MockObject $output; + private MockObject & ProcessHelper $helper; + private MockObject & DebugFormatterHelper $formatter; + private MockObject & Process $process; + private MockObject & OutputInterface $output; protected function setUp(): void { diff --git a/module/CLI/test/Util/ShlinkTableTest.php b/module/CLI/test/Util/ShlinkTableTest.php index 01e1be1f..829e56d9 100644 --- a/module/CLI/test/Util/ShlinkTableTest.php +++ b/module/CLI/test/Util/ShlinkTableTest.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Output\OutputInterface; class ShlinkTableTest extends TestCase { private ShlinkTable $shlinkTable; - private MockObject $baseTable; + private MockObject & Table $baseTable; protected function setUp(): void { diff --git a/module/Core/test/Action/PixelActionTest.php b/module/Core/test/Action/PixelActionTest.php index e2e75144..b493e2cb 100644 --- a/module/Core/test/Action/PixelActionTest.php +++ b/module/Core/test/Action/PixelActionTest.php @@ -18,8 +18,8 @@ use Shlinkio\Shlink\Core\Visit\RequestTrackerInterface; class PixelActionTest extends TestCase { private PixelAction $action; - private MockObject $urlResolver; - private MockObject $requestTracker; + private MockObject & ShortUrlResolverInterface $urlResolver; + private MockObject & RequestTrackerInterface $requestTracker; protected function setUp(): void { diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index 06b07ecf..497fb009 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -30,7 +30,7 @@ class QrCodeActionTest extends TestCase private const WHITE = 0xFFFFFF; private const BLACK = 0x0; - private MockObject $urlResolver; + private MockObject & ShortUrlResolverInterface $urlResolver; protected function setUp(): void { diff --git a/module/Core/test/Action/RedirectActionTest.php b/module/Core/test/Action/RedirectActionTest.php index de572d2c..7e4d1cb0 100644 --- a/module/Core/test/Action/RedirectActionTest.php +++ b/module/Core/test/Action/RedirectActionTest.php @@ -23,9 +23,9 @@ class RedirectActionTest extends TestCase private const LONG_URL = 'https://domain.com/foo/bar?some=thing'; private RedirectAction $action; - private MockObject $urlResolver; - private MockObject $requestTracker; - private MockObject $redirectRespHelper; + private MockObject & ShortUrlResolverInterface $urlResolver; + private MockObject & RequestTrackerInterface $requestTracker; + private MockObject & RedirectResponseHelperInterface $redirectRespHelper; protected function setUp(): void { diff --git a/module/Core/test/Action/RobotsActionTest.php b/module/Core/test/Action/RobotsActionTest.php index 4f405506..db1f7f90 100644 --- a/module/Core/test/Action/RobotsActionTest.php +++ b/module/Core/test/Action/RobotsActionTest.php @@ -13,7 +13,7 @@ use Shlinkio\Shlink\Core\Crawling\CrawlingHelperInterface; class RobotsActionTest extends TestCase { private RobotsAction $action; - private MockObject $helper; + private MockObject & CrawlingHelperInterface $helper; protected function setUp(): void { diff --git a/module/Core/test/Config/NotFoundRedirectResolverTest.php b/module/Core/test/Config/NotFoundRedirectResolverTest.php index 7ca2bb82..d2d03807 100644 --- a/module/Core/test/Config/NotFoundRedirectResolverTest.php +++ b/module/Core/test/Config/NotFoundRedirectResolverTest.php @@ -24,7 +24,7 @@ use Shlinkio\Shlink\Core\Util\RedirectResponseHelperInterface; class NotFoundRedirectResolverTest extends TestCase { private NotFoundRedirectResolver $resolver; - private MockObject $helper; + private MockObject & RedirectResponseHelperInterface $helper; protected function setUp(): void { diff --git a/module/Core/test/Crawling/CrawlingHelperTest.php b/module/Core/test/Crawling/CrawlingHelperTest.php index 8af3396a..1843d35c 100644 --- a/module/Core/test/Crawling/CrawlingHelperTest.php +++ b/module/Core/test/Crawling/CrawlingHelperTest.php @@ -14,7 +14,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface; class CrawlingHelperTest extends TestCase { private CrawlingHelper $helper; - private MockObject $em; + private MockObject & EntityManagerInterface $em; protected function setUp(): void { diff --git a/module/Core/test/Domain/DomainServiceTest.php b/module/Core/test/Domain/DomainServiceTest.php index 38d925f2..4d4606b0 100644 --- a/module/Core/test/Domain/DomainServiceTest.php +++ b/module/Core/test/Domain/DomainServiceTest.php @@ -21,7 +21,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class DomainServiceTest extends TestCase { private DomainService $domainService; - private MockObject $em; + private MockObject & EntityManagerInterface $em; protected function setUp(): void { diff --git a/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php b/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php index d9e5615e..c6debfb5 100644 --- a/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php +++ b/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php @@ -22,9 +22,9 @@ class NotFoundRedirectHandlerTest extends TestCase { private NotFoundRedirectHandler $middleware; private NotFoundRedirectOptions $redirectOptions; - private MockObject $resolver; - private MockObject $domainService; - private MockObject $next; + private MockObject & NotFoundRedirectResolverInterface $resolver; + private MockObject & DomainServiceInterface $domainService; + private MockObject & RequestHandlerInterface $next; private ServerRequestInterface $req; protected function setUp(): void diff --git a/module/Core/test/ErrorHandler/NotFoundTrackerMiddlewareTest.php b/module/Core/test/ErrorHandler/NotFoundTrackerMiddlewareTest.php index f475d317..d8687223 100644 --- a/module/Core/test/ErrorHandler/NotFoundTrackerMiddlewareTest.php +++ b/module/Core/test/ErrorHandler/NotFoundTrackerMiddlewareTest.php @@ -17,8 +17,8 @@ class NotFoundTrackerMiddlewareTest extends TestCase { private NotFoundTrackerMiddleware $middleware; private ServerRequestInterface $request; - private MockObject $handler; - private MockObject $requestTracker; + private MockObject & RequestHandlerInterface $handler; + private MockObject & RequestTrackerInterface $requestTracker; protected function setUp(): void { diff --git a/module/Core/test/ErrorHandler/NotFoundTypeResolverMiddlewareTest.php b/module/Core/test/ErrorHandler/NotFoundTypeResolverMiddlewareTest.php index c2ef419b..a58e3713 100644 --- a/module/Core/test/ErrorHandler/NotFoundTypeResolverMiddlewareTest.php +++ b/module/Core/test/ErrorHandler/NotFoundTypeResolverMiddlewareTest.php @@ -17,7 +17,7 @@ use Shlinkio\Shlink\Core\ErrorHandler\NotFoundTypeResolverMiddleware; class NotFoundTypeResolverMiddlewareTest extends TestCase { private NotFoundTypeResolverMiddleware $middleware; - private MockObject $handler; + private MockObject & RequestHandlerInterface $handler; protected function setUp(): void { diff --git a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php index ebdaebdf..7cad7732 100644 --- a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php +++ b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerDelegatorTest.php @@ -13,7 +13,7 @@ use Shlinkio\Shlink\Core\EventDispatcher\CloseDbConnectionEventListenerDelegator class CloseDbConnectionEventListenerDelegatorTest extends TestCase { private CloseDbConnectionEventListenerDelegator $delegator; - private MockObject $container; + private MockObject & ContainerInterface $container; protected function setUp(): void { diff --git a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php index 76b433e1..430f08a9 100644 --- a/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php +++ b/module/Core/test/EventDispatcher/CloseDbConnectionEventListenerTest.php @@ -15,7 +15,7 @@ use Throwable; class CloseDbConnectionEventListenerTest extends TestCase { - private MockObject $em; + private MockObject & ReopeningEntityManagerInterface $em; protected function setUp(): void { diff --git a/module/Core/test/EventDispatcher/LocateVisitTest.php b/module/Core/test/EventDispatcher/LocateVisitTest.php index ca862105..cad6d164 100644 --- a/module/Core/test/EventDispatcher/LocateVisitTest.php +++ b/module/Core/test/EventDispatcher/LocateVisitTest.php @@ -26,11 +26,11 @@ use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface; class LocateVisitTest extends TestCase { private LocateVisit $locateVisit; - private MockObject $ipLocationResolver; - private MockObject $em; - private MockObject $logger; - private MockObject $eventDispatcher; - private MockObject $dbUpdater; + private MockObject & IpLocationResolverInterface $ipLocationResolver; + private MockObject & EntityManagerInterface $em; + private MockObject & LoggerInterface $logger; + private MockObject & EventDispatcherInterface $eventDispatcher; + private MockObject & DbUpdaterInterface $dbUpdater; protected function setUp(): void { diff --git a/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php b/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php index 40ba0a2a..c42bd915 100644 --- a/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php +++ b/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php @@ -19,10 +19,10 @@ use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; class NotifyNewShortUrlToMercureTest extends TestCase { private NotifyNewShortUrlToMercure $listener; - private MockObject $helper; - private MockObject $updatesGenerator; - private MockObject $em; - private MockObject $logger; + private MockObject & PublishingHelperInterface $helper; + private MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator; + private MockObject & EntityManagerInterface $em; + private MockObject & LoggerInterface $logger; protected function setUp(): void { diff --git a/module/Core/test/EventDispatcher/Mercure/NotifyVisitToMercureTest.php b/module/Core/test/EventDispatcher/Mercure/NotifyVisitToMercureTest.php index 00d521f5..1cecada7 100644 --- a/module/Core/test/EventDispatcher/Mercure/NotifyVisitToMercureTest.php +++ b/module/Core/test/EventDispatcher/Mercure/NotifyVisitToMercureTest.php @@ -22,10 +22,10 @@ use Shlinkio\Shlink\Core\Visit\Model\VisitType; class NotifyVisitToMercureTest extends TestCase { private NotifyVisitToMercure $listener; - private MockObject $helper; - private MockObject $updatesGenerator; - private MockObject $em; - private MockObject $logger; + private MockObject & PublishingHelperInterface $helper; + private MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator; + private MockObject & EntityManagerInterface $em; + private MockObject & LoggerInterface $logger; protected function setUp(): void { diff --git a/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php b/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php index be4741d6..7a5cb888 100644 --- a/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php +++ b/module/Core/test/EventDispatcher/NotifyVisitToWebHooksTest.php @@ -30,9 +30,9 @@ use function Functional\contains; class NotifyVisitToWebHooksTest extends TestCase { - private MockObject $httpClient; - private MockObject $em; - private MockObject $logger; + private MockObject & ClientInterface $httpClient; + private MockObject & EntityManagerInterface $em; + private MockObject & LoggerInterface $logger; protected function setUp(): void { diff --git a/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php b/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php index ffa1b505..764f7949 100644 --- a/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php +++ b/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php @@ -23,10 +23,10 @@ use Throwable; class NotifyNewShortUrlToRabbitMqTest extends TestCase { - private MockObject $helper; - private MockObject $updatesGenerator; - private MockObject $em; - private MockObject $logger; + private MockObject & PublishingHelperInterface $helper; + private MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator; + private MockObject & EntityManagerInterface $em; + private MockObject & LoggerInterface $logger; protected function setUp(): void { diff --git a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php index 5abb15f0..0def544e 100644 --- a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php +++ b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php @@ -31,10 +31,10 @@ use function Functional\noop; class NotifyVisitToRabbitMqTest extends TestCase { - private MockObject $helper; - private MockObject $updatesGenerator; - private MockObject $em; - private MockObject $logger; + private MockObject & PublishingHelperInterface $helper; + private MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator; + private MockObject & EntityManagerInterface $em; + private MockObject & LoggerInterface $logger; protected function setUp(): void { diff --git a/module/Core/test/EventDispatcher/RedisPubSub/NotifyNewShortUrlToRedisTest.php b/module/Core/test/EventDispatcher/RedisPubSub/NotifyNewShortUrlToRedisTest.php index 347d5029..0b5dfd27 100644 --- a/module/Core/test/EventDispatcher/RedisPubSub/NotifyNewShortUrlToRedisTest.php +++ b/module/Core/test/EventDispatcher/RedisPubSub/NotifyNewShortUrlToRedisTest.php @@ -22,10 +22,10 @@ use Throwable; class NotifyNewShortUrlToRedisTest extends TestCase { - private MockObject $helper; - private MockObject $updatesGenerator; - private MockObject $em; - private MockObject $logger; + private MockObject & PublishingHelperInterface $helper; + private MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator; + private MockObject & EntityManagerInterface $em; + private MockObject & LoggerInterface $logger; protected function setUp(): void { diff --git a/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php b/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php index e74508d3..5b496123 100644 --- a/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php +++ b/module/Core/test/EventDispatcher/UpdateGeoLiteDbTest.php @@ -19,9 +19,9 @@ use function Functional\map; class UpdateGeoLiteDbTest extends TestCase { private UpdateGeoLiteDb $listener; - private MockObject $dbUpdater; - private MockObject $logger; - private MockObject $eventDispatcher; + private MockObject & GeolocationDbUpdaterInterface $dbUpdater; + private MockObject & LoggerInterface $logger; + private MockObject & EventDispatcherInterface $eventDispatcher; protected function setUp(): void { diff --git a/module/Core/test/Importer/ImportedLinksProcessorTest.php b/module/Core/test/Importer/ImportedLinksProcessorTest.php index 3f5fa495..382d912c 100644 --- a/module/Core/test/Importer/ImportedLinksProcessorTest.php +++ b/module/Core/test/Importer/ImportedLinksProcessorTest.php @@ -32,10 +32,10 @@ use function str_contains; class ImportedLinksProcessorTest extends TestCase { private ImportedLinksProcessor $processor; - private MockObject $em; - private MockObject $shortCodeHelper; - private MockObject $repo; - private MockObject $io; + private MockObject & EntityManagerInterface $em; + private MockObject & ShortCodeUniquenessHelperInterface $shortCodeHelper; + private MockObject & ShortUrlRepositoryInterface $repo; + private MockObject & StyleInterface $io; protected function setUp(): void { diff --git a/module/Core/test/ShortUrl/Helper/ShortUrlTitleResolutionHelperTest.php b/module/Core/test/ShortUrl/Helper/ShortUrlTitleResolutionHelperTest.php index 52255be7..2d48b294 100644 --- a/module/Core/test/ShortUrl/Helper/ShortUrlTitleResolutionHelperTest.php +++ b/module/Core/test/ShortUrl/Helper/ShortUrlTitleResolutionHelperTest.php @@ -13,7 +13,7 @@ use Shlinkio\Shlink\Core\Util\UrlValidatorInterface; class ShortUrlTitleResolutionHelperTest extends TestCase { private ShortUrlTitleResolutionHelper $helper; - private MockObject $urlValidator; + private MockObject & UrlValidatorInterface $urlValidator; protected function setUp(): void { diff --git a/module/Core/test/ShortUrl/Middleware/ExtraPathRedirectMiddlewareTest.php b/module/Core/test/ShortUrl/Middleware/ExtraPathRedirectMiddlewareTest.php index 80491132..355bec0e 100644 --- a/module/Core/test/ShortUrl/Middleware/ExtraPathRedirectMiddlewareTest.php +++ b/module/Core/test/ShortUrl/Middleware/ExtraPathRedirectMiddlewareTest.php @@ -30,11 +30,11 @@ use function str_starts_with; class ExtraPathRedirectMiddlewareTest extends TestCase { - private MockObject $resolver; - private MockObject $requestTracker; - private MockObject $redirectionBuilder; - private MockObject $redirectResponseHelper; - private MockObject $handler; + private MockObject & ShortUrlResolverInterface $resolver; + private MockObject & RequestTrackerInterface $requestTracker; + private MockObject & ShortUrlRedirectionBuilderInterface $redirectionBuilder; + private MockObject & RedirectResponseHelperInterface $redirectResponseHelper; + private MockObject & RequestHandlerInterface $handler; protected function setUp(): void { diff --git a/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php b/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php index 1df421bb..4cbc9eae 100644 --- a/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php +++ b/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php @@ -17,7 +17,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ShortUrlRepositoryAdapterTest extends TestCase { - private MockObject $repo; + private MockObject & ShortUrlRepositoryInterface $repo; protected function setUp(): void { diff --git a/module/Core/test/ShortUrl/Resolver/PersistenceShortUrlRelationResolverTest.php b/module/Core/test/ShortUrl/Resolver/PersistenceShortUrlRelationResolverTest.php index 995c61cd..37a9f2e2 100644 --- a/module/Core/test/ShortUrl/Resolver/PersistenceShortUrlRelationResolverTest.php +++ b/module/Core/test/ShortUrl/Resolver/PersistenceShortUrlRelationResolverTest.php @@ -19,7 +19,7 @@ use function count; class PersistenceShortUrlRelationResolverTest extends TestCase { private PersistenceShortUrlRelationResolver $resolver; - private MockObject $em; + private MockObject & EntityManagerInterface $em; protected function setUp(): void { diff --git a/module/Core/test/Tag/Paginator/Adapter/TagsInfoPaginatorAdapterTest.php b/module/Core/test/Tag/Paginator/Adapter/TagsInfoPaginatorAdapterTest.php index 99ebddf1..fe105ce1 100644 --- a/module/Core/test/Tag/Paginator/Adapter/TagsInfoPaginatorAdapterTest.php +++ b/module/Core/test/Tag/Paginator/Adapter/TagsInfoPaginatorAdapterTest.php @@ -13,7 +13,7 @@ use Shlinkio\Shlink\Core\Tag\Repository\TagRepositoryInterface; class TagsInfoPaginatorAdapterTest extends TestCase { private TagsInfoPaginatorAdapter $adapter; - private MockObject $repo; + private MockObject & TagRepositoryInterface $repo; protected function setUp(): void { diff --git a/module/Core/test/Tag/Paginator/Adapter/TagsPaginatorAdapterTest.php b/module/Core/test/Tag/Paginator/Adapter/TagsPaginatorAdapterTest.php index bce7d9cc..a3b36215 100644 --- a/module/Core/test/Tag/Paginator/Adapter/TagsPaginatorAdapterTest.php +++ b/module/Core/test/Tag/Paginator/Adapter/TagsPaginatorAdapterTest.php @@ -13,7 +13,7 @@ use Shlinkio\Shlink\Core\Tag\Repository\TagRepositoryInterface; class TagsPaginatorAdapterTest extends TestCase { private TagsPaginatorAdapter $adapter; - private MockObject $repo; + private MockObject & TagRepositoryInterface $repo; protected function setUp(): void { diff --git a/module/Core/test/Tag/TagServiceTest.php b/module/Core/test/Tag/TagServiceTest.php index 9cba4875..069bca20 100644 --- a/module/Core/test/Tag/TagServiceTest.php +++ b/module/Core/test/Tag/TagServiceTest.php @@ -27,8 +27,8 @@ class TagServiceTest extends TestCase use ApiKeyHelpersTrait; private TagService $service; - private MockObject $em; - private MockObject $repo; + private MockObject & EntityManagerInterface $em; + private MockObject & TagRepository $repo; protected function setUp(): void { diff --git a/module/Core/test/Util/DoctrineBatchHelperTest.php b/module/Core/test/Util/DoctrineBatchHelperTest.php index aec4f0b6..2fc0f985 100644 --- a/module/Core/test/Util/DoctrineBatchHelperTest.php +++ b/module/Core/test/Util/DoctrineBatchHelperTest.php @@ -13,7 +13,7 @@ use Shlinkio\Shlink\Core\Util\DoctrineBatchHelper; class DoctrineBatchHelperTest extends TestCase { private DoctrineBatchHelper $helper; - private MockObject $em; + private MockObject & EntityManagerInterface $em; protected function setUp(): void { diff --git a/module/Core/test/Util/UrlValidatorTest.php b/module/Core/test/Util/UrlValidatorTest.php index 2610b7fd..90ab2fd7 100644 --- a/module/Core/test/Util/UrlValidatorTest.php +++ b/module/Core/test/Util/UrlValidatorTest.php @@ -20,7 +20,7 @@ use Shlinkio\Shlink\Core\Util\UrlValidator; class UrlValidatorTest extends TestCase { - private MockObject $httpClient; + private MockObject & ClientInterface $httpClient; protected function setUp(): void { diff --git a/module/Core/test/Visit/Geolocation/VisitLocatorTest.php b/module/Core/test/Visit/Geolocation/VisitLocatorTest.php index 4141f7c5..a9ed7aa2 100644 --- a/module/Core/test/Visit/Geolocation/VisitLocatorTest.php +++ b/module/Core/test/Visit/Geolocation/VisitLocatorTest.php @@ -30,8 +30,8 @@ use function sprintf; class VisitLocatorTest extends TestCase { private VisitLocator $visitService; - private MockObject $em; - private MockObject $repo; + private MockObject & EntityManager $em; + private MockObject & VisitRepositoryInterface $repo; protected function setUp(): void { diff --git a/module/Core/test/Visit/Paginator/Adapter/NonOrphanVisitsPaginatorAdapterTest.php b/module/Core/test/Visit/Paginator/Adapter/NonOrphanVisitsPaginatorAdapterTest.php index 1bd03109..9c4a6e63 100644 --- a/module/Core/test/Visit/Paginator/Adapter/NonOrphanVisitsPaginatorAdapterTest.php +++ b/module/Core/test/Visit/Paginator/Adapter/NonOrphanVisitsPaginatorAdapterTest.php @@ -18,7 +18,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class NonOrphanVisitsPaginatorAdapterTest extends TestCase { private NonOrphanVisitsPaginatorAdapter $adapter; - private MockObject $repo; + private MockObject & VisitRepositoryInterface $repo; private VisitsParams $params; private ApiKey $apiKey; diff --git a/module/Core/test/Visit/Paginator/Adapter/OrphanVisitsPaginatorAdapterTest.php b/module/Core/test/Visit/Paginator/Adapter/OrphanVisitsPaginatorAdapterTest.php index adad4322..8981d05f 100644 --- a/module/Core/test/Visit/Paginator/Adapter/OrphanVisitsPaginatorAdapterTest.php +++ b/module/Core/test/Visit/Paginator/Adapter/OrphanVisitsPaginatorAdapterTest.php @@ -17,7 +17,7 @@ use Shlinkio\Shlink\Core\Visit\Repository\VisitRepositoryInterface; class OrphanVisitsPaginatorAdapterTest extends TestCase { private OrphanVisitsPaginatorAdapter $adapter; - private MockObject $repo; + private MockObject & VisitRepositoryInterface $repo; private VisitsParams $params; protected function setUp(): void diff --git a/module/Core/test/Visit/Paginator/Adapter/ShortUrlVisitsPaginatorAdapterTest.php b/module/Core/test/Visit/Paginator/Adapter/ShortUrlVisitsPaginatorAdapterTest.php index 3e6c3737..8ebf1afc 100644 --- a/module/Core/test/Visit/Paginator/Adapter/ShortUrlVisitsPaginatorAdapterTest.php +++ b/module/Core/test/Visit/Paginator/Adapter/ShortUrlVisitsPaginatorAdapterTest.php @@ -17,7 +17,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ShortUrlVisitsPaginatorAdapterTest extends TestCase { - private MockObject $repo; + private MockObject & VisitRepositoryInterface $repo; protected function setUp(): void { diff --git a/module/Core/test/Visit/Paginator/Adapter/VisitsForTagPaginatorAdapterTest.php b/module/Core/test/Visit/Paginator/Adapter/VisitsForTagPaginatorAdapterTest.php index 3a92c8d3..a9aec03f 100644 --- a/module/Core/test/Visit/Paginator/Adapter/VisitsForTagPaginatorAdapterTest.php +++ b/module/Core/test/Visit/Paginator/Adapter/VisitsForTagPaginatorAdapterTest.php @@ -16,7 +16,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class VisitsForTagPaginatorAdapterTest extends TestCase { - private MockObject $repo; + private MockObject & VisitRepositoryInterface $repo; protected function setUp(): void { diff --git a/module/Core/test/Visit/RequestTrackerTest.php b/module/Core/test/Visit/RequestTrackerTest.php index 495963fb..0e1705ee 100644 --- a/module/Core/test/Visit/RequestTrackerTest.php +++ b/module/Core/test/Visit/RequestTrackerTest.php @@ -23,8 +23,8 @@ class RequestTrackerTest extends TestCase private const LONG_URL = 'https://domain.com/foo/bar?some=thing'; private RequestTracker $requestTracker; - private MockObject $notFoundType; - private MockObject $visitsTracker; + private MockObject & VisitsTrackerInterface $visitsTracker; + private MockObject & NotFoundType $notFoundType; private ServerRequestInterface $request; protected function setUp(): void diff --git a/module/Core/test/Visit/VisitsStatsHelperTest.php b/module/Core/test/Visit/VisitsStatsHelperTest.php index 4f3ffee2..8afd56db 100644 --- a/module/Core/test/Visit/VisitsStatsHelperTest.php +++ b/module/Core/test/Visit/VisitsStatsHelperTest.php @@ -38,7 +38,7 @@ class VisitsStatsHelperTest extends TestCase use ApiKeyHelpersTrait; private VisitsStatsHelper $helper; - private MockObject $em; + private MockObject & EntityManagerInterface $em; protected function setUp(): void { diff --git a/module/Core/test/Visit/VisitsTrackerTest.php b/module/Core/test/Visit/VisitsTrackerTest.php index abdf8fb3..d981f755 100644 --- a/module/Core/test/Visit/VisitsTrackerTest.php +++ b/module/Core/test/Visit/VisitsTrackerTest.php @@ -17,8 +17,8 @@ use Shlinkio\Shlink\Core\Visit\VisitsTracker; class VisitsTrackerTest extends TestCase { - private MockObject $em; - private MockObject $eventDispatcher; + private MockObject & EntityManager $em; + private MockObject & EventDispatcherInterface $eventDispatcher; protected function setUp(): void { diff --git a/module/Rest/test/Action/Domain/DomainRedirectsActionTest.php b/module/Rest/test/Action/Domain/DomainRedirectsActionTest.php index a620ee01..5ff409a0 100644 --- a/module/Rest/test/Action/Domain/DomainRedirectsActionTest.php +++ b/module/Rest/test/Action/Domain/DomainRedirectsActionTest.php @@ -21,7 +21,7 @@ use function array_key_exists; class DomainRedirectsActionTest extends TestCase { private DomainRedirectsAction $action; - private MockObject $domainService; + private MockObject & DomainServiceInterface $domainService; protected function setUp(): void { diff --git a/module/Rest/test/Action/Domain/ListDomainsActionTest.php b/module/Rest/test/Action/Domain/ListDomainsActionTest.php index 8c7fcc94..ac31cd0e 100644 --- a/module/Rest/test/Action/Domain/ListDomainsActionTest.php +++ b/module/Rest/test/Action/Domain/ListDomainsActionTest.php @@ -19,7 +19,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ListDomainsActionTest extends TestCase { private ListDomainsAction $action; - private MockObject $domainService; + private MockObject & DomainServiceInterface $domainService; private NotFoundRedirectOptions $options; protected function setUp(): void diff --git a/module/Rest/test/Action/HealthActionTest.php b/module/Rest/test/Action/HealthActionTest.php index 48289290..4ce00578 100644 --- a/module/Rest/test/Action/HealthActionTest.php +++ b/module/Rest/test/Action/HealthActionTest.php @@ -19,7 +19,7 @@ use Shlinkio\Shlink\Rest\Action\HealthAction; class HealthActionTest extends TestCase { private HealthAction $action; - private MockObject $conn; + private MockObject & Connection $conn; protected function setUp(): void { diff --git a/module/Rest/test/Action/MercureInfoActionTest.php b/module/Rest/test/Action/MercureInfoActionTest.php index f99a8a37..ada836c1 100644 --- a/module/Rest/test/Action/MercureInfoActionTest.php +++ b/module/Rest/test/Action/MercureInfoActionTest.php @@ -15,7 +15,7 @@ use Shlinkio\Shlink\Rest\Exception\MercureException; class MercureInfoActionTest extends TestCase { - private MockObject $provider; + private MockObject & JwtProviderInterface $provider; protected function setUp(): void { diff --git a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php index 140f0230..246b2edf 100644 --- a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php @@ -22,8 +22,8 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class CreateShortUrlActionTest extends TestCase { private CreateShortUrlAction $action; - private MockObject $urlShortener; - private MockObject $transformer; + private MockObject & UrlShortener $urlShortener; + private MockObject & DataTransformerInterface $transformer; protected function setUp(): void { diff --git a/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php index 30a8e647..d68e3608 100644 --- a/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php @@ -14,7 +14,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class DeleteShortUrlActionTest extends TestCase { private DeleteShortUrlAction $action; - private MockObject $service; + private MockObject & DeleteShortUrlServiceInterface $service; protected function setUp(): void { diff --git a/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php index 94f3dbae..dde17ca6 100644 --- a/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php @@ -18,7 +18,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class EditShortUrlActionTest extends TestCase { private EditShortUrlAction $action; - private MockObject $shortUrlService; + private MockObject & ShortUrlServiceInterface $shortUrlService; protected function setUp(): void { diff --git a/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php b/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php index 068677d3..5ccd20ec 100644 --- a/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php +++ b/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php @@ -21,7 +21,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ListShortUrlsActionTest extends TestCase { private ListShortUrlsAction $action; - private MockObject $service; + private MockObject & ShortUrlService $service; protected function setUp(): void { diff --git a/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php index 0e91290f..c7d6fd26 100644 --- a/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php @@ -18,7 +18,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ResolveShortUrlActionTest extends TestCase { private ResolveShortUrlAction $action; - private MockObject $urlResolver; + private MockObject & ShortUrlResolverInterface $urlResolver; protected function setUp(): void { diff --git a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php index 5e0867e7..14848696 100644 --- a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php @@ -18,7 +18,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class SingleStepCreateShortUrlActionTest extends TestCase { private SingleStepCreateShortUrlAction $action; - private MockObject $urlShortener; + private MockObject & UrlShortenerInterface $urlShortener; protected function setUp(): void { diff --git a/module/Rest/test/Action/Tag/DeleteTagsActionTest.php b/module/Rest/test/Action/Tag/DeleteTagsActionTest.php index cca532a4..63d30c4b 100644 --- a/module/Rest/test/Action/Tag/DeleteTagsActionTest.php +++ b/module/Rest/test/Action/Tag/DeleteTagsActionTest.php @@ -14,7 +14,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class DeleteTagsActionTest extends TestCase { private DeleteTagsAction $action; - private MockObject $tagService; + private MockObject & TagServiceInterface $tagService; protected function setUp(): void { diff --git a/module/Rest/test/Action/Tag/ListTagsActionTest.php b/module/Rest/test/Action/Tag/ListTagsActionTest.php index d340c82f..dc4c2c06 100644 --- a/module/Rest/test/Action/Tag/ListTagsActionTest.php +++ b/module/Rest/test/Action/Tag/ListTagsActionTest.php @@ -22,7 +22,7 @@ use function count; class ListTagsActionTest extends TestCase { private ListTagsAction $action; - private MockObject $tagService; + private MockObject & TagServiceInterface $tagService; protected function setUp(): void { diff --git a/module/Rest/test/Action/Tag/TagsStatsActionTest.php b/module/Rest/test/Action/Tag/TagsStatsActionTest.php index 8e0e8e74..0694e7bf 100644 --- a/module/Rest/test/Action/Tag/TagsStatsActionTest.php +++ b/module/Rest/test/Action/Tag/TagsStatsActionTest.php @@ -21,7 +21,7 @@ use function count; class TagsStatsActionTest extends TestCase { private TagsStatsAction $action; - private MockObject $tagService; + private MockObject & TagServiceInterface $tagService; protected function setUp(): void { diff --git a/module/Rest/test/Action/Tag/UpdateTagActionTest.php b/module/Rest/test/Action/Tag/UpdateTagActionTest.php index d1424849..d08d00e2 100644 --- a/module/Rest/test/Action/Tag/UpdateTagActionTest.php +++ b/module/Rest/test/Action/Tag/UpdateTagActionTest.php @@ -18,7 +18,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class UpdateTagActionTest extends TestCase { private UpdateTagAction $action; - private MockObject $tagService; + private MockObject & TagServiceInterface $tagService; protected function setUp(): void { diff --git a/module/Rest/test/Action/Visit/DomainVisitsActionTest.php b/module/Rest/test/Action/Visit/DomainVisitsActionTest.php index b56f557b..e4b714e4 100644 --- a/module/Rest/test/Action/Visit/DomainVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/DomainVisitsActionTest.php @@ -17,7 +17,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class DomainVisitsActionTest extends TestCase { private DomainVisitsAction $action; - private MockObject $visitsHelper; + private MockObject & VisitsStatsHelperInterface $visitsHelper; protected function setUp(): void { diff --git a/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php b/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php index 87830cb5..bd078eaa 100644 --- a/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/GlobalVisitsActionTest.php @@ -16,7 +16,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class GlobalVisitsActionTest extends TestCase { private GlobalVisitsAction $action; - private MockObject $helper; + private MockObject & VisitsStatsHelperInterface $helper; protected function setUp(): void { diff --git a/module/Rest/test/Action/Visit/NonOrphanVisitsActionTest.php b/module/Rest/test/Action/Visit/NonOrphanVisitsActionTest.php index 4cf6d8a2..9065d318 100644 --- a/module/Rest/test/Action/Visit/NonOrphanVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/NonOrphanVisitsActionTest.php @@ -18,7 +18,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class NonOrphanVisitsActionTest extends TestCase { private NonOrphanVisitsAction $action; - private MockObject $visitsHelper; + private MockObject & VisitsStatsHelperInterface $visitsHelper; protected function setUp(): void { diff --git a/module/Rest/test/Action/Visit/OrphanVisitsActionTest.php b/module/Rest/test/Action/Visit/OrphanVisitsActionTest.php index 7ad77425..6facfb1c 100644 --- a/module/Rest/test/Action/Visit/OrphanVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/OrphanVisitsActionTest.php @@ -22,8 +22,8 @@ use function count; class OrphanVisitsActionTest extends TestCase { private OrphanVisitsAction $action; - private MockObject $visitsHelper; - private MockObject $orphanVisitTransformer; + private MockObject & VisitsStatsHelperInterface $visitsHelper; + private MockObject & DataTransformerInterface $orphanVisitTransformer; protected function setUp(): void { diff --git a/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php b/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php index 8302dd27..b9c92d6c 100644 --- a/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/ShortUrlVisitsActionTest.php @@ -21,7 +21,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ShortUrlVisitsActionTest extends TestCase { private ShortUrlVisitsAction $action; - private MockObject $visitsHelper; + private MockObject & VisitsStatsHelperInterface $visitsHelper; protected function setUp(): void { diff --git a/module/Rest/test/Action/Visit/TagVisitsActionTest.php b/module/Rest/test/Action/Visit/TagVisitsActionTest.php index fd924d17..1d5b9447 100644 --- a/module/Rest/test/Action/Visit/TagVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/TagVisitsActionTest.php @@ -17,7 +17,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class TagVisitsActionTest extends TestCase { private TagVisitsAction $action; - private MockObject $visitsHelper; + private MockObject & VisitsStatsHelperInterface $visitsHelper; protected function setUp(): void { diff --git a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php index e9b294da..de87f34f 100644 --- a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php +++ b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php @@ -14,7 +14,7 @@ use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware; class CrossDomainMiddlewareTest extends TestCase { private CrossDomainMiddleware $middleware; - private MockObject $handler; + private MockObject & RequestHandlerInterface $handler; protected function setUp(): void { diff --git a/module/Rest/test/Middleware/Mercure/NotConfiguredMercureErrorHandlerTest.php b/module/Rest/test/Middleware/Mercure/NotConfiguredMercureErrorHandlerTest.php index 9fe595ec..4f576d7d 100644 --- a/module/Rest/test/Middleware/Mercure/NotConfiguredMercureErrorHandlerTest.php +++ b/module/Rest/test/Middleware/Mercure/NotConfiguredMercureErrorHandlerTest.php @@ -17,9 +17,9 @@ use Shlinkio\Shlink\Rest\Middleware\Mercure\NotConfiguredMercureErrorHandler; class NotConfiguredMercureErrorHandlerTest extends TestCase { private NotConfiguredMercureErrorHandler $middleware; - private MockObject $respFactory; - private MockObject $logger; - private MockObject $handler; + private MockObject & ProblemDetailsResponseFactory $respFactory; + private MockObject & LoggerInterface $logger; + private MockObject & RequestHandlerInterface $handler; protected function setUp(): void { diff --git a/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php index 1e56d690..58a3f34d 100644 --- a/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php +++ b/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php @@ -16,7 +16,7 @@ use Shlinkio\Shlink\Rest\Middleware\ShortUrl\CreateShortUrlContentNegotiationMid class CreateShortUrlContentNegotiationMiddlewareTest extends TestCase { private CreateShortUrlContentNegotiationMiddleware $middleware; - private MockObject $requestHandler; + private MockObject & RequestHandlerInterface $requestHandler; protected function setUp(): void { diff --git a/module/Rest/test/Middleware/ShortUrl/DefaultShortCodesLengthMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/DefaultShortCodesLengthMiddlewareTest.php index f7a647b1..d8fb3092 100644 --- a/module/Rest/test/Middleware/ShortUrl/DefaultShortCodesLengthMiddlewareTest.php +++ b/module/Rest/test/Middleware/ShortUrl/DefaultShortCodesLengthMiddlewareTest.php @@ -17,7 +17,7 @@ use Shlinkio\Shlink\Rest\Middleware\ShortUrl\DefaultShortCodesLengthMiddleware; class DefaultShortCodesLengthMiddlewareTest extends TestCase { private DefaultShortCodesLengthMiddleware $middleware; - private MockObject $handler; + private MockObject & RequestHandlerInterface $handler; protected function setUp(): void { @@ -34,7 +34,7 @@ class DefaultShortCodesLengthMiddlewareTest extends TestCase $request = ServerRequestFactory::fromGlobals()->withParsedBody($body); $this->handler->expects($this->once())->method('handle')->with($this->callback( function (ServerRequestInterface $req) use ($expectedLength) { - $parsedBody = $req->getParsedBody(); + $parsedBody = (array) $req->getParsedBody(); Assert::assertArrayHasKey(ShortUrlInputFilter::SHORT_CODE_LENGTH, $parsedBody); Assert::assertEquals($expectedLength, $parsedBody[ShortUrlInputFilter::SHORT_CODE_LENGTH]); diff --git a/module/Rest/test/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddlewareTest.php index eb018ad3..1af34a48 100644 --- a/module/Rest/test/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddlewareTest.php +++ b/module/Rest/test/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddlewareTest.php @@ -16,7 +16,7 @@ use Shlinkio\Shlink\Rest\Middleware\ShortUrl\DropDefaultDomainFromRequestMiddlew class DropDefaultDomainFromRequestMiddlewareTest extends TestCase { private DropDefaultDomainFromRequestMiddleware $middleware; - private MockObject $next; + private MockObject & RequestHandlerInterface $next; protected function setUp(): void { diff --git a/module/Rest/test/Middleware/ShortUrl/OverrideDomainMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/OverrideDomainMiddlewareTest.php index f4f0b4d1..ad558abf 100644 --- a/module/Rest/test/Middleware/ShortUrl/OverrideDomainMiddlewareTest.php +++ b/module/Rest/test/Middleware/ShortUrl/OverrideDomainMiddlewareTest.php @@ -21,9 +21,9 @@ use Shlinkio\Shlink\Rest\Middleware\ShortUrl\OverrideDomainMiddleware; class OverrideDomainMiddlewareTest extends TestCase { private OverrideDomainMiddleware $middleware; - private MockObject $domainService; - private MockObject $apiKey; - private MockObject $handler; + private MockObject & DomainServiceInterface $domainService; + private MockObject & ApiKey $apiKey; + private MockObject & RequestHandlerInterface $handler; protected function setUp(): void { diff --git a/module/Rest/test/Service/ApiKeyServiceTest.php b/module/Rest/test/Service/ApiKeyServiceTest.php index 9336da9e..a592313d 100644 --- a/module/Rest/test/Service/ApiKeyServiceTest.php +++ b/module/Rest/test/Service/ApiKeyServiceTest.php @@ -19,8 +19,8 @@ use Shlinkio\Shlink\Rest\Service\ApiKeyService; class ApiKeyServiceTest extends TestCase { private ApiKeyService $service; - private MockObject $em; - private MockObject $repo; + private MockObject & EntityManager $em; + private MockObject & EntityRepository $repo; protected function setUp(): void { @@ -50,10 +50,13 @@ class ApiKeyServiceTest extends TestCase public function provideCreationDate(): iterable { + $domain = Domain::withAuthority(''); + $domain->setId('123'); + yield 'no expiration date or name' => [null, null, []]; yield 'expiration date' => [Chronos::parse('2030-01-01'), null, []]; yield 'roles' => [null, null, [ - RoleDefinition::forDomain(Domain::withAuthority('')->setId('123')), + RoleDefinition::forDomain($domain), RoleDefinition::forAuthoredShortUrls(), ]]; yield 'single name' => [null, 'Alice', []]; diff --git a/phpstan.neon b/phpstan.neon index aa5dab08..eee4853b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,6 +1,8 @@ includes: - vendor/phpstan/phpstan-doctrine/extension.neon - vendor/phpstan/phpstan-symfony/extension.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon parameters: checkMissingIterableValueType: false checkGenericClassInNonGenericObjectType: false From 1650499a38c2d0d67494f06e46ddee808fb58386 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 24 Oct 2022 19:59:03 +0200 Subject: [PATCH 127/182] Added more stricter types for mocks --- .../test/Command/Domain/GetDomainVisitsCommandTest.php | 4 ++-- .../Command/ShortUrl/GetShortUrlVisitsCommandTest.php | 2 +- module/CLI/test/Command/Tag/GetTagVisitsCommandTest.php | 4 ++-- .../test/Command/Visit/GetNonOrphanVisitsCommandTest.php | 4 ++-- .../CLI/test/Command/Visit/GetOrphanVisitsCommandTest.php | 2 +- module/CLI/test/GeoLite/GeolocationDbUpdaterTest.php | 6 +++--- .../test/EventDispatcher/LocateUnlocatedVisitsTest.php | 4 ++-- .../RedisPubSub/NotifyVisitToRedisTest.php | 8 ++++---- module/Core/test/ShortUrl/DeleteShortUrlServiceTest.php | 4 ++-- .../ShortUrl/Helper/ShortCodeUniquenessHelperTest.php | 4 ++-- .../Middleware/TrimTrailingSlashMiddlewareTest.php | 2 +- module/Core/test/ShortUrl/ShortUrlResolverTest.php | 4 ++-- module/Core/test/ShortUrl/ShortUrlServiceTest.php | 6 +++--- module/Core/test/ShortUrl/UrlShortenerTest.php | 6 +++--- .../test/Visit/Geolocation/VisitToLocationHelperTest.php | 2 +- module/Rest/test/ApiKey/InitialApiKeyDelegatorTest.php | 2 +- .../Rest/test/Middleware/AuthenticationMiddlewareTest.php | 4 ++-- .../BackwardsCompatibleProblemDetailsHandlerTest.php | 1 + 18 files changed, 35 insertions(+), 34 deletions(-) diff --git a/module/CLI/test/Command/Domain/GetDomainVisitsCommandTest.php b/module/CLI/test/Command/Domain/GetDomainVisitsCommandTest.php index 7fffbcc4..e02aa36a 100644 --- a/module/CLI/test/Command/Domain/GetDomainVisitsCommandTest.php +++ b/module/CLI/test/Command/Domain/GetDomainVisitsCommandTest.php @@ -24,8 +24,8 @@ class GetDomainVisitsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $visitsHelper; - private MockObject $stringifier; + private MockObject & VisitsStatsHelperInterface $visitsHelper; + private MockObject & ShortUrlStringifierInterface $stringifier; protected function setUp(): void { diff --git a/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php b/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php index dd2f4ddc..8706699b 100644 --- a/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php @@ -30,7 +30,7 @@ class GetShortUrlVisitsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $visitsHelper; + private MockObject & VisitsStatsHelperInterface $visitsHelper; protected function setUp(): void { diff --git a/module/CLI/test/Command/Tag/GetTagVisitsCommandTest.php b/module/CLI/test/Command/Tag/GetTagVisitsCommandTest.php index c10c9d8d..be56cdee 100644 --- a/module/CLI/test/Command/Tag/GetTagVisitsCommandTest.php +++ b/module/CLI/test/Command/Tag/GetTagVisitsCommandTest.php @@ -24,8 +24,8 @@ class GetTagVisitsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $visitsHelper; - private MockObject $stringifier; + private MockObject & VisitsStatsHelperInterface $visitsHelper; + private MockObject & ShortUrlStringifierInterface $stringifier; protected function setUp(): void { diff --git a/module/CLI/test/Command/Visit/GetNonOrphanVisitsCommandTest.php b/module/CLI/test/Command/Visit/GetNonOrphanVisitsCommandTest.php index 71008d16..90147541 100644 --- a/module/CLI/test/Command/Visit/GetNonOrphanVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/GetNonOrphanVisitsCommandTest.php @@ -24,8 +24,8 @@ class GetNonOrphanVisitsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $visitsHelper; - private MockObject $stringifier; + private MockObject & VisitsStatsHelperInterface $visitsHelper; + private MockObject & ShortUrlStringifierInterface $stringifier; protected function setUp(): void { diff --git a/module/CLI/test/Command/Visit/GetOrphanVisitsCommandTest.php b/module/CLI/test/Command/Visit/GetOrphanVisitsCommandTest.php index 71f8f7b2..199578f3 100644 --- a/module/CLI/test/Command/Visit/GetOrphanVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/GetOrphanVisitsCommandTest.php @@ -22,7 +22,7 @@ class GetOrphanVisitsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject $visitsHelper; + private MockObject & VisitsStatsHelperInterface $visitsHelper; protected function setUp(): void { diff --git a/module/CLI/test/GeoLite/GeolocationDbUpdaterTest.php b/module/CLI/test/GeoLite/GeolocationDbUpdaterTest.php index 0b890e46..e1ebb4c1 100644 --- a/module/CLI/test/GeoLite/GeolocationDbUpdaterTest.php +++ b/module/CLI/test/GeoLite/GeolocationDbUpdaterTest.php @@ -23,9 +23,9 @@ use function range; class GeolocationDbUpdaterTest extends TestCase { - private MockObject $dbUpdater; - private MockObject $geoLiteDbReader; - private MockObject $lock; + private MockObject & DbUpdaterInterface $dbUpdater; + private MockObject & Reader $geoLiteDbReader; + private MockObject & Lock\LockInterface $lock; protected function setUp(): void { diff --git a/module/Core/test/EventDispatcher/LocateUnlocatedVisitsTest.php b/module/Core/test/EventDispatcher/LocateUnlocatedVisitsTest.php index 8b10821c..7315e286 100644 --- a/module/Core/test/EventDispatcher/LocateUnlocatedVisitsTest.php +++ b/module/Core/test/EventDispatcher/LocateUnlocatedVisitsTest.php @@ -17,8 +17,8 @@ use Shlinkio\Shlink\IpGeolocation\Model\Location; class LocateUnlocatedVisitsTest extends TestCase { private LocateUnlocatedVisits $listener; - private MockObject $locator; - private MockObject $visitToLocation; + private MockObject & VisitLocatorInterface $locator; + private MockObject & VisitToLocationHelperInterface $visitToLocation; protected function setUp(): void { diff --git a/module/Core/test/EventDispatcher/RedisPubSub/NotifyVisitToRedisTest.php b/module/Core/test/EventDispatcher/RedisPubSub/NotifyVisitToRedisTest.php index 92bf4583..f50cf906 100644 --- a/module/Core/test/EventDispatcher/RedisPubSub/NotifyVisitToRedisTest.php +++ b/module/Core/test/EventDispatcher/RedisPubSub/NotifyVisitToRedisTest.php @@ -22,10 +22,10 @@ use Throwable; class NotifyVisitToRedisTest extends TestCase { - private MockObject $helper; - private MockObject $updatesGenerator; - private MockObject $em; - private MockObject $logger; + private MockObject & PublishingHelperInterface $helper; + private MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator; + private MockObject & EntityManagerInterface $em; + private MockObject & LoggerInterface $logger; protected function setUp(): void { diff --git a/module/Core/test/ShortUrl/DeleteShortUrlServiceTest.php b/module/Core/test/ShortUrl/DeleteShortUrlServiceTest.php index 4ce1568b..be036264 100644 --- a/module/Core/test/ShortUrl/DeleteShortUrlServiceTest.php +++ b/module/Core/test/ShortUrl/DeleteShortUrlServiceTest.php @@ -23,8 +23,8 @@ use function sprintf; class DeleteShortUrlServiceTest extends TestCase { - private MockObject $em; - private MockObject $urlResolver; + private MockObject & EntityManagerInterface $em; + private MockObject & ShortUrlResolverInterface $urlResolver; private string $shortCode; protected function setUp(): void diff --git a/module/Core/test/ShortUrl/Helper/ShortCodeUniquenessHelperTest.php b/module/Core/test/ShortUrl/Helper/ShortCodeUniquenessHelperTest.php index 2dfd4433..cc18be07 100644 --- a/module/Core/test/ShortUrl/Helper/ShortCodeUniquenessHelperTest.php +++ b/module/Core/test/ShortUrl/Helper/ShortCodeUniquenessHelperTest.php @@ -16,8 +16,8 @@ use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepository; class ShortCodeUniquenessHelperTest extends TestCase { private ShortCodeUniquenessHelper $helper; - private MockObject $em; - private MockObject $shortUrl; + private MockObject & EntityManagerInterface $em; + private MockObject & ShortUrl $shortUrl; protected function setUp(): void { diff --git a/module/Core/test/ShortUrl/Middleware/TrimTrailingSlashMiddlewareTest.php b/module/Core/test/ShortUrl/Middleware/TrimTrailingSlashMiddlewareTest.php index ca971b00..eb078902 100644 --- a/module/Core/test/ShortUrl/Middleware/TrimTrailingSlashMiddlewareTest.php +++ b/module/Core/test/ShortUrl/Middleware/TrimTrailingSlashMiddlewareTest.php @@ -19,7 +19,7 @@ use function Functional\const_function; class TrimTrailingSlashMiddlewareTest extends TestCase { - private MockObject $requestHandler; + private MockObject & RequestHandlerInterface $requestHandler; protected function setUp(): void { diff --git a/module/Core/test/ShortUrl/ShortUrlResolverTest.php b/module/Core/test/ShortUrl/ShortUrlResolverTest.php index e15bb124..1e86ec1c 100644 --- a/module/Core/test/ShortUrl/ShortUrlResolverTest.php +++ b/module/Core/test/ShortUrl/ShortUrlResolverTest.php @@ -28,8 +28,8 @@ class ShortUrlResolverTest extends TestCase use ApiKeyHelpersTrait; private ShortUrlResolver $urlResolver; - private MockObject $em; - private MockObject $repo; + private MockObject & EntityManagerInterface $em; + private MockObject & ShortUrlRepositoryInterface $repo; protected function setUp(): void { diff --git a/module/Core/test/ShortUrl/ShortUrlServiceTest.php b/module/Core/test/ShortUrl/ShortUrlServiceTest.php index 9513b6dc..876d322b 100644 --- a/module/Core/test/ShortUrl/ShortUrlServiceTest.php +++ b/module/Core/test/ShortUrl/ShortUrlServiceTest.php @@ -27,9 +27,9 @@ class ShortUrlServiceTest extends TestCase use ApiKeyHelpersTrait; private ShortUrlService $service; - private MockObject $em; - private MockObject $urlResolver; - private MockObject $titleResolutionHelper; + private MockObject & EntityManagerInterface $em; + private MockObject & ShortUrlResolverInterface $urlResolver; + private MockObject & ShortUrlTitleResolutionHelperInterface $titleResolutionHelper; protected function setUp(): void { diff --git a/module/Core/test/ShortUrl/UrlShortenerTest.php b/module/Core/test/ShortUrl/UrlShortenerTest.php index e5fe7eca..4c9e8646 100644 --- a/module/Core/test/ShortUrl/UrlShortenerTest.php +++ b/module/Core/test/ShortUrl/UrlShortenerTest.php @@ -21,9 +21,9 @@ use Shlinkio\Shlink\Core\ShortUrl\UrlShortener; class UrlShortenerTest extends TestCase { private UrlShortener $urlShortener; - private MockObject $em; - private MockObject $titleResolutionHelper; - private MockObject $shortCodeHelper; + private MockObject & EntityManager $em; + private MockObject & ShortUrlTitleResolutionHelperInterface $titleResolutionHelper; + private MockObject & ShortCodeUniquenessHelperInterface $shortCodeHelper; protected function setUp(): void { diff --git a/module/Core/test/Visit/Geolocation/VisitToLocationHelperTest.php b/module/Core/test/Visit/Geolocation/VisitToLocationHelperTest.php index b32dc037..7d0fb7f1 100644 --- a/module/Core/test/Visit/Geolocation/VisitToLocationHelperTest.php +++ b/module/Core/test/Visit/Geolocation/VisitToLocationHelperTest.php @@ -17,7 +17,7 @@ use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface; class VisitToLocationHelperTest extends TestCase { private VisitToLocationHelper $helper; - private MockObject $ipLocationResolver; + private MockObject & IpLocationResolverInterface $ipLocationResolver; protected function setUp(): void { diff --git a/module/Rest/test/ApiKey/InitialApiKeyDelegatorTest.php b/module/Rest/test/ApiKey/InitialApiKeyDelegatorTest.php index a44700a6..cf32ba10 100644 --- a/module/Rest/test/ApiKey/InitialApiKeyDelegatorTest.php +++ b/module/Rest/test/ApiKey/InitialApiKeyDelegatorTest.php @@ -17,7 +17,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class InitialApiKeyDelegatorTest extends TestCase { private InitialApiKeyDelegator $delegator; - private MockObject $container; + private MockObject & ContainerInterface $container; protected function setUp(): void { diff --git a/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php b/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php index 03379921..3eb77dcb 100644 --- a/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php +++ b/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php @@ -28,8 +28,8 @@ use function Laminas\Stratigility\middleware; class AuthenticationMiddlewareTest extends TestCase { private AuthenticationMiddleware $middleware; - private MockObject $apiKeyService; - private MockObject $handler; + private MockObject & ApiKeyServiceInterface $apiKeyService; + private MockObject & RequestHandlerInterface $handler; protected function setUp(): void { diff --git a/module/Rest/test/Middleware/ErrorHandler/BackwardsCompatibleProblemDetailsHandlerTest.php b/module/Rest/test/Middleware/ErrorHandler/BackwardsCompatibleProblemDetailsHandlerTest.php index ab468803..40ba6965 100644 --- a/module/Rest/test/Middleware/ErrorHandler/BackwardsCompatibleProblemDetailsHandlerTest.php +++ b/module/Rest/test/Middleware/ErrorHandler/BackwardsCompatibleProblemDetailsHandlerTest.php @@ -23,6 +23,7 @@ class BackwardsCompatibleProblemDetailsHandlerTest extends TestCase } /** + * @param class-string $expectedException * @test * @dataProvider provideExceptions */ From 85e18a475471d779b0ce972de33c1e8cb4fb60dd Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 24 Oct 2022 20:11:25 +0200 Subject: [PATCH 128/182] Fixed all phpstan inspections on tests --- module/CLI/test/ApiKey/RoleResolverTest.php | 10 ++++++++-- module/CLI/test/Command/Api/ListKeysCommandTest.php | 10 ++++++++-- .../Command/ShortUrl/CreateShortUrlCommandTest.php | 5 ++++- module/CLI/test/GeoLite/GeolocationDbUpdaterTest.php | 2 -- module/Core/test/Action/QrCodeActionTest.php | 7 +++++-- module/Core/test/Domain/DomainServiceTest.php | 7 +++++-- .../Core/test/Visit/Geolocation/VisitLocatorTest.php | 7 ------- .../Adapter/NonOrphanVisitsPaginatorAdapterTest.php | 2 ++ .../Adapter/OrphanVisitsPaginatorAdapterTest.php | 2 ++ module/Rest/test/ApiKey/Model/RoleDefinitionTest.php | 3 ++- 10 files changed, 36 insertions(+), 19 deletions(-) diff --git a/module/CLI/test/ApiKey/RoleResolverTest.php b/module/CLI/test/ApiKey/RoleResolverTest.php index af50e17e..9f2ec2aa 100644 --- a/module/CLI/test/ApiKey/RoleResolverTest.php +++ b/module/CLI/test/ApiKey/RoleResolverTest.php @@ -38,7 +38,7 @@ class RoleResolverTest extends TestCase ): void { $this->domainService->expects($this->exactly($expectedDomainCalls))->method('getOrCreate')->with( 'example.com', - )->willReturn(Domain::withAuthority('example.com')->setId('1')); + )->willReturn($this->domainWithId(Domain::withAuthority('example.com'))); $result = $this->resolver->determineRoles($input); @@ -47,7 +47,7 @@ class RoleResolverTest extends TestCase public function provideRoles(): iterable { - $domain = Domain::withAuthority('example.com')->setId('1'); + $domain = $this->domainWithId(Domain::withAuthority('example.com')); $buildInput = function (array $definition): InputInterface { $input = $this->createStub(InputInterface::class); $input->method('getOption')->willReturnMap( @@ -113,4 +113,10 @@ class RoleResolverTest extends TestCase $this->resolver->determineRoles($input); } + + private function domainWithId(Domain $domain): Domain + { + $domain->setId('1'); + return $domain; + } } diff --git a/module/CLI/test/Command/Api/ListKeysCommandTest.php b/module/CLI/test/Command/Api/ListKeysCommandTest.php index a0e7d833..f4101ec6 100644 --- a/module/CLI/test/Command/Api/ListKeysCommandTest.php +++ b/module/CLI/test/Command/Api/ListKeysCommandTest.php @@ -86,12 +86,12 @@ class ListKeysCommandTest extends TestCase $apiKey1 = ApiKey::create(), $apiKey2 = $this->apiKeyWithRoles([RoleDefinition::forAuthoredShortUrls()]), $apiKey3 = $this->apiKeyWithRoles( - [RoleDefinition::forDomain(Domain::withAuthority('example.com')->setId('1'))], + [RoleDefinition::forDomain($this->domainWithId(Domain::withAuthority('example.com')))], ), $apiKey4 = ApiKey::create(), $apiKey5 = $this->apiKeyWithRoles([ RoleDefinition::forAuthoredShortUrls(), - RoleDefinition::forDomain(Domain::withAuthority('example.com')->setId('1')), + RoleDefinition::forDomain($this->domainWithId(Domain::withAuthority('example.com'))), ]), $apiKey6 = ApiKey::create(), ], @@ -150,4 +150,10 @@ class ListKeysCommandTest extends TestCase return $apiKey; } + + private function domainWithId(Domain $domain): Domain + { + $domain->setId('1'); + return $domain; + } } diff --git a/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php index 16b85290..734089c9 100644 --- a/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php @@ -37,7 +37,10 @@ class CreateShortUrlCommandTest extends TestCase $command = new CreateShortUrlCommand( $this->urlShortener, $this->stringifier, - new UrlShortenerOptions(domain: ['hostname' => self::DEFAULT_DOMAIN], defaultShortCodesLength: 5), + new UrlShortenerOptions( + domain: ['hostname' => self::DEFAULT_DOMAIN, 'schema' => ''], + defaultShortCodesLength: 5, + ), ); $this->commandTester = $this->testerForCommand($command); } diff --git a/module/CLI/test/GeoLite/GeolocationDbUpdaterTest.php b/module/CLI/test/GeoLite/GeolocationDbUpdaterTest.php index e1ebb4c1..8d6188e9 100644 --- a/module/CLI/test/GeoLite/GeolocationDbUpdaterTest.php +++ b/module/CLI/test/GeoLite/GeolocationDbUpdaterTest.php @@ -31,8 +31,6 @@ class GeolocationDbUpdaterTest extends TestCase { $this->dbUpdater = $this->createMock(DbUpdaterInterface::class); $this->geoLiteDbReader = $this->createMock(Reader::class); - $this->trackingOptions = new TrackingOptions(); - $this->lock = $this->createMock(Lock\LockInterface::class); $this->lock->method('acquire')->with($this->isTrue())->willReturn(true); } diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index 497fb009..89c105c0 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -115,8 +115,10 @@ class QrCodeActionTest extends TestCase $delegate = $this->createMock(RequestHandlerInterface::class); $resp = $this->action($defaultOptions)->process($req->withAttribute('shortCode', $code), $delegate); - [$size] = getimagesizefromstring($resp->getBody()->__toString()); + $result = getimagesizefromstring($resp->getBody()->__toString()); + self::assertNotFalse($result); + [$size] = $result; self::assertEquals($expectedSize, $size); } @@ -207,8 +209,9 @@ class QrCodeActionTest extends TestCase $resp = $this->action($defaultOptions)->process($req, $delegate); $image = imagecreatefromstring($resp->getBody()->__toString()); - $color = imagecolorat($image, 1, 1); + self::assertNotFalse($image); + $color = imagecolorat($image, 1, 1); self::assertEquals($color, $expectedColor); } diff --git a/module/Core/test/Domain/DomainServiceTest.php b/module/Core/test/Domain/DomainServiceTest.php index 4d4606b0..46d3e567 100644 --- a/module/Core/test/Domain/DomainServiceTest.php +++ b/module/Core/test/Domain/DomainServiceTest.php @@ -48,8 +48,10 @@ class DomainServiceTest extends TestCase { $default = DomainItem::forDefaultDomain('default.com', new EmptyNotFoundRedirectConfig()); $adminApiKey = ApiKey::create(); + $domain = Domain::withAuthority(''); + $domain->setId('123'); $domainSpecificApiKey = ApiKey::fromMeta( - ApiKeyMeta::withRoles(RoleDefinition::forDomain(Domain::withAuthority('')->setId('123'))), + ApiKeyMeta::withRoles(RoleDefinition::forDomain($domain)), ); yield 'empty list without API key' => [[], [$default], null]; @@ -147,7 +149,8 @@ class DomainServiceTest extends TestCase public function getOrCreateThrowsExceptionForApiKeysWithDomainRole(): void { $authority = 'example.com'; - $domain = Domain::withAuthority($authority)->setId('1'); + $domain = Domain::withAuthority($authority); + $domain->setId('1'); $apiKey = ApiKey::fromMeta(ApiKeyMeta::withRoles(RoleDefinition::forDomain($domain))); $repo = $this->createMock(DomainRepositoryInterface::class); $repo->method('findOneByAuthority')->with($authority, $apiKey)->willReturn(null); diff --git a/module/Core/test/Visit/Geolocation/VisitLocatorTest.php b/module/Core/test/Visit/Geolocation/VisitLocatorTest.php index a9ed7aa2..a39940ae 100644 --- a/module/Core/test/Visit/Geolocation/VisitLocatorTest.php +++ b/module/Core/test/Visit/Geolocation/VisitLocatorTest.php @@ -6,7 +6,6 @@ namespace ShlinkioTest\Shlink\Core\Visit\Geolocation; use Doctrine\ORM\EntityManager; use Exception; -use PHPUnit\Framework\Assert; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException; @@ -19,10 +18,8 @@ use Shlinkio\Shlink\Core\Visit\Model\Visitor; use Shlinkio\Shlink\Core\Visit\Repository\VisitRepositoryInterface; use Shlinkio\Shlink\IpGeolocation\Model\Location; -use function array_shift; use function count; use function floor; -use function func_get_args; use function Functional\map; use function range; use function sprintf; @@ -72,10 +69,6 @@ class VisitLocatorTest extends TestCase public function onVisitLocated(VisitLocation $visitLocation, Visit $visit): void { - $args = func_get_args(); - - Assert::assertInstanceOf(VisitLocation::class, array_shift($args)); - Assert::assertInstanceOf(Visit::class, array_shift($args)); } }); } diff --git a/module/Core/test/Visit/Paginator/Adapter/NonOrphanVisitsPaginatorAdapterTest.php b/module/Core/test/Visit/Paginator/Adapter/NonOrphanVisitsPaginatorAdapterTest.php index 9c4a6e63..ec256eeb 100644 --- a/module/Core/test/Visit/Paginator/Adapter/NonOrphanVisitsPaginatorAdapterTest.php +++ b/module/Core/test/Visit/Paginator/Adapter/NonOrphanVisitsPaginatorAdapterTest.php @@ -45,6 +45,8 @@ class NonOrphanVisitsPaginatorAdapterTest extends TestCase } /** + * @param int<0, max> $limit + * @param int<0, max> $offset * @test * @dataProvider provideLimitAndOffset */ diff --git a/module/Core/test/Visit/Paginator/Adapter/OrphanVisitsPaginatorAdapterTest.php b/module/Core/test/Visit/Paginator/Adapter/OrphanVisitsPaginatorAdapterTest.php index 8981d05f..6b91a20b 100644 --- a/module/Core/test/Visit/Paginator/Adapter/OrphanVisitsPaginatorAdapterTest.php +++ b/module/Core/test/Visit/Paginator/Adapter/OrphanVisitsPaginatorAdapterTest.php @@ -41,6 +41,8 @@ class OrphanVisitsPaginatorAdapterTest extends TestCase } /** + * @param int<0, max> $limit + * @param int<0, max> $offset * @test * @dataProvider provideLimitAndOffset */ diff --git a/module/Rest/test/ApiKey/Model/RoleDefinitionTest.php b/module/Rest/test/ApiKey/Model/RoleDefinitionTest.php index 4198aa9b..ac513959 100644 --- a/module/Rest/test/ApiKey/Model/RoleDefinitionTest.php +++ b/module/Rest/test/ApiKey/Model/RoleDefinitionTest.php @@ -23,7 +23,8 @@ class RoleDefinitionTest extends TestCase /** @test */ public function forDomainCreatesRoleDefinitionAsExpected(): void { - $domain = Domain::withAuthority('foo.com')->setId('123'); + $domain = Domain::withAuthority('foo.com'); + $domain->setId('123'); $definition = RoleDefinition::forDomain($domain); self::assertEquals(Role::DOMAIN_SPECIFIC, $definition->role); From f459a99e7eb0962506b16a0f15e2e0a9b39dedbd Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 24 Oct 2022 20:14:48 +0200 Subject: [PATCH 129/182] Added db tests to phpstan checks --- composer.json | 2 +- .../test-db/Tag/Paginator/Adapter/TagsPaginatorAdapterTest.php | 2 ++ module/Core/test-db/Visit/Repository/VisitRepositoryTest.php | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index f052e27c..6fcba050 100644 --- a/composer.json +++ b/composer.json @@ -109,7 +109,7 @@ ], "cs": "phpcs", "cs:fix": "phpcbf", - "stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/test module/*/config config docker/config data/migrations --level=8", + "stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/test module/*/test-db module/*/config config docker/config data/migrations --level=8", "test": [ "@parallel test:unit test:db", "@parallel test:api test:cli" diff --git a/module/Core/test-db/Tag/Paginator/Adapter/TagsPaginatorAdapterTest.php b/module/Core/test-db/Tag/Paginator/Adapter/TagsPaginatorAdapterTest.php index 849b768f..385f2335 100644 --- a/module/Core/test-db/Tag/Paginator/Adapter/TagsPaginatorAdapterTest.php +++ b/module/Core/test-db/Tag/Paginator/Adapter/TagsPaginatorAdapterTest.php @@ -22,6 +22,8 @@ class TagsPaginatorAdapterTest extends DatabaseTestCase } /** + * @param int<0, max> $offset + * @param int<0, max> $length * @test * @dataProvider provideFilters */ diff --git a/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php b/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php index 0eaa87e1..25512f15 100644 --- a/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php +++ b/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php @@ -213,7 +213,6 @@ class VisitRepositoryTest extends DatabaseTestCase { $foo = 'foo'; - /** @var ShortUrl $shortUrl */ $this->createShortUrlsAndVisits(false, [$foo]); $this->getEntityManager()->flush(); From 01e0a95e1492fc2334690e3b46a7d5d37eaa4274 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 24 Oct 2022 20:25:06 +0200 Subject: [PATCH 130/182] Added rest of tests to phpstan check --- composer.json | 2 +- module/Rest/test-api/Action/CreateShortUrlTest.php | 2 +- module/Rest/test-api/Action/EditShortUrlTest.php | 4 ++-- module/Rest/test-api/Fixtures/ShortUrlsFixture.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 6fcba050..f9c68dbb 100644 --- a/composer.json +++ b/composer.json @@ -109,7 +109,7 @@ ], "cs": "phpcs", "cs:fix": "phpcbf", - "stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/test module/*/test-db module/*/config config docker/config data/migrations --level=8", + "stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/test* module/*/config config docker/config data/migrations --level=8", "test": [ "@parallel test:unit test:db", "@parallel test:api test:cli" diff --git a/module/Rest/test-api/Action/CreateShortUrlTest.php b/module/Rest/test-api/Action/CreateShortUrlTest.php index 26d271f0..889b67af 100644 --- a/module/Rest/test-api/Action/CreateShortUrlTest.php +++ b/module/Rest/test-api/Action/CreateShortUrlTest.php @@ -363,7 +363,7 @@ class CreateShortUrlTest extends ApiTestCase } /** - * @return array{int $statusCode, array $payload} + * @return array{int, array} */ private function createShortUrl(array $body = [], string $apiKey = 'valid_api_key', string $version = '2'): array { diff --git a/module/Rest/test-api/Action/EditShortUrlTest.php b/module/Rest/test-api/Action/EditShortUrlTest.php index 92f9bbe0..fefbdcba 100644 --- a/module/Rest/test-api/Action/EditShortUrlTest.php +++ b/module/Rest/test-api/Action/EditShortUrlTest.php @@ -62,13 +62,13 @@ class EditShortUrlTest extends ApiTestCase ]]; } - private function findShortUrlMetaByShortCode(string $shortCode): ?array + private function findShortUrlMetaByShortCode(string $shortCode): array { $matchingShortUrl = $this->getJsonResponsePayload( $this->callApiWithKey(self::METHOD_GET, '/short-urls/' . $shortCode), ); - return $matchingShortUrl['meta'] ?? null; + return $matchingShortUrl['meta'] ?? []; } /** diff --git a/module/Rest/test-api/Fixtures/ShortUrlsFixture.php b/module/Rest/test-api/Fixtures/ShortUrlsFixture.php index a159737e..eadf60ee 100644 --- a/module/Rest/test-api/Fixtures/ShortUrlsFixture.php +++ b/module/Rest/test-api/Fixtures/ShortUrlsFixture.php @@ -23,7 +23,7 @@ class ShortUrlsFixture extends AbstractFixture implements DependentFixtureInterf public function load(ObjectManager $manager): void { - $relationResolver = new PersistenceShortUrlRelationResolver($manager); + $relationResolver = new PersistenceShortUrlRelationResolver($manager); // @phpstan-ignore-line /** @var ApiKey $authorApiKey */ $authorApiKey = $this->getReference('author_api_key'); From 910864eaaf7ae79e00575237d91b69eaaee6e70b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 5 Nov 2022 10:54:12 +0100 Subject: [PATCH 131/182] Reduced required MSI to 80 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f9c68dbb..3f794838 100644 --- a/composer.json +++ b/composer.json @@ -132,7 +132,7 @@ "test:cli:ci": "GENERATE_COVERAGE=yes composer test:cli", "test:cli:pretty": "GENERATE_COVERAGE=pretty composer test:cli", "infect:ci:base": "infection --threads=max --only-covered --only-covering-test-cases --skip-initial-tests", - "infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=84", + "infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=80", "infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json5", "infect:ci:api": "@infect:ci:base --coverage=build/coverage-api --min-msi=80 --configuration=infection-api.json5", "infect:ci:cli": "@infect:ci:base --coverage=build/coverage-cli --min-msi=80 --configuration=infection-cli.json5", From 891438c6724b605469898be87725d2929a317845 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 11 Nov 2022 16:33:02 +0100 Subject: [PATCH 132/182] Updated shlink-config --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3f794838..ec8e440b 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ "pugx/shortid-php": "^1.1", "ramsey/uuid": "^4.5", "shlinkio/shlink-common": "dev-main#7515008 as 5.2", - "shlinkio/shlink-config": "dev-main#e75d27a as 2.2", + "shlinkio/shlink-config": "dev-main#e9ad156 as 2.3", "shlinkio/shlink-event-dispatcher": "^2.6", "shlinkio/shlink-importer": "^4.0", "shlinkio/shlink-installer": "^8.2", From 58e6b0b68370bb0d55cd46f95f120458b8dec291 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 17 Nov 2022 19:57:47 +0100 Subject: [PATCH 133/182] Added badge for Mastodon follow --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bb99634e..c6dfa953 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![Docker pulls](https://img.shields.io/docker/pulls/shlinkio/shlink.svg?logo=docker&style=flat-square)](https://hub.docker.com/r/shlinkio/shlink/) [![License](https://img.shields.io/github/license/shlinkio/shlink.svg?style=flat-square)](https://github.com/shlinkio/shlink/blob/main/LICENSE) [![Twitter](https://img.shields.io/twitter/follow/shlinkio?color=blue&label=follow&logo=twitter&style=flat-square)](https://twitter.com/shlinkio) +[![Mastodon](https://img.shields.io/mastodon/follow/109329425426175098?color=%236364ff&domain=https%3A%2F%2Ffosstodon.org&label=follow&logo=mastodon&logoColor=white&style=flat-square)](https://fosstodon.org/@shlinkio) [![Paypal donate](https://img.shields.io/badge/Donate-paypal-blue.svg?style=flat-square&logo=paypal&colorA=aaaaaa)](https://slnk.to/donate) A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own domain. From 42234080909dc835eb5e36374e551b0b5ef43633 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 28 Nov 2022 15:47:59 +0100 Subject: [PATCH 134/182] Updated to common-config with support for valinor 1.0.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ec8e440b..16378c67 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ "pugx/shortid-php": "^1.1", "ramsey/uuid": "^4.5", "shlinkio/shlink-common": "dev-main#7515008 as 5.2", - "shlinkio/shlink-config": "dev-main#e9ad156 as 2.3", + "shlinkio/shlink-config": "dev-main#96c81fb as 2.3", "shlinkio/shlink-event-dispatcher": "^2.6", "shlinkio/shlink-importer": "^4.0", "shlinkio/shlink-installer": "^8.2", From 4b66aaba5c0fd37f7e30c3ac44331e2746d5e170 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 4 Dec 2022 12:28:44 +0100 Subject: [PATCH 135/182] Updated to latest shlink-importer --- composer.json | 2 +- module/Core/src/Importer/ImportedLinksProcessor.php | 8 +++----- .../Core/test/Importer/ImportedLinksProcessorTest.php | 11 ++++++----- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 16378c67..762ead49 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "shlinkio/shlink-common": "dev-main#7515008 as 5.2", "shlinkio/shlink-config": "dev-main#96c81fb as 2.3", "shlinkio/shlink-event-dispatcher": "^2.6", - "shlinkio/shlink-importer": "^4.0", + "shlinkio/shlink-importer": "dev-main#c97662b as 5.0", "shlinkio/shlink-installer": "^8.2", "shlinkio/shlink-ip-geolocation": "dev-main#e208963 as 3.2", "spiral/roadrunner": "^2.11", diff --git a/module/Core/src/Importer/ImportedLinksProcessor.php b/module/Core/src/Importer/ImportedLinksProcessor.php index 0f28c7fa..b33670ac 100644 --- a/module/Core/src/Importer/ImportedLinksProcessor.php +++ b/module/Core/src/Importer/ImportedLinksProcessor.php @@ -13,6 +13,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface; use Shlinkio\Shlink\Core\Util\DoctrineBatchHelperInterface; use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; +use Shlinkio\Shlink\Importer\Model\ImportResult; use Shlinkio\Shlink\Importer\Params\ImportParams; use Shlinkio\Shlink\Importer\Sources\ImportSource; use Symfony\Component\Console\Style\OutputStyle; @@ -34,14 +35,11 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface $this->shortUrlRepo = $this->em->getRepository(ShortUrl::class); } - /** - * @param iterable $shlinkUrls - */ - public function process(StyleInterface $io, iterable $shlinkUrls, ImportParams $params): void + public function process(StyleInterface $io, ImportResult $result, ImportParams $params): void { $importShortCodes = $params->importShortCodes; $source = $params->source; - $iterable = $this->batchHelper->wrapIterable($shlinkUrls, $source === ImportSource::SHLINK ? 10 : 100); + $iterable = $this->batchHelper->wrapIterable($result->shlinkUrls, $source === ImportSource::SHLINK ? 10 : 100); /** @var ImportedShlinkUrl $importedUrl */ foreach ($iterable as $importedUrl) { diff --git a/module/Core/test/Importer/ImportedLinksProcessorTest.php b/module/Core/test/Importer/ImportedLinksProcessorTest.php index 382d912c..a419858f 100644 --- a/module/Core/test/Importer/ImportedLinksProcessorTest.php +++ b/module/Core/test/Importer/ImportedLinksProcessorTest.php @@ -19,6 +19,7 @@ use Shlinkio\Shlink\Core\Util\DoctrineBatchHelperInterface; use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit; +use Shlinkio\Shlink\Importer\Model\ImportResult; use Shlinkio\Shlink\Importer\Params\ImportParams; use Shlinkio\Shlink\Importer\Sources\ImportSource; use stdClass; @@ -76,7 +77,7 @@ class ImportedLinksProcessorTest extends TestCase ); $this->io->expects($this->exactly($expectedCalls))->method('text')->with($this->isType('string')); - $this->processor->process($this->io, $urls, $this->buildParams()); + $this->processor->process($this->io, ImportResult::withShortUrls($urls), $this->buildParams()); } /** @test */ @@ -99,7 +100,7 @@ class ImportedLinksProcessorTest extends TestCase }); $textCalls = $this->setUpIoText('Skipped. Reason: Whatever error', 'Imported'); - $this->processor->process($this->io, $urls, $this->buildParams()); + $this->processor->process($this->io, ImportResult::withShortUrls($urls), $this->buildParams()); self::assertEquals(2, $textCalls->importedCount); self::assertEquals(1, $textCalls->skippedCount); @@ -124,7 +125,7 @@ class ImportedLinksProcessorTest extends TestCase $this->em->expects($this->exactly(2))->method('persist')->with($this->isInstanceOf(ShortUrl::class)); $textCalls = $this->setUpIoText(); - $this->processor->process($this->io, $urls, $this->buildParams()); + $this->processor->process($this->io, ImportResult::withShortUrls($urls), $this->buildParams()); self::assertEquals(2, $textCalls->importedCount); self::assertEquals(3, $textCalls->skippedCount); @@ -151,7 +152,7 @@ class ImportedLinksProcessorTest extends TestCase }); $textCalls = $this->setUpIoText('Error'); - $this->processor->process($this->io, $urls, $this->buildParams()); + $this->processor->process($this->io, ImportResult::withShortUrls($urls), $this->buildParams()); self::assertEquals(2, $textCalls->importedCount); self::assertEquals(3, $textCalls->skippedCount); @@ -176,7 +177,7 @@ class ImportedLinksProcessorTest extends TestCase )->with($this->callback(fn (object $arg) => $arg instanceof ShortUrl || $arg instanceof Visit)); $this->io->expects($this->once())->method('text')->with($this->stringContains($expectedOutput)); - $this->processor->process($this->io, [$importedUrl], $this->buildParams()); + $this->processor->process($this->io, ImportResult::withShortUrls([$importedUrl]), $this->buildParams()); } public function provideUrlsWithVisits(): iterable From 55c9773a02adf995c3a931b7dc33ab4668617f48 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 4 Dec 2022 20:35:38 +0100 Subject: [PATCH 136/182] Added logic to import orphan visits --- module/Core/functions/functions.php | 13 ++++- .../src/Importer/ImportedLinksProcessor.php | 48 ++++++++++++++++++- .../Core/src/Importer/ShortUrlImporting.php | 4 +- module/Core/src/ShortUrl/Entity/ShortUrl.php | 16 ++----- .../src/ShortUrl/Model/ShortUrlCreation.php | 6 +-- .../src/ShortUrl/Model/ShortUrlEdition.php | 6 +-- .../src/ShortUrl/Model/ShortUrlsParams.php | 6 +-- .../src/Util/DoctrineBatchHelperInterface.php | 5 ++ module/Core/src/Visit/Entity/Visit.php | 26 ++++++++-- .../src/Visit/Repository/VisitRepository.php | 15 ++++++ .../Repository/VisitRepositoryInterface.php | 2 + 11 files changed, 118 insertions(+), 29 deletions(-) diff --git a/module/Core/functions/functions.php b/module/Core/functions/functions.php index d34175c7..814aa56a 100644 --- a/module/Core/functions/functions.php +++ b/module/Core/functions/functions.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core; use Cake\Chronos\Chronos; +use Cake\Chronos\ChronosInterface; use DateTimeInterface; use Doctrine\ORM\Mapping\Builder\FieldBuilder; use Jaybizzle\CrawlerDetect\CrawlerDetect; @@ -35,7 +36,7 @@ function generateRandomShortCode(int $length): string function parseDateFromQuery(array $query, string $dateName): ?Chronos { - return normalizeDate(empty($query[$dateName] ?? null) ? null : Chronos::parse($query[$dateName])); + return normalizeOptionalDate(empty($query[$dateName] ?? null) ? null : Chronos::parse($query[$dateName])); } function parseDateRangeFromQuery(array $query, string $startDateName, string $endDateName): DateRange @@ -46,7 +47,10 @@ function parseDateRangeFromQuery(array $query, string $startDateName, string $en return buildDateRange($startDate, $endDate); } -function normalizeDate(string|DateTimeInterface|Chronos|null $date): ?Chronos +/** + * @return ($date is null ? null : Chronos) + */ +function normalizeOptionalDate(string|DateTimeInterface|ChronosInterface|null $date): ?Chronos { $parsedDate = match (true) { $date === null || $date instanceof Chronos => $date, @@ -57,6 +61,11 @@ function normalizeDate(string|DateTimeInterface|Chronos|null $date): ?Chronos return $parsedDate?->setTimezone(date_default_timezone_get()); } +function normalizeDate(string|DateTimeInterface|ChronosInterface $date): Chronos +{ + return normalizeOptionalDate($date); +} + function getOptionalIntFromInputFilter(InputFilter $inputFilter, string $fieldName): ?int { $value = $inputFilter->getValue($fieldName); diff --git a/module/Core/src/Importer/ImportedLinksProcessor.php b/module/Core/src/Importer/ImportedLinksProcessor.php index b33670ac..c302471b 100644 --- a/module/Core/src/Importer/ImportedLinksProcessor.php +++ b/module/Core/src/Importer/ImportedLinksProcessor.php @@ -11,7 +11,10 @@ use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortCodeUniquenessHelperInterface; use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface; use Shlinkio\Shlink\Core\Util\DoctrineBatchHelperInterface; +use Shlinkio\Shlink\Core\Visit\Entity\Visit; +use Shlinkio\Shlink\Core\Visit\Repository\VisitRepositoryInterface; use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface; +use Shlinkio\Shlink\Importer\Model\ImportedShlinkOrphanVisit; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Importer\Model\ImportResult; use Shlinkio\Shlink\Importer\Params\ImportParams; @@ -20,6 +23,7 @@ use Symfony\Component\Console\Style\OutputStyle; use Symfony\Component\Console\Style\StyleInterface; use Throwable; +use function Shlinkio\Shlink\Core\normalizeDate; use function sprintf; class ImportedLinksProcessor implements ImportedLinksProcessorInterface @@ -36,12 +40,27 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface } public function process(StyleInterface $io, ImportResult $result, ImportParams $params): void + { + $io->title('Importing short URLs'); + $this->importShortUrls($io, $result->shlinkUrls, $params); + + if ($params->importOrphanVisits) { + $io->title('Importing orphan visits'); + $this->importOrphanVisits($io, $result->orphanVisits); + } + + $io->success('Data properly imported!'); + } + + /** + * @param iterable $shlinkUrls + */ + private function importShortUrls(StyleInterface $io, iterable $shlinkUrls, ImportParams $params): void { $importShortCodes = $params->importShortCodes; $source = $params->source; - $iterable = $this->batchHelper->wrapIterable($result->shlinkUrls, $source === ImportSource::SHLINK ? 10 : 100); + $iterable = $this->batchHelper->wrapIterable($shlinkUrls, $source === ImportSource::SHLINK ? 10 : 100); - /** @var ImportedShlinkUrl $importedUrl */ foreach ($iterable as $importedUrl) { $skipOnShortCodeConflict = static fn (): bool => $io->choice(sprintf( 'Failed to import URL "%s" because its short-code "%s" is already in use. Do you want to generate ' @@ -105,4 +124,29 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface return $this->shortCodeHelper->ensureShortCodeUniqueness($shortUrl, false); } + + /** + * @param iterable $orphanVisits + */ + private function importOrphanVisits(StyleInterface $io, iterable $orphanVisits): void + { + $iterable = $this->batchHelper->wrapIterable($orphanVisits, 100); + + /** @var VisitRepositoryInterface $visitRepo */ + $visitRepo = $this->em->getRepository(Visit::class); + $mostRecentOrphanVisit = $visitRepo->findMostRecentOrphanVisit(); + + $importedVisits = 0; + foreach ($iterable as $importedOrphanVisit) { + // Skip visits which are older than the most recent already imported visit's date + if ($mostRecentOrphanVisit?->getDate()->gte(normalizeDate($importedOrphanVisit->date))) { + continue; + } + + $this->em->persist(Visit::fromOrphanImport($importedOrphanVisit)); + $importedVisits++; + } + + $io->text(sprintf('Imported %s orphan visits.', $importedVisits)); + } } diff --git a/module/Core/src/Importer/ShortUrlImporting.php b/module/Core/src/Importer/ShortUrlImporting.php index ae7f595d..0165c7c3 100644 --- a/module/Core/src/Importer/ShortUrlImporting.php +++ b/module/Core/src/Importer/ShortUrlImporting.php @@ -4,12 +4,12 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Importer; -use Cake\Chronos\Chronos; use Doctrine\ORM\EntityManagerInterface; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit; +use function Shlinkio\Shlink\Core\normalizeDate; use function sprintf; final class ShortUrlImporting @@ -38,7 +38,7 @@ final class ShortUrlImporting $importedVisits = 0; foreach ($visits as $importedVisit) { // Skip visits which are older than the most recent already imported visit's date - if ($mostRecentImportedDate?->gte(Chronos::instance($importedVisit->date))) { + if ($mostRecentImportedDate?->gte(normalizeDate($importedVisit->date))) { continue; } diff --git a/module/Core/src/ShortUrl/Entity/ShortUrl.php b/module/Core/src/ShortUrl/Entity/ShortUrl.php index 66607987..7316c50b 100644 --- a/module/Core/src/ShortUrl/Entity/ShortUrl.php +++ b/module/Core/src/ShortUrl/Entity/ShortUrl.php @@ -25,6 +25,8 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; use function count; use function Shlinkio\Shlink\Core\generateRandomShortCode; +use function Shlinkio\Shlink\Core\normalizeDate; +use function Shlinkio\Shlink\Core\normalizeOptionalDate; class ShortUrl extends AbstractEntity { @@ -109,19 +111,11 @@ class ShortUrl extends AbstractEntity $instance = self::fromMeta(ShortUrlCreation::fromRawData($meta), $relationResolver); - $validSince = $url->meta->validSince; - if ($validSince !== null) { - $instance->validSince = Chronos::instance($validSince); - } - - $validUntil = $url->meta->validUntil; - if ($validUntil !== null) { - $instance->validUntil = Chronos::instance($validUntil); - } - $instance->importSource = $url->source->value; $instance->importOriginalShortCode = $url->shortCode; - $instance->dateCreated = Chronos::instance($url->createdAt); + $instance->validSince = normalizeOptionalDate($url->meta->validSince); + $instance->validUntil = normalizeOptionalDate($url->meta->validUntil); + $instance->dateCreated = normalizeDate($url->createdAt); return $instance; } diff --git a/module/Core/src/ShortUrl/Model/ShortUrlCreation.php b/module/Core/src/ShortUrl/Model/ShortUrlCreation.php index 41a95a34..9e37234c 100644 --- a/module/Core/src/ShortUrl/Model/ShortUrlCreation.php +++ b/module/Core/src/ShortUrl/Model/ShortUrlCreation.php @@ -12,7 +12,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; use function Shlinkio\Shlink\Core\getOptionalBoolFromInputFilter; use function Shlinkio\Shlink\Core\getOptionalIntFromInputFilter; -use function Shlinkio\Shlink\Core\normalizeDate; +use function Shlinkio\Shlink\Core\normalizeOptionalDate; use const Shlinkio\Shlink\DEFAULT_SHORT_CODES_LENGTH; @@ -68,8 +68,8 @@ final class ShortUrlCreation implements TitleResolutionModelInterface } $this->longUrl = $inputFilter->getValue(ShortUrlInputFilter::LONG_URL); - $this->validSince = normalizeDate($inputFilter->getValue(ShortUrlInputFilter::VALID_SINCE)); - $this->validUntil = normalizeDate($inputFilter->getValue(ShortUrlInputFilter::VALID_UNTIL)); + $this->validSince = normalizeOptionalDate($inputFilter->getValue(ShortUrlInputFilter::VALID_SINCE)); + $this->validUntil = normalizeOptionalDate($inputFilter->getValue(ShortUrlInputFilter::VALID_UNTIL)); $this->customSlug = $inputFilter->getValue(ShortUrlInputFilter::CUSTOM_SLUG); $this->maxVisits = getOptionalIntFromInputFilter($inputFilter, ShortUrlInputFilter::MAX_VISITS); $this->findIfExists = $inputFilter->getValue(ShortUrlInputFilter::FIND_IF_EXISTS); diff --git a/module/Core/src/ShortUrl/Model/ShortUrlEdition.php b/module/Core/src/ShortUrl/Model/ShortUrlEdition.php index 13ea2961..fadc9b1e 100644 --- a/module/Core/src/ShortUrl/Model/ShortUrlEdition.php +++ b/module/Core/src/ShortUrl/Model/ShortUrlEdition.php @@ -12,7 +12,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter; use function array_key_exists; use function Shlinkio\Shlink\Core\getOptionalBoolFromInputFilter; use function Shlinkio\Shlink\Core\getOptionalIntFromInputFilter; -use function Shlinkio\Shlink\Core\normalizeDate; +use function Shlinkio\Shlink\Core\normalizeOptionalDate; final class ShortUrlEdition implements TitleResolutionModelInterface { @@ -69,8 +69,8 @@ final class ShortUrlEdition implements TitleResolutionModelInterface $this->forwardQueryPropWasProvided = array_key_exists(ShortUrlInputFilter::FORWARD_QUERY, $data); $this->longUrl = $inputFilter->getValue(ShortUrlInputFilter::LONG_URL); - $this->validSince = normalizeDate($inputFilter->getValue(ShortUrlInputFilter::VALID_SINCE)); - $this->validUntil = normalizeDate($inputFilter->getValue(ShortUrlInputFilter::VALID_UNTIL)); + $this->validSince = normalizeOptionalDate($inputFilter->getValue(ShortUrlInputFilter::VALID_SINCE)); + $this->validUntil = normalizeOptionalDate($inputFilter->getValue(ShortUrlInputFilter::VALID_UNTIL)); $this->maxVisits = getOptionalIntFromInputFilter($inputFilter, ShortUrlInputFilter::MAX_VISITS); $this->validateUrl = getOptionalBoolFromInputFilter($inputFilter, ShortUrlInputFilter::VALIDATE_URL) ?? false; $this->tags = $inputFilter->getValue(ShortUrlInputFilter::TAGS); diff --git a/module/Core/src/ShortUrl/Model/ShortUrlsParams.php b/module/Core/src/ShortUrl/Model/ShortUrlsParams.php index bf760777..6f8b0f47 100644 --- a/module/Core/src/ShortUrl/Model/ShortUrlsParams.php +++ b/module/Core/src/ShortUrl/Model/ShortUrlsParams.php @@ -10,7 +10,7 @@ use Shlinkio\Shlink\Core\Model\Ordering; use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlsParamsInputFilter; use function Shlinkio\Shlink\Common\buildDateRange; -use function Shlinkio\Shlink\Core\normalizeDate; +use function Shlinkio\Shlink\Core\normalizeOptionalDate; final class ShortUrlsParams { @@ -59,8 +59,8 @@ final class ShortUrlsParams $this->searchTerm = $inputFilter->getValue(ShortUrlsParamsInputFilter::SEARCH_TERM); $this->tags = (array) $inputFilter->getValue(ShortUrlsParamsInputFilter::TAGS); $this->dateRange = buildDateRange( - normalizeDate($inputFilter->getValue(ShortUrlsParamsInputFilter::START_DATE)), - normalizeDate($inputFilter->getValue(ShortUrlsParamsInputFilter::END_DATE)), + normalizeOptionalDate($inputFilter->getValue(ShortUrlsParamsInputFilter::START_DATE)), + normalizeOptionalDate($inputFilter->getValue(ShortUrlsParamsInputFilter::END_DATE)), ); $this->orderBy = Ordering::fromTuple($inputFilter->getValue(ShortUrlsParamsInputFilter::ORDER_BY)); $this->itemsPerPage = (int) ( diff --git a/module/Core/src/Util/DoctrineBatchHelperInterface.php b/module/Core/src/Util/DoctrineBatchHelperInterface.php index 941561ed..4e0d66c4 100644 --- a/module/Core/src/Util/DoctrineBatchHelperInterface.php +++ b/module/Core/src/Util/DoctrineBatchHelperInterface.php @@ -6,5 +6,10 @@ namespace Shlinkio\Shlink\Core\Util; interface DoctrineBatchHelperInterface { + /** + * @template T + * @param iterable $resultSet + * @return iterable + */ public function wrapIterable(iterable $resultSet, int $batchSize): iterable; } diff --git a/module/Core/src/Visit/Entity/Visit.php b/module/Core/src/Visit/Entity/Visit.php index 6451e1ba..86b56f5e 100644 --- a/module/Core/src/Visit/Entity/Visit.php +++ b/module/Core/src/Visit/Entity/Visit.php @@ -10,12 +10,13 @@ use Shlinkio\Shlink\Common\Entity\AbstractEntity; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Common\Util\IpAddress; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; -use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation; use Shlinkio\Shlink\Core\Visit\Model\Visitor; use Shlinkio\Shlink\Core\Visit\Model\VisitType; +use Shlinkio\Shlink\Importer\Model\ImportedShlinkOrphanVisit; use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit; use function Shlinkio\Shlink\Core\isCrawler; +use function Shlinkio\Shlink\Core\normalizeDate; class Visit extends AbstractEntity implements JsonSerializable { @@ -46,11 +47,30 @@ class Visit extends AbstractEntity implements JsonSerializable public static function fromImport(ShortUrl $shortUrl, ImportedShlinkVisit $importedVisit): self { - $instance = new self($shortUrl, VisitType::IMPORTED); + return self::fromImportOrOrphanImport($importedVisit, VisitType::IMPORTED, $shortUrl); + } + + public static function fromOrphanImport(ImportedShlinkOrphanVisit $importedVisit): self + { + $instance = self::fromImportOrOrphanImport( + $importedVisit, + VisitType::tryFrom($importedVisit->type) ?? VisitType::IMPORTED, + ); + $instance->visitedUrl = $importedVisit->visitedUrl; + + return $instance; + } + + private static function fromImportOrOrphanImport( + ImportedShlinkVisit|ImportedShlinkOrphanVisit $importedVisit, + VisitType $type, + ?ShortUrl $shortUrl = null, + ): self { + $instance = new self($shortUrl, $type); $instance->userAgent = $importedVisit->userAgent; $instance->potentialBot = isCrawler($instance->userAgent); $instance->referer = $importedVisit->referer; - $instance->date = Chronos::instance($importedVisit->date); + $instance->date = normalizeDate($importedVisit->date); $importedLocation = $importedVisit->location; $instance->visitLocation = $importedLocation !== null ? VisitLocation::fromImport($importedLocation) : null; diff --git a/module/Core/src/Visit/Repository/VisitRepository.php b/module/Core/src/Visit/Repository/VisitRepository.php index 456a1118..af1647c7 100644 --- a/module/Core/src/Visit/Repository/VisitRepository.php +++ b/module/Core/src/Visit/Repository/VisitRepository.php @@ -286,4 +286,19 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo return $this->getEntityManager()->createNativeQuery($nativeQb->getSQL(), $rsm)->getResult(); } + + public function findMostRecentOrphanVisit(): ?Visit + { + $dql = <<getEntityManager()->createQuery($dql); + $query->setMaxResults(1); + + return $query->getOneOrNullResult(); + } } diff --git a/module/Core/src/Visit/Repository/VisitRepositoryInterface.php b/module/Core/src/Visit/Repository/VisitRepositoryInterface.php index b7052aec..ebc4f4fe 100644 --- a/module/Core/src/Visit/Repository/VisitRepositoryInterface.php +++ b/module/Core/src/Visit/Repository/VisitRepositoryInterface.php @@ -65,4 +65,6 @@ interface VisitRepositoryInterface extends ObjectRepository, EntitySpecification public function findNonOrphanVisits(VisitsListFiltering $filtering): array; public function countNonOrphanVisits(VisitsCountFiltering $filtering): int; + + public function findMostRecentOrphanVisit(): ?Visit; } From 47f99cf6cc53d2bb046d9eda90b55b9076541b81 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 4 Dec 2022 20:38:07 +0100 Subject: [PATCH 137/182] Updated changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d6c7aa5..7f12615a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ## [Unreleased] ### Added -* *Nothing* +* [#1616](https://github.com/shlinkio/shlink/issues/1616) Added support to import orphan visits when importing short URLs from another Shlink instance. ### Changed * [#1563](https://github.com/shlinkio/shlink/issues/1563) Moved logic to reuse command options to option classes instead of base abstract command classes. @@ -19,7 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * *Nothing* ### Fixed -* *Nothing* +* [#1618](https://github.com/shlinkio/shlink/issues/1618) Fixed imported short URLs and visits dates not being set to the target server timezone. ## [3.3.2] - 2022-10-18 From 0aab1bdc4e781f9f294ee2e8eff2d945da4282f7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 4 Dec 2022 20:42:28 +0100 Subject: [PATCH 138/182] Added test for findMostRecentOrphanVisit --- .../Visit/Repository/VisitRepositoryTest.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php b/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php index 25512f15..b69190e5 100644 --- a/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php +++ b/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php @@ -491,6 +491,24 @@ class VisitRepositoryTest extends DatabaseTestCase self::assertCount(5, $this->repo->findNonOrphanVisits(new VisitsListFiltering(null, false, null, 5, 5))); } + /** @test */ + public function findMostRecentOrphanVisitReturnsExpectedVisit(): void + { + $this->assertNull($this->repo->findMostRecentOrphanVisit()); + + $lastVisit = Visit::forBasePath(Visitor::emptyInstance()); + $this->getEntityManager()->persist($lastVisit); + $this->getEntityManager()->flush(); + + $this->assertSame($lastVisit, $this->repo->findMostRecentOrphanVisit()); + + $lastVisit2 = Visit::forRegularNotFound(Visitor::botInstance()); + $this->getEntityManager()->persist($lastVisit2); + $this->getEntityManager()->flush(); + + $this->assertSame($lastVisit2, $this->repo->findMostRecentOrphanVisit()); + } + /** * @return array{string, string, \Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl} */ From 739f5eb42187818b6408940f505c058c1dfef175 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 5 Dec 2022 14:42:26 +0100 Subject: [PATCH 139/182] Added test for orphan visits import --- .../src/Importer/ImportedLinksProcessor.php | 7 +- .../Importer/ImportedLinksProcessorTest.php | 65 ++++++++++++++++++- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/module/Core/src/Importer/ImportedLinksProcessor.php b/module/Core/src/Importer/ImportedLinksProcessor.php index c302471b..7248e0d3 100644 --- a/module/Core/src/Importer/ImportedLinksProcessor.php +++ b/module/Core/src/Importer/ImportedLinksProcessor.php @@ -28,15 +28,12 @@ use function sprintf; class ImportedLinksProcessor implements ImportedLinksProcessorInterface { - private ShortUrlRepositoryInterface $shortUrlRepo; - public function __construct( private readonly EntityManagerInterface $em, private readonly ShortUrlRelationResolverInterface $relationResolver, private readonly ShortCodeUniquenessHelperInterface $shortCodeHelper, private readonly DoctrineBatchHelperInterface $batchHelper, ) { - $this->shortUrlRepo = $this->em->getRepository(ShortUrl::class); } public function process(StyleInterface $io, ImportResult $result, ImportParams $params): void @@ -95,7 +92,9 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface bool $importShortCodes, callable $skipOnShortCodeConflict, ): ShortUrlImporting { - $alreadyImportedShortUrl = $this->shortUrlRepo->findOneByImportedUrl($importedUrl); + /** @var ShortUrlRepositoryInterface $shortUrlRepo */ + $shortUrlRepo = $this->em->getRepository(ShortUrl::class); + $alreadyImportedShortUrl = $shortUrlRepo->findOneByImportedUrl($importedUrl); if ($alreadyImportedShortUrl !== null) { return ShortUrlImporting::fromExistingShortUrl($alreadyImportedShortUrl); } diff --git a/module/Core/test/Importer/ImportedLinksProcessorTest.php b/module/Core/test/Importer/ImportedLinksProcessorTest.php index a419858f..ccb7855f 100644 --- a/module/Core/test/Importer/ImportedLinksProcessorTest.php +++ b/module/Core/test/Importer/ImportedLinksProcessorTest.php @@ -17,6 +17,8 @@ use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\ShortUrl\Resolver\SimpleShortUrlRelationResolver; use Shlinkio\Shlink\Core\Util\DoctrineBatchHelperInterface; use Shlinkio\Shlink\Core\Visit\Entity\Visit; +use Shlinkio\Shlink\Core\Visit\Repository\VisitRepositoryInterface; +use Shlinkio\Shlink\Importer\Model\ImportedShlinkOrphanVisit; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit; use Shlinkio\Shlink\Importer\Model\ImportResult; @@ -28,6 +30,7 @@ use Symfony\Component\Console\Style\StyleInterface; use function count; use function Functional\contains; use function Functional\some; +use function sprintf; use function str_contains; class ImportedLinksProcessorTest extends TestCase @@ -42,7 +45,6 @@ class ImportedLinksProcessorTest extends TestCase { $this->em = $this->createMock(EntityManagerInterface::class); $this->repo = $this->createMock(ShortUrlRepositoryInterface::class); - $this->em->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); $this->shortCodeHelper = $this->createMock(ShortCodeUniquenessHelperInterface::class); $batchHelper = $this->createMock(DoctrineBatchHelperInterface::class); @@ -68,6 +70,7 @@ class ImportedLinksProcessorTest extends TestCase ]; $expectedCalls = count($urls); + $this->em->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); $this->repo->expects($this->exactly($expectedCalls))->method('findOneByImportedUrl')->willReturn(null); $this->shortCodeHelper->expects($this->exactly($expectedCalls)) ->method('ensureShortCodeUniqueness') @@ -89,6 +92,7 @@ class ImportedLinksProcessorTest extends TestCase new ImportedShlinkUrl(ImportSource::BITLY, 'baz', [], Chronos::now(), null, 'baz', null), ]; + $this->em->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); $this->repo->expects($this->exactly(3))->method('findOneByImportedUrl')->willReturn(null); $this->shortCodeHelper->expects($this->exactly(3))->method('ensureShortCodeUniqueness')->willReturn(true); $this->em->expects($this->exactly(3))->method('persist')->with( @@ -117,6 +121,7 @@ class ImportedLinksProcessorTest extends TestCase new ImportedShlinkUrl(ImportSource::BITLY, 'baz3', [], Chronos::now(), null, 'baz3', null), ]; + $this->em->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); $this->repo->expects($this->exactly(count($urls)))->method('findOneByImportedUrl')->willReturnCallback( fn (ImportedShlinkUrl $url): ?ShortUrl => contains(['foo', 'baz2', 'baz3'], $url->longUrl) ? ShortUrl::fromImport($url, true) : null, @@ -142,6 +147,7 @@ class ImportedLinksProcessorTest extends TestCase new ImportedShlinkUrl(ImportSource::BITLY, 'baz3', [], Chronos::now(), null, 'baz3', 'bar'), ]; + $this->em->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); $this->repo->expects($this->exactly(count($urls)))->method('findOneByImportedUrl')->willReturn(null); $this->shortCodeHelper->expects($this->exactly(7))->method('ensureShortCodeUniqueness')->willReturnCallback( fn ($_, bool $hasCustomSlug) => ! $hasCustomSlug, @@ -168,6 +174,7 @@ class ImportedLinksProcessorTest extends TestCase int $amountOfPersistedVisits, ?ShortUrl $foundShortUrl, ): void { + $this->em->method('getRepository')->with(ShortUrl::class)->willReturn($this->repo); $this->repo->expects($this->once())->method('findOneByImportedUrl')->willReturn($foundShortUrl); $this->shortCodeHelper->expects($this->exactly($foundShortUrl === null ? 1 : 0)) ->method('ensureShortCodeUniqueness') @@ -220,9 +227,61 @@ class ImportedLinksProcessorTest extends TestCase ]; } - private function buildParams(): ImportParams + /** + * @param iterable $visits + * @test + * @dataProvider provideOrphanVisits + */ + public function properAmountOfOrphanVisitsIsImported( + bool $importOrphanVisits, + iterable $visits, + int $expectedImportedVisits, + ): void { + $this->io->expects($this->exactly($importOrphanVisits ? 2 : 1))->method('title'); + $this->io->expects($importOrphanVisits ? $this->once() : $this->never())->method('text')->with( + sprintf('Imported %s orphan visits.', $expectedImportedVisits), + ); + + $visitRepo = $this->createMock(VisitRepositoryInterface::class); + $visitRepo->expects($importOrphanVisits ? $this->once() : $this->never())->method( + 'findMostRecentOrphanVisit', + )->willReturn(null); + $this->em->expects($importOrphanVisits ? $this->once() : $this->never())->method('getRepository')->with( + Visit::class, + )->willReturn($visitRepo); + $this->em->expects($importOrphanVisits ? $this->exactly($expectedImportedVisits) : $this->never())->method( + 'persist', + )->with($this->isInstanceOf(Visit::class)); + + $this->processor->process( + $this->io, + ImportResult::withShortUrlsAndOrphanVisits([], $visits), + $this->buildParams($importOrphanVisits), + ); + } + + public function provideOrphanVisits(): iterable { - return ImportSource::BITLY->toParamsWithCallableMap(['import_short_codes' => static fn () => true]); + yield 'import orphan disable without visits' => [false, [], 0]; + yield 'import orphan enabled without visits' => [true, [], 0]; + yield 'import orphan disabled with visits' => [false, [ + new ImportedShlinkOrphanVisit('', '', Chronos::now(), '', '', null), + ], 0]; + yield 'import orphan enabled with visits' => [true, [ + new ImportedShlinkOrphanVisit('', '', Chronos::now(), '', '', null), + new ImportedShlinkOrphanVisit('', '', Chronos::now(), '', '', null), + new ImportedShlinkOrphanVisit('', '', Chronos::now(), '', '', null), + new ImportedShlinkOrphanVisit('', '', Chronos::now(), '', '', null), + new ImportedShlinkOrphanVisit('', '', Chronos::now(), '', '', null), + ], 5]; + } + + private function buildParams(bool $importOrphanVisits = false): ImportParams + { + return ImportSource::BITLY->toParamsWithCallableMap([ + ImportParams::IMPORT_SHORT_CODES_PARAM => static fn () => true, + ImportParams::IMPORT_ORPHAN_VISITS_PARAM => static fn () => $importOrphanVisits, + ]); } public function setUpIoText(string $skippedText = 'Skipped', string $importedText = 'Imported'): stdClass From 05d55c4000271f666022cf202b5d67cad63f9f78 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 5 Dec 2022 14:48:24 +0100 Subject: [PATCH 140/182] Added one more case to cover import orphan visits when visits already exist --- .../Importer/ImportedLinksProcessorTest.php | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/module/Core/test/Importer/ImportedLinksProcessorTest.php b/module/Core/test/Importer/ImportedLinksProcessorTest.php index ccb7855f..c480e11a 100644 --- a/module/Core/test/Importer/ImportedLinksProcessorTest.php +++ b/module/Core/test/Importer/ImportedLinksProcessorTest.php @@ -17,6 +17,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\ShortUrl\Resolver\SimpleShortUrlRelationResolver; use Shlinkio\Shlink\Core\Util\DoctrineBatchHelperInterface; use Shlinkio\Shlink\Core\Visit\Entity\Visit; +use Shlinkio\Shlink\Core\Visit\Model\Visitor; use Shlinkio\Shlink\Core\Visit\Repository\VisitRepositoryInterface; use Shlinkio\Shlink\Importer\Model\ImportedShlinkOrphanVisit; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; @@ -235,6 +236,7 @@ class ImportedLinksProcessorTest extends TestCase public function properAmountOfOrphanVisitsIsImported( bool $importOrphanVisits, iterable $visits, + ?Visit $lastOrphanVisit, int $expectedImportedVisits, ): void { $this->io->expects($this->exactly($importOrphanVisits ? 2 : 1))->method('title'); @@ -245,7 +247,7 @@ class ImportedLinksProcessorTest extends TestCase $visitRepo = $this->createMock(VisitRepositoryInterface::class); $visitRepo->expects($importOrphanVisits ? $this->once() : $this->never())->method( 'findMostRecentOrphanVisit', - )->willReturn(null); + )->willReturn($lastOrphanVisit); $this->em->expects($importOrphanVisits ? $this->once() : $this->never())->method('getRepository')->with( Visit::class, )->willReturn($visitRepo); @@ -262,18 +264,25 @@ class ImportedLinksProcessorTest extends TestCase public function provideOrphanVisits(): iterable { - yield 'import orphan disable without visits' => [false, [], 0]; - yield 'import orphan enabled without visits' => [true, [], 0]; + yield 'import orphan disable without visits' => [false, [], null, 0]; + yield 'import orphan enabled without visits' => [true, [], null, 0]; yield 'import orphan disabled with visits' => [false, [ new ImportedShlinkOrphanVisit('', '', Chronos::now(), '', '', null), - ], 0]; + ], null, 0]; yield 'import orphan enabled with visits' => [true, [ new ImportedShlinkOrphanVisit('', '', Chronos::now(), '', '', null), new ImportedShlinkOrphanVisit('', '', Chronos::now(), '', '', null), new ImportedShlinkOrphanVisit('', '', Chronos::now(), '', '', null), new ImportedShlinkOrphanVisit('', '', Chronos::now(), '', '', null), new ImportedShlinkOrphanVisit('', '', Chronos::now(), '', '', null), - ], 5]; + ], null, 5]; + yield 'existing orphan visit' => [true, [ + new ImportedShlinkOrphanVisit('', '', Chronos::now()->subDays(3), '', '', null), + new ImportedShlinkOrphanVisit('', '', Chronos::now()->subDays(2), '', '', null), + new ImportedShlinkOrphanVisit('', '', Chronos::now()->addDay(), '', '', null), + new ImportedShlinkOrphanVisit('', '', Chronos::now()->addDay(), '', '', null), + new ImportedShlinkOrphanVisit('', '', Chronos::now()->addDay(), '', '', null), + ], Visit::forBasePath(Visitor::botInstance()), 3]; } private function buildParams(bool $importOrphanVisits = false): ImportParams From f41d947cf7ed0d6296d11120eef715c3f44a8e2b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 7 Dec 2022 19:06:05 +0100 Subject: [PATCH 141/182] Ensured empty string is ignored as the domain during short URL creation --- module/Core/functions/functions.php | 6 +++++ .../src/ShortUrl/Model/ShortUrlCreation.php | 3 ++- .../ShortUrl/Model/ShortUrlCreationTest.php | 22 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/module/Core/functions/functions.php b/module/Core/functions/functions.php index 814aa56a..e7dff2ad 100644 --- a/module/Core/functions/functions.php +++ b/module/Core/functions/functions.php @@ -78,6 +78,12 @@ function getOptionalBoolFromInputFilter(InputFilter $inputFilter, string $fieldN return $value !== null ? (bool) $value : null; } +function getNonEmptyOptionalValueFromInputFilter(InputFilter $inputFilter, string $fieldName): mixed +{ + $value = $inputFilter->getValue($fieldName); + return empty($value) ? null : $value; +} + function arrayToString(array $array, int $indentSize = 4): string { $indent = str_repeat(' ', $indentSize); diff --git a/module/Core/src/ShortUrl/Model/ShortUrlCreation.php b/module/Core/src/ShortUrl/Model/ShortUrlCreation.php index 9e37234c..bbdd9ab0 100644 --- a/module/Core/src/ShortUrl/Model/ShortUrlCreation.php +++ b/module/Core/src/ShortUrl/Model/ShortUrlCreation.php @@ -10,6 +10,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Helper\TitleResolutionModelInterface; use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter; use Shlinkio\Shlink\Rest\Entity\ApiKey; +use function Shlinkio\Shlink\Core\getNonEmptyOptionalValueFromInputFilter; use function Shlinkio\Shlink\Core\getOptionalBoolFromInputFilter; use function Shlinkio\Shlink\Core\getOptionalIntFromInputFilter; use function Shlinkio\Shlink\Core\normalizeOptionalDate; @@ -74,7 +75,7 @@ final class ShortUrlCreation implements TitleResolutionModelInterface $this->maxVisits = getOptionalIntFromInputFilter($inputFilter, ShortUrlInputFilter::MAX_VISITS); $this->findIfExists = $inputFilter->getValue(ShortUrlInputFilter::FIND_IF_EXISTS); $this->validateUrl = getOptionalBoolFromInputFilter($inputFilter, ShortUrlInputFilter::VALIDATE_URL) ?? false; - $this->domain = $inputFilter->getValue(ShortUrlInputFilter::DOMAIN); + $this->domain = getNonEmptyOptionalValueFromInputFilter($inputFilter, ShortUrlInputFilter::DOMAIN); $this->shortCodeLength = getOptionalIntFromInputFilter( $inputFilter, ShortUrlInputFilter::SHORT_CODE_LENGTH, diff --git a/module/Core/test/ShortUrl/Model/ShortUrlCreationTest.php b/module/Core/test/ShortUrl/Model/ShortUrlCreationTest.php index 0f9dc419..51457264 100644 --- a/module/Core/test/ShortUrl/Model/ShortUrlCreationTest.php +++ b/module/Core/test/ShortUrl/Model/ShortUrlCreationTest.php @@ -146,4 +146,26 @@ class ShortUrlCreationTest extends TestCase yield [str_pad('', 600, 'd'), str_pad('', 512, 'd')]; yield [str_pad('', 800, 'e'), str_pad('', 512, 'e')]; } + + /** + * @test + * @dataProvider provideDomains + */ + public function emptyDomainIsDiscarded(?string $domain, ?string $expectedDomain): void + { + $meta = ShortUrlCreation::fromRawData([ + 'domain' => $domain, + 'longUrl' => '', + ]); + + self::assertSame($expectedDomain, $meta->getDomain()); + } + + public function provideDomains(): iterable + { + yield 'null domain' => [null, null]; + yield 'empty domain' => ['', null]; + yield 'trimmable domain' => [' ', null]; + yield 'valid domain' => ['doma.in', 'doma.in']; + } } From edf2b5b4c281af8e5cf31a4ed8e8714bd300c19e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 7 Dec 2022 19:06:58 +0100 Subject: [PATCH 142/182] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f12615a..c25f9eb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ### Fixed * [#1618](https://github.com/shlinkio/shlink/issues/1618) Fixed imported short URLs and visits dates not being set to the target server timezone. +* [#1578](https://github.com/shlinkio/shlink/issues/1578) Fixed short URL allowing an empty string as the domain during creation. ## [3.3.2] - 2022-10-18 From dfcac525bc23cc259c482721784765598d30b31f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 8 Dec 2022 20:21:14 +0100 Subject: [PATCH 143/182] Enabled search by default domain --- module/Core/config/dependencies.config.php | 1 + .../src/ShortUrl/Model/ShortUrlsParams.php | 91 +++++-------------- .../Adapter/ShortUrlRepositoryAdapter.php | 21 +++-- .../Persistence/ShortUrlsCountFiltering.php | 56 +++++------- .../Persistence/ShortUrlsListFiltering.php | 44 ++++----- .../Repository/ShortUrlRepository.php | 25 +++-- module/Core/src/ShortUrl/ShortUrlService.php | 9 +- .../Adapter/ShortUrlRepositoryAdapterTest.php | 10 +- .../test/ShortUrl/ShortUrlServiceTest.php | 2 + 9 files changed, 108 insertions(+), 151 deletions(-) diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index a7ebd1b7..5f51c2d7 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -108,6 +108,7 @@ return [ ShortUrl\ShortUrlResolver::class, ShortUrl\Helper\ShortUrlTitleResolutionHelper::class, ShortUrl\Resolver\PersistenceShortUrlRelationResolver::class, + Options\UrlShortenerOptions::class, ], Visit\Geolocation\VisitLocator::class => ['em'], Visit\Geolocation\VisitToLocationHelper::class => [IpLocationResolverInterface::class], diff --git a/module/Core/src/ShortUrl/Model/ShortUrlsParams.php b/module/Core/src/ShortUrl/Model/ShortUrlsParams.php index 6f8b0f47..14e88132 100644 --- a/module/Core/src/ShortUrl/Model/ShortUrlsParams.php +++ b/module/Core/src/ShortUrl/Model/ShortUrlsParams.php @@ -17,16 +17,15 @@ final class ShortUrlsParams public const ORDERABLE_FIELDS = ['longUrl', 'shortCode', 'dateCreated', 'title', 'visits']; public const DEFAULT_ITEMS_PER_PAGE = 10; - private int $page; - private int $itemsPerPage; - private ?string $searchTerm; - private array $tags; - private TagsMode $tagsMode = TagsMode::ANY; - private Ordering $orderBy; - private ?DateRange $dateRange; - - private function __construct() - { + private function __construct( + public readonly int $page, + public readonly int $itemsPerPage, + public readonly ?string $searchTerm, + public readonly array $tags, + public readonly Ordering $orderBy, + public readonly ?DateRange $dateRange, + public readonly TagsMode $tagsMode = TagsMode::ANY, + ) { } public static function emptyInstance(): self @@ -38,38 +37,29 @@ final class ShortUrlsParams * @throws ValidationException */ public static function fromRawData(array $query): self - { - $instance = new self(); - $instance->validateAndInit($query); - - return $instance; - } - - /** - * @throws ValidationException - */ - private function validateAndInit(array $query): void { $inputFilter = new ShortUrlsParamsInputFilter($query); if (! $inputFilter->isValid()) { throw ValidationException::fromInputFilter($inputFilter); } - $this->page = (int) ($inputFilter->getValue(ShortUrlsParamsInputFilter::PAGE) ?? 1); - $this->searchTerm = $inputFilter->getValue(ShortUrlsParamsInputFilter::SEARCH_TERM); - $this->tags = (array) $inputFilter->getValue(ShortUrlsParamsInputFilter::TAGS); - $this->dateRange = buildDateRange( - normalizeOptionalDate($inputFilter->getValue(ShortUrlsParamsInputFilter::START_DATE)), - normalizeOptionalDate($inputFilter->getValue(ShortUrlsParamsInputFilter::END_DATE)), + return new self( + page: (int) ($inputFilter->getValue(ShortUrlsParamsInputFilter::PAGE) ?? 1), + itemsPerPage: (int) ( + $inputFilter->getValue(ShortUrlsParamsInputFilter::ITEMS_PER_PAGE) ?? self::DEFAULT_ITEMS_PER_PAGE + ), + searchTerm: $inputFilter->getValue(ShortUrlsParamsInputFilter::SEARCH_TERM), + tags: (array) $inputFilter->getValue(ShortUrlsParamsInputFilter::TAGS), + orderBy: Ordering::fromTuple($inputFilter->getValue(ShortUrlsParamsInputFilter::ORDER_BY)), + dateRange: buildDateRange( + normalizeOptionalDate($inputFilter->getValue(ShortUrlsParamsInputFilter::START_DATE)), + normalizeOptionalDate($inputFilter->getValue(ShortUrlsParamsInputFilter::END_DATE)), + ), + tagsMode: self::resolveTagsMode($inputFilter->getValue(ShortUrlsParamsInputFilter::TAGS_MODE)), ); - $this->orderBy = Ordering::fromTuple($inputFilter->getValue(ShortUrlsParamsInputFilter::ORDER_BY)); - $this->itemsPerPage = (int) ( - $inputFilter->getValue(ShortUrlsParamsInputFilter::ITEMS_PER_PAGE) ?? self::DEFAULT_ITEMS_PER_PAGE - ); - $this->tagsMode = $this->resolveTagsMode($inputFilter->getValue(ShortUrlsParamsInputFilter::TAGS_MODE)); } - private function resolveTagsMode(?string $rawTagsMode): TagsMode + private static function resolveTagsMode(?string $rawTagsMode): TagsMode { if ($rawTagsMode === null) { return TagsMode::ANY; @@ -77,39 +67,4 @@ final class ShortUrlsParams return TagsMode::tryFrom($rawTagsMode) ?? TagsMode::ANY; } - - public function page(): int - { - return $this->page; - } - - public function itemsPerPage(): int - { - return $this->itemsPerPage; - } - - public function searchTerm(): ?string - { - return $this->searchTerm; - } - - public function tags(): array - { - return $this->tags; - } - - public function orderBy(): Ordering - { - return $this->orderBy; - } - - public function dateRange(): ?DateRange - { - return $this->dateRange; - } - - public function tagsMode(): TagsMode - { - return $this->tagsMode; - } } diff --git a/module/Core/src/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapter.php b/module/Core/src/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapter.php index f576106f..d88d8b81 100644 --- a/module/Core/src/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapter.php +++ b/module/Core/src/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapter.php @@ -14,21 +14,28 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ShortUrlRepositoryAdapter implements AdapterInterface { public function __construct( - private ShortUrlRepositoryInterface $repository, - private ShortUrlsParams $params, - private ?ApiKey $apiKey, + private readonly ShortUrlRepositoryInterface $repository, + private readonly ShortUrlsParams $params, + private readonly ?ApiKey $apiKey, + private readonly string $defaultDomain, ) { } public function getSlice(int $offset, int $length): iterable { - return $this->repository->findList( - ShortUrlsListFiltering::fromLimitsAndParams($length, $offset, $this->params, $this->apiKey), - ); + return $this->repository->findList(ShortUrlsListFiltering::fromLimitsAndParams( + $length, + $offset, + $this->params, + $this->apiKey, + $this->defaultDomain, + )); } public function getNbResults(): int { - return $this->repository->countList(ShortUrlsCountFiltering::fromParams($this->params, $this->apiKey)); + return $this->repository->countList( + ShortUrlsCountFiltering::fromParams($this->params, $this->apiKey, $this->defaultDomain), + ); } } diff --git a/module/Core/src/ShortUrl/Persistence/ShortUrlsCountFiltering.php b/module/Core/src/ShortUrl/Persistence/ShortUrlsCountFiltering.php index c4b07281..2d1b1f21 100644 --- a/module/Core/src/ShortUrl/Persistence/ShortUrlsCountFiltering.php +++ b/module/Core/src/ShortUrl/Persistence/ShortUrlsCountFiltering.php @@ -9,44 +9,36 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode; use Shlinkio\Shlink\Rest\Entity\ApiKey; +use function str_contains; +use function strtolower; + class ShortUrlsCountFiltering { + public readonly bool $searchIncludesDefaultDomain; + public function __construct( - private ?string $searchTerm = null, - private array $tags = [], - private ?TagsMode $tagsMode = null, - private ?DateRange $dateRange = null, - private ?ApiKey $apiKey = null, + public readonly ?string $searchTerm = null, + public readonly array $tags = [], + public readonly ?TagsMode $tagsMode = null, + public readonly ?DateRange $dateRange = null, + public readonly ?ApiKey $apiKey = null, + ?string $defaultDomain = null, ) { + $this->searchIncludesDefaultDomain = !empty($searchTerm) && !empty($defaultDomain) && str_contains( + strtolower($defaultDomain), + strtolower($searchTerm), + ); } - public static function fromParams(ShortUrlsParams $params, ?ApiKey $apiKey): self + public static function fromParams(ShortUrlsParams $params, ?ApiKey $apiKey, string $defaultDomain): self { - return new self($params->searchTerm(), $params->tags(), $params->tagsMode(), $params->dateRange(), $apiKey); - } - - public function searchTerm(): ?string - { - return $this->searchTerm; - } - - public function tags(): array - { - return $this->tags; - } - - public function tagsMode(): ?TagsMode - { - return $this->tagsMode; - } - - public function dateRange(): ?DateRange - { - return $this->dateRange; - } - - public function apiKey(): ?ApiKey - { - return $this->apiKey; + return new self( + $params->searchTerm, + $params->tags, + $params->tagsMode, + $params->dateRange, + $apiKey, + $defaultDomain, + ); } } diff --git a/module/Core/src/ShortUrl/Persistence/ShortUrlsListFiltering.php b/module/Core/src/ShortUrl/Persistence/ShortUrlsListFiltering.php index 6e32d93d..dd7eb0aa 100644 --- a/module/Core/src/ShortUrl/Persistence/ShortUrlsListFiltering.php +++ b/module/Core/src/ShortUrl/Persistence/ShortUrlsListFiltering.php @@ -13,44 +13,36 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ShortUrlsListFiltering extends ShortUrlsCountFiltering { public function __construct( - private ?int $limit, - private ?int $offset, - private Ordering $orderBy, + public readonly ?int $limit, + public readonly ?int $offset, + public readonly Ordering $orderBy, ?string $searchTerm = null, array $tags = [], ?TagsMode $tagsMode = null, ?DateRange $dateRange = null, ?ApiKey $apiKey = null, + ?string $defaultDomain = null, ) { - parent::__construct($searchTerm, $tags, $tagsMode, $dateRange, $apiKey); + parent::__construct($searchTerm, $tags, $tagsMode, $dateRange, $apiKey, $defaultDomain); } - public static function fromLimitsAndParams(int $limit, int $offset, ShortUrlsParams $params, ?ApiKey $apiKey): self - { + public static function fromLimitsAndParams( + int $limit, + int $offset, + ShortUrlsParams $params, + ?ApiKey $apiKey, + string $defaultDomain, + ): self { return new self( $limit, $offset, - $params->orderBy(), - $params->searchTerm(), - $params->tags(), - $params->tagsMode(), - $params->dateRange(), + $params->orderBy, + $params->searchTerm, + $params->tags, + $params->tagsMode, + $params->dateRange, $apiKey, + $defaultDomain, ); } - - public function offset(): ?int - { - return $this->offset; - } - - public function limit(): ?int - { - return $this->limit; - } - - public function orderBy(): Ordering - { - return $this->orderBy; - } } diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php index 8271b5f9..ec2f7a94 100644 --- a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php +++ b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php @@ -33,12 +33,12 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU { $qb = $this->createListQueryBuilder($filtering); $qb->select('DISTINCT s') - ->setMaxResults($filtering->limit()) - ->setFirstResult($filtering->offset()); + ->setMaxResults($filtering->limit) + ->setFirstResult($filtering->offset); // In case the ordering has been specified, the query could be more complex. Process it - if ($filtering->orderBy()->hasOrderField()) { - return $this->processOrderByForList($qb, $filtering->orderBy()); + if ($filtering->orderBy->hasOrderField()) { + return $this->processOrderByForList($qb, $filtering->orderBy); } // With no explicit order by, fallback to dateCreated-DESC @@ -83,7 +83,7 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU $qb->from(ShortUrl::class, 's') ->where('1=1'); - $dateRange = $filtering->dateRange(); + $dateRange = $filtering->dateRange; if ($dateRange?->startDate !== null) { $qb->andWhere($qb->expr()->gte('s.dateCreated', ':startDate')); $qb->setParameter('startDate', $dateRange->startDate, ChronosDateTimeType::CHRONOS_DATETIME); @@ -93,8 +93,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU $qb->setParameter('endDate', $dateRange->endDate, ChronosDateTimeType::CHRONOS_DATETIME); } - $searchTerm = $filtering->searchTerm(); - $tags = $filtering->tags(); + $searchTerm = $filtering->searchTerm; + $tags = $filtering->tags; // Apply search term to every searchable field if not empty if (! empty($searchTerm)) { // Left join with tags only if no tags were provided. In case of tags, an inner join will be done later @@ -110,8 +110,13 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU $qb->expr()->like('d.authority', ':searchPattern'), ]; + // Include default domain in search if provided + if ($filtering->searchIncludesDefaultDomain) { + $conditions[] = $qb->expr()->isNull('s.domain'); + } + // Apply tag conditions, only when not filtering by all provided tags - $tagsMode = $filtering->tagsMode() ?? TagsMode::ANY; + $tagsMode = $filtering->tagsMode ?? TagsMode::ANY; if (empty($tags) || $tagsMode === TagsMode::ANY) { $conditions[] = $qb->expr()->like('t.name', ':searchPattern'); } @@ -123,13 +128,13 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU // Filter by tags if provided if (! empty($tags)) { - $tagsMode = $filtering->tagsMode() ?? TagsMode::ANY; + $tagsMode = $filtering->tagsMode ?? TagsMode::ANY; $tagsMode === TagsMode::ANY ? $qb->join('s.tags', 't')->andWhere($qb->expr()->in('t.name', $tags)) : $this->joinAllTags($qb, $tags); } - $this->applySpecification($qb, $filtering->apiKey()?->spec(), 's'); + $this->applySpecification($qb, $filtering->apiKey?->spec(), 's'); return $qb; } diff --git a/module/Core/src/ShortUrl/ShortUrlService.php b/module/Core/src/ShortUrl/ShortUrlService.php index d4ab984a..07549e5b 100644 --- a/module/Core/src/ShortUrl/ShortUrlService.php +++ b/module/Core/src/ShortUrl/ShortUrlService.php @@ -8,6 +8,7 @@ use Doctrine\ORM; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; +use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlTitleResolutionHelperInterface; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlEdition; @@ -25,6 +26,7 @@ class ShortUrlService implements ShortUrlServiceInterface private readonly ShortUrlResolverInterface $urlResolver, private readonly ShortUrlTitleResolutionHelperInterface $titleResolutionHelper, private readonly ShortUrlRelationResolverInterface $relationResolver, + private readonly UrlShortenerOptions $urlShortenerOptions, ) { } @@ -35,9 +37,10 @@ class ShortUrlService implements ShortUrlServiceInterface { /** @var ShortUrlRepository $repo */ $repo = $this->em->getRepository(ShortUrl::class); - $paginator = new Paginator(new ShortUrlRepositoryAdapter($repo, $params, $apiKey)); - $paginator->setMaxPerPage($params->itemsPerPage()) - ->setCurrentPage($params->page()); + $defaultDomain = $this->urlShortenerOptions->domain['hostname'] ?? ''; + $paginator = new Paginator(new ShortUrlRepositoryAdapter($repo, $params, $apiKey, $defaultDomain)); + $paginator->setMaxPerPage($params->itemsPerPage) + ->setCurrentPage($params->page); return $paginator; } diff --git a/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php b/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php index 4cbc9eae..aa8efbd5 100644 --- a/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php +++ b/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php @@ -42,9 +42,9 @@ class ShortUrlRepositoryAdapterTest extends TestCase 'endDate' => $endDate, 'orderBy' => $orderBy, ]); - $adapter = new ShortUrlRepositoryAdapter($this->repo, $params, null); - $orderBy = $params->orderBy(); - $dateRange = $params->dateRange(); + $adapter = new ShortUrlRepositoryAdapter($this->repo, $params, null, ''); + $orderBy = $params->orderBy; + $dateRange = $params->dateRange; $this->repo->expects($this->once())->method('findList')->with( new ShortUrlsListFiltering(10, 5, $orderBy, $searchTerm, $tags, TagsMode::ANY, $dateRange), @@ -70,8 +70,8 @@ class ShortUrlRepositoryAdapterTest extends TestCase 'endDate' => $endDate, ]); $apiKey = ApiKey::create(); - $adapter = new ShortUrlRepositoryAdapter($this->repo, $params, $apiKey); - $dateRange = $params->dateRange(); + $adapter = new ShortUrlRepositoryAdapter($this->repo, $params, $apiKey, ''); + $dateRange = $params->dateRange; $this->repo->expects($this->once())->method('countList')->with( new ShortUrlsCountFiltering($searchTerm, $tags, TagsMode::ANY, $dateRange, $apiKey), diff --git a/module/Core/test/ShortUrl/ShortUrlServiceTest.php b/module/Core/test/ShortUrl/ShortUrlServiceTest.php index 876d322b..7b27f4e4 100644 --- a/module/Core/test/ShortUrl/ShortUrlServiceTest.php +++ b/module/Core/test/ShortUrl/ShortUrlServiceTest.php @@ -8,6 +8,7 @@ use Cake\Chronos\Chronos; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlTitleResolutionHelperInterface; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlEdition; @@ -45,6 +46,7 @@ class ShortUrlServiceTest extends TestCase $this->urlResolver, $this->titleResolutionHelper, new SimpleShortUrlRelationResolver(), + new UrlShortenerOptions(), ); } From 6bce219eb3a05cd2fbce7a3388d9f6cc2cd34cc1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 8 Dec 2022 20:32:48 +0100 Subject: [PATCH 144/182] Added test to cover searching short URLs by default domain --- .../Repository/ShortUrlRepositoryTest.php | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php b/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php index 0ba1275f..e41eee15 100644 --- a/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php @@ -303,6 +303,42 @@ class ShortUrlRepositoryTest extends DatabaseTestCase )); } + /** @test */ + public function findListReturnsOnlyThoseWithMatchingDomains(): void + { + $shortUrl1 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo1', + 'domain' => null, + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl1); + $shortUrl2 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo2', + 'domain' => null, + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl2); + $shortUrl3 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo3', + 'domain' => 'another.com', + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl3); + + $this->getEntityManager()->flush(); + + $buildFiltering = static fn (string $searchTerm) => new ShortUrlsListFiltering( + null, + null, + Ordering::emptyInstance(), + searchTerm: $searchTerm, + defaultDomain: 'deFaulT-domain.com', + ); + + self::assertCount(2, $this->repo->findList($buildFiltering('default-dom'))); + self::assertCount(2, $this->repo->findList($buildFiltering('DOM'))); + self::assertCount(1, $this->repo->findList($buildFiltering('another'))); + self::assertCount(3, $this->repo->findList($buildFiltering('foo'))); + self::assertCount(0, $this->repo->findList($buildFiltering('no results'))); + } + /** @test */ public function shortCodeIsInUseLooksForShortUrlInProperSetOfTables(): void { From 71e7938b7a8acd1ac9d310ebb3296fff1b521508 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 8 Dec 2022 20:33:59 +0100 Subject: [PATCH 145/182] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f12615a..1e57e962 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ## [Unreleased] ### Added * [#1616](https://github.com/shlinkio/shlink/issues/1616) Added support to import orphan visits when importing short URLs from another Shlink instance. +* [#1519](https://github.com/shlinkio/shlink/issues/1519) Allowing to search short URLs by default domain. ### Changed * [#1563](https://github.com/shlinkio/shlink/issues/1563) Moved logic to reuse command options to option classes instead of base abstract command classes. From 29b747c192290db2a8d39489cd3f7a4861720346 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 10 Dec 2022 10:11:25 +0100 Subject: [PATCH 146/182] Added missing namespace for cache adapters, causing full cache to be flushed in some circumstances --- CHANGELOG.md | 1 + config/autoload/redis.global.php | 23 ++++++++++------------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fa211ec..3a944d90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ### Fixed * [#1618](https://github.com/shlinkio/shlink/issues/1618) Fixed imported short URLs and visits dates not being set to the target server timezone. * [#1578](https://github.com/shlinkio/shlink/issues/1578) Fixed short URL allowing an empty string as the domain during creation. +* [#1580](https://github.com/shlinkio/shlink/issues/1580) Fixed `FLUSHDB` being run on Shlink docker start-up when using redis, causing full cache to be flushed. ## [3.3.2] - 2022-10-18 diff --git a/config/autoload/redis.global.php b/config/autoload/redis.global.php index 1d035055..614b140f 100644 --- a/config/autoload/redis.global.php +++ b/config/autoload/redis.global.php @@ -6,22 +6,19 @@ use Shlinkio\Shlink\Core\Config\EnvVars; return (static function (): array { $redisServers = EnvVars::REDIS_SERVERS->loadFromEnv(); - $pubSub = [ + $redis = ['pub_sub_enabled' => $redisServers !== null && EnvVars::REDIS_PUB_SUB_ENABLED->loadFromEnv(false)]; + $cacheRedisBlock = $redisServers === null ? [] : [ 'redis' => [ - 'pub_sub_enabled' => $redisServers !== null && EnvVars::REDIS_PUB_SUB_ENABLED->loadFromEnv(false), + 'servers' => $redisServers, + 'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE->loadFromEnv(), ], ]; - return match ($redisServers) { - null => $pubSub, - default => [ - 'cache' => [ - 'redis' => [ - 'servers' => $redisServers, - 'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE->loadFromEnv(), - ], - ], - ...$pubSub, + return [ + 'cache' => [ + 'namespace' => 'Shlink', + ...$cacheRedisBlock, ], - }; + 'redis' => $redis, + ]; })(); From e68ef87c66378219d662309852cfda39853a6b05 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 10 Dec 2022 10:12:56 +0100 Subject: [PATCH 147/182] Renamed config file from redis to cache --- config/autoload/{redis.global.php => cache.global.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename config/autoload/{redis.global.php => cache.global.php} (100%) diff --git a/config/autoload/redis.global.php b/config/autoload/cache.global.php similarity index 100% rename from config/autoload/redis.global.php rename to config/autoload/cache.global.php From dab0ebeb99ba80a84ba94e5bcbb2da6accc39a67 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 10 Dec 2022 17:29:52 +0100 Subject: [PATCH 148/182] Updated dockerimages to PHP 8.2 and added full support for this version --- .github/actions/ci-setup/action.yml | 5 +---- .github/workflows/ci.yml | 5 +---- .github/workflows/publish-release.yml | 4 ++-- Dockerfile | 2 +- data/infra/php.Dockerfile | 2 +- data/infra/roadrunner.Dockerfile | 2 +- data/infra/swoole.Dockerfile | 2 +- 7 files changed, 8 insertions(+), 14 deletions(-) diff --git a/.github/actions/ci-setup/action.yml b/.github/actions/ci-setup/action.yml index 7ff7ad8a..78cbdf1c 100644 --- a/.github/actions/ci-setup/action.yml +++ b/.github/actions/ci-setup/action.yml @@ -41,10 +41,7 @@ runs: extensions: ${{ inputs.php-extensions }} coverage: pcov ini-values: pcov.directory=module - - run: echo "::set-output name=composerArgs::${{ inputs.php-version == '8.2' && '--ignore-platform-req=php+' || '' }}" - id: composer_args - shell: bash - name: Install dependencies if: ${{ inputs.install-deps == 'yes' }} - run: composer install --no-interaction --prefer-dist ${{ steps.composer_args.outputs.composerArgs }} + run: composer install --no-interaction --prefer-dist shell: bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba4c77dc..ca34c07d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,10 +51,7 @@ jobs: with: php-version: ${{ matrix.php-version }} tools: composer - - run: echo "::set-output name=composerArgs::${{ matrix.php-version == '8.2' && '--ignore-platform-req=php+' || '' }}" - id: composer_args - shell: bash - - run: composer install --no-interaction --prefer-dist ${{ steps.composer_args.outputs.composerArgs }} + - run: composer install --no-interaction --prefer-dist - run: ./vendor/bin/rr get --no-interaction --location bin/ && chmod +x bin/rr - run: composer test:api:rr diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index d9625125..792513be 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - php-version: ['8.1'] + php-version: ['8.1', '8.2'] swoole: ['yes', 'no'] steps: - uses: actions/checkout@v3 @@ -51,7 +51,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - php-version: ['8.1'] + php-version: ['8.1', '8.2'] swoole: ['yes', 'no'] steps: - uses: geekyeggo/delete-artifact@v1 diff --git a/Dockerfile b/Dockerfile index c498894e..b0803ec3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.1.9-alpine3.16 as base +FROM php:8.2.0-alpine3.17 as base ARG SHLINK_VERSION=latest ENV SHLINK_VERSION ${SHLINK_VERSION} diff --git a/data/infra/php.Dockerfile b/data/infra/php.Dockerfile index a2066752..9ef87f1c 100644 --- a/data/infra/php.Dockerfile +++ b/data/infra/php.Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.1.9-fpm-alpine3.16 +FROM php:8.2.0-fpm-alpine3.17 MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.21 diff --git a/data/infra/roadrunner.Dockerfile b/data/infra/roadrunner.Dockerfile index 8520b92d..6c290edd 100644 --- a/data/infra/roadrunner.Dockerfile +++ b/data/infra/roadrunner.Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.1.9-alpine3.16 +FROM php:8.2.0-alpine3.17 MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.21 diff --git a/data/infra/swoole.Dockerfile b/data/infra/swoole.Dockerfile index 294ad71b..e4435b54 100644 --- a/data/infra/swoole.Dockerfile +++ b/data/infra/swoole.Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.1.9-alpine3.16 +FROM php:8.2.0-alpine3.17 MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.21 From 38b313a25dba3b2e02bef544495d14525e2a36f9 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 10 Dec 2022 17:30:35 +0100 Subject: [PATCH 149/182] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a944d90..1cbab421 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ### Added * [#1616](https://github.com/shlinkio/shlink/issues/1616) Added support to import orphan visits when importing short URLs from another Shlink instance. * [#1519](https://github.com/shlinkio/shlink/issues/1519) Allowing to search short URLs by default domain. +* [#1555](https://github.com/shlinkio/shlink/issues/1555) Added full support for PHP 8.2, pdating the dockr image to this version. ### Changed * [#1563](https://github.com/shlinkio/shlink/issues/1563) Moved logic to reuse command options to option classes instead of base abstract command classes. From 6589c8fce61b95d60359f11c3a1565b1b87d7ac7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 10 Dec 2022 17:58:10 +0100 Subject: [PATCH 150/182] Downgraded docker images to latest php 8.1 --- Dockerfile | 2 +- data/infra/php.Dockerfile | 2 +- data/infra/roadrunner.Dockerfile | 2 +- data/infra/swoole.Dockerfile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index b0803ec3..ff809b3f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.2.0-alpine3.17 as base +FROM php:8.1.13-alpine3.17 as base ARG SHLINK_VERSION=latest ENV SHLINK_VERSION ${SHLINK_VERSION} diff --git a/data/infra/php.Dockerfile b/data/infra/php.Dockerfile index 9ef87f1c..7bfbd24a 100644 --- a/data/infra/php.Dockerfile +++ b/data/infra/php.Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.2.0-fpm-alpine3.17 +FROM php:8.1.13-fpm-alpine3.17 MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.21 diff --git a/data/infra/roadrunner.Dockerfile b/data/infra/roadrunner.Dockerfile index 6c290edd..a1092de3 100644 --- a/data/infra/roadrunner.Dockerfile +++ b/data/infra/roadrunner.Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.2.0-alpine3.17 +FROM php:8.1.13-alpine3.17 MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.21 diff --git a/data/infra/swoole.Dockerfile b/data/infra/swoole.Dockerfile index e4435b54..2314d834 100644 --- a/data/infra/swoole.Dockerfile +++ b/data/infra/swoole.Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.2.0-alpine3.17 +FROM php:8.1.13-alpine3.17 MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.21 From 9bed7ef1561780264c21848a1ab22d59e73f1ea7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 10 Dec 2022 19:15:38 +0100 Subject: [PATCH 151/182] Updated docker images to MS ODBC 18 for PDO MSSQL --- Dockerfile | 9 +++++---- data/infra/php.Dockerfile | 9 +++++---- data/infra/roadrunner.Dockerfile | 9 +++++---- data/infra/swoole.Dockerfile | 9 +++++---- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index ff809b3f..90c2ba6b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,8 @@ ARG SHLINK_RUNTIME=openswoole ENV SHLINK_RUNTIME ${SHLINK_RUNTIME} ENV OPENSWOOLE_VERSION 4.12.0 ENV PDO_SQLSRV_VERSION 5.10.1 -ENV MS_ODBC_SQL_VERSION 17.5.2.2 +ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486' +ENV MS_ODBC_SQL_VERSION 18_18.1.1.1 ENV LC_ALL "C" WORKDIR /etc/shlink @@ -29,11 +30,11 @@ RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \ docker-php-ext-enable openswoole ; \ fi; \ if [ $(uname -m) == "x86_64" ]; then \ - wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ - apk add --no-cache --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ + wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ + apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} && \ docker-php-ext-enable pdo_sqlsrv && \ - rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk ; \ + rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk ; \ fi; \ apk del .phpize-deps diff --git a/data/infra/php.Dockerfile b/data/infra/php.Dockerfile index 7bfbd24a..c43b21cb 100644 --- a/data/infra/php.Dockerfile +++ b/data/infra/php.Dockerfile @@ -3,7 +3,8 @@ MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.21 ENV PDO_SQLSRV_VERSION 5.10.1 -ENV MS_ODBC_SQL_VERSION 17.5.2.2 +ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486' +ENV MS_ODBC_SQL_VERSION 18_18.1.1.1 RUN apk update @@ -44,13 +45,13 @@ RUN mkdir -p /usr/src/php/ext/apcu \ && echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini # Install pcov and sqlsrv driver -RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ - apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ +RUN wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ + apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \ pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \ docker-php-ext-enable pdo_sqlsrv pcov && \ apk del .phpize-deps && \ - rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk + rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk # Install composer COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer diff --git a/data/infra/roadrunner.Dockerfile b/data/infra/roadrunner.Dockerfile index a1092de3..f019d969 100644 --- a/data/infra/roadrunner.Dockerfile +++ b/data/infra/roadrunner.Dockerfile @@ -3,7 +3,8 @@ MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.21 ENV PDO_SQLSRV_VERSION 5.10.1 -ENV MS_ODBC_SQL_VERSION 17.5.2.2 +ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486' +ENV MS_ODBC_SQL_VERSION 18_18.1.1.1 RUN apk update @@ -44,13 +45,13 @@ RUN mkdir -p /usr/src/php/ext/apcu \ && echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini # Install pcov and sqlsrv driver -RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ - apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ +RUN wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ + apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \ pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \ docker-php-ext-enable pdo_sqlsrv pcov && \ apk del .phpize-deps && \ - rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk + rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk # Install composer COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer diff --git a/data/infra/swoole.Dockerfile b/data/infra/swoole.Dockerfile index 2314d834..68fa0db2 100644 --- a/data/infra/swoole.Dockerfile +++ b/data/infra/swoole.Dockerfile @@ -5,7 +5,8 @@ ENV APCU_VERSION 5.1.21 ENV INOTIFY_VERSION 3.0.0 ENV OPENSWOOLE_VERSION 4.12.0 ENV PDO_SQLSRV_VERSION 5.10.1 -ENV MS_ODBC_SQL_VERSION 17.5.2.2 +ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486' +ENV MS_ODBC_SQL_VERSION 18_18.1.1.1 RUN apk update @@ -54,13 +55,13 @@ RUN mkdir -p /usr/src/php/ext/inotify \ && rm /tmp/inotify.tar.gz # Install openswoole, pcov and mssql driver -RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ - apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ +RUN wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ + apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \ apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \ pecl install openswoole-${OPENSWOOLE_VERSION} pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \ docker-php-ext-enable openswoole pdo_sqlsrv pcov && \ apk del .phpize-deps && \ - rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk + rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk # Install composer COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer From 0bc9bd9281baf253ad266b00e5391e91ff309325 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 10 Dec 2022 19:40:33 +0100 Subject: [PATCH 152/182] Added TrustServerCertificate=true to mssql connections --- config/autoload/entity-manager.global.php | 3 +++ config/test/test_config.global.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/config/autoload/entity-manager.global.php b/config/autoload/entity-manager.global.php index 5a75ca6b..58899217 100644 --- a/config/autoload/entity-manager.global.php +++ b/config/autoload/entity-manager.global.php @@ -42,6 +42,9 @@ return (static function (): array { 'port' => EnvVars::DB_PORT->loadFromEnv($resolveDefaultPort()), 'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET->loadFromEnv() : null, 'charset' => $resolveCharset(), + 'driverOptions' => $driver !== 'mssql' ? [] : [ + 'TrustServerCertificate' => 'true', + ], ], }; diff --git a/config/test/test_config.global.php b/config/test/test_config.global.php index 678e1b05..368a5f4e 100644 --- a/config/test/test_config.global.php +++ b/config/test/test_config.global.php @@ -101,6 +101,9 @@ $buildDbConnection = static function (): array { 'user' => 'sa', 'password' => 'Passw0rd!', 'dbname' => 'shlink_test', + 'driverOptions' => [ + 'TrustServerCertificate' => 'true', + ], ], default => [ // mysql and maria 'driver' => 'pdo_mysql', From 42a5296f935957305ddde93f6a1836bf9d0d4260 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 9 Dec 2022 17:42:14 +0100 Subject: [PATCH 153/182] Added new params to short URLs list to filter out 'disabled' short ones --- composer.json | 2 +- module/Core/src/ShortUrl/Model/ShortUrlsParams.php | 4 ++++ module/Core/src/ShortUrl/Model/TagsMode.php | 7 +++++++ .../Model/Validation/ShortUrlsParamsInputFilter.php | 7 ++++++- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 762ead49..6a5bb317 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "php-middleware/request-id": "^4.1", "pugx/shortid-php": "^1.1", "ramsey/uuid": "^4.5", - "shlinkio/shlink-common": "dev-main#7515008 as 5.2", + "shlinkio/shlink-common": "dev-main#f4101bc as 5.2", "shlinkio/shlink-config": "dev-main#96c81fb as 2.3", "shlinkio/shlink-event-dispatcher": "^2.6", "shlinkio/shlink-importer": "dev-main#c97662b as 5.0", diff --git a/module/Core/src/ShortUrl/Model/ShortUrlsParams.php b/module/Core/src/ShortUrl/Model/ShortUrlsParams.php index 14e88132..e053a283 100644 --- a/module/Core/src/ShortUrl/Model/ShortUrlsParams.php +++ b/module/Core/src/ShortUrl/Model/ShortUrlsParams.php @@ -24,6 +24,8 @@ final class ShortUrlsParams public readonly array $tags, public readonly Ordering $orderBy, public readonly ?DateRange $dateRange, + public readonly bool $excludeMaxVisitsReached, + public readonly bool $excludePastValidUntil, public readonly TagsMode $tagsMode = TagsMode::ANY, ) { } @@ -55,6 +57,8 @@ final class ShortUrlsParams normalizeOptionalDate($inputFilter->getValue(ShortUrlsParamsInputFilter::START_DATE)), normalizeOptionalDate($inputFilter->getValue(ShortUrlsParamsInputFilter::END_DATE)), ), + excludeMaxVisitsReached: $inputFilter->getValue(ShortUrlsParamsInputFilter::EXCLUDE_MAX_VISITS_REACHED), + excludePastValidUntil: $inputFilter->getValue(ShortUrlsParamsInputFilter::EXCLUDE_PAST_VALID_UNTIL), tagsMode: self::resolveTagsMode($inputFilter->getValue(ShortUrlsParamsInputFilter::TAGS_MODE)), ); } diff --git a/module/Core/src/ShortUrl/Model/TagsMode.php b/module/Core/src/ShortUrl/Model/TagsMode.php index 593d6d83..01cdcc3b 100644 --- a/module/Core/src/ShortUrl/Model/TagsMode.php +++ b/module/Core/src/ShortUrl/Model/TagsMode.php @@ -4,8 +4,15 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\ShortUrl\Model; +use function Functional\map; + enum TagsMode: string { case ANY = 'any'; case ALL = 'all'; + + public static function values(): array + { + return map(self::cases(), static fn (TagsMode $mode) => $mode->value); + } } diff --git a/module/Core/src/ShortUrl/Model/Validation/ShortUrlsParamsInputFilter.php b/module/Core/src/ShortUrl/Model/Validation/ShortUrlsParamsInputFilter.php index a5301f21..3bdea8e2 100644 --- a/module/Core/src/ShortUrl/Model/Validation/ShortUrlsParamsInputFilter.php +++ b/module/Core/src/ShortUrl/Model/Validation/ShortUrlsParamsInputFilter.php @@ -23,6 +23,8 @@ class ShortUrlsParamsInputFilter extends InputFilter public const ITEMS_PER_PAGE = 'itemsPerPage'; public const TAGS_MODE = 'tagsMode'; public const ORDER_BY = 'orderBy'; + public const EXCLUDE_MAX_VISITS_REACHED = 'excludeMaxVisitsReached'; + public const EXCLUDE_PAST_VALID_UNTIL = 'excludePastValidUntil'; public function __construct(array $data) { @@ -44,11 +46,14 @@ class ShortUrlsParamsInputFilter extends InputFilter $tagsMode = $this->createInput(self::TAGS_MODE, false); $tagsMode->getValidatorChain()->attach(new InArray([ - 'haystack' => [TagsMode::ALL->value, TagsMode::ANY->value], + 'haystack' => TagsMode::values(), 'strict' => InArray::COMPARE_STRICT, ])); $this->add($tagsMode); $this->add($this->createOrderByInput(self::ORDER_BY, ShortUrlsParams::ORDERABLE_FIELDS)); + + $this->add($this->createBooleanInput(self::EXCLUDE_MAX_VISITS_REACHED, false)); + $this->add($this->createBooleanInput(self::EXCLUDE_PAST_VALID_UNTIL, false)); } } From c3ab87136614f04b2a95f103ab1cb6956e11ed31 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 9 Dec 2022 17:59:59 +0100 Subject: [PATCH 154/182] Exposed new short URLs list filtering params --- .../Persistence/ShortUrlsCountFiltering.php | 4 ++++ .../Persistence/ShortUrlsListFiltering.php | 15 ++++++++++++++- .../Adapter/ShortUrlRepositoryAdapterTest.php | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/module/Core/src/ShortUrl/Persistence/ShortUrlsCountFiltering.php b/module/Core/src/ShortUrl/Persistence/ShortUrlsCountFiltering.php index 2d1b1f21..906adc63 100644 --- a/module/Core/src/ShortUrl/Persistence/ShortUrlsCountFiltering.php +++ b/module/Core/src/ShortUrl/Persistence/ShortUrlsCountFiltering.php @@ -21,6 +21,8 @@ class ShortUrlsCountFiltering public readonly array $tags = [], public readonly ?TagsMode $tagsMode = null, public readonly ?DateRange $dateRange = null, + public readonly bool $excludeMaxVisitsReached = false, + public readonly bool $excludePastValidUntil = false, public readonly ?ApiKey $apiKey = null, ?string $defaultDomain = null, ) { @@ -37,6 +39,8 @@ class ShortUrlsCountFiltering $params->tags, $params->tagsMode, $params->dateRange, + $params->excludeMaxVisitsReached, + $params->excludePastValidUntil, $apiKey, $defaultDomain, ); diff --git a/module/Core/src/ShortUrl/Persistence/ShortUrlsListFiltering.php b/module/Core/src/ShortUrl/Persistence/ShortUrlsListFiltering.php index dd7eb0aa..ed8793be 100644 --- a/module/Core/src/ShortUrl/Persistence/ShortUrlsListFiltering.php +++ b/module/Core/src/ShortUrl/Persistence/ShortUrlsListFiltering.php @@ -20,10 +20,21 @@ class ShortUrlsListFiltering extends ShortUrlsCountFiltering array $tags = [], ?TagsMode $tagsMode = null, ?DateRange $dateRange = null, + bool $excludeMaxVisitsReached = false, + bool $excludePastValidUntil = false, ?ApiKey $apiKey = null, ?string $defaultDomain = null, ) { - parent::__construct($searchTerm, $tags, $tagsMode, $dateRange, $apiKey, $defaultDomain); + parent::__construct( + $searchTerm, + $tags, + $tagsMode, + $dateRange, + $excludeMaxVisitsReached, + $excludePastValidUntil, + $apiKey, + $defaultDomain, + ); } public static function fromLimitsAndParams( @@ -41,6 +52,8 @@ class ShortUrlsListFiltering extends ShortUrlsCountFiltering $params->tags, $params->tagsMode, $params->dateRange, + $params->excludePastValidUntil, + $params->excludePastValidUntil, $apiKey, $defaultDomain, ); diff --git a/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php b/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php index aa8efbd5..e271448c 100644 --- a/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php +++ b/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php @@ -74,7 +74,7 @@ class ShortUrlRepositoryAdapterTest extends TestCase $dateRange = $params->dateRange; $this->repo->expects($this->once())->method('countList')->with( - new ShortUrlsCountFiltering($searchTerm, $tags, TagsMode::ANY, $dateRange, $apiKey), + new ShortUrlsCountFiltering($searchTerm, $tags, TagsMode::ANY, $dateRange, apiKey: $apiKey), ); $adapter->getNbResults(); } From 40794c476fe2066e04af421f16102016797450d2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 9 Dec 2022 18:03:20 +0100 Subject: [PATCH 155/182] Updated API docs with new short URLs list filters --- docs/swagger/paths/v1_short-urls.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/swagger/paths/v1_short-urls.json b/docs/swagger/paths/v1_short-urls.json index 2675ab61..c5041ecc 100644 --- a/docs/swagger/paths/v1_short-urls.json +++ b/docs/swagger/paths/v1_short-urls.json @@ -97,6 +97,32 @@ "schema": { "type": "string" } + }, + { + "name": "excludeMaxVisitsReached", + "in": "query", + "description": "If true, short URLs which already reached their maximum amount of visits will be excluded.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "false", + "true" + ] + } + }, + { + "name": "excludePastValidUntil", + "in": "query", + "description": "If true, short URLs which validUntil date is on the past will be excluded.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "false", + "true" + ] + } } ], "security": [ From 7ba2cfc0105a786c863b9c48001073eb3fdb2517 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 10 Dec 2022 09:56:34 +0100 Subject: [PATCH 156/182] Moved true before false in swagger docs --- docs/swagger/paths/v1_short-urls.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/swagger/paths/v1_short-urls.json b/docs/swagger/paths/v1_short-urls.json index c5041ecc..05c5973a 100644 --- a/docs/swagger/paths/v1_short-urls.json +++ b/docs/swagger/paths/v1_short-urls.json @@ -106,8 +106,8 @@ "schema": { "type": "string", "enum": [ - "false", - "true" + "true", + "false" ] } }, @@ -119,8 +119,8 @@ "schema": { "type": "string", "enum": [ - "false", - "true" + "true", + "false" ] } } From 805c8c87ba0513dadaed132ca7e5f9dab3c67e2a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 10 Dec 2022 09:56:56 +0100 Subject: [PATCH 157/182] Fixed nasty typo --- module/Core/src/ShortUrl/Persistence/ShortUrlsListFiltering.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/Core/src/ShortUrl/Persistence/ShortUrlsListFiltering.php b/module/Core/src/ShortUrl/Persistence/ShortUrlsListFiltering.php index ed8793be..db8b9a70 100644 --- a/module/Core/src/ShortUrl/Persistence/ShortUrlsListFiltering.php +++ b/module/Core/src/ShortUrl/Persistence/ShortUrlsListFiltering.php @@ -52,7 +52,7 @@ class ShortUrlsListFiltering extends ShortUrlsCountFiltering $params->tags, $params->tagsMode, $params->dateRange, - $params->excludePastValidUntil, + $params->excludeMaxVisitsReached, $params->excludePastValidUntil, $apiKey, $defaultDomain, From 463dfe972941c172855ace595aa1149ff081ad32 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 11 Dec 2022 10:17:21 +0100 Subject: [PATCH 158/182] Added support to filter out expired short URLs from list --- .../Repository/ShortUrlRepository.php | 69 ++++++++++++++----- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php index ec2f7a94..2e18bc8c 100644 --- a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php +++ b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\ShortUrl\Repository; +use Cake\Chronos\Chronos; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\ORM\Query\Expr\Join; @@ -11,7 +12,6 @@ use Doctrine\ORM\QueryBuilder; use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository; use Happyr\DoctrineSpecification\Specification\Specification; use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType; -use Shlinkio\Shlink\Core\Model\Ordering; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; @@ -38,43 +38,58 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU // In case the ordering has been specified, the query could be more complex. Process it if ($filtering->orderBy->hasOrderField()) { - return $this->processOrderByForList($qb, $filtering->orderBy); + $this->processOrderByForList($qb, $filtering); } - // With no explicit order by, fallback to dateCreated-DESC - return $qb->orderBy('s.dateCreated', 'DESC')->getQuery()->getResult(); + $result = $qb->getQuery()->getResult(); + if ($filtering->excludeMaxVisitsReached || $filtering->orderBy->field === 'visits') { + return array_column($result, 0); + } + + return $result; } - private function processOrderByForList(QueryBuilder $qb, Ordering $orderBy): array + private function processOrderByForList(QueryBuilder $qb, ShortUrlsListFiltering $filtering): void { - $fieldName = $orderBy->field; - $order = $orderBy->direction; + $fieldName = $filtering->orderBy->field; + $order = $filtering->orderBy->direction; if ($fieldName === 'visits') { // FIXME This query is inefficient. // Diagnostic: It might need to use a sub-query, as done with the tags list query. - $qb->addSelect('COUNT(DISTINCT v) AS totalVisits') - ->leftJoin('s.visits', 'v') - ->groupBy('s') - ->orderBy('totalVisits', $order); + if (! $filtering->excludeMaxVisitsReached) { + // Left join only if this was not true, otherwise this left join already happened + $this->leftJoinShortUrlsWithVisitsCount($qb); + } - return array_column($qb->getQuery()->getResult(), 0); - } - - $orderableFields = ['longUrl', 'shortCode', 'dateCreated', 'title']; - if (contains($orderableFields, $fieldName)) { + $qb->orderBy('totalVisits', $order); + } elseif (contains(['longUrl', 'shortCode', 'dateCreated', 'title'], $fieldName)) { $qb->orderBy('s.' . $fieldName, $order); + } else { + // With no explicit order by, fallback to dateCreated-DESC + $qb->orderBy('s.dateCreated', 'DESC'); } - - return $qb->getQuery()->getResult(); } public function countList(ShortUrlsCountFiltering $filtering): int { $qb = $this->createListQueryBuilder($filtering); $qb->select('COUNT(DISTINCT s)'); + $query = $qb->getQuery(); - return (int) $qb->getQuery()->getSingleScalarResult(); +// dump($query->getSQL()); + + // TODO This is crap... + return $filtering->excludeMaxVisitsReached + ? count($query->getSingleColumnResult()) + : (int) $query->getSingleScalarResult(); + } + + private function leftJoinShortUrlsWithVisitsCount(QueryBuilder $qb): void + { + $qb->addSelect('COUNT(DISTINCT v) AS totalVisits') + ->leftJoin('s.visits', 'v') + ->groupBy('s'); } private function createListQueryBuilder(ShortUrlsCountFiltering $filtering): QueryBuilder @@ -134,6 +149,22 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU : $this->joinAllTags($qb, $tags); } + if ($filtering->excludeMaxVisitsReached) { + $this->leftJoinShortUrlsWithVisitsCount($qb); + $qb->having($qb->expr()->orX( + $qb->expr()->isNull('s.maxVisits'), + $qb->expr()->gt('s.maxVisits', 'COUNT(DISTINCT v)'), + )); + } + + if ($filtering->excludePastValidUntil) { + $qb->andWhere($qb->expr()->orX( + $qb->expr()->isNull('s.validUntil'), + $qb->expr()->gte('s.validUntil', ':minValidSince'), + )) + ->setParameter('minValidSince', Chronos::now()->toDateTimeString()); + } + $this->applySpecification($qb, $filtering->apiKey?->spec(), 's'); return $qb; From cdde59b5435a57d59a73ca52adf53a939441cf77 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 11 Dec 2022 11:41:37 +0100 Subject: [PATCH 159/182] Added db test for filtering of disabled short URLs --- module/Core/src/ShortUrl/Entity/ShortUrl.php | 32 +++++++------- .../Repository/ShortUrlRepository.php | 21 ++++++--- .../Repository/ShortUrlRepositoryTest.php | 44 +++++++++++++++++++ 3 files changed, 74 insertions(+), 23 deletions(-) diff --git a/module/Core/src/ShortUrl/Entity/ShortUrl.php b/module/Core/src/ShortUrl/Entity/ShortUrl.php index 7316c50b..28647bdc 100644 --- a/module/Core/src/ShortUrl/Entity/ShortUrl.php +++ b/module/Core/src/ShortUrl/Entity/ShortUrl.php @@ -65,29 +65,29 @@ class ShortUrl extends AbstractEntity return self::fromMeta(ShortUrlCreation::fromRawData([ShortUrlInputFilter::LONG_URL => $longUrl])); } - public static function fromMeta( - ShortUrlCreation $meta, + public static function fromMeta( // TODO Rename to create(...) + ShortUrlCreation $creation, ?ShortUrlRelationResolverInterface $relationResolver = null, ): self { $instance = new self(); $relationResolver = $relationResolver ?? new SimpleShortUrlRelationResolver(); - $instance->longUrl = $meta->getLongUrl(); + $instance->longUrl = $creation->getLongUrl(); $instance->dateCreated = Chronos::now(); $instance->visits = new ArrayCollection(); - $instance->tags = $relationResolver->resolveTags($meta->getTags()); - $instance->validSince = $meta->getValidSince(); - $instance->validUntil = $meta->getValidUntil(); - $instance->maxVisits = $meta->getMaxVisits(); - $instance->customSlugWasProvided = $meta->hasCustomSlug(); - $instance->shortCodeLength = $meta->getShortCodeLength(); - $instance->shortCode = $meta->getCustomSlug() ?? generateRandomShortCode($instance->shortCodeLength); - $instance->domain = $relationResolver->resolveDomain($meta->getDomain()); - $instance->authorApiKey = $meta->getApiKey(); - $instance->title = $meta->getTitle(); - $instance->titleWasAutoResolved = $meta->titleWasAutoResolved(); - $instance->crawlable = $meta->isCrawlable(); - $instance->forwardQuery = $meta->forwardQuery(); + $instance->tags = $relationResolver->resolveTags($creation->getTags()); + $instance->validSince = $creation->getValidSince(); + $instance->validUntil = $creation->getValidUntil(); + $instance->maxVisits = $creation->getMaxVisits(); + $instance->customSlugWasProvided = $creation->hasCustomSlug(); + $instance->shortCodeLength = $creation->getShortCodeLength(); + $instance->shortCode = $creation->getCustomSlug() ?? generateRandomShortCode($instance->shortCodeLength); + $instance->domain = $relationResolver->resolveDomain($creation->getDomain()); + $instance->authorApiKey = $creation->getApiKey(); + $instance->title = $creation->getTitle(); + $instance->titleWasAutoResolved = $creation->titleWasAutoResolved(); + $instance->crawlable = $creation->isCrawlable(); + $instance->forwardQuery = $creation->forwardQuery(); return $instance; } diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php index 2e18bc8c..37121bdb 100644 --- a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php +++ b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php @@ -36,6 +36,11 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU ->setMaxResults($filtering->limit) ->setFirstResult($filtering->offset); + // Some DB engines (postgres and ms) require aggregates in the select for ordering and filtering + if ($filtering->excludeMaxVisitsReached || $filtering->orderBy->field === 'visits') { + $qb->addSelect('COUNT(DISTINCT v)'); + } + // In case the ordering has been specified, the query could be more complex. Process it if ($filtering->orderBy->hasOrderField()) { $this->processOrderByForList($qb, $filtering); @@ -59,10 +64,10 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU // Diagnostic: It might need to use a sub-query, as done with the tags list query. if (! $filtering->excludeMaxVisitsReached) { // Left join only if this was not true, otherwise this left join already happened - $this->leftJoinShortUrlsWithVisitsCount($qb); + $this->leftJoinShortUrlsWithVisits($qb); } - $qb->orderBy('totalVisits', $order); + $qb->orderBy('COUNT(DISTINCT v)', $order); } elseif (contains(['longUrl', 'shortCode', 'dateCreated', 'title'], $fieldName)) { $qb->orderBy('s.' . $fieldName, $order); } else { @@ -77,7 +82,10 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU $qb->select('COUNT(DISTINCT s)'); $query = $qb->getQuery(); -// dump($query->getSQL()); + // TODO Check if this is needed +// if ($filtering->excludeMaxVisitsReached) { +// $qb->addSelect('COUNT(DISTINCT v)'); +// } // TODO This is crap... return $filtering->excludeMaxVisitsReached @@ -85,10 +93,9 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU : (int) $query->getSingleScalarResult(); } - private function leftJoinShortUrlsWithVisitsCount(QueryBuilder $qb): void + private function leftJoinShortUrlsWithVisits(QueryBuilder $qb): void { - $qb->addSelect('COUNT(DISTINCT v) AS totalVisits') - ->leftJoin('s.visits', 'v') + $qb->leftJoin('s.visits', 'v') ->groupBy('s'); } @@ -150,7 +157,7 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU } if ($filtering->excludeMaxVisitsReached) { - $this->leftJoinShortUrlsWithVisitsCount($qb); + $this->leftJoinShortUrlsWithVisits($qb); $qb->having($qb->expr()->orX( $qb->expr()->isNull('s.maxVisits'), $qb->expr()->gt('s.maxVisits', 'COUNT(DISTINCT v)'), diff --git a/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php b/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php index e41eee15..186a4ecf 100644 --- a/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php @@ -339,6 +339,50 @@ class ShortUrlRepositoryTest extends DatabaseTestCase self::assertCount(0, $this->repo->findList($buildFiltering('no results'))); } + /** @test */ + public function findListReturnsOnlyThoseWithoutExcludedUrls(): void + { + $shortUrl1 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo1', + 'validUntil' => Chronos::now()->addDays(1)->toAtomString(), + 'maxVisits' => 100, + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl1); + $shortUrl2 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo2', + 'validUntil' => Chronos::now()->subDays(1)->toAtomString(), + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl2); + $shortUrl3 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo3', + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl3); + $shortUrl4 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo4', + 'maxVisits' => 3, + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl4); + $this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::emptyInstance())); + $this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::emptyInstance())); + $this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::emptyInstance())); + + $this->getEntityManager()->flush(); + + $filtering = static fn (bool $excludeMaxVisitsReached, bool $excludePastValidUntil) => + new ShortUrlsListFiltering( + null, + null, + Ordering::emptyInstance(), + excludeMaxVisitsReached: $excludeMaxVisitsReached, + excludePastValidUntil: $excludePastValidUntil, + ); + + self::assertCount(4, $this->repo->findList($filtering(false, false))); + self::assertCount(3, $this->repo->findList($filtering(true, false))); + self::assertCount(3, $this->repo->findList($filtering(false, true))); + self::assertCount(2, $this->repo->findList($filtering(true, true))); + } + /** @test */ public function shortCodeIsInUseLooksForShortUrlInProperSetOfTables(): void { From d83213341038f69584f2e9a11f2f7d7263946fe3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 11 Dec 2022 12:33:17 +0100 Subject: [PATCH 160/182] Enhanced db tests for expired short urls filtering --- module/Core/src/ShortUrl/Repository/ShortUrlRepository.php | 5 ----- .../test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php index 37121bdb..f2a66fda 100644 --- a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php +++ b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php @@ -82,11 +82,6 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU $qb->select('COUNT(DISTINCT s)'); $query = $qb->getQuery(); - // TODO Check if this is needed -// if ($filtering->excludeMaxVisitsReached) { -// $qb->addSelect('COUNT(DISTINCT v)'); -// } - // TODO This is crap... return $filtering->excludeMaxVisitsReached ? count($query->getSingleColumnResult()) diff --git a/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php b/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php index 186a4ecf..7f2a5a89 100644 --- a/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php @@ -378,9 +378,13 @@ class ShortUrlRepositoryTest extends DatabaseTestCase ); self::assertCount(4, $this->repo->findList($filtering(false, false))); + self::assertEquals(4, $this->repo->countList($filtering(false, false))); self::assertCount(3, $this->repo->findList($filtering(true, false))); + self::assertEquals(3, $this->repo->countList($filtering(true, false))); self::assertCount(3, $this->repo->findList($filtering(false, true))); + self::assertEquals(3, $this->repo->countList($filtering(false, true))); self::assertCount(2, $this->repo->findList($filtering(true, true))); + self::assertEquals(2, $this->repo->countList($filtering(true, true))); } /** @test */ From 8807a7846389c9e3df180876bf3250e987c7e775 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 11 Dec 2022 13:00:06 +0100 Subject: [PATCH 161/182] Improved performance when filtering out shortUrls which reached their limit by using a sub-query --- .../Repository/ShortUrlRepository.php | 36 ++++++------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php index f2a66fda..34c3b34c 100644 --- a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php +++ b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php @@ -18,6 +18,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode; use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering; use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering; +use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use function array_column; @@ -36,18 +37,13 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU ->setMaxResults($filtering->limit) ->setFirstResult($filtering->offset); - // Some DB engines (postgres and ms) require aggregates in the select for ordering and filtering - if ($filtering->excludeMaxVisitsReached || $filtering->orderBy->field === 'visits') { - $qb->addSelect('COUNT(DISTINCT v)'); - } - // In case the ordering has been specified, the query could be more complex. Process it if ($filtering->orderBy->hasOrderField()) { $this->processOrderByForList($qb, $filtering); } $result = $qb->getQuery()->getResult(); - if ($filtering->excludeMaxVisitsReached || $filtering->orderBy->field === 'visits') { + if ($filtering->orderBy->field === 'visits') { return array_column($result, 0); } @@ -62,12 +58,10 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU if ($fieldName === 'visits') { // FIXME This query is inefficient. // Diagnostic: It might need to use a sub-query, as done with the tags list query. - if (! $filtering->excludeMaxVisitsReached) { - // Left join only if this was not true, otherwise this left join already happened - $this->leftJoinShortUrlsWithVisits($qb); - } - - $qb->orderBy('COUNT(DISTINCT v)', $order); + $qb->addSelect('COUNT(DISTINCT v)') + ->leftJoin('s.visits', 'v') + ->groupBy('s') + ->orderBy('COUNT(DISTINCT v)', $order); } elseif (contains(['longUrl', 'shortCode', 'dateCreated', 'title'], $fieldName)) { $qb->orderBy('s.' . $fieldName, $order); } else { @@ -80,18 +74,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU { $qb = $this->createListQueryBuilder($filtering); $qb->select('COUNT(DISTINCT s)'); - $query = $qb->getQuery(); - // TODO This is crap... - return $filtering->excludeMaxVisitsReached - ? count($query->getSingleColumnResult()) - : (int) $query->getSingleScalarResult(); - } - - private function leftJoinShortUrlsWithVisits(QueryBuilder $qb): void - { - $qb->leftJoin('s.visits', 'v') - ->groupBy('s'); + return (int) $qb->getQuery()->getSingleScalarResult(); } private function createListQueryBuilder(ShortUrlsCountFiltering $filtering): QueryBuilder @@ -152,10 +136,10 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU } if ($filtering->excludeMaxVisitsReached) { - $this->leftJoinShortUrlsWithVisits($qb); - $qb->having($qb->expr()->orX( + $visitEntity = Visit::class; + $qb->andWhere($qb->expr()->orX( $qb->expr()->isNull('s.maxVisits'), - $qb->expr()->gt('s.maxVisits', 'COUNT(DISTINCT v)'), + $qb->expr()->gt('s.maxVisits', "(SELECT COUNT(innerV.id) FROM $visitEntity as innerV WHERE innerV.shortUrl=s)"), )); } From 931bdb0cd76e2f4cc434e5197aae6478de7c2eda Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 11 Dec 2022 13:03:19 +0100 Subject: [PATCH 162/182] Fixed coding styles --- .../src/ShortUrl/Repository/ShortUrlRepository.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php index 34c3b34c..b1c82973 100644 --- a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php +++ b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php @@ -144,11 +144,12 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU } if ($filtering->excludePastValidUntil) { - $qb->andWhere($qb->expr()->orX( - $qb->expr()->isNull('s.validUntil'), - $qb->expr()->gte('s.validUntil', ':minValidSince'), - )) - ->setParameter('minValidSince', Chronos::now()->toDateTimeString()); + $qb + ->andWhere($qb->expr()->orX( + $qb->expr()->isNull('s.validUntil'), + $qb->expr()->gte('s.validUntil', ':minValidSince'), + )) + ->setParameter('minValidSince', Chronos::now()->toDateTimeString()); } $this->applySpecification($qb, $filtering->apiKey?->spec(), 's'); From 0d7a0ee9ea3d3a0c0bd00dfaf355e49f92aa4c99 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 11 Dec 2022 13:11:43 +0100 Subject: [PATCH 163/182] Fixed more coding styles --- module/Core/src/ShortUrl/Repository/ShortUrlRepository.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php index b1c82973..f51d2fa4 100644 --- a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php +++ b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php @@ -24,6 +24,7 @@ use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use function array_column; use function count; use function Functional\contains; +use function sprintf; class ShortUrlRepository extends EntitySpecificationRepository implements ShortUrlRepositoryInterface { @@ -136,10 +137,12 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU } if ($filtering->excludeMaxVisitsReached) { - $visitEntity = Visit::class; $qb->andWhere($qb->expr()->orX( $qb->expr()->isNull('s.maxVisits'), - $qb->expr()->gt('s.maxVisits', "(SELECT COUNT(innerV.id) FROM $visitEntity as innerV WHERE innerV.shortUrl=s)"), + $qb->expr()->gt( + 's.maxVisits', + sprintf('(SELECT COUNT(innerV.id) FROM %s as innerV WHERE innerV.shortUrl=s)', Visit::class), + ), )); } From 0c3523c34a5fa30a2118a84e48d56f0f41458d2a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 11 Dec 2022 13:22:16 +0100 Subject: [PATCH 164/182] Fixed E2E test suites --- .../src/ShortUrl/Repository/ShortUrlRepository.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php index f51d2fa4..d6559973 100644 --- a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php +++ b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php @@ -39,9 +39,7 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU ->setFirstResult($filtering->offset); // In case the ordering has been specified, the query could be more complex. Process it - if ($filtering->orderBy->hasOrderField()) { - $this->processOrderByForList($qb, $filtering); - } + $this->processOrderByForList($qb, $filtering); $result = $qb->getQuery()->getResult(); if ($filtering->orderBy->field === 'visits') { @@ -53,6 +51,12 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU private function processOrderByForList(QueryBuilder $qb, ShortUrlsListFiltering $filtering): void { + // With no explicit order by, fallback to dateCreated-DESC + if (! $filtering->orderBy->hasOrderField()) { + $qb->orderBy('s.dateCreated', 'DESC'); + return; + } + $fieldName = $filtering->orderBy->field; $order = $filtering->orderBy->direction; @@ -65,9 +69,6 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU ->orderBy('COUNT(DISTINCT v)', $order); } elseif (contains(['longUrl', 'shortCode', 'dateCreated', 'title'], $fieldName)) { $qb->orderBy('s.' . $fieldName, $order); - } else { - // With no explicit order by, fallback to dateCreated-DESC - $qb->orderBy('s.dateCreated', 'DESC'); } } From 201f25e0ad698d3f760787c82e46e80a1b785f3d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 11 Dec 2022 13:38:11 +0100 Subject: [PATCH 165/182] Improved API tests to cover exlucding disabled URLs from lists --- CHANGELOG.md | 7 +++++++ .../Rest/test-api/Action/ListShortUrlsTest.php | 18 ++++++++++++++++-- .../test-api/Fixtures/ShortUrlsFixture.php | 9 ++++++--- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cbab421..31cd05d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ## [Unreleased] ### Added +* [#1612](https://github.com/shlinkio/shlink/issues/1612) Allowed to filter short URLs out of lists, when `validUntil` date is in the past or have reached their maximum amount of visits. + + This can be done by: + + * Providing `excludeMaxVisitsReached=true` and/or `excludePastValidUntil=true` to the `GET /short-urls` endpoint. + * Providing `--exclude-max-visits-reached` and/or `--exclude-past-valid-until` to the `short-urls:list` command. + * [#1616](https://github.com/shlinkio/shlink/issues/1616) Added support to import orphan visits when importing short URLs from another Shlink instance. * [#1519](https://github.com/shlinkio/shlink/issues/1519) Allowing to search short URLs by default domain. * [#1555](https://github.com/shlinkio/shlink/issues/1555) Added full support for PHP 8.2, pdating the dockr image to this version. diff --git a/module/Rest/test-api/Action/ListShortUrlsTest.php b/module/Rest/test-api/Action/ListShortUrlsTest.php index b28a0b5d..63664a6d 100644 --- a/module/Rest/test-api/Action/ListShortUrlsTest.php +++ b/module/Rest/test-api/Action/ListShortUrlsTest.php @@ -22,7 +22,7 @@ class ListShortUrlsTest extends ApiTestCase 'meta' => [ 'validSince' => null, 'validUntil' => null, - 'maxVisits' => null, + 'maxVisits' => 2, ], 'domain' => null, 'title' => 'My cool title', @@ -38,7 +38,7 @@ class ListShortUrlsTest extends ApiTestCase 'tags' => [], 'meta' => [ 'validSince' => null, - 'validUntil' => null, + 'validUntil' => '2020-05-01T00:00:00+00:00', 'maxVisits' => null, ], 'domain' => null, @@ -147,6 +147,20 @@ class ListShortUrlsTest extends ApiTestCase self::SHORT_URL_SHLINK_WITH_TITLE, self::SHORT_URL_DOCS, ], 'valid_api_key']; + yield [['excludePastValidUntil' => 'true'], [ + self::SHORT_URL_CUSTOM_DOMAIN, + self::SHORT_URL_CUSTOM_SLUG, + self::SHORT_URL_META, + self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, + self::SHORT_URL_SHLINK_WITH_TITLE, + ], 'valid_api_key']; + yield [['excludeMaxVisitsReached' => 'true'], [ + self::SHORT_URL_CUSTOM_DOMAIN, + self::SHORT_URL_CUSTOM_SLUG, + self::SHORT_URL_META, + self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, + self::SHORT_URL_DOCS, + ], 'valid_api_key']; yield [['orderBy' => 'shortCode'], [ self::SHORT_URL_SHLINK_WITH_TITLE, self::SHORT_URL_CUSTOM_SLUG, diff --git a/module/Rest/test-api/Fixtures/ShortUrlsFixture.php b/module/Rest/test-api/Fixtures/ShortUrlsFixture.php index eadf60ee..432e5cad 100644 --- a/module/Rest/test-api/Fixtures/ShortUrlsFixture.php +++ b/module/Rest/test-api/Fixtures/ShortUrlsFixture.php @@ -36,6 +36,7 @@ class ShortUrlsFixture extends AbstractFixture implements DependentFixtureInterf 'tags' => ['foo'], 'title' => 'My cool title', 'crawlable' => true, + 'maxVisits' => 2, ]), $relationResolver), '2018-05-01', ); @@ -61,9 +62,11 @@ class ShortUrlsFixture extends AbstractFixture implements DependentFixtureInterf $manager->persist($customShortUrl); $ghiShortUrl = $this->setShortUrlDate( - ShortUrl::fromMeta(ShortUrlCreation::fromRawData( - ['customSlug' => 'ghi789', 'longUrl' => 'https://shlink.io/documentation/'], - )), + ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + 'customSlug' => 'ghi789', + 'longUrl' => 'https://shlink.io/documentation/', + 'validUntil' => Chronos::parse('2020-05-01'), // In the past + ])), '2018-05-01', ); $manager->persist($ghiShortUrl); From c4f28b3a3215c059e880808166fa1cf0d1fd67a1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 11 Dec 2022 18:24:47 +0100 Subject: [PATCH 166/182] Renamed ShortUrl::fromMeta to ShortUrl::create --- .../Command/ShortUrl/ListShortUrlsCommand.php | 12 ++++ .../ShortUrl/ListShortUrlsCommandTest.php | 2 +- module/Core/src/ShortUrl/Entity/ShortUrl.php | 8 +-- module/Core/src/ShortUrl/UrlShortener.php | 2 +- .../Repository/DomainRepositoryTest.php | 2 +- .../Repository/ShortUrlRepositoryTest.php | 64 +++++++++---------- .../Tag/Repository/TagRepositoryTest.php | 12 ++-- .../Visit/Repository/VisitRepositoryTest.php | 20 +++--- .../PublishingUpdatesGeneratorTest.php | 4 +- .../RabbitMq/NotifyVisitToRabbitMqTest.php | 2 +- .../test/ShortUrl/Entity/ShortUrlTest.php | 4 +- .../Helper/ShortUrlRedirectionBuilderTest.php | 2 +- .../Helper/ShortUrlStringifierTest.php | 2 +- .../test/ShortUrl/ShortUrlResolverTest.php | 8 +-- .../ShortUrlDataTransformerTest.php | 8 +-- .../Core/test/ShortUrl/UrlShortenerTest.php | 12 ++-- .../test-api/Fixtures/ShortUrlsFixture.php | 12 ++-- 17 files changed, 94 insertions(+), 82 deletions(-) diff --git a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php index 11443abc..86a9dd57 100644 --- a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php +++ b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php @@ -77,6 +77,18 @@ class ListShortUrlsCommand extends Command InputOption::VALUE_NONE, 'If tags is provided, returns only short URLs having ALL tags.', ) + ->addOption( + 'exclude-max-visits-reached', + null, // TODO + InputOption::VALUE_NONE, + 'Excludes short URLs which reached their max amount of visits.', + ) + ->addOption( + 'exclude-past-valid-until', + null, // TODO + InputOption::VALUE_NONE, + 'Excludes short URLs which have a "validUntil" date in the past.', + ) ->addOption( 'order-by', 'o', diff --git a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php index 3b186bd1..84b138d4 100644 --- a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php @@ -114,7 +114,7 @@ class ListShortUrlsCommandTest extends TestCase $this->shortUrlService->expects($this->once())->method('listShortUrls')->with( ShortUrlsParams::emptyInstance(), )->willReturn(new Paginator(new ArrayAdapter([ - ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo.com', 'tags' => ['foo', 'bar', 'baz'], 'apiKey' => $apiKey, diff --git a/module/Core/src/ShortUrl/Entity/ShortUrl.php b/module/Core/src/ShortUrl/Entity/ShortUrl.php index 28647bdc..c7f10b75 100644 --- a/module/Core/src/ShortUrl/Entity/ShortUrl.php +++ b/module/Core/src/ShortUrl/Entity/ShortUrl.php @@ -57,15 +57,15 @@ class ShortUrl extends AbstractEntity public static function createEmpty(): self { - return self::fromMeta(ShortUrlCreation::createEmpty()); + return self::create(ShortUrlCreation::createEmpty()); } public static function withLongUrl(string $longUrl): self { - return self::fromMeta(ShortUrlCreation::fromRawData([ShortUrlInputFilter::LONG_URL => $longUrl])); + return self::create(ShortUrlCreation::fromRawData([ShortUrlInputFilter::LONG_URL => $longUrl])); } - public static function fromMeta( // TODO Rename to create(...) + public static function create( ShortUrlCreation $creation, ?ShortUrlRelationResolverInterface $relationResolver = null, ): self { @@ -109,7 +109,7 @@ class ShortUrl extends AbstractEntity $meta[ShortUrlInputFilter::CUSTOM_SLUG] = $url->shortCode; } - $instance = self::fromMeta(ShortUrlCreation::fromRawData($meta), $relationResolver); + $instance = self::create(ShortUrlCreation::fromRawData($meta), $relationResolver); $instance->importSource = $url->source->value; $instance->importOriginalShortCode = $url->shortCode; diff --git a/module/Core/src/ShortUrl/UrlShortener.php b/module/Core/src/ShortUrl/UrlShortener.php index 4f979921..d3f54650 100644 --- a/module/Core/src/ShortUrl/UrlShortener.php +++ b/module/Core/src/ShortUrl/UrlShortener.php @@ -44,7 +44,7 @@ class UrlShortener implements UrlShortenerInterface /** @var ShortUrl $newShortUrl */ $newShortUrl = $this->em->wrapInTransaction(function () use ($meta) { - $shortUrl = ShortUrl::fromMeta($meta, $this->relationResolver); + $shortUrl = ShortUrl::create($meta, $this->relationResolver); $this->verifyShortCodeUniqueness($meta, $shortUrl); $this->em->persist($shortUrl); diff --git a/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php b/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php index 17f65abc..c96d70ff 100644 --- a/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php +++ b/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php @@ -129,7 +129,7 @@ class DomainRepositoryTest extends DatabaseTestCase private function createShortUrl(Domain $domain, ?ApiKey $apiKey = null): ShortUrl { - return ShortUrl::fromMeta( + return ShortUrl::create( ShortUrlCreation::fromRawData( ['domain' => $domain->getAuthority(), 'apiKey' => $apiKey, 'longUrl' => 'foo'], ), diff --git a/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php b/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php index 7f2a5a89..dc1d6420 100644 --- a/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php @@ -43,15 +43,15 @@ class ShortUrlRepositoryTest extends DatabaseTestCase /** @test */ public function findOneWithDomainFallbackReturnsProperData(): void { - $regularOne = ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['customSlug' => 'foo', 'longUrl' => 'foo'])); + $regularOne = ShortUrl::create(ShortUrlCreation::fromRawData(['customSlug' => 'foo', 'longUrl' => 'foo'])); $this->getEntityManager()->persist($regularOne); - $withDomain = ShortUrl::fromMeta(ShortUrlCreation::fromRawData( + $withDomain = ShortUrl::create(ShortUrlCreation::fromRawData( ['domain' => 'example.com', 'customSlug' => 'domain-short-code', 'longUrl' => 'foo'], )); $this->getEntityManager()->persist($withDomain); - $withDomainDuplicatingRegular = ShortUrl::fromMeta(ShortUrlCreation::fromRawData( + $withDomainDuplicatingRegular = ShortUrl::create(ShortUrlCreation::fromRawData( ['domain' => 'doma.in', 'customSlug' => 'foo', 'longUrl' => 'foo_with_domain'], )); $this->getEntityManager()->persist($withDomainDuplicatingRegular); @@ -101,7 +101,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase /** @test */ public function findListProperlyFiltersResult(): void { - $foo = ShortUrl::fromMeta( + $foo = ShortUrl::create( ShortUrlCreation::fromRawData(['longUrl' => 'foo', 'tags' => ['bar']]), $this->relationResolver, ); @@ -197,27 +197,27 @@ class ShortUrlRepositoryTest extends DatabaseTestCase /** @test */ public function findListReturnsOnlyThoseWithMatchingTags(): void { - $shortUrl1 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo1', 'tags' => ['foo', 'bar'], ]), $this->relationResolver); $this->getEntityManager()->persist($shortUrl1); - $shortUrl2 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo2', 'tags' => ['foo', 'baz'], ]), $this->relationResolver); $this->getEntityManager()->persist($shortUrl2); - $shortUrl3 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo3', 'tags' => ['foo'], ]), $this->relationResolver); $this->getEntityManager()->persist($shortUrl3); - $shortUrl4 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl4 = ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo4', 'tags' => ['bar', 'baz'], ]), $this->relationResolver); $this->getEntityManager()->persist($shortUrl4); - $shortUrl5 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl5 = ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo5', 'tags' => ['bar', 'baz'], ]), $this->relationResolver); @@ -306,17 +306,17 @@ class ShortUrlRepositoryTest extends DatabaseTestCase /** @test */ public function findListReturnsOnlyThoseWithMatchingDomains(): void { - $shortUrl1 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo1', 'domain' => null, ]), $this->relationResolver); $this->getEntityManager()->persist($shortUrl1); - $shortUrl2 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo2', 'domain' => null, ]), $this->relationResolver); $this->getEntityManager()->persist($shortUrl2); - $shortUrl3 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo3', 'domain' => 'another.com', ]), $this->relationResolver); @@ -342,22 +342,22 @@ class ShortUrlRepositoryTest extends DatabaseTestCase /** @test */ public function findListReturnsOnlyThoseWithoutExcludedUrls(): void { - $shortUrl1 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo1', 'validUntil' => Chronos::now()->addDays(1)->toAtomString(), 'maxVisits' => 100, ]), $this->relationResolver); $this->getEntityManager()->persist($shortUrl1); - $shortUrl2 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo2', 'validUntil' => Chronos::now()->subDays(1)->toAtomString(), ]), $this->relationResolver); $this->getEntityManager()->persist($shortUrl2); - $shortUrl3 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo3', ]), $this->relationResolver); $this->getEntityManager()->persist($shortUrl3); - $shortUrl4 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl4 = ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo4', 'maxVisits' => 3, ]), $this->relationResolver); @@ -390,12 +390,12 @@ class ShortUrlRepositoryTest extends DatabaseTestCase /** @test */ public function shortCodeIsInUseLooksForShortUrlInProperSetOfTables(): void { - $shortUrlWithoutDomain = ShortUrl::fromMeta( + $shortUrlWithoutDomain = ShortUrl::create( ShortUrlCreation::fromRawData(['customSlug' => 'my-cool-slug', 'longUrl' => 'foo']), ); $this->getEntityManager()->persist($shortUrlWithoutDomain); - $shortUrlWithDomain = ShortUrl::fromMeta( + $shortUrlWithDomain = ShortUrl::create( ShortUrlCreation::fromRawData(['domain' => 'doma.in', 'customSlug' => 'another-slug', 'longUrl' => 'foo']), ); $this->getEntityManager()->persist($shortUrlWithDomain); @@ -419,12 +419,12 @@ class ShortUrlRepositoryTest extends DatabaseTestCase /** @test */ public function findOneLooksForShortUrlInProperSetOfTables(): void { - $shortUrlWithoutDomain = ShortUrl::fromMeta( + $shortUrlWithoutDomain = ShortUrl::create( ShortUrlCreation::fromRawData(['customSlug' => 'my-cool-slug', 'longUrl' => 'foo']), ); $this->getEntityManager()->persist($shortUrlWithoutDomain); - $shortUrlWithDomain = ShortUrl::fromMeta( + $shortUrlWithDomain = ShortUrl::create( ShortUrlCreation::fromRawData(['domain' => 'doma.in', 'customSlug' => 'another-slug', 'longUrl' => 'foo']), ); $this->getEntityManager()->persist($shortUrlWithDomain); @@ -465,29 +465,29 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $start = Chronos::parse('2020-03-05 20:18:30'); $end = Chronos::parse('2021-03-05 20:18:30'); - $shortUrl = ShortUrl::fromMeta( + $shortUrl = ShortUrl::create( ShortUrlCreation::fromRawData(['validSince' => $start, 'longUrl' => 'foo', 'tags' => ['foo', 'bar']]), $this->relationResolver, ); $this->getEntityManager()->persist($shortUrl); - $shortUrl2 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['validUntil' => $end, 'longUrl' => 'bar'])); + $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData(['validUntil' => $end, 'longUrl' => 'bar'])); $this->getEntityManager()->persist($shortUrl2); - $shortUrl3 = ShortUrl::fromMeta( + $shortUrl3 = ShortUrl::create( ShortUrlCreation::fromRawData(['validSince' => $start, 'validUntil' => $end, 'longUrl' => 'baz']), ); $this->getEntityManager()->persist($shortUrl3); - $shortUrl4 = ShortUrl::fromMeta( + $shortUrl4 = ShortUrl::create( ShortUrlCreation::fromRawData(['customSlug' => 'custom', 'validUntil' => $end, 'longUrl' => 'foo']), ); $this->getEntityManager()->persist($shortUrl4); - $shortUrl5 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['maxVisits' => 3, 'longUrl' => 'foo'])); + $shortUrl5 = ShortUrl::create(ShortUrlCreation::fromRawData(['maxVisits' => 3, 'longUrl' => 'foo'])); $this->getEntityManager()->persist($shortUrl5); - $shortUrl6 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['domain' => 'doma.in', 'longUrl' => 'foo'])); + $shortUrl6 = ShortUrl::create(ShortUrlCreation::fromRawData(['domain' => 'doma.in', 'longUrl' => 'foo'])); $this->getEntityManager()->persist($shortUrl6); $this->getEntityManager()->flush(); @@ -537,15 +537,15 @@ class ShortUrlRepositoryTest extends DatabaseTestCase ['validSince' => $start, 'maxVisits' => 50, 'longUrl' => 'foo', 'tags' => $tags], ); - $shortUrl1 = ShortUrl::fromMeta($meta, $this->relationResolver); + $shortUrl1 = ShortUrl::create($meta, $this->relationResolver); $this->getEntityManager()->persist($shortUrl1); $this->getEntityManager()->flush(); - $shortUrl2 = ShortUrl::fromMeta($meta, $this->relationResolver); + $shortUrl2 = ShortUrl::create($meta, $this->relationResolver); $this->getEntityManager()->persist($shortUrl2); $this->getEntityManager()->flush(); - $shortUrl3 = ShortUrl::fromMeta($meta, $this->relationResolver); + $shortUrl3 = ShortUrl::create($meta, $this->relationResolver); $this->getEntityManager()->persist($shortUrl3); $this->getEntityManager()->flush(); @@ -579,7 +579,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $adminApiKey = ApiKey::create(); $this->getEntityManager()->persist($adminApiKey); - $shortUrl = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([ 'validSince' => $start, 'apiKey' => $apiKey, 'domain' => $rightDomain->getAuthority(), @@ -588,7 +588,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase ]), $this->relationResolver); $this->getEntityManager()->persist($shortUrl); - $nonDomainShortUrl = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $nonDomainShortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([ 'apiKey' => $apiKey, 'longUrl' => 'non-domain', ]), $this->relationResolver); @@ -707,7 +707,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase /** @test */ public function findCrawlableShortCodesReturnsExpectedResult(): void { - $createShortUrl = fn (bool $crawlable) => ShortUrl::fromMeta( + $createShortUrl = fn (bool $crawlable) => ShortUrl::create( ShortUrlCreation::fromRawData(['crawlable' => $crawlable, 'longUrl' => 'foo.com']), ); diff --git a/module/Core/test-db/Tag/Repository/TagRepositoryTest.php b/module/Core/test-db/Tag/Repository/TagRepositoryTest.php index b71577b9..ce0efff9 100644 --- a/module/Core/test-db/Tag/Repository/TagRepositoryTest.php +++ b/module/Core/test-db/Tag/Repository/TagRepositoryTest.php @@ -77,22 +77,22 @@ class TagRepositoryTest extends DatabaseTestCase ['longUrl' => '', 'tags' => $tags, 'apiKey' => $apiKey], ); - $shortUrl = ShortUrl::fromMeta($metaWithTags($firstUrlTags, $apiKey), $this->relationResolver); + $shortUrl = ShortUrl::create($metaWithTags($firstUrlTags, $apiKey), $this->relationResolver); $this->getEntityManager()->persist($shortUrl); $this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance())); $this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance())); $this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance())); - $shortUrl2 = ShortUrl::fromMeta($metaWithTags($secondUrlTags, null), $this->relationResolver); + $shortUrl2 = ShortUrl::create($metaWithTags($secondUrlTags, null), $this->relationResolver); $this->getEntityManager()->persist($shortUrl2); $this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl2, Visitor::emptyInstance())); // One of the tags has two extra short URLs, but with no visits $this->getEntityManager()->persist( - ShortUrl::fromMeta($metaWithTags(['bar'], null), $this->relationResolver), + ShortUrl::create($metaWithTags(['bar'], null), $this->relationResolver), ); $this->getEntityManager()->persist( - ShortUrl::fromMeta($metaWithTags(['bar'], $apiKey), $this->relationResolver), + ShortUrl::create($metaWithTags(['bar'], $apiKey), $this->relationResolver), ); $this->getEntityManager()->flush(); @@ -222,13 +222,13 @@ class TagRepositoryTest extends DatabaseTestCase [$firstUrlTags, $secondUrlTags] = array_chunk($names, 3); - $shortUrl = ShortUrl::fromMeta( + $shortUrl = ShortUrl::create( ShortUrlCreation::fromRawData(['apiKey' => $authorApiKey, 'longUrl' => '', 'tags' => $firstUrlTags]), $this->relationResolver, ); $this->getEntityManager()->persist($shortUrl); - $shortUrl2 = ShortUrl::fromMeta( + $shortUrl2 = ShortUrl::create( ShortUrlCreation::fromRawData( ['domain' => $domain->getAuthority(), 'longUrl' => '', 'tags' => $secondUrlTags], ), diff --git a/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php b/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php index b69190e5..da475832 100644 --- a/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php +++ b/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php @@ -313,7 +313,7 @@ class VisitRepositoryTest extends DatabaseTestCase $apiKey1 = ApiKey::fromMeta(ApiKeyMeta::withRoles(RoleDefinition::forAuthoredShortUrls())); $this->getEntityManager()->persist($apiKey1); - $shortUrl = ShortUrl::fromMeta( + $shortUrl = ShortUrl::create( ShortUrlCreation::fromRawData(['apiKey' => $apiKey1, 'domain' => $domain->getAuthority(), 'longUrl' => '']), $this->relationResolver, ); @@ -322,11 +322,11 @@ class VisitRepositoryTest extends DatabaseTestCase $apiKey2 = ApiKey::fromMeta(ApiKeyMeta::withRoles(RoleDefinition::forAuthoredShortUrls())); $this->getEntityManager()->persist($apiKey2); - $shortUrl2 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['apiKey' => $apiKey2, 'longUrl' => ''])); + $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData(['apiKey' => $apiKey2, 'longUrl' => ''])); $this->getEntityManager()->persist($shortUrl2); $this->createVisitsForShortUrl($shortUrl2, 5); - $shortUrl3 = ShortUrl::fromMeta( + $shortUrl3 = ShortUrl::create( ShortUrlCreation::fromRawData(['apiKey' => $apiKey2, 'domain' => $domain->getAuthority(), 'longUrl' => '']), $this->relationResolver, ); @@ -365,7 +365,7 @@ class VisitRepositoryTest extends DatabaseTestCase /** @test */ public function findOrphanVisitsReturnsExpectedResult(): void { - $shortUrl = ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['longUrl' => ''])); + $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData(['longUrl' => ''])); $this->getEntityManager()->persist($shortUrl); $this->createVisitsForShortUrl($shortUrl, 7); @@ -414,7 +414,7 @@ class VisitRepositoryTest extends DatabaseTestCase /** @test */ public function countOrphanVisitsReturnsExpectedResult(): void { - $shortUrl = ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['longUrl' => ''])); + $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData(['longUrl' => ''])); $this->getEntityManager()->persist($shortUrl); $this->createVisitsForShortUrl($shortUrl, 7); @@ -451,15 +451,15 @@ class VisitRepositoryTest extends DatabaseTestCase /** @test */ public function findNonOrphanVisitsReturnsExpectedResult(): void { - $shortUrl = ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['longUrl' => '1'])); + $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData(['longUrl' => '1'])); $this->getEntityManager()->persist($shortUrl); $this->createVisitsForShortUrl($shortUrl, 7); - $shortUrl2 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['longUrl' => '2'])); + $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData(['longUrl' => '2'])); $this->getEntityManager()->persist($shortUrl2); $this->createVisitsForShortUrl($shortUrl2, 4); - $shortUrl3 = ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['longUrl' => '3'])); + $shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData(['longUrl' => '3'])); $this->getEntityManager()->persist($shortUrl3); $this->createVisitsForShortUrl($shortUrl3, 10); @@ -517,7 +517,7 @@ class VisitRepositoryTest extends DatabaseTestCase array $tags = [], ?ApiKey $apiKey = null, ): array { - $shortUrl = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([ ShortUrlInputFilter::LONG_URL => '', ShortUrlInputFilter::TAGS => $tags, ShortUrlInputFilter::API_KEY => $apiKey, @@ -529,7 +529,7 @@ class VisitRepositoryTest extends DatabaseTestCase $this->createVisitsForShortUrl($shortUrl); if ($withDomain !== false) { - $shortUrlWithDomain = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrlWithDomain = ShortUrl::create(ShortUrlCreation::fromRawData([ 'customSlug' => $shortCode, 'domain' => $domain, 'longUrl' => '', diff --git a/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php b/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php index 99ebb820..3ac690d0 100644 --- a/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php +++ b/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php @@ -35,7 +35,7 @@ class PublishingUpdatesGeneratorTest extends TestCase */ public function visitIsProperlySerializedIntoUpdate(string $method, string $expectedTopic, ?string $title): void { - $shortUrl = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([ 'customSlug' => 'foo', 'longUrl' => '', 'title' => $title, @@ -114,7 +114,7 @@ class PublishingUpdatesGeneratorTest extends TestCase /** @test */ public function shortUrlIsProperlySerializedIntoUpdate(): void { - $shortUrl = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([ 'customSlug' => 'foo', 'longUrl' => '', 'title' => 'The title', diff --git a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php index 0def544e..8b7b392c 100644 --- a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php +++ b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php @@ -98,7 +98,7 @@ class NotifyVisitToRabbitMqTest extends TestCase yield 'orphan visit' => [Visit::forBasePath($visitor), ['newOrphanVisitUpdate']]; yield 'non-orphan visit' => [ Visit::forValidShortUrl( - ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'foo', 'customSlug' => 'bar', ])), diff --git a/module/Core/test/ShortUrl/Entity/ShortUrlTest.php b/module/Core/test/ShortUrl/Entity/ShortUrlTest.php index d874dbf5..026778ae 100644 --- a/module/Core/test/ShortUrl/Entity/ShortUrlTest.php +++ b/module/Core/test/ShortUrl/Entity/ShortUrlTest.php @@ -38,7 +38,7 @@ class ShortUrlTest extends TestCase public function provideInvalidShortUrls(): iterable { yield 'with custom slug' => [ - ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['customSlug' => 'custom-slug', 'longUrl' => ''])), + ShortUrl::create(ShortUrlCreation::fromRawData(['customSlug' => 'custom-slug', 'longUrl' => ''])), 'The short code cannot be regenerated on ShortUrls where a custom slug was provided.', ]; yield 'already persisted' => [ @@ -77,7 +77,7 @@ class ShortUrlTest extends TestCase */ public function shortCodesHaveExpectedLength(?int $length, int $expectedLength): void { - $shortUrl = ShortUrl::fromMeta(ShortUrlCreation::fromRawData( + $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData( [ShortUrlInputFilter::SHORT_CODE_LENGTH => $length, 'longUrl' => ''], )); diff --git a/module/Core/test/ShortUrl/Helper/ShortUrlRedirectionBuilderTest.php b/module/Core/test/ShortUrl/Helper/ShortUrlRedirectionBuilderTest.php index 1a077c19..cb94a9f1 100644 --- a/module/Core/test/ShortUrl/Helper/ShortUrlRedirectionBuilderTest.php +++ b/module/Core/test/ShortUrl/Helper/ShortUrlRedirectionBuilderTest.php @@ -30,7 +30,7 @@ class ShortUrlRedirectionBuilderTest extends TestCase ?string $extraPath, ?bool $forwardQuery, ): void { - $shortUrl = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([ 'longUrl' => 'https://domain.com/foo/bar?some=thing', 'forwardQuery' => $forwardQuery, ])); diff --git a/module/Core/test/ShortUrl/Helper/ShortUrlStringifierTest.php b/module/Core/test/ShortUrl/Helper/ShortUrlStringifierTest.php index d46fbf92..b6d5a123 100644 --- a/module/Core/test/ShortUrl/Helper/ShortUrlStringifierTest.php +++ b/module/Core/test/ShortUrl/Helper/ShortUrlStringifierTest.php @@ -28,7 +28,7 @@ class ShortUrlStringifierTest extends TestCase public function provideConfigAndShortUrls(): iterable { - $shortUrlWithShortCode = fn (string $shortCode, ?string $domain = null) => ShortUrl::fromMeta( + $shortUrlWithShortCode = fn (string $shortCode, ?string $domain = null) => ShortUrl::create( ShortUrlCreation::fromRawData([ 'longUrl' => '', 'customSlug' => $shortCode, diff --git a/module/Core/test/ShortUrl/ShortUrlResolverTest.php b/module/Core/test/ShortUrl/ShortUrlResolverTest.php index 1e86ec1c..9c2bcab3 100644 --- a/module/Core/test/ShortUrl/ShortUrlResolverTest.php +++ b/module/Core/test/ShortUrl/ShortUrlResolverTest.php @@ -114,7 +114,7 @@ class ShortUrlResolverTest extends TestCase $now = Chronos::now(); yield 'maxVisits reached' => [(function () { - $shortUrl = ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['maxVisits' => 3, 'longUrl' => ''])); + $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData(['maxVisits' => 3, 'longUrl' => ''])); $shortUrl->setVisits(new ArrayCollection(map( range(0, 4), fn () => Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance()), @@ -122,14 +122,14 @@ class ShortUrlResolverTest extends TestCase return $shortUrl; })()]; - yield 'future validSince' => [ShortUrl::fromMeta(ShortUrlCreation::fromRawData( + yield 'future validSince' => [ShortUrl::create(ShortUrlCreation::fromRawData( ['validSince' => $now->addMonth()->toAtomString(), 'longUrl' => ''], ))]; - yield 'past validUntil' => [ShortUrl::fromMeta(ShortUrlCreation::fromRawData( + yield 'past validUntil' => [ShortUrl::create(ShortUrlCreation::fromRawData( ['validUntil' => $now->subMonth()->toAtomString(), 'longUrl' => ''], ))]; yield 'mixed' => [(function () use ($now) { - $shortUrl = ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([ 'maxVisits' => 3, 'validUntil' => $now->subMonth()->toAtomString(), 'longUrl' => '', diff --git a/module/Core/test/ShortUrl/Transformer/ShortUrlDataTransformerTest.php b/module/Core/test/ShortUrl/Transformer/ShortUrlDataTransformerTest.php index 376e3d03..c9df4e38 100644 --- a/module/Core/test/ShortUrl/Transformer/ShortUrlDataTransformerTest.php +++ b/module/Core/test/ShortUrl/Transformer/ShortUrlDataTransformerTest.php @@ -43,7 +43,7 @@ class ShortUrlDataTransformerTest extends TestCase 'validUntil' => null, 'maxVisits' => null, ]]; - yield 'max visits only' => [ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + yield 'max visits only' => [ShortUrl::create(ShortUrlCreation::fromRawData([ 'maxVisits' => $maxVisits, 'longUrl' => '', ])), [ @@ -52,7 +52,7 @@ class ShortUrlDataTransformerTest extends TestCase 'maxVisits' => $maxVisits, ]]; yield 'max visits and valid since' => [ - ShortUrl::fromMeta(ShortUrlCreation::fromRawData( + ShortUrl::create(ShortUrlCreation::fromRawData( ['validSince' => $now, 'maxVisits' => $maxVisits, 'longUrl' => ''], )), [ @@ -62,7 +62,7 @@ class ShortUrlDataTransformerTest extends TestCase ], ]; yield 'both dates' => [ - ShortUrl::fromMeta(ShortUrlCreation::fromRawData( + ShortUrl::create(ShortUrlCreation::fromRawData( ['validSince' => $now, 'validUntil' => $now->subDays(10), 'longUrl' => ''], )), [ @@ -72,7 +72,7 @@ class ShortUrlDataTransformerTest extends TestCase ], ]; yield 'everything' => [ - ShortUrl::fromMeta(ShortUrlCreation::fromRawData( + ShortUrl::create(ShortUrlCreation::fromRawData( ['validSince' => $now, 'validUntil' => $now->subDays(5), 'maxVisits' => $maxVisits, 'longUrl' => ''], )), [ diff --git a/module/Core/test/ShortUrl/UrlShortenerTest.php b/module/Core/test/ShortUrl/UrlShortenerTest.php index 4c9e8646..d59de634 100644 --- a/module/Core/test/ShortUrl/UrlShortenerTest.php +++ b/module/Core/test/ShortUrl/UrlShortenerTest.php @@ -107,17 +107,17 @@ class UrlShortenerTest extends TestCase ), ShortUrl::withLongUrl($url)]; yield [ ShortUrlCreation::fromRawData(['findIfExists' => true, 'longUrl' => $url, 'tags' => ['foo', 'bar']]), - ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['longUrl' => $url, 'tags' => ['foo', 'bar']])), + ShortUrl::create(ShortUrlCreation::fromRawData(['longUrl' => $url, 'tags' => ['foo', 'bar']])), ]; yield [ ShortUrlCreation::fromRawData(['findIfExists' => true, 'maxVisits' => 3, 'longUrl' => $url]), - ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['maxVisits' => 3, 'longUrl' => $url])), + ShortUrl::create(ShortUrlCreation::fromRawData(['maxVisits' => 3, 'longUrl' => $url])), ]; yield [ ShortUrlCreation::fromRawData( ['findIfExists' => true, 'validSince' => Chronos::parse('2017-01-01'), 'longUrl' => $url], ), - ShortUrl::fromMeta( + ShortUrl::create( ShortUrlCreation::fromRawData(['validSince' => Chronos::parse('2017-01-01'), 'longUrl' => $url]), ), ]; @@ -125,13 +125,13 @@ class UrlShortenerTest extends TestCase ShortUrlCreation::fromRawData( ['findIfExists' => true, 'validUntil' => Chronos::parse('2017-01-01'), 'longUrl' => $url], ), - ShortUrl::fromMeta( + ShortUrl::create( ShortUrlCreation::fromRawData(['validUntil' => Chronos::parse('2017-01-01'), 'longUrl' => $url]), ), ]; yield [ ShortUrlCreation::fromRawData(['findIfExists' => true, 'domain' => 'example.com', 'longUrl' => $url]), - ShortUrl::fromMeta(ShortUrlCreation::fromRawData(['domain' => 'example.com', 'longUrl' => $url])), + ShortUrl::create(ShortUrlCreation::fromRawData(['domain' => 'example.com', 'longUrl' => $url])), ]; yield [ ShortUrlCreation::fromRawData([ @@ -141,7 +141,7 @@ class UrlShortenerTest extends TestCase 'longUrl' => $url, 'tags' => ['baz', 'foo', 'bar'], ]), - ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + ShortUrl::create(ShortUrlCreation::fromRawData([ 'validUntil' => Chronos::parse('2017-01-01'), 'maxVisits' => 4, 'longUrl' => $url, diff --git a/module/Rest/test-api/Fixtures/ShortUrlsFixture.php b/module/Rest/test-api/Fixtures/ShortUrlsFixture.php index 432e5cad..9a876463 100644 --- a/module/Rest/test-api/Fixtures/ShortUrlsFixture.php +++ b/module/Rest/test-api/Fixtures/ShortUrlsFixture.php @@ -29,7 +29,7 @@ class ShortUrlsFixture extends AbstractFixture implements DependentFixtureInterf $authorApiKey = $this->getReference('author_api_key'); $abcShortUrl = $this->setShortUrlDate( - ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + ShortUrl::create(ShortUrlCreation::fromRawData([ 'customSlug' => 'abc123', 'apiKey' => $authorApiKey, 'longUrl' => 'https://shlink.io', @@ -42,7 +42,7 @@ class ShortUrlsFixture extends AbstractFixture implements DependentFixtureInterf ); $manager->persist($abcShortUrl); - $defShortUrl = $this->setShortUrlDate(ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $defShortUrl = $this->setShortUrlDate(ShortUrl::create(ShortUrlCreation::fromRawData([ 'validSince' => Chronos::parse('2020-05-01'), 'customSlug' => 'def456', 'apiKey' => $authorApiKey, @@ -52,7 +52,7 @@ class ShortUrlsFixture extends AbstractFixture implements DependentFixtureInterf ]), $relationResolver), '2019-01-01 00:00:10'); $manager->persist($defShortUrl); - $customShortUrl = $this->setShortUrlDate(ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $customShortUrl = $this->setShortUrlDate(ShortUrl::create(ShortUrlCreation::fromRawData([ 'customSlug' => 'custom', 'maxVisits' => 2, 'apiKey' => $authorApiKey, @@ -62,7 +62,7 @@ class ShortUrlsFixture extends AbstractFixture implements DependentFixtureInterf $manager->persist($customShortUrl); $ghiShortUrl = $this->setShortUrlDate( - ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + ShortUrl::create(ShortUrlCreation::fromRawData([ 'customSlug' => 'ghi789', 'longUrl' => 'https://shlink.io/documentation/', 'validUntil' => Chronos::parse('2020-05-01'), // In the past @@ -71,7 +71,7 @@ class ShortUrlsFixture extends AbstractFixture implements DependentFixtureInterf ); $manager->persist($ghiShortUrl); - $withDomainDuplicatingShortCode = $this->setShortUrlDate(ShortUrl::fromMeta(ShortUrlCreation::fromRawData([ + $withDomainDuplicatingShortCode = $this->setShortUrlDate(ShortUrl::create(ShortUrlCreation::fromRawData([ 'domain' => 'example.com', 'customSlug' => 'ghi789', 'longUrl' => 'https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-' @@ -80,7 +80,7 @@ class ShortUrlsFixture extends AbstractFixture implements DependentFixtureInterf ]), $relationResolver), '2019-01-01 00:00:30'); $manager->persist($withDomainDuplicatingShortCode); - $withDomainAndSlugShortUrl = $this->setShortUrlDate(ShortUrl::fromMeta(ShortUrlCreation::fromRawData( + $withDomainAndSlugShortUrl = $this->setShortUrlDate(ShortUrl::create(ShortUrlCreation::fromRawData( ['domain' => 'some-domain.com', 'customSlug' => 'custom-with-domain', 'longUrl' => 'https://google.com'], )), '2018-10-20'); $manager->persist($withDomainAndSlugShortUrl); From 0952c488be5689551961bebff1b93d6ec5bcae46 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 11 Dec 2022 18:33:40 +0100 Subject: [PATCH 167/182] Added exclusion flags to ListShortUrlsCommand --- .../CLI/src/Command/ShortUrl/ListShortUrlsCommand.php | 6 ++++-- module/CLI/test-cli/Command/ListShortUrlsTest.php | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php index 86a9dd57..6e735221 100644 --- a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php +++ b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php @@ -79,13 +79,13 @@ class ListShortUrlsCommand extends Command ) ->addOption( 'exclude-max-visits-reached', - null, // TODO + null, InputOption::VALUE_NONE, 'Excludes short URLs which reached their max amount of visits.', ) ->addOption( 'exclude-past-valid-until', - null, // TODO + null, InputOption::VALUE_NONE, 'Excludes short URLs which have a "validUntil" date in the past.', ) @@ -145,6 +145,8 @@ class ListShortUrlsCommand extends Command ShortUrlsParamsInputFilter::ORDER_BY => $orderBy, ShortUrlsParamsInputFilter::START_DATE => $startDate?->toAtomString(), ShortUrlsParamsInputFilter::END_DATE => $endDate?->toAtomString(), + ShortUrlsParamsInputFilter::EXCLUDE_MAX_VISITS_REACHED => $input->getOption('exclude-max-visits-reached'), + ShortUrlsParamsInputFilter::EXCLUDE_PAST_VALID_UNTIL => $input->getOption('exclude-past-valid-until'), ]; if ($all) { diff --git a/module/CLI/test-cli/Command/ListShortUrlsTest.php b/module/CLI/test-cli/Command/ListShortUrlsTest.php index faa47a2f..c98573a5 100644 --- a/module/CLI/test-cli/Command/ListShortUrlsTest.php +++ b/module/CLI/test-cli/Command/ListShortUrlsTest.php @@ -61,6 +61,16 @@ class ListShortUrlsTest extends CliTestCase | custom-with-domain | | http://some-domain.com/custom-with-domain | https://google.com | 2018-10-20T00:00:00+00:00 | 0 | +--------------------+-------+-------------------------------------------+----------------------------- Page 1 of 1 -----------------------------------------------------------+---------------------------+--------------+ OUTPUT]; + yield 'expired excluded' => [['--exclude-max-visits-reached', '--exclude-past-valid-until'], << Date: Sun, 11 Dec 2022 18:36:46 +0100 Subject: [PATCH 168/182] Fixed typo --- module/Core/src/ShortUrl/Repository/ShortUrlRepository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php index d6559973..6acf1c2c 100644 --- a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php +++ b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php @@ -151,9 +151,9 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU $qb ->andWhere($qb->expr()->orX( $qb->expr()->isNull('s.validUntil'), - $qb->expr()->gte('s.validUntil', ':minValidSince'), + $qb->expr()->gte('s.validUntil', ':minValidUntil'), )) - ->setParameter('minValidSince', Chronos::now()->toDateTimeString()); + ->setParameter('minValidUntil', Chronos::now()->toDateTimeString()); } $this->applySpecification($qb, $filtering->apiKey?->spec(), 's'); From 2bf3e6a13b968f830035737b9f6be7e717c24e81 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 12 Dec 2022 20:50:21 +0100 Subject: [PATCH 169/182] Addedsupport for credentials on redis --- composer.json | 2 +- config/autoload/redis.local.php.local | 2 ++ data/infra/redis/redis-acl.conf | 2 ++ docker-compose.yml | 14 +++++++++++++- 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 data/infra/redis/redis-acl.conf diff --git a/composer.json b/composer.json index 6a5bb317..5b58b62b 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "php-middleware/request-id": "^4.1", "pugx/shortid-php": "^1.1", "ramsey/uuid": "^4.5", - "shlinkio/shlink-common": "dev-main#f4101bc as 5.2", + "shlinkio/shlink-common": "dev-main#8d06f0e as 5.2", "shlinkio/shlink-config": "dev-main#96c81fb as 2.3", "shlinkio/shlink-event-dispatcher": "^2.6", "shlinkio/shlink-importer": "dev-main#c97662b as 5.0", diff --git a/config/autoload/redis.local.php.local b/config/autoload/redis.local.php.local index 9bd8fea6..7fd57112 100644 --- a/config/autoload/redis.local.php.local +++ b/config/autoload/redis.local.php.local @@ -7,6 +7,8 @@ return [ 'cache' => [ 'redis' => [ 'servers' => 'tcp://shlink_redis:6379', +// 'servers' => 'tcp://barbar@shlink_redis_acl:6379', +// 'servers' => 'tcp://foo:bar@shlink_redis_acl:6379', ], ], diff --git a/data/infra/redis/redis-acl.conf b/data/infra/redis/redis-acl.conf new file mode 100644 index 00000000..44c26d66 --- /dev/null +++ b/data/infra/redis/redis-acl.conf @@ -0,0 +1,2 @@ +user foo allcommands allkeys on >bar +requirepass barbar diff --git a/docker-compose.yml b/docker-compose.yml index 8293ab03..f3affecb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,7 @@ services: - shlink_db_maria - shlink_db_ms - shlink_redis + - shlink_redis_acl - shlink_mercure - shlink_mercure_proxy - shlink_rabbitmq @@ -65,6 +66,7 @@ services: - shlink_db_maria - shlink_db_ms - shlink_redis + - shlink_redis_acl - shlink_mercure - shlink_mercure_proxy - shlink_rabbitmq @@ -89,6 +91,7 @@ services: - shlink_db_maria - shlink_db_ms - shlink_redis + - shlink_redis_acl - shlink_mercure - shlink_mercure_proxy - shlink_rabbitmq @@ -146,10 +149,19 @@ services: shlink_redis: container_name: shlink_redis - image: redis:6.0-alpine + image: redis:6.2-alpine ports: - "6380:6379" + shlink_redis_acl: + container_name: shlink_redis_acl + image: redis:6.2-alpine + command: ["redis-server", "/usr/local/etc/redis/redis.conf"] + ports: + - "6382:6379" + volumes: + - ./data/infra/redis/redis-acl.conf:/usr/local/etc/redis/redis.conf + shlink_mercure_proxy: container_name: shlink_mercure_proxy image: nginx:1.19.6-alpine From dd345c82eaad5919a9232a45f01d6c09719337a0 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 12 Dec 2022 20:51:43 +0100 Subject: [PATCH 170/182] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31cd05d0..ce5b8204 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * Providing `excludeMaxVisitsReached=true` and/or `excludePastValidUntil=true` to the `GET /short-urls` endpoint. * Providing `--exclude-max-visits-reached` and/or `--exclude-past-valid-until` to the `short-urls:list` command. +* [#1599](https://github.com/shlinkio/shlink/issues/1599) Added support for credentials on redis DSNs, either only password, or both username and password. * [#1616](https://github.com/shlinkio/shlink/issues/1616) Added support to import orphan visits when importing short URLs from another Shlink instance. * [#1519](https://github.com/shlinkio/shlink/issues/1519) Allowing to search short URLs by default domain. * [#1555](https://github.com/shlinkio/shlink/issues/1555) Added full support for PHP 8.2, pdating the dockr image to this version. From 92a83b82a01dcbfc3003016b0cd23a6d17856081 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 13 Dec 2022 19:37:02 +0100 Subject: [PATCH 171/182] Split short URL listing capabilities on its own repo and service --- composer.json | 2 +- module/CLI/config/dependencies.config.php | 2 +- .../Command/ShortUrl/ListShortUrlsCommand.php | 4 +- .../ShortUrl/ListShortUrlsCommandTest.php | 6 +- module/Core/config/dependencies.config.php | 9 + .../Adapter/ShortUrlRepositoryAdapter.php | 4 +- .../Repository/ShortUrlListRepository.php | 165 +++++++++ .../ShortUrlListRepositoryInterface.php | 19 + .../Repository/ShortUrlRepository.php | 141 -------- .../ShortUrlRepositoryInterface.php | 6 - .../Core/src/ShortUrl/ShortUrlListService.php | 31 ++ .../ShortUrl/ShortUrlListServiceInterface.php | 18 + module/Core/src/ShortUrl/ShortUrlService.php | 21 -- .../src/ShortUrl/ShortUrlServiceInterface.php | 7 - .../Repository/ShortUrlListRepositoryTest.php | 337 ++++++++++++++++++ .../Repository/ShortUrlRepositoryTest.php | 312 ---------------- .../Adapter/ShortUrlRepositoryAdapterTest.php | 6 +- .../test/ShortUrl/ShortUrlListServiceTest.php | 53 +++ .../test/ShortUrl/ShortUrlServiceTest.php | 30 -- module/Rest/config/dependencies.config.php | 5 +- .../Action/ShortUrl/ListShortUrlsAction.php | 6 +- .../ShortUrl/ListShortUrlsActionTest.php | 6 +- 22 files changed, 654 insertions(+), 536 deletions(-) create mode 100644 module/Core/src/ShortUrl/Repository/ShortUrlListRepository.php create mode 100644 module/Core/src/ShortUrl/Repository/ShortUrlListRepositoryInterface.php create mode 100644 module/Core/src/ShortUrl/ShortUrlListService.php create mode 100644 module/Core/src/ShortUrl/ShortUrlListServiceInterface.php create mode 100644 module/Core/test-db/ShortUrl/Repository/ShortUrlListRepositoryTest.php create mode 100644 module/Core/test/ShortUrl/ShortUrlListServiceTest.php diff --git a/composer.json b/composer.json index 5b58b62b..033920ca 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "php-middleware/request-id": "^4.1", "pugx/shortid-php": "^1.1", "ramsey/uuid": "^4.5", - "shlinkio/shlink-common": "dev-main#8d06f0e as 5.2", + "shlinkio/shlink-common": "dev-main#e2a5bb7 as 5.2", "shlinkio/shlink-config": "dev-main#96c81fb as 2.3", "shlinkio/shlink-event-dispatcher": "^2.6", "shlinkio/shlink-importer": "dev-main#c97662b as 5.0", diff --git a/module/CLI/config/dependencies.config.php b/module/CLI/config/dependencies.config.php index 51a3f2d7..177e4c8b 100644 --- a/module/CLI/config/dependencies.config.php +++ b/module/CLI/config/dependencies.config.php @@ -84,7 +84,7 @@ return [ ], Command\ShortUrl\ResolveUrlCommand::class => [ShortUrl\ShortUrlResolver::class], Command\ShortUrl\ListShortUrlsCommand::class => [ - ShortUrl\ShortUrlService::class, + ShortUrl\ShortUrlListService::class, ShortUrl\Transformer\ShortUrlDataTransformer::class, ], Command\ShortUrl\GetShortUrlVisitsCommand::class => [Visit\VisitsStatsHelper::class], diff --git a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php index 6e735221..7a9c77af 100644 --- a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php +++ b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php @@ -15,7 +15,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode; use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlsParamsInputFilter; -use Shlinkio\Shlink\Core\ShortUrl\ShortUrlServiceInterface; +use Shlinkio\Shlink\Core\ShortUrl\ShortUrlListServiceInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -39,7 +39,7 @@ class ListShortUrlsCommand extends Command private readonly EndDateOption $endDateOption; public function __construct( - private readonly ShortUrlServiceInterface $shortUrlService, + private readonly ShortUrlListServiceInterface $shortUrlService, private readonly DataTransformerInterface $transformer, ) { parent::__construct(); diff --git a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php index 84b138d4..9b45869f 100644 --- a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php @@ -15,7 +15,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode; -use Shlinkio\Shlink\Core\ShortUrl\ShortUrlServiceInterface; +use Shlinkio\Shlink\Core\ShortUrl\ShortUrlListServiceInterface; use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer; use Shlinkio\Shlink\Rest\ApiKey\Model\ApiKeyMeta; use Shlinkio\Shlink\Rest\Entity\ApiKey; @@ -30,11 +30,11 @@ class ListShortUrlsCommandTest extends TestCase use CliTestUtilsTrait; private CommandTester $commandTester; - private MockObject & ShortUrlServiceInterface $shortUrlService; + private MockObject & ShortUrlListServiceInterface $shortUrlService; protected function setUp(): void { - $this->shortUrlService = $this->createMock(ShortUrlServiceInterface::class); + $this->shortUrlService = $this->createMock(ShortUrlListServiceInterface::class); $command = new ListShortUrlsCommand($this->shortUrlService, new ShortUrlDataTransformer( new ShortUrlStringifier([]), )); diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 5f51c2d7..708bb8a3 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -7,6 +7,7 @@ namespace Shlinkio\Shlink\Core; use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory; use Laminas\ServiceManager\Factory\InvokableFactory; use Psr\EventDispatcher\EventDispatcherInterface; +use Shlinkio\Shlink\Common\Doctrine\EntityRepositoryFactory; use Shlinkio\Shlink\Config\Factory\ValinorConfigFactory; use Shlinkio\Shlink\Core\ErrorHandler; use Shlinkio\Shlink\Core\Options\NotFoundRedirectOptions; @@ -34,6 +35,7 @@ return [ ShortUrl\UrlShortener::class => ConfigAbstractFactory::class, ShortUrl\ShortUrlService::class => ConfigAbstractFactory::class, + ShortUrl\ShortUrlListService::class => ConfigAbstractFactory::class, ShortUrl\DeleteShortUrlService::class => ConfigAbstractFactory::class, ShortUrl\ShortUrlResolver::class => ConfigAbstractFactory::class, ShortUrl\Helper\ShortCodeUniquenessHelper::class => ConfigAbstractFactory::class, @@ -44,6 +46,10 @@ return [ ShortUrl\Transformer\ShortUrlDataTransformer::class => ConfigAbstractFactory::class, ShortUrl\Middleware\ExtraPathRedirectMiddleware::class => ConfigAbstractFactory::class, ShortUrl\Middleware\TrimTrailingSlashMiddleware::class => ConfigAbstractFactory::class, + ShortUrl\Repository\ShortUrlListRepository::class => [ + EntityRepositoryFactory::class, + ShortUrl\Entity\ShortUrl::class, + ], Tag\TagService::class => ConfigAbstractFactory::class, @@ -108,6 +114,9 @@ return [ ShortUrl\ShortUrlResolver::class, ShortUrl\Helper\ShortUrlTitleResolutionHelper::class, ShortUrl\Resolver\PersistenceShortUrlRelationResolver::class, + ], + ShortUrl\ShortUrlListService::class => [ + ShortUrl\Repository\ShortUrlListRepository::class, Options\UrlShortenerOptions::class, ], Visit\Geolocation\VisitLocator::class => ['em'], diff --git a/module/Core/src/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapter.php b/module/Core/src/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapter.php index d88d8b81..83ce8bd9 100644 --- a/module/Core/src/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapter.php +++ b/module/Core/src/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapter.php @@ -8,13 +8,13 @@ use Pagerfanta\Adapter\AdapterInterface; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering; use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering; -use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface; +use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlListRepositoryInterface; use Shlinkio\Shlink\Rest\Entity\ApiKey; class ShortUrlRepositoryAdapter implements AdapterInterface { public function __construct( - private readonly ShortUrlRepositoryInterface $repository, + private readonly ShortUrlListRepositoryInterface $repository, private readonly ShortUrlsParams $params, private readonly ?ApiKey $apiKey, private readonly string $defaultDomain, diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlListRepository.php b/module/Core/src/ShortUrl/Repository/ShortUrlListRepository.php new file mode 100644 index 00000000..35d7996f --- /dev/null +++ b/module/Core/src/ShortUrl/Repository/ShortUrlListRepository.php @@ -0,0 +1,165 @@ +createListQueryBuilder($filtering); + $qb->select('DISTINCT s') + ->setMaxResults($filtering->limit) + ->setFirstResult($filtering->offset); + + // In case the ordering has been specified, the query could be more complex. Process it + $this->processOrderByForList($qb, $filtering); + + $result = $qb->getQuery()->getResult(); + if ($filtering->orderBy->field === 'visits') { + return array_column($result, 0); + } + + return $result; + } + + private function processOrderByForList(QueryBuilder $qb, ShortUrlsListFiltering $filtering): void + { + // With no explicit order by, fallback to dateCreated-DESC + if (! $filtering->orderBy->hasOrderField()) { + $qb->orderBy('s.dateCreated', 'DESC'); + return; + } + + $fieldName = $filtering->orderBy->field; + $order = $filtering->orderBy->direction; + + if ($fieldName === 'visits') { + // FIXME This query is inefficient. + // Diagnostic: It might need to use a sub-query, as done with the tags list query. + $qb->addSelect('COUNT(DISTINCT v)') + ->leftJoin('s.visits', 'v') + ->groupBy('s') + ->orderBy('COUNT(DISTINCT v)', $order); + } elseif (contains(['longUrl', 'shortCode', 'dateCreated', 'title'], $fieldName)) { + $qb->orderBy('s.' . $fieldName, $order); + } + } + + public function countList(ShortUrlsCountFiltering $filtering): int + { + $qb = $this->createListQueryBuilder($filtering); + $qb->select('COUNT(DISTINCT s)'); + + return (int) $qb->getQuery()->getSingleScalarResult(); + } + + private function createListQueryBuilder(ShortUrlsCountFiltering $filtering): QueryBuilder + { + $qb = $this->getEntityManager()->createQueryBuilder(); + $qb->from(ShortUrl::class, 's') + ->where('1=1'); + + $dateRange = $filtering->dateRange; + if ($dateRange?->startDate !== null) { + $qb->andWhere($qb->expr()->gte('s.dateCreated', ':startDate')); + $qb->setParameter('startDate', $dateRange->startDate, ChronosDateTimeType::CHRONOS_DATETIME); + } + if ($dateRange?->endDate !== null) { + $qb->andWhere($qb->expr()->lte('s.dateCreated', ':endDate')); + $qb->setParameter('endDate', $dateRange->endDate, ChronosDateTimeType::CHRONOS_DATETIME); + } + + $searchTerm = $filtering->searchTerm; + $tags = $filtering->tags; + // Apply search term to every searchable field if not empty + if (! empty($searchTerm)) { + // Left join with tags only if no tags were provided. In case of tags, an inner join will be done later + if (empty($tags)) { + $qb->leftJoin('s.tags', 't'); + } + + // Apply general search conditions + $conditions = [ + $qb->expr()->like('s.longUrl', ':searchPattern'), + $qb->expr()->like('s.shortCode', ':searchPattern'), + $qb->expr()->like('s.title', ':searchPattern'), + $qb->expr()->like('d.authority', ':searchPattern'), + ]; + + // Include default domain in search if provided + if ($filtering->searchIncludesDefaultDomain) { + $conditions[] = $qb->expr()->isNull('s.domain'); + } + + // Apply tag conditions, only when not filtering by all provided tags + $tagsMode = $filtering->tagsMode ?? TagsMode::ANY; + if (empty($tags) || $tagsMode === TagsMode::ANY) { + $conditions[] = $qb->expr()->like('t.name', ':searchPattern'); + } + + $qb->leftJoin('s.domain', 'd') + ->andWhere($qb->expr()->orX(...$conditions)) + ->setParameter('searchPattern', '%' . $searchTerm . '%'); + } + + // Filter by tags if provided + if (! empty($tags)) { + $tagsMode = $filtering->tagsMode ?? TagsMode::ANY; + $tagsMode === TagsMode::ANY + ? $qb->join('s.tags', 't')->andWhere($qb->expr()->in('t.name', $tags)) + : $this->joinAllTags($qb, $tags); + } + + if ($filtering->excludeMaxVisitsReached) { + $qb->andWhere($qb->expr()->orX( + $qb->expr()->isNull('s.maxVisits'), + $qb->expr()->gt( + 's.maxVisits', + sprintf('(SELECT COUNT(innerV.id) FROM %s as innerV WHERE innerV.shortUrl=s)', Visit::class), + ), + )); + } + + if ($filtering->excludePastValidUntil) { + $qb + ->andWhere($qb->expr()->orX( + $qb->expr()->isNull('s.validUntil'), + $qb->expr()->gte('s.validUntil', ':minValidUntil'), + )) + ->setParameter('minValidUntil', Chronos::now()->toDateTimeString()); + } + + $this->applySpecification($qb, $filtering->apiKey?->spec(), 's'); + + return $qb; + } + + private function joinAllTags(QueryBuilder $qb, array $tags): void + { + foreach ($tags as $index => $tag) { + $alias = 't_' . $index; + $qb->join('s.tags', $alias, Join::WITH, $alias . '.name = :tag' . $index) + ->setParameter('tag' . $index, $tag); + } + } +} diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlListRepositoryInterface.php b/module/Core/src/ShortUrl/Repository/ShortUrlListRepositoryInterface.php new file mode 100644 index 00000000..130e0db7 --- /dev/null +++ b/module/Core/src/ShortUrl/Repository/ShortUrlListRepositoryInterface.php @@ -0,0 +1,19 @@ +createListQueryBuilder($filtering); - $qb->select('DISTINCT s') - ->setMaxResults($filtering->limit) - ->setFirstResult($filtering->offset); - - // In case the ordering has been specified, the query could be more complex. Process it - $this->processOrderByForList($qb, $filtering); - - $result = $qb->getQuery()->getResult(); - if ($filtering->orderBy->field === 'visits') { - return array_column($result, 0); - } - - return $result; - } - - private function processOrderByForList(QueryBuilder $qb, ShortUrlsListFiltering $filtering): void - { - // With no explicit order by, fallback to dateCreated-DESC - if (! $filtering->orderBy->hasOrderField()) { - $qb->orderBy('s.dateCreated', 'DESC'); - return; - } - - $fieldName = $filtering->orderBy->field; - $order = $filtering->orderBy->direction; - - if ($fieldName === 'visits') { - // FIXME This query is inefficient. - // Diagnostic: It might need to use a sub-query, as done with the tags list query. - $qb->addSelect('COUNT(DISTINCT v)') - ->leftJoin('s.visits', 'v') - ->groupBy('s') - ->orderBy('COUNT(DISTINCT v)', $order); - } elseif (contains(['longUrl', 'shortCode', 'dateCreated', 'title'], $fieldName)) { - $qb->orderBy('s.' . $fieldName, $order); - } - } - - public function countList(ShortUrlsCountFiltering $filtering): int - { - $qb = $this->createListQueryBuilder($filtering); - $qb->select('COUNT(DISTINCT s)'); - - return (int) $qb->getQuery()->getSingleScalarResult(); - } - - private function createListQueryBuilder(ShortUrlsCountFiltering $filtering): QueryBuilder - { - $qb = $this->getEntityManager()->createQueryBuilder(); - $qb->from(ShortUrl::class, 's') - ->where('1=1'); - - $dateRange = $filtering->dateRange; - if ($dateRange?->startDate !== null) { - $qb->andWhere($qb->expr()->gte('s.dateCreated', ':startDate')); - $qb->setParameter('startDate', $dateRange->startDate, ChronosDateTimeType::CHRONOS_DATETIME); - } - if ($dateRange?->endDate !== null) { - $qb->andWhere($qb->expr()->lte('s.dateCreated', ':endDate')); - $qb->setParameter('endDate', $dateRange->endDate, ChronosDateTimeType::CHRONOS_DATETIME); - } - - $searchTerm = $filtering->searchTerm; - $tags = $filtering->tags; - // Apply search term to every searchable field if not empty - if (! empty($searchTerm)) { - // Left join with tags only if no tags were provided. In case of tags, an inner join will be done later - if (empty($tags)) { - $qb->leftJoin('s.tags', 't'); - } - - // Apply general search conditions - $conditions = [ - $qb->expr()->like('s.longUrl', ':searchPattern'), - $qb->expr()->like('s.shortCode', ':searchPattern'), - $qb->expr()->like('s.title', ':searchPattern'), - $qb->expr()->like('d.authority', ':searchPattern'), - ]; - - // Include default domain in search if provided - if ($filtering->searchIncludesDefaultDomain) { - $conditions[] = $qb->expr()->isNull('s.domain'); - } - - // Apply tag conditions, only when not filtering by all provided tags - $tagsMode = $filtering->tagsMode ?? TagsMode::ANY; - if (empty($tags) || $tagsMode === TagsMode::ANY) { - $conditions[] = $qb->expr()->like('t.name', ':searchPattern'); - } - - $qb->leftJoin('s.domain', 'd') - ->andWhere($qb->expr()->orX(...$conditions)) - ->setParameter('searchPattern', '%' . $searchTerm . '%'); - } - - // Filter by tags if provided - if (! empty($tags)) { - $tagsMode = $filtering->tagsMode ?? TagsMode::ANY; - $tagsMode === TagsMode::ANY - ? $qb->join('s.tags', 't')->andWhere($qb->expr()->in('t.name', $tags)) - : $this->joinAllTags($qb, $tags); - } - - if ($filtering->excludeMaxVisitsReached) { - $qb->andWhere($qb->expr()->orX( - $qb->expr()->isNull('s.maxVisits'), - $qb->expr()->gt( - 's.maxVisits', - sprintf('(SELECT COUNT(innerV.id) FROM %s as innerV WHERE innerV.shortUrl=s)', Visit::class), - ), - )); - } - - if ($filtering->excludePastValidUntil) { - $qb - ->andWhere($qb->expr()->orX( - $qb->expr()->isNull('s.validUntil'), - $qb->expr()->gte('s.validUntil', ':minValidUntil'), - )) - ->setParameter('minValidUntil', Chronos::now()->toDateTimeString()); - } - - $this->applySpecification($qb, $filtering->apiKey?->spec(), 's'); - - return $qb; - } - public function findOneWithDomainFallback(ShortUrlIdentifier $identifier): ?ShortUrl { // When ordering DESC, Postgres puts nulls at the beginning while the rest of supported DB engines put them at diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlRepositoryInterface.php b/module/Core/src/ShortUrl/Repository/ShortUrlRepositoryInterface.php index 79cd0352..ad5e3a5d 100644 --- a/module/Core/src/ShortUrl/Repository/ShortUrlRepositoryInterface.php +++ b/module/Core/src/ShortUrl/Repository/ShortUrlRepositoryInterface.php @@ -10,16 +10,10 @@ use Happyr\DoctrineSpecification\Specification\Specification; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; -use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering; -use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; interface ShortUrlRepositoryInterface extends ObjectRepository, EntitySpecificationRepositoryInterface { - public function findList(ShortUrlsListFiltering $filtering): array; - - public function countList(ShortUrlsCountFiltering $filtering): int; - public function findOneWithDomainFallback(ShortUrlIdentifier $identifier): ?ShortUrl; public function findOne(ShortUrlIdentifier $identifier, ?Specification $spec = null): ?ShortUrl; diff --git a/module/Core/src/ShortUrl/ShortUrlListService.php b/module/Core/src/ShortUrl/ShortUrlListService.php new file mode 100644 index 00000000..5287f14d --- /dev/null +++ b/module/Core/src/ShortUrl/ShortUrlListService.php @@ -0,0 +1,31 @@ +urlShortenerOptions->domain['hostname'] ?? ''; + $paginator = new Paginator(new ShortUrlRepositoryAdapter($this->repo, $params, $apiKey, $defaultDomain)); + $paginator->setMaxPerPage($params->itemsPerPage) + ->setCurrentPage($params->page); + + return $paginator; + } +} diff --git a/module/Core/src/ShortUrl/ShortUrlListServiceInterface.php b/module/Core/src/ShortUrl/ShortUrlListServiceInterface.php new file mode 100644 index 00000000..ef7b31c2 --- /dev/null +++ b/module/Core/src/ShortUrl/ShortUrlListServiceInterface.php @@ -0,0 +1,18 @@ +em->getRepository(ShortUrl::class); - $defaultDomain = $this->urlShortenerOptions->domain['hostname'] ?? ''; - $paginator = new Paginator(new ShortUrlRepositoryAdapter($repo, $params, $apiKey, $defaultDomain)); - $paginator->setMaxPerPage($params->itemsPerPage) - ->setCurrentPage($params->page); - - return $paginator; - } - /** * @throws ShortUrlNotFoundException * @throws InvalidUrlException diff --git a/module/Core/src/ShortUrl/ShortUrlServiceInterface.php b/module/Core/src/ShortUrl/ShortUrlServiceInterface.php index 0ada5fe0..3365374e 100644 --- a/module/Core/src/ShortUrl/ShortUrlServiceInterface.php +++ b/module/Core/src/ShortUrl/ShortUrlServiceInterface.php @@ -4,22 +4,15 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\ShortUrl; -use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlEdition; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; -use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlsParams; use Shlinkio\Shlink\Rest\Entity\ApiKey; interface ShortUrlServiceInterface { - /** - * @return ShortUrl[]|Paginator - */ - public function listShortUrls(ShortUrlsParams $params, ?ApiKey $apiKey = null): Paginator; - /** * @throws ShortUrlNotFoundException * @throws InvalidUrlException diff --git a/module/Core/test-db/ShortUrl/Repository/ShortUrlListRepositoryTest.php b/module/Core/test-db/ShortUrl/Repository/ShortUrlListRepositoryTest.php new file mode 100644 index 00000000..76c87bd0 --- /dev/null +++ b/module/Core/test-db/ShortUrl/Repository/ShortUrlListRepositoryTest.php @@ -0,0 +1,337 @@ +getEntityManager(); + $this->repo = new ShortUrlListRepository($em, $em->getClassMetadata(ShortUrl::class)); + $this->relationResolver = new PersistenceShortUrlRelationResolver($em); + } + + /** @test */ + public function countListReturnsProperNumberOfResults(): void + { + $count = 5; + for ($i = 0; $i < $count; $i++) { + $this->getEntityManager()->persist(ShortUrl::withLongUrl((string) $i)); + } + $this->getEntityManager()->flush(); + + self::assertEquals($count, $this->repo->countList(new ShortUrlsCountFiltering())); + } + + /** @test */ + public function findListProperlyFiltersResult(): void + { + $foo = ShortUrl::create( + ShortUrlCreation::fromRawData(['longUrl' => 'foo', 'tags' => ['bar']]), + $this->relationResolver, + ); + $this->getEntityManager()->persist($foo); + + $bar = ShortUrl::withLongUrl('bar'); + $visit = Visit::forValidShortUrl($bar, Visitor::emptyInstance()); + $this->getEntityManager()->persist($visit); + $bar->setVisits(new ArrayCollection([$visit])); + $this->getEntityManager()->persist($bar); + + $foo2 = ShortUrl::withLongUrl('foo_2'); + $ref = new ReflectionObject($foo2); + $dateProp = $ref->getProperty('dateCreated'); + $dateProp->setAccessible(true); + $dateProp->setValue($foo2, Chronos::now()->subDays(5)); + $this->getEntityManager()->persist($foo2); + + $this->getEntityManager()->flush(); + + $result = $this->repo->findList( + new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), 'foo', ['bar']), + ); + self::assertCount(1, $result); + self::assertEquals(1, $this->repo->countList(new ShortUrlsCountFiltering('foo', ['bar']))); + self::assertSame($foo, $result[0]); + + // Assert searched text also applies to tags + $result = $this->repo->findList(new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), 'bar')); + self::assertCount(2, $result); + self::assertEquals(2, $this->repo->countList(new ShortUrlsCountFiltering('bar'))); + self::assertContains($foo, $result); + + $result = $this->repo->findList(new ShortUrlsListFiltering(null, null, Ordering::emptyInstance())); + self::assertCount(3, $result); + + $result = $this->repo->findList(new ShortUrlsListFiltering(2, null, Ordering::emptyInstance())); + self::assertCount(2, $result); + + $result = $this->repo->findList(new ShortUrlsListFiltering(2, 1, Ordering::emptyInstance())); + self::assertCount(2, $result); + + self::assertCount(1, $this->repo->findList(new ShortUrlsListFiltering(2, 2, Ordering::emptyInstance()))); + + $result = $this->repo->findList( + new ShortUrlsListFiltering(null, null, Ordering::fromTuple(['visits', 'DESC'])), + ); + self::assertCount(3, $result); + self::assertSame($bar, $result[0]); + + $result = $this->repo->findList( + new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, [], null, DateRange::until( + Chronos::now()->subDays(2), + )), + ); + self::assertCount(1, $result); + self::assertEquals(1, $this->repo->countList(new ShortUrlsCountFiltering(null, [], null, DateRange::until( + Chronos::now()->subDays(2), + )))); + self::assertSame($foo2, $result[0]); + + self::assertCount(2, $this->repo->findList( + new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, [], null, DateRange::since( + Chronos::now()->subDays(2), + )), + )); + self::assertEquals(2, $this->repo->countList( + new ShortUrlsCountFiltering(null, [], null, DateRange::since(Chronos::now()->subDays(2))), + )); + } + + /** @test */ + public function findListProperlyMapsFieldNamesToColumnNamesWhenOrdering(): void + { + $urls = ['a', 'z', 'c', 'b']; + foreach ($urls as $url) { + $this->getEntityManager()->persist(ShortUrl::withLongUrl($url)); + } + + $this->getEntityManager()->flush(); + + $result = $this->repo->findList( + new ShortUrlsListFiltering(null, null, Ordering::fromTuple(['longUrl', 'ASC'])), + ); + + 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 */ + public function findListReturnsOnlyThoseWithMatchingTags(): void + { + $shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo1', + 'tags' => ['foo', 'bar'], + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl1); + $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo2', + 'tags' => ['foo', 'baz'], + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl2); + $shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo3', + 'tags' => ['foo'], + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl3); + $shortUrl4 = ShortUrl::create(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo4', + 'tags' => ['bar', 'baz'], + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl4); + $shortUrl5 = ShortUrl::create(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo5', + 'tags' => ['bar', 'baz'], + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl5); + + $this->getEntityManager()->flush(); + + self::assertCount(5, $this->repo->findList( + new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, ['foo', 'bar']), + )); + self::assertCount(5, $this->repo->findList(new ShortUrlsListFiltering( + null, + null, + Ordering::emptyInstance(), + null, + ['foo', 'bar'], + TagsMode::ANY, + ))); + self::assertCount(1, $this->repo->findList(new ShortUrlsListFiltering( + null, + null, + Ordering::emptyInstance(), + null, + ['foo', 'bar'], + TagsMode::ALL, + ))); + self::assertEquals(5, $this->repo->countList(new ShortUrlsCountFiltering(null, ['foo', 'bar']))); + self::assertEquals(5, $this->repo->countList(new ShortUrlsCountFiltering(null, ['foo', 'bar'], TagsMode::ANY))); + self::assertEquals(1, $this->repo->countList(new ShortUrlsCountFiltering(null, ['foo', 'bar'], TagsMode::ALL))); + + self::assertCount(4, $this->repo->findList( + new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, ['bar', 'baz']), + )); + self::assertCount(4, $this->repo->findList(new ShortUrlsListFiltering( + null, + null, + Ordering::emptyInstance(), + null, + ['bar', 'baz'], + TagsMode::ANY, + ))); + self::assertCount(2, $this->repo->findList(new ShortUrlsListFiltering( + null, + null, + Ordering::emptyInstance(), + null, + ['bar', 'baz'], + TagsMode::ALL, + ))); + self::assertEquals(4, $this->repo->countList(new ShortUrlsCountFiltering(null, ['bar', 'baz']))); + self::assertEquals(4, $this->repo->countList( + new ShortUrlsCountFiltering(null, ['bar', 'baz'], TagsMode::ANY), + )); + self::assertEquals(2, $this->repo->countList( + new ShortUrlsCountFiltering(null, ['bar', 'baz'], TagsMode::ALL), + )); + + self::assertCount(5, $this->repo->findList( + new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, ['foo', 'bar', 'baz']), + )); + self::assertCount(5, $this->repo->findList(new ShortUrlsListFiltering( + null, + null, + Ordering::emptyInstance(), + null, + ['foo', 'bar', 'baz'], + TagsMode::ANY, + ))); + self::assertCount(0, $this->repo->findList(new ShortUrlsListFiltering( + null, + null, + Ordering::emptyInstance(), + null, + ['foo', 'bar', 'baz'], + TagsMode::ALL, + ))); + self::assertEquals(5, $this->repo->countList(new ShortUrlsCountFiltering(null, ['foo', 'bar', 'baz']))); + self::assertEquals(5, $this->repo->countList( + new ShortUrlsCountFiltering(null, ['foo', 'bar', 'baz'], TagsMode::ANY), + )); + self::assertEquals(0, $this->repo->countList( + new ShortUrlsCountFiltering(null, ['foo', 'bar', 'baz'], TagsMode::ALL), + )); + } + + /** @test */ + public function findListReturnsOnlyThoseWithMatchingDomains(): void + { + $shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo1', + 'domain' => null, + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl1); + $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo2', + 'domain' => null, + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl2); + $shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo3', + 'domain' => 'another.com', + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl3); + + $this->getEntityManager()->flush(); + + $buildFiltering = static fn (string $searchTerm) => new ShortUrlsListFiltering( + null, + null, + Ordering::emptyInstance(), + searchTerm: $searchTerm, + defaultDomain: 'deFaulT-domain.com', + ); + + self::assertCount(2, $this->repo->findList($buildFiltering('default-dom'))); + self::assertCount(2, $this->repo->findList($buildFiltering('DOM'))); + self::assertCount(1, $this->repo->findList($buildFiltering('another'))); + self::assertCount(3, $this->repo->findList($buildFiltering('foo'))); + self::assertCount(0, $this->repo->findList($buildFiltering('no results'))); + } + + /** @test */ + public function findListReturnsOnlyThoseWithoutExcludedUrls(): void + { + $shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo1', + 'validUntil' => Chronos::now()->addDays(1)->toAtomString(), + 'maxVisits' => 100, + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl1); + $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo2', + 'validUntil' => Chronos::now()->subDays(1)->toAtomString(), + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl2); + $shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo3', + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl3); + $shortUrl4 = ShortUrl::create(ShortUrlCreation::fromRawData([ + 'longUrl' => 'foo4', + 'maxVisits' => 3, + ]), $this->relationResolver); + $this->getEntityManager()->persist($shortUrl4); + $this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::emptyInstance())); + $this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::emptyInstance())); + $this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::emptyInstance())); + + $this->getEntityManager()->flush(); + + $filtering = static fn (bool $excludeMaxVisitsReached, bool $excludePastValidUntil) => + new ShortUrlsListFiltering( + null, + null, + Ordering::emptyInstance(), + excludeMaxVisitsReached: $excludeMaxVisitsReached, + excludePastValidUntil: $excludePastValidUntil, + ); + + self::assertCount(4, $this->repo->findList($filtering(false, false))); + self::assertEquals(4, $this->repo->countList($filtering(false, false))); + self::assertCount(3, $this->repo->findList($filtering(true, false))); + self::assertEquals(3, $this->repo->countList($filtering(true, false))); + self::assertCount(3, $this->repo->findList($filtering(false, true))); + self::assertEquals(3, $this->repo->countList($filtering(false, true))); + self::assertCount(2, $this->repo->findList($filtering(true, true))); + self::assertEquals(2, $this->repo->countList($filtering(true, true))); + } +} diff --git a/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php b/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php index dc1d6420..a477eff8 100644 --- a/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php @@ -5,21 +5,12 @@ declare(strict_types=1); namespace ShlinkioDbTest\Shlink\Core\ShortUrl\Repository; use Cake\Chronos\Chronos; -use Doctrine\Common\Collections\ArrayCollection; -use ReflectionObject; -use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Domain\Entity\Domain; -use Shlinkio\Shlink\Core\Model\Ordering; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; -use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode; -use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering; -use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering; use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\ShortUrl\Resolver\PersistenceShortUrlRelationResolver; -use Shlinkio\Shlink\Core\Visit\Entity\Visit; -use Shlinkio\Shlink\Core\Visit\Model\Visitor; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Importer\Sources\ImportSource; use Shlinkio\Shlink\Rest\ApiKey\Model\ApiKeyMeta; @@ -27,8 +18,6 @@ use Shlinkio\Shlink\Rest\ApiKey\Model\RoleDefinition; use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase; -use function count; - class ShortUrlRepositoryTest extends DatabaseTestCase { private ShortUrlRepository $repo; @@ -86,307 +75,6 @@ class ShortUrlRepositoryTest extends DatabaseTestCase )); } - /** @test */ - public function countListReturnsProperNumberOfResults(): void - { - $count = 5; - for ($i = 0; $i < $count; $i++) { - $this->getEntityManager()->persist(ShortUrl::withLongUrl((string) $i)); - } - $this->getEntityManager()->flush(); - - self::assertEquals($count, $this->repo->countList(new ShortUrlsCountFiltering())); - } - - /** @test */ - public function findListProperlyFiltersResult(): void - { - $foo = ShortUrl::create( - ShortUrlCreation::fromRawData(['longUrl' => 'foo', 'tags' => ['bar']]), - $this->relationResolver, - ); - $this->getEntityManager()->persist($foo); - - $bar = ShortUrl::withLongUrl('bar'); - $visit = Visit::forValidShortUrl($bar, Visitor::emptyInstance()); - $this->getEntityManager()->persist($visit); - $bar->setVisits(new ArrayCollection([$visit])); - $this->getEntityManager()->persist($bar); - - $foo2 = ShortUrl::withLongUrl('foo_2'); - $ref = new ReflectionObject($foo2); - $dateProp = $ref->getProperty('dateCreated'); - $dateProp->setAccessible(true); - $dateProp->setValue($foo2, Chronos::now()->subDays(5)); - $this->getEntityManager()->persist($foo2); - - $this->getEntityManager()->flush(); - - $result = $this->repo->findList( - new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), 'foo', ['bar']), - ); - self::assertCount(1, $result); - self::assertEquals(1, $this->repo->countList(new ShortUrlsCountFiltering('foo', ['bar']))); - self::assertSame($foo, $result[0]); - - // Assert searched text also applies to tags - $result = $this->repo->findList(new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), 'bar')); - self::assertCount(2, $result); - self::assertEquals(2, $this->repo->countList(new ShortUrlsCountFiltering('bar'))); - self::assertContains($foo, $result); - - $result = $this->repo->findList(new ShortUrlsListFiltering(null, null, Ordering::emptyInstance())); - self::assertCount(3, $result); - - $result = $this->repo->findList(new ShortUrlsListFiltering(2, null, Ordering::emptyInstance())); - self::assertCount(2, $result); - - $result = $this->repo->findList(new ShortUrlsListFiltering(2, 1, Ordering::emptyInstance())); - self::assertCount(2, $result); - - self::assertCount(1, $this->repo->findList(new ShortUrlsListFiltering(2, 2, Ordering::emptyInstance()))); - - $result = $this->repo->findList( - new ShortUrlsListFiltering(null, null, Ordering::fromTuple(['visits', 'DESC'])), - ); - self::assertCount(3, $result); - self::assertSame($bar, $result[0]); - - $result = $this->repo->findList( - new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, [], null, DateRange::until( - Chronos::now()->subDays(2), - )), - ); - self::assertCount(1, $result); - self::assertEquals(1, $this->repo->countList(new ShortUrlsCountFiltering(null, [], null, DateRange::until( - Chronos::now()->subDays(2), - )))); - self::assertSame($foo2, $result[0]); - - self::assertCount(2, $this->repo->findList( - new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, [], null, DateRange::since( - Chronos::now()->subDays(2), - )), - )); - self::assertEquals(2, $this->repo->countList( - new ShortUrlsCountFiltering(null, [], null, DateRange::since(Chronos::now()->subDays(2))), - )); - } - - /** @test */ - public function findListProperlyMapsFieldNamesToColumnNamesWhenOrdering(): void - { - $urls = ['a', 'z', 'c', 'b']; - foreach ($urls as $url) { - $this->getEntityManager()->persist(ShortUrl::withLongUrl($url)); - } - - $this->getEntityManager()->flush(); - - $result = $this->repo->findList( - new ShortUrlsListFiltering(null, null, Ordering::fromTuple(['longUrl', 'ASC'])), - ); - - 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 */ - public function findListReturnsOnlyThoseWithMatchingTags(): void - { - $shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([ - 'longUrl' => 'foo1', - 'tags' => ['foo', 'bar'], - ]), $this->relationResolver); - $this->getEntityManager()->persist($shortUrl1); - $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([ - 'longUrl' => 'foo2', - 'tags' => ['foo', 'baz'], - ]), $this->relationResolver); - $this->getEntityManager()->persist($shortUrl2); - $shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([ - 'longUrl' => 'foo3', - 'tags' => ['foo'], - ]), $this->relationResolver); - $this->getEntityManager()->persist($shortUrl3); - $shortUrl4 = ShortUrl::create(ShortUrlCreation::fromRawData([ - 'longUrl' => 'foo4', - 'tags' => ['bar', 'baz'], - ]), $this->relationResolver); - $this->getEntityManager()->persist($shortUrl4); - $shortUrl5 = ShortUrl::create(ShortUrlCreation::fromRawData([ - 'longUrl' => 'foo5', - 'tags' => ['bar', 'baz'], - ]), $this->relationResolver); - $this->getEntityManager()->persist($shortUrl5); - - $this->getEntityManager()->flush(); - - self::assertCount(5, $this->repo->findList( - new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, ['foo', 'bar']), - )); - self::assertCount(5, $this->repo->findList(new ShortUrlsListFiltering( - null, - null, - Ordering::emptyInstance(), - null, - ['foo', 'bar'], - TagsMode::ANY, - ))); - self::assertCount(1, $this->repo->findList(new ShortUrlsListFiltering( - null, - null, - Ordering::emptyInstance(), - null, - ['foo', 'bar'], - TagsMode::ALL, - ))); - self::assertEquals(5, $this->repo->countList(new ShortUrlsCountFiltering(null, ['foo', 'bar']))); - self::assertEquals(5, $this->repo->countList(new ShortUrlsCountFiltering(null, ['foo', 'bar'], TagsMode::ANY))); - self::assertEquals(1, $this->repo->countList(new ShortUrlsCountFiltering(null, ['foo', 'bar'], TagsMode::ALL))); - - self::assertCount(4, $this->repo->findList( - new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, ['bar', 'baz']), - )); - self::assertCount(4, $this->repo->findList(new ShortUrlsListFiltering( - null, - null, - Ordering::emptyInstance(), - null, - ['bar', 'baz'], - TagsMode::ANY, - ))); - self::assertCount(2, $this->repo->findList(new ShortUrlsListFiltering( - null, - null, - Ordering::emptyInstance(), - null, - ['bar', 'baz'], - TagsMode::ALL, - ))); - self::assertEquals(4, $this->repo->countList(new ShortUrlsCountFiltering(null, ['bar', 'baz']))); - self::assertEquals(4, $this->repo->countList( - new ShortUrlsCountFiltering(null, ['bar', 'baz'], TagsMode::ANY), - )); - self::assertEquals(2, $this->repo->countList( - new ShortUrlsCountFiltering(null, ['bar', 'baz'], TagsMode::ALL), - )); - - self::assertCount(5, $this->repo->findList( - new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, ['foo', 'bar', 'baz']), - )); - self::assertCount(5, $this->repo->findList(new ShortUrlsListFiltering( - null, - null, - Ordering::emptyInstance(), - null, - ['foo', 'bar', 'baz'], - TagsMode::ANY, - ))); - self::assertCount(0, $this->repo->findList(new ShortUrlsListFiltering( - null, - null, - Ordering::emptyInstance(), - null, - ['foo', 'bar', 'baz'], - TagsMode::ALL, - ))); - self::assertEquals(5, $this->repo->countList(new ShortUrlsCountFiltering(null, ['foo', 'bar', 'baz']))); - self::assertEquals(5, $this->repo->countList( - new ShortUrlsCountFiltering(null, ['foo', 'bar', 'baz'], TagsMode::ANY), - )); - self::assertEquals(0, $this->repo->countList( - new ShortUrlsCountFiltering(null, ['foo', 'bar', 'baz'], TagsMode::ALL), - )); - } - - /** @test */ - public function findListReturnsOnlyThoseWithMatchingDomains(): void - { - $shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([ - 'longUrl' => 'foo1', - 'domain' => null, - ]), $this->relationResolver); - $this->getEntityManager()->persist($shortUrl1); - $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([ - 'longUrl' => 'foo2', - 'domain' => null, - ]), $this->relationResolver); - $this->getEntityManager()->persist($shortUrl2); - $shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([ - 'longUrl' => 'foo3', - 'domain' => 'another.com', - ]), $this->relationResolver); - $this->getEntityManager()->persist($shortUrl3); - - $this->getEntityManager()->flush(); - - $buildFiltering = static fn (string $searchTerm) => new ShortUrlsListFiltering( - null, - null, - Ordering::emptyInstance(), - searchTerm: $searchTerm, - defaultDomain: 'deFaulT-domain.com', - ); - - self::assertCount(2, $this->repo->findList($buildFiltering('default-dom'))); - self::assertCount(2, $this->repo->findList($buildFiltering('DOM'))); - self::assertCount(1, $this->repo->findList($buildFiltering('another'))); - self::assertCount(3, $this->repo->findList($buildFiltering('foo'))); - self::assertCount(0, $this->repo->findList($buildFiltering('no results'))); - } - - /** @test */ - public function findListReturnsOnlyThoseWithoutExcludedUrls(): void - { - $shortUrl1 = ShortUrl::create(ShortUrlCreation::fromRawData([ - 'longUrl' => 'foo1', - 'validUntil' => Chronos::now()->addDays(1)->toAtomString(), - 'maxVisits' => 100, - ]), $this->relationResolver); - $this->getEntityManager()->persist($shortUrl1); - $shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData([ - 'longUrl' => 'foo2', - 'validUntil' => Chronos::now()->subDays(1)->toAtomString(), - ]), $this->relationResolver); - $this->getEntityManager()->persist($shortUrl2); - $shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData([ - 'longUrl' => 'foo3', - ]), $this->relationResolver); - $this->getEntityManager()->persist($shortUrl3); - $shortUrl4 = ShortUrl::create(ShortUrlCreation::fromRawData([ - 'longUrl' => 'foo4', - 'maxVisits' => 3, - ]), $this->relationResolver); - $this->getEntityManager()->persist($shortUrl4); - $this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::emptyInstance())); - $this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::emptyInstance())); - $this->getEntityManager()->persist(Visit::forValidShortUrl($shortUrl4, Visitor::emptyInstance())); - - $this->getEntityManager()->flush(); - - $filtering = static fn (bool $excludeMaxVisitsReached, bool $excludePastValidUntil) => - new ShortUrlsListFiltering( - null, - null, - Ordering::emptyInstance(), - excludeMaxVisitsReached: $excludeMaxVisitsReached, - excludePastValidUntil: $excludePastValidUntil, - ); - - self::assertCount(4, $this->repo->findList($filtering(false, false))); - self::assertEquals(4, $this->repo->countList($filtering(false, false))); - self::assertCount(3, $this->repo->findList($filtering(true, false))); - self::assertEquals(3, $this->repo->countList($filtering(true, false))); - self::assertCount(3, $this->repo->findList($filtering(false, true))); - self::assertEquals(3, $this->repo->countList($filtering(false, true))); - self::assertCount(2, $this->repo->findList($filtering(true, true))); - self::assertEquals(2, $this->repo->countList($filtering(true, true))); - } - /** @test */ public function shortCodeIsInUseLooksForShortUrlInProperSetOfTables(): void { diff --git a/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php b/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php index e271448c..684c1528 100644 --- a/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php +++ b/module/Core/test/ShortUrl/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php @@ -12,16 +12,16 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode; use Shlinkio\Shlink\Core\ShortUrl\Paginator\Adapter\ShortUrlRepositoryAdapter; use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering; use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering; -use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface; +use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlListRepositoryInterface; use Shlinkio\Shlink\Rest\Entity\ApiKey; class ShortUrlRepositoryAdapterTest extends TestCase { - private MockObject & ShortUrlRepositoryInterface $repo; + private MockObject & ShortUrlListRepositoryInterface $repo; protected function setUp(): void { - $this->repo = $this->createMock(ShortUrlRepositoryInterface::class); + $this->repo = $this->createMock(ShortUrlListRepositoryInterface::class); } /** diff --git a/module/Core/test/ShortUrl/ShortUrlListServiceTest.php b/module/Core/test/ShortUrl/ShortUrlListServiceTest.php new file mode 100644 index 00000000..be8eb852 --- /dev/null +++ b/module/Core/test/ShortUrl/ShortUrlListServiceTest.php @@ -0,0 +1,53 @@ +repo = $this->createMock(ShortUrlListRepositoryInterface::class); + $this->service = new ShortUrlListService($this->repo, new UrlShortenerOptions()); + } + + /** + * @test + * @dataProvider provideAdminApiKeys + */ + public function listedUrlsAreReturnedFromEntityManager(?ApiKey $apiKey): void + { + $list = [ + ShortUrl::createEmpty(), + ShortUrl::createEmpty(), + ShortUrl::createEmpty(), + ShortUrl::createEmpty(), + ]; + + $this->repo->expects($this->once())->method('findList')->willReturn($list); + $this->repo->expects($this->once())->method('countList')->willReturn(count($list)); + + $paginator = $this->service->listShortUrls(ShortUrlsParams::emptyInstance(), $apiKey); + + self::assertCount(4, $paginator); + self::assertCount(4, $paginator->getCurrentPageResults()); + } +} diff --git a/module/Core/test/ShortUrl/ShortUrlServiceTest.php b/module/Core/test/ShortUrl/ShortUrlServiceTest.php index 7b27f4e4..9cc0d955 100644 --- a/module/Core/test/ShortUrl/ShortUrlServiceTest.php +++ b/module/Core/test/ShortUrl/ShortUrlServiceTest.php @@ -8,21 +8,16 @@ use Cake\Chronos\Chronos; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlTitleResolutionHelperInterface; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlEdition; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; -use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlsParams; -use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\ShortUrl\Resolver\SimpleShortUrlRelationResolver; use Shlinkio\Shlink\Core\ShortUrl\ShortUrlResolverInterface; use Shlinkio\Shlink\Core\ShortUrl\ShortUrlService; use Shlinkio\Shlink\Rest\Entity\ApiKey; use ShlinkioTest\Shlink\Core\Util\ApiKeyHelpersTrait; -use function count; - class ShortUrlServiceTest extends TestCase { use ApiKeyHelpersTrait; @@ -46,34 +41,9 @@ class ShortUrlServiceTest extends TestCase $this->urlResolver, $this->titleResolutionHelper, new SimpleShortUrlRelationResolver(), - new UrlShortenerOptions(), ); } - /** - * @test - * @dataProvider provideAdminApiKeys - */ - public function listedUrlsAreReturnedFromEntityManager(?ApiKey $apiKey): void - { - $list = [ - ShortUrl::createEmpty(), - ShortUrl::createEmpty(), - ShortUrl::createEmpty(), - ShortUrl::createEmpty(), - ]; - - $repo = $this->createMock(ShortUrlRepository::class); - $repo->expects($this->once())->method('findList')->willReturn($list); - $repo->expects($this->once())->method('countList')->willReturn(count($list)); - $this->em->method('getRepository')->with(ShortUrl::class)->willReturn($repo); - - $paginator = $this->service->listShortUrls(ShortUrlsParams::emptyInstance(), $apiKey); - - self::assertCount(4, $paginator); - self::assertCount(4, $paginator->getCurrentPageResults()); - } - /** * @test * @dataProvider provideShortUrlEdits diff --git a/module/Rest/config/dependencies.config.php b/module/Rest/config/dependencies.config.php index ce917b7b..cf394740 100644 --- a/module/Rest/config/dependencies.config.php +++ b/module/Rest/config/dependencies.config.php @@ -90,7 +90,10 @@ return [ Visit\Transformer\OrphanVisitDataTransformer::class, ], Action\Visit\NonOrphanVisitsAction::class => [Visit\VisitsStatsHelper::class], - Action\ShortUrl\ListShortUrlsAction::class => [ShortUrl\ShortUrlService::class, ShortUrlDataTransformer::class], + Action\ShortUrl\ListShortUrlsAction::class => [ + ShortUrl\ShortUrlListService::class, + ShortUrlDataTransformer::class, + ], Action\Tag\ListTagsAction::class => [TagService::class], Action\Tag\TagsStatsAction::class => [TagService::class], Action\Tag\DeleteTagsAction::class => [TagService::class], diff --git a/module/Rest/src/Action/ShortUrl/ListShortUrlsAction.php b/module/Rest/src/Action/ShortUrl/ListShortUrlsAction.php index 1f915ea1..8ca247a7 100644 --- a/module/Rest/src/Action/ShortUrl/ListShortUrlsAction.php +++ b/module/Rest/src/Action/ShortUrl/ListShortUrlsAction.php @@ -10,7 +10,7 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtilsTrait; use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlsParams; -use Shlinkio\Shlink\Core\ShortUrl\ShortUrlServiceInterface; +use Shlinkio\Shlink\Core\ShortUrl\ShortUrlListServiceInterface; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware; @@ -22,8 +22,8 @@ class ListShortUrlsAction extends AbstractRestAction protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET]; public function __construct( - private ShortUrlServiceInterface $shortUrlService, - private DataTransformerInterface $transformer, + private readonly ShortUrlListServiceInterface $shortUrlService, + private readonly DataTransformerInterface $transformer, ) { } diff --git a/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php b/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php index 5ccd20ec..4164e78b 100644 --- a/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php +++ b/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php @@ -13,7 +13,7 @@ use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlsParams; -use Shlinkio\Shlink\Core\ShortUrl\ShortUrlService; +use Shlinkio\Shlink\Core\ShortUrl\ShortUrlListServiceInterface; use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer; use Shlinkio\Shlink\Rest\Action\ShortUrl\ListShortUrlsAction; use Shlinkio\Shlink\Rest\Entity\ApiKey; @@ -21,11 +21,11 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class ListShortUrlsActionTest extends TestCase { private ListShortUrlsAction $action; - private MockObject & ShortUrlService $service; + private MockObject & ShortUrlListServiceInterface $service; protected function setUp(): void { - $this->service = $this->createMock(ShortUrlService::class); + $this->service = $this->createMock(ShortUrlListServiceInterface::class); $this->action = new ListShortUrlsAction($this->service, new ShortUrlDataTransformer( new ShortUrlStringifier([ From 73c8b538825f077d6c2ca2b4173c8aca1c11178f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 14 Dec 2022 12:28:23 +0100 Subject: [PATCH 172/182] Split some logic from VisitRepository into its own injectable repository --- CHANGELOG.md | 1 + composer.json | 4 +- module/Core/config/dependencies.config.php | 6 +- .../src/Visit/Geolocation/VisitLocator.php | 13 ++-- .../Repository/VisitLocationRepository.php | 74 +++++++++++++++++++ .../VisitLocationRepositoryInterface.php | 27 +++++++ .../src/Visit/Repository/VisitRepository.php | 59 --------------- .../Repository/VisitRepositoryInterface.php | 18 ----- .../VisitLocationRepositoryTest.php | 63 ++++++++++++++++ .../Visit/Repository/VisitRepositoryTest.php | 50 ------------- .../Visit/Geolocation/VisitLocatorTest.php | 11 ++- 11 files changed, 182 insertions(+), 144 deletions(-) create mode 100644 module/Core/src/Visit/Repository/VisitLocationRepository.php create mode 100644 module/Core/src/Visit/Repository/VisitLocationRepositoryInterface.php create mode 100644 module/Core/test-db/Visit/Repository/VisitLocationRepositoryTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5b8204..94fae122 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 * [#1563](https://github.com/shlinkio/shlink/issues/1563) Moved logic to reuse command options to option classes instead of base abstract command classes. * [#1569](https://github.com/shlinkio/shlink/issues/1569) Migrated test doubles from phpspec/prophecy to PHPUnit mocks. +* [#1329](https://github.com/shlinkio/shlink/issues/1329) Split some logic from `VisitRepository` and `ShortUrlRepository` into separated repository classes. ### Deprecated * *Nothing* diff --git a/composer.json b/composer.json index 033920ca..6d169d8a 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "php-middleware/request-id": "^4.1", "pugx/shortid-php": "^1.1", "ramsey/uuid": "^4.5", - "shlinkio/shlink-common": "dev-main#e2a5bb7 as 5.2", + "shlinkio/shlink-common": "dev-main#107b753 as 5.2", "shlinkio/shlink-config": "dev-main#96c81fb as 2.3", "shlinkio/shlink-event-dispatcher": "^2.6", "shlinkio/shlink-importer": "dev-main#c97662b as 5.0", @@ -135,7 +135,7 @@ "infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=80", "infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json5", "infect:ci:api": "@infect:ci:base --coverage=build/coverage-api --min-msi=80 --configuration=infection-api.json5", - "infect:ci:cli": "@infect:ci:base --coverage=build/coverage-cli --min-msi=80 --configuration=infection-cli.json5", + "infect:ci:cli": "@infect:ci:base --coverage=build/coverage-cli --min-msi=90 --configuration=infection-cli.json5", "infect:ci": "@parallel infect:ci:unit infect:ci:db infect:ci:api infect:ci:cli", "infect:test": [ "@parallel test:unit:ci test:db:sqlite:ci test:api:ci", diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 708bb8a3..0a501566 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -61,6 +61,10 @@ return [ Visit\Geolocation\VisitToLocationHelper::class => ConfigAbstractFactory::class, Visit\VisitsStatsHelper::class => ConfigAbstractFactory::class, Visit\Transformer\OrphanVisitDataTransformer::class => InvokableFactory::class, + Visit\Repository\VisitLocationRepository::class => [ + EntityRepositoryFactory::class, + Visit\Entity\Visit::class, + ], Util\UrlValidator::class => ConfigAbstractFactory::class, Util\DoctrineBatchHelper::class => ConfigAbstractFactory::class, @@ -119,7 +123,7 @@ return [ ShortUrl\Repository\ShortUrlListRepository::class, Options\UrlShortenerOptions::class, ], - Visit\Geolocation\VisitLocator::class => ['em'], + Visit\Geolocation\VisitLocator::class => ['em', Visit\Repository\VisitLocationRepository::class], Visit\Geolocation\VisitToLocationHelper::class => [IpLocationResolverInterface::class], Visit\VisitsStatsHelper::class => ['em'], Tag\TagService::class => ['em'], diff --git a/module/Core/src/Visit/Geolocation/VisitLocator.php b/module/Core/src/Visit/Geolocation/VisitLocator.php index 12900260..4b3b8e22 100644 --- a/module/Core/src/Visit/Geolocation/VisitLocator.php +++ b/module/Core/src/Visit/Geolocation/VisitLocator.php @@ -8,18 +8,15 @@ use Doctrine\ORM\EntityManagerInterface; use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException; use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation; -use Shlinkio\Shlink\Core\Visit\Repository\VisitRepositoryInterface; +use Shlinkio\Shlink\Core\Visit\Repository\VisitLocationRepositoryInterface; use Shlinkio\Shlink\IpGeolocation\Model\Location; class VisitLocator implements VisitLocatorInterface { - private VisitRepositoryInterface $repo; - - public function __construct(private EntityManagerInterface $em) - { - /** @var VisitRepositoryInterface $repo */ - $repo = $em->getRepository(Visit::class); - $this->repo = $repo; + public function __construct( + private readonly EntityManagerInterface $em, + private readonly VisitLocationRepositoryInterface $repo, + ) { } public function locateUnlocatedVisits(VisitGeolocationHelperInterface $helper): void diff --git a/module/Core/src/Visit/Repository/VisitLocationRepository.php b/module/Core/src/Visit/Repository/VisitLocationRepository.php new file mode 100644 index 00000000..6db1a4f8 --- /dev/null +++ b/module/Core/src/Visit/Repository/VisitLocationRepository.php @@ -0,0 +1,74 @@ + + */ + public function findUnlocatedVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable + { + $qb = $this->getEntityManager()->createQueryBuilder(); + $qb->select('v') + ->from(Visit::class, 'v') + ->where($qb->expr()->isNull('v.visitLocation')); + + return $this->visitsIterableForQuery($qb, $blockSize); + } + + /** + * @return iterable + */ + public function findVisitsWithEmptyLocation(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable + { + $qb = $this->getEntityManager()->createQueryBuilder(); + $qb->select('v') + ->from(Visit::class, 'v') + ->join('v.visitLocation', 'vl') + ->where($qb->expr()->isNotNull('v.visitLocation')) + ->andWhere($qb->expr()->eq('vl.isEmpty', ':isEmpty')) + ->setParameter('isEmpty', true); + + return $this->visitsIterableForQuery($qb, $blockSize); + } + + /** + * @return iterable + */ + public function findAllVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable + { + $qb = $this->createQueryBuilder('v'); + return $this->visitsIterableForQuery($qb, $blockSize); + } + + private function visitsIterableForQuery(QueryBuilder $qb, int $blockSize): iterable + { + $originalQueryBuilder = $qb->setMaxResults($blockSize) + ->orderBy('v.id', 'ASC'); + $lastId = '0'; + + do { + $qb = (clone $originalQueryBuilder)->andWhere($qb->expr()->gt('v.id', $lastId)); + $iterator = $qb->getQuery()->toIterable(); + $resultsFound = false; + /** @var Visit|null $lastProcessedVisit */ + $lastProcessedVisit = null; + + foreach ($iterator as $key => $visit) { + $resultsFound = true; + $lastProcessedVisit = $visit; + yield $key => $visit; + } + + // As the query is ordered by ID, we can take the last one every time in order to exclude the whole list + $lastId = $lastProcessedVisit?->getId() ?? $lastId; + } while ($resultsFound); + } +} diff --git a/module/Core/src/Visit/Repository/VisitLocationRepositoryInterface.php b/module/Core/src/Visit/Repository/VisitLocationRepositoryInterface.php new file mode 100644 index 00000000..083d61f2 --- /dev/null +++ b/module/Core/src/Visit/Repository/VisitLocationRepositoryInterface.php @@ -0,0 +1,27 @@ + + */ + public function findUnlocatedVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable; + + /** + * @return iterable + */ + public function findVisitsWithEmptyLocation(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable; + + /** + * @return iterable + */ + public function findAllVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable; +} diff --git a/module/Core/src/Visit/Repository/VisitRepository.php b/module/Core/src/Visit/Repository/VisitRepository.php index af1647c7..7021e70b 100644 --- a/module/Core/src/Visit/Repository/VisitRepository.php +++ b/module/Core/src/Visit/Repository/VisitRepository.php @@ -22,65 +22,6 @@ use const PHP_INT_MAX; class VisitRepository extends EntitySpecificationRepository implements VisitRepositoryInterface { - /** - * @return iterable|Visit[] - */ - public function findUnlocatedVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable - { - $qb = $this->getEntityManager()->createQueryBuilder(); - $qb->select('v') - ->from(Visit::class, 'v') - ->where($qb->expr()->isNull('v.visitLocation')); - - return $this->visitsIterableForQuery($qb, $blockSize); - } - - /** - * @return iterable|Visit[] - */ - public function findVisitsWithEmptyLocation(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable - { - $qb = $this->getEntityManager()->createQueryBuilder(); - $qb->select('v') - ->from(Visit::class, 'v') - ->join('v.visitLocation', 'vl') - ->where($qb->expr()->isNotNull('v.visitLocation')) - ->andWhere($qb->expr()->eq('vl.isEmpty', ':isEmpty')) - ->setParameter('isEmpty', true); - - return $this->visitsIterableForQuery($qb, $blockSize); - } - - public function findAllVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable - { - $qb = $this->createQueryBuilder('v'); - return $this->visitsIterableForQuery($qb, $blockSize); - } - - private function visitsIterableForQuery(QueryBuilder $qb, int $blockSize): iterable - { - $originalQueryBuilder = $qb->setMaxResults($blockSize) - ->orderBy('v.id', 'ASC'); - $lastId = '0'; - - do { - $qb = (clone $originalQueryBuilder)->andWhere($qb->expr()->gt('v.id', $lastId)); - $iterator = $qb->getQuery()->toIterable(); - $resultsFound = false; - /** @var Visit|null $lastProcessedVisit */ - $lastProcessedVisit = null; - - foreach ($iterator as $key => $visit) { - $resultsFound = true; - $lastProcessedVisit = $visit; - yield $key => $visit; - } - - // As the query is ordered by ID, we can take the last one every time in order to exclude the whole list - $lastId = $lastProcessedVisit?->getId() ?? $lastId; - } while ($resultsFound); - } - /** * @return Visit[] */ diff --git a/module/Core/src/Visit/Repository/VisitRepositoryInterface.php b/module/Core/src/Visit/Repository/VisitRepositoryInterface.php index ebc4f4fe..4e53db2b 100644 --- a/module/Core/src/Visit/Repository/VisitRepositoryInterface.php +++ b/module/Core/src/Visit/Repository/VisitRepositoryInterface.php @@ -11,26 +11,8 @@ use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Core\Visit\Persistence\VisitsCountFiltering; use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering; -// TODO Split into VisitsListsRepository and VisitsLocationRepository interface VisitRepositoryInterface extends ObjectRepository, EntitySpecificationRepositoryInterface { - public const DEFAULT_BLOCK_SIZE = 10000; - - /** - * @return iterable|Visit[] - */ - public function findUnlocatedVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable; - - /** - * @return iterable|Visit[] - */ - public function findVisitsWithEmptyLocation(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable; - - /** - * @return iterable|Visit[] - */ - public function findAllVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable; - /** * @return Visit[] */ diff --git a/module/Core/test-db/Visit/Repository/VisitLocationRepositoryTest.php b/module/Core/test-db/Visit/Repository/VisitLocationRepositoryTest.php new file mode 100644 index 00000000..77f4c1e6 --- /dev/null +++ b/module/Core/test-db/Visit/Repository/VisitLocationRepositoryTest.php @@ -0,0 +1,63 @@ +getEntityManager(); + $this->repo = new VisitLocationRepository($em, $em->getClassMetadata(Visit::class)); + } + + /** + * @test + * @dataProvider provideBlockSize + */ + public function findVisitsReturnsProperVisits(int $blockSize): void + { + $shortUrl = ShortUrl::createEmpty(); + $this->getEntityManager()->persist($shortUrl); + + for ($i = 0; $i < 6; $i++) { + $visit = Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance()); + + if ($i >= 2) { + $location = VisitLocation::fromGeolocation(Location::emptyInstance()); + $this->getEntityManager()->persist($location); + $visit->locate($location); + } + + $this->getEntityManager()->persist($visit); + } + $this->getEntityManager()->flush(); + + $withEmptyLocation = $this->repo->findVisitsWithEmptyLocation($blockSize); + $unlocated = $this->repo->findUnlocatedVisits($blockSize); + $all = $this->repo->findAllVisits($blockSize); + + self::assertCount(2, [...$unlocated]); + self::assertCount(4, [...$withEmptyLocation]); + self::assertCount(6, [...$all]); + } + + public function provideBlockSize(): iterable + { + return map(range(1, 10), fn (int $value) => [$value]); + } +} diff --git a/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php b/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php index da475832..eb806208 100644 --- a/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php +++ b/module/Core/test-db/Visit/Repository/VisitRepositoryTest.php @@ -14,20 +14,16 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter; use Shlinkio\Shlink\Core\ShortUrl\Resolver\PersistenceShortUrlRelationResolver; use Shlinkio\Shlink\Core\Visit\Entity\Visit; -use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation; use Shlinkio\Shlink\Core\Visit\Model\Visitor; use Shlinkio\Shlink\Core\Visit\Persistence\VisitsCountFiltering; use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering; use Shlinkio\Shlink\Core\Visit\Repository\VisitRepository; -use Shlinkio\Shlink\IpGeolocation\Model\Location; use Shlinkio\Shlink\Rest\ApiKey\Model\ApiKeyMeta; use Shlinkio\Shlink\Rest\ApiKey\Model\RoleDefinition; use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase; -use function Functional\map; use function is_string; -use function range; use function sprintf; use function str_pad; @@ -44,52 +40,6 @@ class VisitRepositoryTest extends DatabaseTestCase $this->relationResolver = new PersistenceShortUrlRelationResolver($this->getEntityManager()); } - /** - * @test - * @dataProvider provideBlockSize - */ - public function findVisitsReturnsProperVisits(int $blockSize): void - { - $shortUrl = ShortUrl::createEmpty(); - $this->getEntityManager()->persist($shortUrl); - $countIterable = static function (iterable $results): int { - $resultsCount = 0; - foreach ($results as $value) { - $resultsCount++; - } - - return $resultsCount; - }; - - for ($i = 0; $i < 6; $i++) { - $visit = Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance()); - - if ($i >= 2) { - $location = VisitLocation::fromGeolocation(Location::emptyInstance()); - $this->getEntityManager()->persist($location); - $visit->locate($location); - } - - $this->getEntityManager()->persist($visit); - } - $this->getEntityManager()->flush(); - - $withEmptyLocation = $this->repo->findVisitsWithEmptyLocation($blockSize); - $unlocated = $this->repo->findUnlocatedVisits($blockSize); - $all = $this->repo->findAllVisits($blockSize); - - // Important! assertCount will not work here, as this iterable object loads data dynamically and the count - // is 0 if not iterated - self::assertEquals(2, $countIterable($unlocated)); - self::assertEquals(4, $countIterable($withEmptyLocation)); - self::assertEquals(6, $countIterable($all)); - } - - public function provideBlockSize(): iterable - { - return map(range(1, 10), fn (int $value) => [$value]); - } - /** @test */ public function findVisitsByShortCodeReturnsProperData(): void { diff --git a/module/Core/test/Visit/Geolocation/VisitLocatorTest.php b/module/Core/test/Visit/Geolocation/VisitLocatorTest.php index a39940ae..ba0d70c4 100644 --- a/module/Core/test/Visit/Geolocation/VisitLocatorTest.php +++ b/module/Core/test/Visit/Geolocation/VisitLocatorTest.php @@ -15,7 +15,7 @@ use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation; use Shlinkio\Shlink\Core\Visit\Geolocation\VisitGeolocationHelperInterface; use Shlinkio\Shlink\Core\Visit\Geolocation\VisitLocator; use Shlinkio\Shlink\Core\Visit\Model\Visitor; -use Shlinkio\Shlink\Core\Visit\Repository\VisitRepositoryInterface; +use Shlinkio\Shlink\Core\Visit\Repository\VisitLocationRepositoryInterface; use Shlinkio\Shlink\IpGeolocation\Model\Location; use function count; @@ -28,15 +28,14 @@ class VisitLocatorTest extends TestCase { private VisitLocator $visitService; private MockObject & EntityManager $em; - private MockObject & VisitRepositoryInterface $repo; + private MockObject & VisitLocationRepositoryInterface $repo; protected function setUp(): void { $this->em = $this->createMock(EntityManager::class); - $this->repo = $this->createMock(VisitRepositoryInterface::class); - $this->em->method('getRepository')->with(Visit::class)->willReturn($this->repo); + $this->repo = $this->createMock(VisitLocationRepositoryInterface::class); - $this->visitService = new VisitLocator($this->em); + $this->visitService = new VisitLocator($this->em, $this->repo); } /** @@ -103,7 +102,7 @@ class VisitLocatorTest extends TestCase $this->visitService->{$serviceMethodName}( new class ($isNonLocatableAddress) implements VisitGeolocationHelperInterface { - public function __construct(private bool $isNonLocatableAddress) + public function __construct(private readonly bool $isNonLocatableAddress) { } From 60ef98b836145fb87a689104120f582082e04ef2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 14 Dec 2022 14:38:22 +0100 Subject: [PATCH 173/182] Extracted method to find crawlable short codes to its own query object --- module/Core/config/dependencies.config.php | 4 ++ module/Core/src/Crawling/CrawlingHelper.php | 10 ++-- .../Repository/CrawlableShortCodesQuery.php | 38 ++++++++++++++ .../CrawlableShortCodesQueryInterface.php | 13 +++++ .../Repository/ShortUrlRepository.php | 24 --------- .../ShortUrlRepositoryInterface.php | 2 - .../CrawlableShortCodesQueryTest.php | 50 +++++++++++++++++++ .../Repository/ShortUrlRepositoryTest.php | 33 ------------ .../Core/test/Crawling/CrawlingHelperTest.php | 20 +++----- 9 files changed, 114 insertions(+), 80 deletions(-) create mode 100644 module/Core/src/ShortUrl/Repository/CrawlableShortCodesQuery.php create mode 100644 module/Core/src/ShortUrl/Repository/CrawlableShortCodesQueryInterface.php create mode 100644 module/Core/test-db/ShortUrl/Repository/CrawlableShortCodesQueryTest.php diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 0a501566..48fe3cb2 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -50,6 +50,10 @@ return [ EntityRepositoryFactory::class, ShortUrl\Entity\ShortUrl::class, ], + ShortUrl\Repository\CrawlableShortCodesQuery::class => [ + EntityRepositoryFactory::class, + ShortUrl\Entity\ShortUrl::class, + ], Tag\TagService::class => ConfigAbstractFactory::class, diff --git a/module/Core/src/Crawling/CrawlingHelper.php b/module/Core/src/Crawling/CrawlingHelper.php index 2c38fabd..958cb96e 100644 --- a/module/Core/src/Crawling/CrawlingHelper.php +++ b/module/Core/src/Crawling/CrawlingHelper.php @@ -4,20 +4,16 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Crawling; -use Doctrine\ORM\EntityManagerInterface; -use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; -use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface; +use Shlinkio\Shlink\Core\ShortUrl\Repository\CrawlableShortCodesQueryInterface; class CrawlingHelper implements CrawlingHelperInterface { - public function __construct(private EntityManagerInterface $em) + public function __construct(private readonly CrawlableShortCodesQueryInterface $query) { } public function listCrawlableShortCodes(): iterable { - /** @var ShortUrlRepositoryInterface $repo */ - $repo = $this->em->getRepository(ShortUrl::class); - yield from $repo->findCrawlableShortCodes(); + yield from ($this->query)(); } } diff --git a/module/Core/src/ShortUrl/Repository/CrawlableShortCodesQuery.php b/module/Core/src/ShortUrl/Repository/CrawlableShortCodesQuery.php new file mode 100644 index 00000000..7b3821d8 --- /dev/null +++ b/module/Core/src/ShortUrl/Repository/CrawlableShortCodesQuery.php @@ -0,0 +1,38 @@ + + */ + public function __invoke(): iterable + { + $blockSize = 1000; + $qb = $this->getEntityManager()->createQueryBuilder(); + $qb->select('DISTINCT s.shortCode') + ->from(ShortUrl::class, 's') + ->where($qb->expr()->eq('s.crawlable', ':crawlable')) + ->setParameter('crawlable', true) + ->setMaxResults($blockSize); + + $page = 0; + do { + $qbClone = (clone $qb)->setFirstResult($blockSize * $page); + $iterator = $qbClone->getQuery()->toIterable(); + $resultsFound = false; + $page++; + + foreach ($iterator as ['shortCode' => $shortCode]) { + $resultsFound = true; + yield $shortCode; + } + } while ($resultsFound); + } +} diff --git a/module/Core/src/ShortUrl/Repository/CrawlableShortCodesQueryInterface.php b/module/Core/src/ShortUrl/Repository/CrawlableShortCodesQueryInterface.php new file mode 100644 index 00000000..9e8211e5 --- /dev/null +++ b/module/Core/src/ShortUrl/Repository/CrawlableShortCodesQueryInterface.php @@ -0,0 +1,13 @@ + + */ + public function __invoke(): iterable; +} diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php index 216c4579..5e95f777 100644 --- a/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php +++ b/module/Core/src/ShortUrl/Repository/ShortUrlRepository.php @@ -190,28 +190,4 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU $qb->andWhere($qb->expr()->isNull('s.domain')); } } - - public function findCrawlableShortCodes(): iterable - { - $blockSize = 1000; - $qb = $this->getEntityManager()->createQueryBuilder(); - $qb->select('DISTINCT s.shortCode') - ->from(ShortUrl::class, 's') - ->where($qb->expr()->eq('s.crawlable', ':crawlable')) - ->setParameter('crawlable', true) - ->setMaxResults($blockSize); - - $page = 0; - do { - $qbClone = (clone $qb)->setFirstResult($blockSize * $page); - $iterator = $qbClone->getQuery()->toIterable(); - $resultsFound = false; - $page++; - - foreach ($iterator as ['shortCode' => $shortCode]) { - $resultsFound = true; - yield $shortCode; - } - } while ($resultsFound); - } } diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlRepositoryInterface.php b/module/Core/src/ShortUrl/Repository/ShortUrlRepositoryInterface.php index ad5e3a5d..cc574ac5 100644 --- a/module/Core/src/ShortUrl/Repository/ShortUrlRepositoryInterface.php +++ b/module/Core/src/ShortUrl/Repository/ShortUrlRepositoryInterface.php @@ -25,6 +25,4 @@ interface ShortUrlRepositoryInterface extends ObjectRepository, EntitySpecificat public function findOneMatching(ShortUrlCreation $meta): ?ShortUrl; public function findOneByImportedUrl(ImportedShlinkUrl $url): ?ShortUrl; - - public function findCrawlableShortCodes(): iterable; } diff --git a/module/Core/test-db/ShortUrl/Repository/CrawlableShortCodesQueryTest.php b/module/Core/test-db/ShortUrl/Repository/CrawlableShortCodesQueryTest.php new file mode 100644 index 00000000..04c670fa --- /dev/null +++ b/module/Core/test-db/ShortUrl/Repository/CrawlableShortCodesQueryTest.php @@ -0,0 +1,50 @@ +getEntityManager(); + $this->query = new CrawlableShortCodesQuery($em, $em->getClassMetadata(ShortUrl::class)); + } + + /** @test */ + public function invokingQueryReturnsExpectedResult(): void + { + $createShortUrl = fn (bool $crawlable) => ShortUrl::create( + ShortUrlCreation::fromRawData(['crawlable' => $crawlable, 'longUrl' => 'foo.com']), + ); + + $shortUrl1 = $createShortUrl(true); + $this->getEntityManager()->persist($shortUrl1); + $shortUrl2 = $createShortUrl(false); + $this->getEntityManager()->persist($shortUrl2); + $shortUrl3 = $createShortUrl(true); + $this->getEntityManager()->persist($shortUrl3); + $shortUrl4 = $createShortUrl(true); + $this->getEntityManager()->persist($shortUrl4); + $shortUrl5 = $createShortUrl(false); + $this->getEntityManager()->persist($shortUrl5); + $this->getEntityManager()->flush(); + + $results = [...($this->query)()]; + + self::assertCount(3, $results); + self::assertContains($shortUrl1->getShortCode(), $results); + self::assertContains($shortUrl3->getShortCode(), $results); + self::assertContains($shortUrl4->getShortCode(), $results); + self::assertNotContains($shortUrl2->getShortCode(), $results); + self::assertNotContains($shortUrl5->getShortCode(), $results); + } +} diff --git a/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php b/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php index a477eff8..c842bcb4 100644 --- a/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-db/ShortUrl/Repository/ShortUrlRepositoryTest.php @@ -391,37 +391,4 @@ class ShortUrlRepositoryTest extends DatabaseTestCase self::assertNull($this->repo->findOneByImportedUrl($buildImported('my-cool-slug', 'doma.in'))); self::assertNull($this->repo->findOneByImportedUrl($buildImported('another-slug'))); } - - /** @test */ - public function findCrawlableShortCodesReturnsExpectedResult(): void - { - $createShortUrl = fn (bool $crawlable) => ShortUrl::create( - ShortUrlCreation::fromRawData(['crawlable' => $crawlable, 'longUrl' => 'foo.com']), - ); - - $shortUrl1 = $createShortUrl(true); - $this->getEntityManager()->persist($shortUrl1); - $shortUrl2 = $createShortUrl(false); - $this->getEntityManager()->persist($shortUrl2); - $shortUrl3 = $createShortUrl(true); - $this->getEntityManager()->persist($shortUrl3); - $shortUrl4 = $createShortUrl(true); - $this->getEntityManager()->persist($shortUrl4); - $shortUrl5 = $createShortUrl(false); - $this->getEntityManager()->persist($shortUrl5); - $this->getEntityManager()->flush(); - - $iterable = $this->repo->findCrawlableShortCodes(); - $results = []; - foreach ($iterable as $shortCode) { - $results[] = $shortCode; - } - - self::assertCount(3, $results); - self::assertContains($shortUrl1->getShortCode(), $results); - self::assertContains($shortUrl3->getShortCode(), $results); - self::assertContains($shortUrl4->getShortCode(), $results); - self::assertNotContains($shortUrl2->getShortCode(), $results); - self::assertNotContains($shortUrl5->getShortCode(), $results); - } } diff --git a/module/Core/test/Crawling/CrawlingHelperTest.php b/module/Core/test/Crawling/CrawlingHelperTest.php index 1843d35c..295b7ec3 100644 --- a/module/Core/test/Crawling/CrawlingHelperTest.php +++ b/module/Core/test/Crawling/CrawlingHelperTest.php @@ -4,34 +4,26 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Crawling; -use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Core\Crawling\CrawlingHelper; -use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; -use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface; +use Shlinkio\Shlink\Core\ShortUrl\Repository\CrawlableShortCodesQueryInterface; class CrawlingHelperTest extends TestCase { private CrawlingHelper $helper; - private MockObject & EntityManagerInterface $em; + private MockObject & CrawlableShortCodesQueryInterface $query; protected function setUp(): void { - $this->em = $this->createMock(EntityManagerInterface::class); - $this->helper = new CrawlingHelper($this->em); + $this->query = $this->createMock(CrawlableShortCodesQueryInterface::class); + $this->helper = new CrawlingHelper($this->query); } /** @test */ public function listCrawlableShortCodesDelegatesIntoRepository(): void { - $repo = $this->createMock(ShortUrlRepositoryInterface::class); - $repo->expects($this->once())->method('findCrawlableShortCodes')->willReturn([]); - $this->em->expects($this->once())->method('getRepository')->with(ShortUrl::class)->willReturn($repo); - - $result = $this->helper->listCrawlableShortCodes(); - foreach ($result as $shortCode) { - // $result is a generator and therefore, it needs to be iterated - } + $this->query->expects($this->once())->method('__invoke')->willReturn([]); + [...$this->helper->listCrawlableShortCodes()]; } } From 30edfdbdc555191aa4d727cd13157e2f64c56738 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 14 Dec 2022 15:01:00 +0100 Subject: [PATCH 174/182] Updated docker images to PHP 8.2 --- CHANGELOG.md | 2 +- Dockerfile | 4 ++-- data/infra/php.Dockerfile | 6 ++++-- data/infra/roadrunner.Dockerfile | 6 ++++-- data/infra/swoole.Dockerfile | 6 ++++-- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94fae122..6031a772 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * [#1599](https://github.com/shlinkio/shlink/issues/1599) Added support for credentials on redis DSNs, either only password, or both username and password. * [#1616](https://github.com/shlinkio/shlink/issues/1616) Added support to import orphan visits when importing short URLs from another Shlink instance. * [#1519](https://github.com/shlinkio/shlink/issues/1519) Allowing to search short URLs by default domain. -* [#1555](https://github.com/shlinkio/shlink/issues/1555) Added full support for PHP 8.2, pdating the dockr image to this version. +* [#1555](https://github.com/shlinkio/shlink/issues/1555) and [#1625](https://github.com/shlinkio/shlink/issues/1625) Added full support for PHP 8.2, updating the docker image to this version. ### Changed * [#1563](https://github.com/shlinkio/shlink/issues/1563) Moved logic to reuse command options to option classes instead of base abstract command classes. diff --git a/Dockerfile b/Dockerfile index 90c2ba6b..8c38653b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.1.13-alpine3.17 as base +FROM php:8.2-alpine3.17 as base ARG SHLINK_VERSION=latest ENV SHLINK_VERSION ${SHLINK_VERSION} @@ -15,7 +15,7 @@ WORKDIR /etc/shlink # Install required PHP extensions RUN \ # Temp install dev dependencies needed to compile the extensions - apk add --no-cache --virtual .dev-deps sqlite-dev postgresql-dev icu-dev libzip-dev zlib-dev libpng-dev && \ + apk add --no-cache --virtual .dev-deps sqlite-dev postgresql-dev icu-dev libzip-dev zlib-dev libpng-dev linux-headers && \ docker-php-ext-install -j"$(nproc)" pdo_mysql pdo_pgsql intl calendar sockets bcmath zip gd && \ apk add --no-cache sqlite-libs && \ docker-php-ext-install -j"$(nproc)" pdo_sqlite && \ diff --git a/data/infra/php.Dockerfile b/data/infra/php.Dockerfile index c43b21cb..90ccab23 100644 --- a/data/infra/php.Dockerfile +++ b/data/infra/php.Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.1.13-fpm-alpine3.17 +FROM php:8.2-fpm-alpine3.17 MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.21 @@ -31,7 +31,9 @@ RUN docker-php-ext-install gd RUN apk add --no-cache postgresql-dev RUN docker-php-ext-install pdo_pgsql -RUN docker-php-ext-install sockets +RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \ + docker-php-ext-install sockets && \ + apk del .phpize-deps RUN docker-php-ext-install bcmath # Install APCu extension diff --git a/data/infra/roadrunner.Dockerfile b/data/infra/roadrunner.Dockerfile index f019d969..383099e4 100644 --- a/data/infra/roadrunner.Dockerfile +++ b/data/infra/roadrunner.Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.1.13-alpine3.17 +FROM php:8.2-alpine3.17 MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.21 @@ -31,7 +31,9 @@ RUN docker-php-ext-install gd RUN apk add --no-cache postgresql-dev RUN docker-php-ext-install pdo_pgsql -RUN docker-php-ext-install sockets +RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \ + docker-php-ext-install sockets && \ + apk del .phpize-deps RUN docker-php-ext-install bcmath # Install APCu extension diff --git a/data/infra/swoole.Dockerfile b/data/infra/swoole.Dockerfile index 68fa0db2..21e7d95f 100644 --- a/data/infra/swoole.Dockerfile +++ b/data/infra/swoole.Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.1.13-alpine3.17 +FROM php:8.2-alpine3.17 MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.21 @@ -33,7 +33,9 @@ RUN docker-php-ext-install gd RUN apk add --no-cache postgresql-dev RUN docker-php-ext-install pdo_pgsql -RUN docker-php-ext-install sockets +RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \ + docker-php-ext-install sockets && \ + apk del .phpize-deps RUN docker-php-ext-install bcmath # Install APCu extension From 99f28b569be82e6d82ef4b4beacda21e03426207 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 8 Dec 2022 22:06:10 +0100 Subject: [PATCH 175/182] Created method to get non-bot visits count for a short URL --- module/Core/src/ShortUrl/Entity/ShortUrl.php | 14 +++++++++---- .../Transformer/ShortUrlDataTransformer.php | 20 +++++++++++++++++-- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/module/Core/src/ShortUrl/Entity/ShortUrl.php b/module/Core/src/ShortUrl/Entity/ShortUrl.php index c7f10b75..0ebdeb24 100644 --- a/module/Core/src/ShortUrl/Entity/ShortUrl.php +++ b/module/Core/src/ShortUrl/Entity/ShortUrl.php @@ -33,9 +33,9 @@ class ShortUrl extends AbstractEntity private string $longUrl; private string $shortCode; private Chronos $dateCreated; - /** @var Collection|Visit[] */ + /** @var Collection */ private Collection $visits; - /** @var Collection|Tag[] */ + /** @var Collection */ private Collection $tags; private ?Chronos $validSince = null; private ?Chronos $validUntil = null; @@ -141,7 +141,7 @@ class ShortUrl extends AbstractEntity } /** - * @return Collection|Tag[] + * @return Collection */ public function getTags(): Collection { @@ -168,6 +168,12 @@ class ShortUrl extends AbstractEntity return count($this->visits); } + public function nonBotVisitsCount(): int + { + $criteria = Criteria::create()->where(Criteria::expr()->eq('potentialBot', false)); + return count($this->visits->matching($criteria)); + } + public function mostRecentImportedVisitDate(): ?Chronos { /** @var Selectable $visits */ @@ -183,7 +189,7 @@ class ShortUrl extends AbstractEntity } /** - * @param Collection|Visit[] $visits + * @param Collection $visits * @internal */ public function setVisits(Collection $visits): self diff --git a/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php b/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php index 262989ce..6d43f51d 100644 --- a/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php +++ b/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php @@ -13,7 +13,7 @@ use function Functional\invoke_if; class ShortUrlDataTransformer implements DataTransformerInterface { - public function __construct(private ShortUrlStringifierInterface $stringifier) + public function __construct(private readonly ShortUrlStringifierInterface $stringifier) { } @@ -27,13 +27,17 @@ class ShortUrlDataTransformer implements DataTransformerInterface 'shortUrl' => $this->stringifier->stringify($shortUrl), 'longUrl' => $shortUrl->getLongUrl(), 'dateCreated' => $shortUrl->getDateCreated()->toAtomString(), - 'visitsCount' => $shortUrl->getVisitsCount(), + 'nonBotVisitsCount' => $shortUrl->nonBotVisitsCount(), 'tags' => invoke($shortUrl->getTags(), '__toString'), 'meta' => $this->buildMeta($shortUrl), 'domain' => $shortUrl->getDomain(), 'title' => $shortUrl->title(), 'crawlable' => $shortUrl->crawlable(), 'forwardQuery' => $shortUrl->forwardQuery(), + 'visitsSummary' => $this->buildVisitsSummary($shortUrl), + + // Deprecated + 'visitsCount' => $shortUrl->getVisitsCount(), ]; } @@ -49,4 +53,16 @@ class ShortUrlDataTransformer implements DataTransformerInterface 'maxVisits' => $maxVisits, ]; } + + private function buildVisitsSummary(ShortUrl $shortUrl): array + { + $totalVisits = $shortUrl->getVisitsCount(); + $nonBotVisits = $shortUrl->nonBotVisitsCount(); + + return [ + 'total' => $totalVisits, + 'nonBots' => $nonBotVisits, + 'bots' => $totalVisits - $nonBotVisits, + ]; + } } From 936e5b3b8605ca7523ab962cfb525727fc10d09f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 16 Dec 2022 10:36:09 +0100 Subject: [PATCH 176/182] Fixed PublishingUpdatesGeneratorTest --- .../ShortUrl/Transformer/ShortUrlDataTransformer.php | 1 - .../EventDispatcher/PublishingUpdatesGeneratorTest.php | 10 ++++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php b/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php index 6d43f51d..bd82cd9d 100644 --- a/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php +++ b/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php @@ -27,7 +27,6 @@ class ShortUrlDataTransformer implements DataTransformerInterface 'shortUrl' => $this->stringifier->stringify($shortUrl), 'longUrl' => $shortUrl->getLongUrl(), 'dateCreated' => $shortUrl->getDateCreated()->toAtomString(), - 'nonBotVisitsCount' => $shortUrl->nonBotVisitsCount(), 'tags' => invoke($shortUrl->getTags(), '__toString'), 'meta' => $this->buildMeta($shortUrl), 'domain' => $shortUrl->getDomain(), diff --git a/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php b/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php index 3ac690d0..c7a4ecd0 100644 --- a/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php +++ b/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php @@ -63,6 +63,11 @@ class PublishingUpdatesGeneratorTest extends TestCase 'title' => $title, 'crawlable' => false, 'forwardQuery' => true, + 'visitsSummary' => [ + 'total' => 0, + 'nonBots' => 0, + 'bots' => 0, + ], ], 'visit' => [ 'referer' => '', @@ -139,6 +144,11 @@ class PublishingUpdatesGeneratorTest extends TestCase 'title' => $shortUrl->title(), 'crawlable' => false, 'forwardQuery' => true, + 'visitsSummary' => [ + 'total' => 0, + 'nonBots' => 0, + 'bots' => 0, + ], ]], $update->payload); } } From 1f66ec2af50ed29af25af94df06cf552743c6b5e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 16 Dec 2022 10:53:44 +0100 Subject: [PATCH 177/182] Fixed API tests --- .../test-api/Action/ListShortUrlsTest.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/module/Rest/test-api/Action/ListShortUrlsTest.php b/module/Rest/test-api/Action/ListShortUrlsTest.php index 63664a6d..d3a515c1 100644 --- a/module/Rest/test-api/Action/ListShortUrlsTest.php +++ b/module/Rest/test-api/Action/ListShortUrlsTest.php @@ -18,6 +18,11 @@ class ListShortUrlsTest extends ApiTestCase 'longUrl' => 'https://shlink.io', 'dateCreated' => '2018-05-01T00:00:00+00:00', 'visitsCount' => 3, + 'visitsSummary' => [ + 'total' => 3, + 'nonBots' => 3, + 'bots' => 0, + ], 'tags' => ['foo'], 'meta' => [ 'validSince' => null, @@ -35,6 +40,11 @@ class ListShortUrlsTest extends ApiTestCase 'longUrl' => 'https://shlink.io/documentation/', 'dateCreated' => '2018-05-01T00:00:00+00:00', 'visitsCount' => 2, + 'visitsSummary' => [ + 'total' => 2, + 'nonBots' => 2, + 'bots' => 0, + ], 'tags' => [], 'meta' => [ 'validSince' => null, @@ -52,6 +62,11 @@ class ListShortUrlsTest extends ApiTestCase 'longUrl' => 'https://google.com', 'dateCreated' => '2018-10-20T00:00:00+00:00', 'visitsCount' => 0, + 'visitsSummary' => [ + 'total' => 0, + 'nonBots' => 0, + 'bots' => 0, + ], 'tags' => [], 'meta' => [ 'validSince' => null, @@ -71,6 +86,11 @@ class ListShortUrlsTest extends ApiTestCase . '/acmailer-7-0-the-most-important-release-in-a-long-time/', 'dateCreated' => '2019-01-01T00:00:10+00:00', 'visitsCount' => 2, + 'visitsSummary' => [ + 'total' => 2, + 'nonBots' => 1, + 'bots' => 1, + ], 'tags' => ['bar', 'foo'], 'meta' => [ 'validSince' => '2020-05-01T00:00:00+00:00', @@ -88,6 +108,11 @@ class ListShortUrlsTest extends ApiTestCase 'longUrl' => 'https://shlink.io', 'dateCreated' => '2019-01-01T00:00:20+00:00', 'visitsCount' => 0, + 'visitsSummary' => [ + 'total' => 0, + 'nonBots' => 0, + 'bots' => 0, + ], 'tags' => [], 'meta' => [ 'validSince' => null, @@ -107,6 +132,11 @@ class ListShortUrlsTest extends ApiTestCase . '/considerations-to-properly-use-open-source-software-projects/', 'dateCreated' => '2019-01-01T00:00:30+00:00', 'visitsCount' => 0, + 'visitsSummary' => [ + 'total' => 0, + 'nonBots' => 0, + 'bots' => 0, + ], 'tags' => ['foo'], 'meta' => [ 'validSince' => null, From 09078e4c6ac413b4e8580dfed806edda8277e050 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 16 Dec 2022 13:34:34 +0100 Subject: [PATCH 178/182] Updated short URL API docs including new visitsSummary --- docs/swagger/definitions/ShortUrl.json | 7 +++++- .../definitions/ShortUrlVisitsSummary.json | 18 ++++++++++++++ docs/swagger/paths/v1_short-urls.json | 24 +++++++++++++++---- docs/swagger/paths/v1_short-urls_shorten.json | 6 ++++- .../paths/v1_short-urls_{shortCode}.json | 12 ++++++++-- 5 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 docs/swagger/definitions/ShortUrlVisitsSummary.json diff --git a/docs/swagger/definitions/ShortUrl.json b/docs/swagger/definitions/ShortUrl.json index f09e8d7b..ab66f506 100644 --- a/docs/swagger/definitions/ShortUrl.json +++ b/docs/swagger/definitions/ShortUrl.json @@ -6,6 +6,7 @@ "longUrl", "dateCreated", "visitsCount", + "visitsSummary", "tags", "meta", "domain", @@ -32,8 +33,12 @@ "description": "The date in which the short URL was created in ISO format." }, "visitsCount": { + "deprecated": true, "type": "integer", - "description": "The number of visits that this short URL has received." + "description": "**[DEPRECATED]** Use `visitsSummary.total` instead." + }, + "visitsSummary": { + "$ref": "./ShortUrlVisitsSummary.json" }, "tags": { "type": "array", diff --git a/docs/swagger/definitions/ShortUrlVisitsSummary.json b/docs/swagger/definitions/ShortUrlVisitsSummary.json new file mode 100644 index 00000000..404b7a75 --- /dev/null +++ b/docs/swagger/definitions/ShortUrlVisitsSummary.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "required": ["total", "nonBots", "bots"], + "properties": { + "total": { + "description": "The total amount of visits that this short URL has received.", + "type": "integer" + }, + "nonBots": { + "description": "The amount of visits which were not identified as bots.", + "type": "integer" + }, + "bots": { + "description": "The amount of visits that were identified as potential bots.", + "type": "integer" + } + } +} diff --git a/docs/swagger/paths/v1_short-urls.json b/docs/swagger/paths/v1_short-urls.json index 05c5973a..4773bddf 100644 --- a/docs/swagger/paths/v1_short-urls.json +++ b/docs/swagger/paths/v1_short-urls.json @@ -162,7 +162,11 @@ "shortUrl": "https://doma.in/12C18", "longUrl": "https://store.steampowered.com", "dateCreated": "2016-08-21T20:34:16+02:00", - "visitsCount": 328, + "visitsSummary": { + "total": 328, + "nonBots": 328, + "bots": 0 + }, "tags": [ "games", "tech" @@ -181,7 +185,11 @@ "shortUrl": "https://doma.in/12Kb3", "longUrl": "https://shlink.io", "dateCreated": "2016-05-01T20:34:16+02:00", - "visitsCount": 1029, + "visitsSummary": { + "total": 1029, + "nonBots": 900, + "bots": 129 + }, "tags": [ "shlink" ], @@ -199,7 +207,11 @@ "shortUrl": "https://example.com/123bA", "longUrl": "https://www.google.com", "dateCreated": "2015-10-01T20:34:16+02:00", - "visitsCount": 25, + "visitsSummary": { + "total": 25, + "nonBots": 0, + "bots": 25 + }, "tags": [], "meta": { "validSince": "2017-01-21T00:00:00+02:00", @@ -307,7 +319,11 @@ "shortUrl": "https://doma.in/12C18", "longUrl": "https://store.steampowered.com", "dateCreated": "2016-08-21T20:34:16+02:00", - "visitsCount": 0, + "visitsSummary": { + "total": 0, + "nonBots": 0, + "bots": 0 + }, "tags": [ "games", "tech" diff --git a/docs/swagger/paths/v1_short-urls_shorten.json b/docs/swagger/paths/v1_short-urls_shorten.json index aa26fa1b..254a88f2 100644 --- a/docs/swagger/paths/v1_short-urls_shorten.json +++ b/docs/swagger/paths/v1_short-urls_shorten.json @@ -55,7 +55,11 @@ "shortUrl": "https://doma.in/abc123", "shortCode": "abc123", "dateCreated": "2016-08-21T20:34:16+02:00", - "visitsCount": 0, + "visitsSummary": { + "total": 0, + "nonBots": 0, + "bots": 0 + }, "tags": [ "games", "tech" diff --git a/docs/swagger/paths/v1_short-urls_{shortCode}.json b/docs/swagger/paths/v1_short-urls_{shortCode}.json index 1b001cc9..00577f4f 100644 --- a/docs/swagger/paths/v1_short-urls_{shortCode}.json +++ b/docs/swagger/paths/v1_short-urls_{shortCode}.json @@ -41,7 +41,11 @@ "shortUrl": "https://doma.in/12Kb3", "longUrl": "https://shlink.io", "dateCreated": "2016-05-01T20:34:16+02:00", - "visitsCount": 1029, + "visitsSummary": { + "total": 1029, + "nonBots": 820, + "bots": 209 + }, "tags": [ "shlink" ], @@ -159,7 +163,11 @@ "shortUrl": "https://doma.in/12Kb3", "longUrl": "https://shlink.io", "dateCreated": "2016-05-01T20:34:16+02:00", - "visitsCount": 1029, + "visitsSummary": { + "total": 1029, + "nonBots": 900, + "bots": 129 + }, "tags": [ "shlink" ], From 713f7e7bc924fbee45839c81f9bc481a7ad4e117 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 16 Dec 2022 18:18:09 +0100 Subject: [PATCH 179/182] Added missing dock block --- module/Core/src/ShortUrl/ShortUrlListService.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/module/Core/src/ShortUrl/ShortUrlListService.php b/module/Core/src/ShortUrl/ShortUrlListService.php index 5287f14d..d83647f0 100644 --- a/module/Core/src/ShortUrl/ShortUrlListService.php +++ b/module/Core/src/ShortUrl/ShortUrlListService.php @@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Core\ShortUrl; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; +use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\ShortUrl\Paginator\Adapter\ShortUrlRepositoryAdapter; use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlListRepositoryInterface; @@ -19,6 +20,9 @@ class ShortUrlListService implements ShortUrlListServiceInterface ) { } + /** + * @return ShortUrl[]|Paginator + */ public function listShortUrls(ShortUrlsParams $params, ?ApiKey $apiKey = null): Paginator { $defaultDomain = $this->urlShortenerOptions->domain['hostname'] ?? ''; From c7a2f499e001f2685493c2257aab3b66b98ba963 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 16 Dec 2022 19:42:29 +0100 Subject: [PATCH 180/182] Added support to order short URLs list by amount of non-bot visits --- docs/swagger/paths/v1_short-urls.json | 6 ++- .../src/ShortUrl/Model/OrderableField.php | 37 +++++++++++++++++++ .../src/ShortUrl/Model/ShortUrlsParams.php | 1 - .../Validation/ShortUrlsParamsInputFilter.php | 4 +- .../Repository/ShortUrlListRepository.php | 26 +++++++------ .../Repository/ShortUrlListRepositoryTest.php | 30 +++++++++++++-- 6 files changed, 84 insertions(+), 20 deletions(-) create mode 100644 module/Core/src/ShortUrl/Model/OrderableField.php diff --git a/docs/swagger/paths/v1_short-urls.json b/docs/swagger/paths/v1_short-urls.json index 4773bddf..8960234a 100644 --- a/docs/swagger/paths/v1_short-urls.json +++ b/docs/swagger/paths/v1_short-urls.json @@ -73,10 +73,12 @@ "shortCode-DESC", "dateCreated-ASC", "dateCreated-DESC", + "title-ASC", + "title-DESC", "visits-ASC", "visits-DESC", - "title-ASC", - "title-DESC" + "nonBotVisits-ASC", + "nonBotVisits-DESC" ] } }, diff --git a/module/Core/src/ShortUrl/Model/OrderableField.php b/module/Core/src/ShortUrl/Model/OrderableField.php new file mode 100644 index 00000000..1c1c6338 --- /dev/null +++ b/module/Core/src/ShortUrl/Model/OrderableField.php @@ -0,0 +1,37 @@ + $field->value); + } + + public static function isBasicField(string $value): bool + { + return contains( + [self::LONG_URL->value, self::SHORT_CODE->value, self::DATE_CREATED->value, self::TITLE->value], + $value, + ); + } + + public static function isVisitsField(string $value): bool + { + return $value === self::VISITS->value || $value === self::NON_BOT_VISITS->value; + } +} diff --git a/module/Core/src/ShortUrl/Model/ShortUrlsParams.php b/module/Core/src/ShortUrl/Model/ShortUrlsParams.php index e053a283..88e20aa7 100644 --- a/module/Core/src/ShortUrl/Model/ShortUrlsParams.php +++ b/module/Core/src/ShortUrl/Model/ShortUrlsParams.php @@ -14,7 +14,6 @@ use function Shlinkio\Shlink\Core\normalizeOptionalDate; final class ShortUrlsParams { - public const ORDERABLE_FIELDS = ['longUrl', 'shortCode', 'dateCreated', 'title', 'visits']; public const DEFAULT_ITEMS_PER_PAGE = 10; private function __construct( diff --git a/module/Core/src/ShortUrl/Model/Validation/ShortUrlsParamsInputFilter.php b/module/Core/src/ShortUrl/Model/Validation/ShortUrlsParamsInputFilter.php index 3bdea8e2..cb120e8e 100644 --- a/module/Core/src/ShortUrl/Model/Validation/ShortUrlsParamsInputFilter.php +++ b/module/Core/src/ShortUrl/Model/Validation/ShortUrlsParamsInputFilter.php @@ -8,7 +8,7 @@ use Laminas\InputFilter\InputFilter; use Laminas\Validator\InArray; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Common\Validation; -use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlsParams; +use Shlinkio\Shlink\Core\ShortUrl\Model\OrderableField; use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode; class ShortUrlsParamsInputFilter extends InputFilter @@ -51,7 +51,7 @@ class ShortUrlsParamsInputFilter extends InputFilter ])); $this->add($tagsMode); - $this->add($this->createOrderByInput(self::ORDER_BY, ShortUrlsParams::ORDERABLE_FIELDS)); + $this->add($this->createOrderByInput(self::ORDER_BY, OrderableField::values())); $this->add($this->createBooleanInput(self::EXCLUDE_MAX_VISITS_REACHED, false)); $this->add($this->createBooleanInput(self::EXCLUDE_PAST_VALID_UNTIL, false)); diff --git a/module/Core/src/ShortUrl/Repository/ShortUrlListRepository.php b/module/Core/src/ShortUrl/Repository/ShortUrlListRepository.php index 35d7996f..e014ac64 100644 --- a/module/Core/src/ShortUrl/Repository/ShortUrlListRepository.php +++ b/module/Core/src/ShortUrl/Repository/ShortUrlListRepository.php @@ -10,13 +10,13 @@ use Doctrine\ORM\QueryBuilder; use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository; use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; +use Shlinkio\Shlink\Core\ShortUrl\Model\OrderableField; use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode; use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering; use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering; use Shlinkio\Shlink\Core\Visit\Entity\Visit; use function array_column; -use function Functional\contains; use function sprintf; class ShortUrlListRepository extends EntitySpecificationRepository implements ShortUrlListRepositoryInterface @@ -31,11 +31,10 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh ->setMaxResults($filtering->limit) ->setFirstResult($filtering->offset); - // In case the ordering has been specified, the query could be more complex. Process it $this->processOrderByForList($qb, $filtering); $result = $qb->getQuery()->getResult(); - if ($filtering->orderBy->field === 'visits') { + if (OrderableField::isVisitsField($filtering->orderBy->field ?? '')) { return array_column($result, 0); } @@ -45,23 +44,28 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh private function processOrderByForList(QueryBuilder $qb, ShortUrlsListFiltering $filtering): void { // With no explicit order by, fallback to dateCreated-DESC - if (! $filtering->orderBy->hasOrderField()) { + $fieldName = $filtering->orderBy->field; + if ($fieldName === null) { $qb->orderBy('s.dateCreated', 'DESC'); return; } - $fieldName = $filtering->orderBy->field; $order = $filtering->orderBy->direction; - if ($fieldName === 'visits') { + if (OrderableField::isBasicField($fieldName)) { + $qb->orderBy('s.' . $fieldName, $order); + } elseif (OrderableField::isVisitsField($fieldName)) { // FIXME This query is inefficient. // Diagnostic: It might need to use a sub-query, as done with the tags list query. $qb->addSelect('COUNT(DISTINCT v)') - ->leftJoin('s.visits', 'v') - ->groupBy('s') - ->orderBy('COUNT(DISTINCT v)', $order); - } elseif (contains(['longUrl', 'shortCode', 'dateCreated', 'title'], $fieldName)) { - $qb->orderBy('s.' . $fieldName, $order); + ->leftJoin('s.visits', 'v', Join::WITH, $qb->expr()->andX( + $qb->expr()->eq('v.shortUrl', 's'), + $fieldName === OrderableField::NON_BOT_VISITS->value + ? $qb->expr()->eq('v.potentialBot', 'false') + : null, + )) + ->groupBy('s') + ->orderBy('COUNT(DISTINCT v)', $order); } } diff --git a/module/Core/test-db/ShortUrl/Repository/ShortUrlListRepositoryTest.php b/module/Core/test-db/ShortUrl/Repository/ShortUrlListRepositoryTest.php index 76c87bd0..d55c301c 100644 --- a/module/Core/test-db/ShortUrl/Repository/ShortUrlListRepositoryTest.php +++ b/module/Core/test-db/ShortUrl/Repository/ShortUrlListRepositoryTest.php @@ -10,6 +10,7 @@ use ReflectionObject; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Model\Ordering; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; +use Shlinkio\Shlink\Core\ShortUrl\Model\OrderableField; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode; use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering; @@ -21,6 +22,8 @@ use Shlinkio\Shlink\Core\Visit\Model\Visitor; use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase; use function count; +use function Functional\map; +use function range; class ShortUrlListRepositoryTest extends DatabaseTestCase { @@ -56,12 +59,23 @@ class ShortUrlListRepositoryTest extends DatabaseTestCase $this->getEntityManager()->persist($foo); $bar = ShortUrl::withLongUrl('bar'); - $visit = Visit::forValidShortUrl($bar, Visitor::emptyInstance()); - $this->getEntityManager()->persist($visit); - $bar->setVisits(new ArrayCollection([$visit])); + $visits = map(range(0, 5), function () use ($bar) { + $visit = Visit::forValidShortUrl($bar, Visitor::botInstance()); + $this->getEntityManager()->persist($visit); + + return $visit; + }); + $bar->setVisits(new ArrayCollection($visits)); $this->getEntityManager()->persist($bar); $foo2 = ShortUrl::withLongUrl('foo_2'); + $visits2 = map(range(0, 3), function () use ($foo2) { + $visit = Visit::forValidShortUrl($foo2, Visitor::emptyInstance()); + $this->getEntityManager()->persist($visit); + + return $visit; + }); + $foo2->setVisits(new ArrayCollection($visits2)); $ref = new ReflectionObject($foo2); $dateProp = $ref->getProperty('dateCreated'); $dateProp->setAccessible(true); @@ -95,11 +109,19 @@ class ShortUrlListRepositoryTest extends DatabaseTestCase self::assertCount(1, $this->repo->findList(new ShortUrlsListFiltering(2, 2, Ordering::emptyInstance()))); $result = $this->repo->findList( - new ShortUrlsListFiltering(null, null, Ordering::fromTuple(['visits', 'DESC'])), + new ShortUrlsListFiltering(null, null, Ordering::fromTuple([OrderableField::VISITS->value, 'DESC'])), ); self::assertCount(3, $result); self::assertSame($bar, $result[0]); + $result = $this->repo->findList( + new ShortUrlsListFiltering(null, null, Ordering::fromTuple( + [OrderableField::NON_BOT_VISITS->value, 'DESC'], + )), + ); + self::assertCount(3, $result); + self::assertSame($foo2, $result[0]); + $result = $this->repo->findList( new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, [], null, DateRange::until( Chronos::now()->subDays(2), From 5b934c3f9a5cb9827761adce799383f5a127c95e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 16 Dec 2022 19:47:17 +0100 Subject: [PATCH 181/182] Updated changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6031a772..d097eced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * Providing `excludeMaxVisitsReached=true` and/or `excludePastValidUntil=true` to the `GET /short-urls` endpoint. * Providing `--exclude-max-visits-reached` and/or `--exclude-past-valid-until` to the `short-urls:list` command. +* [#1613](https://github.com/shlinkio/shlink/issues/1613) Added amount of visits coming from bots, non-bots and total to every short URL in the short URLs list. + + Additionally, added option to order by non-bot visits, by passing `nonBotVisits-DESC` or `nonBotVisits-ASC`. + * [#1599](https://github.com/shlinkio/shlink/issues/1599) Added support for credentials on redis DSNs, either only password, or both username and password. * [#1616](https://github.com/shlinkio/shlink/issues/1616) Added support to import orphan visits when importing short URLs from another Shlink instance. * [#1519](https://github.com/shlinkio/shlink/issues/1519) Allowing to search short URLs by default domain. From b97af7efb90ebce33565adeb94426175acb22f82 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 16 Dec 2022 22:33:16 +0100 Subject: [PATCH 182/182] Added v3.4.0 to changelog --- CHANGELOG.md | 2 +- composer.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d097eced..c3b9b2aa 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] +## [3.4.0] - 2022-12-16 ### Added * [#1612](https://github.com/shlinkio/shlink/issues/1612) Allowed to filter short URLs out of lists, when `validUntil` date is in the past or have reached their maximum amount of visits. diff --git a/composer.json b/composer.json index 6d169d8a..ddf41fa5 100644 --- a/composer.json +++ b/composer.json @@ -45,12 +45,12 @@ "php-middleware/request-id": "^4.1", "pugx/shortid-php": "^1.1", "ramsey/uuid": "^4.5", - "shlinkio/shlink-common": "dev-main#107b753 as 5.2", - "shlinkio/shlink-config": "dev-main#96c81fb as 2.3", + "shlinkio/shlink-common": "^5.2", + "shlinkio/shlink-config": "^2.3", "shlinkio/shlink-event-dispatcher": "^2.6", - "shlinkio/shlink-importer": "dev-main#c97662b as 5.0", + "shlinkio/shlink-importer": "^5.0", "shlinkio/shlink-installer": "^8.2", - "shlinkio/shlink-ip-geolocation": "dev-main#e208963 as 3.2", + "shlinkio/shlink-ip-geolocation": "^3.2", "spiral/roadrunner": "^2.11", "spiral/roadrunner-jobs": "^2.5", "symfony/console": "^6.1",