diff --git a/.gitattributes b/.gitattributes index eea2583..c49537b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,7 +11,6 @@ docker-compose.override.yml.dist export-ignore docker-compose.yml export-ignore Dockerfile export-ignore indocker export-ignore -infection.json export-ignore phpcs.xml export-ignore phpstan.neon export-ignore phpunit.xml.dist export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10ec1df..7aa46b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,5 +10,3 @@ on: jobs: ci: uses: shlinkio/github-actions/.github/workflows/php-lib-ci.yml@main - secrets: - INFECTION_BADGE_API_KEY: ${{ secrets.INFECTION_BADGE_API_KEY }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 52ab1c4..a2fbf60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ 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). +## [9.0.0] - 2024-03-03 +### Added +* Add QR code options for foreground color, background color and logo URL. + +### Changed +* Update dependencies +* Default value for QR codes enabled for disabled short URLs is now true. +* Title resolution defaults to true now. + +### Deprecated +* *Nothing* + +### Removed +* Remove config options related with webhooks. +* Remove config option to decode redis credentials. +* Remove support for openswoole. +* Remove infection and mutation tests. +* Remove web and task worker config options. + +### Fixed +* *Nothing* + + ## [8.7.0] - 2023-12-26 ### Added * Add config option to enable/disable QR codes for disables short URLs. diff --git a/README.md b/README.md index 2b4986f..4f7e214 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ A PHP command line tool used to install [shlink](https://shlink.io/). [![Build Status](https://img.shields.io/github/actions/workflow/status/shlinkio/shlink-installer/ci.yml?branch=develop&logo=github&style=flat-square)](https://github.com/shlinkio/shlink-installer/actions/workflows/ci.yml?query=workflow%3A%22Continuous+integration%22) [![Code Coverage](https://img.shields.io/codecov/c/gh/shlinkio/shlink-installer/develop?style=flat-square)](https://app.codecov.io/gh/shlinkio/shlink-installer) -[![Infection MSI](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fshlinkio%2Fshlink-installer%2Fdevelop)](https://dashboard.stryker-mutator.io/reports/github.com/shlinkio/shlink-installer/develop) [![Latest Stable Version](https://img.shields.io/github/release/shlinkio/shlink-installer.svg?style=flat-square)](https://packagist.org/packages/shlinkio/shlink-installer) [![License](https://img.shields.io/github/license/shlinkio/shlink-installer.svg?style=flat-square)](https://github.com/shlinkio/shlink-installer/blob/main/LICENSE) [![Paypal donate](https://img.shields.io/badge/Donate-paypal-blue.svg?style=flat-square&logo=paypal&colorA=aaaaaa)](https://slnk.to/donate) diff --git a/composer.json b/composer.json index dd0b5ac..485b0c5 100644 --- a/composer.json +++ b/composer.json @@ -16,21 +16,20 @@ "laminas/laminas-config": "^3.9", "laminas/laminas-config-aggregator": "^1.14", "laminas/laminas-servicemanager": "^3.22", - "laminas/laminas-stdlib": "^3.18", - "shlinkio/shlink-config": "^2.4", - "symfony/console": "^6.3", - "symfony/filesystem": "^6.3", - "symfony/process": "^6.3" + "laminas/laminas-stdlib": "^3.19", + "shlinkio/shlink-config": "^3.0 || ^2.5", + "symfony/console": "^7.0 || ^6.4", + "symfony/filesystem": "^7.0 || ^6.4", + "symfony/process": "^7.0 || ^6.4" }, "require-dev": { "devster/ubench": "^2.1", - "infection/infection": "^0.27.7", "phpstan/phpstan": "^1.10", "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^10.4", + "phpunit/phpunit": "^10.5", "roave/security-advisories": "dev-master", "shlinkio/php-coding-standard": "~2.3.0", - "symfony/var-dumper": "^6.3" + "symfony/var-dumper": "^7.0 || ^6.4" }, "autoload": { "psr-4": { @@ -46,41 +45,28 @@ "ci": [ "@cs", "@stan", - "@test:ci", - "@infect:ci" + "@test:ci" ], "cs": "phpcs", "cs:fix": "phpcbf", "stan": "phpstan analyse src test test-resources config --level=8", "test": "phpunit --order-by=random --testdox --colors=always", - "test:ci": "@test --coverage-clover=build/clover.xml --coverage-xml=build/coverage-xml --log-junit=build/junit.xml", - "test:pretty": "@test --coverage-html build/coverage-html", - "infect": "infection --threads=4 --min-msi=90 --log-verbosity=default --only-covered", - "infect:ci": "@infect --coverage=build --skip-initial-tests", - "infect:show": "@infect --show-mutations", - "infect:show:ci": "@infect --show-mutations --coverage=build", - "infect:test": [ - "@test:ci", - "@infect:show:ci" - ] + "test:ci": "@test --coverage-clover=build/clover.xml", + "test:pretty": "@test --coverage-html=build/coverage-html" }, "scripts-descriptions": { - "ci": "Alias for \"cs\", \"stan\", \"test:ci\" and \"infect:ci\"", + "ci": "Alias for \"cs\", \"stan\" and \"test:ci\"", "cs": "Checks coding styles", "cs:fix": "Fixes coding styles, when possible", "stan": "Inspects code with phpstan", "test": "Runs unit tests with no coverage reports", "test:ci": "Runs unit tests generating coverage reports and logs", - "test:pretty": "Runs unit tests generating coverage reports in html", - "infect": "Checks unit tests quality applying mutation testing", - "infect:ci": "Checks unit tests quality applying mutation testing with existing reports and logs", - "infect:show": "Checks unit tests quality applying mutation testing and shows applied mutators" + "test:pretty": "Runs unit tests generating coverage reports in html" }, "config": { "sort-packages": true, "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true, - "infection/extension-installer": true + "dealerdirect/phpcodesniffer-composer-installer": true } }, "bin": [ diff --git a/config/config.php b/config/config.php index 3febcae..1c9817d 100644 --- a/config/config.php +++ b/config/config.php @@ -63,8 +63,6 @@ => Config\Option\UrlShortener\EnableMultiSegmentSlugsConfigOption::class, 'URL shortener > Trailing slashes' => Config\Option\UrlShortener\EnableTrailingSlashConfigOption::class, 'URL shortener > Mode' => Config\Option\UrlShortener\ShortUrlModeConfigOption::class, - 'Webhooks > List' => Config\Option\Visit\VisitsWebhooksConfigOption::class, - 'Webhooks > Orphan visits' => Config\Option\Visit\OrphanVisitsWebhooksConfigOption::class, 'GeoLite2 license key' => Config\Option\UrlShortener\GeoLiteLicenseKeyConfigOption::class, 'Redirects > Status code (301/302)' => Config\Option\UrlShortener\RedirectStatusCodeConfigOption::class, 'Redirects > Caching life time' => Config\Option\UrlShortener\RedirectCacheLifeTimeConfigOption::class, @@ -91,6 +89,9 @@ 'QR codes > Default format' => Config\Option\QrCode\DefaultFormatConfigOption::class, 'QR codes > Default error correction' => Config\Option\QrCode\DefaultErrorCorrectionConfigOption::class, 'QR codes > Default round block size' => Config\Option\QrCode\DefaultRoundBlockSizeConfigOption::class, + 'QR codes > Default color' => Config\Option\QrCode\DefaultColorConfigOption::class, + 'QR codes > Default background color' => Config\Option\QrCode\DefaultBgColorConfigOption::class, + 'QR codes > Default logo URL' => Config\Option\QrCode\DefaultLogoUrlConfigOption::class, 'QR codes > Enabled for disabled short URLs' => Config\Option\QrCode\EnabledForDisabledShortUrlsConfigOption::class, ], @@ -99,12 +100,9 @@ 'Base path' => Config\Option\BasePathConfigOption::class, 'Timezone' => Config\Option\TimezoneConfigOption::class, 'Cache > namespace' => Config\Option\Cache\CacheNamespaceConfigOption::class, - 'Server > Amount of task workers' => Config\Option\Worker\TaskWorkerNumConfigOption::class, - 'Server > Amount of web workers' => Config\Option\Worker\WebWorkerNumConfigOption::class, ], 'INTEGRATIONS' => [ 'Redis > servers' => Config\Option\Redis\RedisServersConfigOption::class, - 'Redis > decode credentials' => Config\Option\Redis\RedisDecodeCredentialsConfigOption::class, 'Redis > sentinels service' => Config\Option\Redis\RedisSentinelServiceConfigOption::class, 'Redis > Pub/sub enabled' => Config\Option\Redis\RedisPubSubConfigOption::class, Config\Option\Mercure\EnableMercureConfigOption::class, @@ -149,13 +147,8 @@ Config\Option\UrlShortener\EnableTrailingSlashConfigOption::class => InvokableFactory::class, Config\Option\UrlShortener\ShortUrlModeConfigOption::class => InvokableFactory::class, Config\Option\Redis\RedisServersConfigOption::class => InvokableFactory::class, - Config\Option\Redis\RedisDecodeCredentialsConfigOption::class => InvokableFactory::class, Config\Option\Redis\RedisSentinelServiceConfigOption::class => InvokableFactory::class, Config\Option\Redis\RedisPubSubConfigOption::class => InvokableFactory::class, - Config\Option\Visit\VisitsWebhooksConfigOption::class => InvokableFactory::class, - Config\Option\Visit\OrphanVisitsWebhooksConfigOption::class => InvokableFactory::class, - Config\Option\Worker\TaskWorkerNumConfigOption::class => InvokableFactory::class, - Config\Option\Worker\WebWorkerNumConfigOption::class => InvokableFactory::class, Config\Option\UrlShortener\ShortCodeLengthOption::class => InvokableFactory::class, Config\Option\Mercure\EnableMercureConfigOption::class => InvokableFactory::class, Config\Option\Mercure\MercurePublicUrlConfigOption::class => InvokableFactory::class, @@ -188,6 +181,9 @@ Config\Option\QrCode\DefaultFormatConfigOption::class => InvokableFactory::class, Config\Option\QrCode\DefaultErrorCorrectionConfigOption::class => InvokableFactory::class, Config\Option\QrCode\DefaultRoundBlockSizeConfigOption::class => InvokableFactory::class, + Config\Option\QrCode\DefaultColorConfigOption::class => InvokableFactory::class, + Config\Option\QrCode\DefaultBgColorConfigOption::class => InvokableFactory::class, + Config\Option\QrCode\DefaultLogoUrlConfigOption::class => InvokableFactory::class, Config\Option\QrCode\EnabledForDisabledShortUrlsConfigOption::class => InvokableFactory::class, ], ], diff --git a/infection.json5 b/infection.json5 deleted file mode 100644 index 8bd2f44..0000000 --- a/infection.json5 +++ /dev/null @@ -1,26 +0,0 @@ -{ - source: { - directories: [ - 'src' - ] - }, - timeout: 5, - logs: { - text: 'build/infection/infection-log.txt', - html: 'build/infection/infection-log.html', - summary: 'build/infection/summary-log.txt', - debug: 'build/infection/debug-log.txt', - stryker: { - report: 'develop' - } - }, - tmpDir: 'build/infection/temp', - phpUnit: { - configDir: '.' - }, - mutators: { - '@default': true, - IdenticalEqual: false, - NotIdenticalNotEqual: false - } -} diff --git a/src/Command/InitCommand.php b/src/Command/InitCommand.php index 55b2f9c..1bb791a 100644 --- a/src/Command/InitCommand.php +++ b/src/Command/InitCommand.php @@ -47,7 +47,7 @@ protected function configure(): void ); } - protected function execute(InputInterface $input, OutputInterface $output): ?int + protected function execute(InputInterface $input, OutputInterface $output): int { $config = new ShlinkInitConfig( initializeDb: ! $this->skipInitDb->get($input), diff --git a/src/Command/SetOptionCommand.php b/src/Command/SetOptionCommand.php index 901ee09..23dd59c 100644 --- a/src/Command/SetOptionCommand.php +++ b/src/Command/SetOptionCommand.php @@ -76,7 +76,7 @@ protected function interact(InputInterface $input, OutputInterface $output): voi } } - protected function execute(InputInterface $input, OutputInterface $output): ?int + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $optionTitle = $io->choice('What config option do you want to change', array_keys($this->groups)); diff --git a/src/Config/Option/QrCode/DefaultBgColorConfigOption.php b/src/Config/Option/QrCode/DefaultBgColorConfigOption.php new file mode 100644 index 0000000..a1b1a86 --- /dev/null +++ b/src/Config/Option/QrCode/DefaultBgColorConfigOption.php @@ -0,0 +1,26 @@ +ask( + 'What\'s the default background color for generated QR codes', + '#FFFFFF', + ConfigOptionsValidator::validateHexColor(...), + ); + } +} diff --git a/src/Config/Option/QrCode/DefaultColorConfigOption.php b/src/Config/Option/QrCode/DefaultColorConfigOption.php new file mode 100644 index 0000000..43b9f7c --- /dev/null +++ b/src/Config/Option/QrCode/DefaultColorConfigOption.php @@ -0,0 +1,26 @@ +ask( + 'What\'s the default foreground color for generated QR codes', + '#000000', + ConfigOptionsValidator::validateHexColor(...), + ); + } +} diff --git a/src/Config/Option/QrCode/DefaultLogoUrlConfigOption.php b/src/Config/Option/QrCode/DefaultLogoUrlConfigOption.php new file mode 100644 index 0000000..02e5249 --- /dev/null +++ b/src/Config/Option/QrCode/DefaultLogoUrlConfigOption.php @@ -0,0 +1,25 @@ +ask( + 'Provide a URL for a logo to be placed inside the QR code (leave empty to use no logo)', + validator: ConfigOptionsValidator::validateUrl(...), + ); + } +} diff --git a/src/Config/Option/QrCode/DefaultMarginConfigOption.php b/src/Config/Option/QrCode/DefaultMarginConfigOption.php index 6683bee..d7a92b6 100644 --- a/src/Config/Option/QrCode/DefaultMarginConfigOption.php +++ b/src/Config/Option/QrCode/DefaultMarginConfigOption.php @@ -5,13 +5,11 @@ namespace Shlinkio\Shlink\Installer\Config\Option\QrCode; use Shlinkio\Shlink\Installer\Config\Option\BaseConfigOption; -use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidatorsTrait; +use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidator; use Symfony\Component\Console\Style\StyleInterface; class DefaultMarginConfigOption extends BaseConfigOption { - use ConfigOptionsValidatorsTrait; - public function getEnvVar(): string { return 'DEFAULT_QR_CODE_MARGIN'; @@ -22,7 +20,7 @@ public function ask(StyleInterface $io, array $currentOptions): int return $io->ask( 'What\'s the default margin, in pixels, you want generated QR codes to have', '0', - fn (mixed $value) => $this->validateNumberGreaterThan($value, 0), + fn (mixed $value) => ConfigOptionsValidator::validateNumberGreaterThan($value, 0), ); } } diff --git a/src/Config/Option/QrCode/DefaultSizeConfigOption.php b/src/Config/Option/QrCode/DefaultSizeConfigOption.php index b2fae5b..4b22976 100644 --- a/src/Config/Option/QrCode/DefaultSizeConfigOption.php +++ b/src/Config/Option/QrCode/DefaultSizeConfigOption.php @@ -5,13 +5,11 @@ namespace Shlinkio\Shlink\Installer\Config\Option\QrCode; use Shlinkio\Shlink\Installer\Config\Option\BaseConfigOption; -use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidatorsTrait; +use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidator; use Symfony\Component\Console\Style\StyleInterface; class DefaultSizeConfigOption extends BaseConfigOption { - use ConfigOptionsValidatorsTrait; - private const MIN_SIZE = 50; private const MAX_SIZE = 1000; @@ -25,7 +23,7 @@ public function ask(StyleInterface $io, array $currentOptions): int return $io->ask( 'What\'s the default size, in pixels, you want generated QR codes to have (50 to 1000)', '300', - fn (mixed $value) => $this->validateNumberBetween($value, self::MIN_SIZE, self::MAX_SIZE), + fn (mixed $value) => ConfigOptionsValidator::validateNumberBetween($value, self::MIN_SIZE, self::MAX_SIZE), ); } } diff --git a/src/Config/Option/QrCode/EnabledForDisabledShortUrlsConfigOption.php b/src/Config/Option/QrCode/EnabledForDisabledShortUrlsConfigOption.php index 674225e..e8e8b6f 100644 --- a/src/Config/Option/QrCode/EnabledForDisabledShortUrlsConfigOption.php +++ b/src/Config/Option/QrCode/EnabledForDisabledShortUrlsConfigOption.php @@ -20,8 +20,6 @@ public function ask(StyleInterface $io, array $currentOptions): bool 'Should Shlink be able to generate QR codes for short URLs which are not enabled? (Short URLs are not ' . 'enabled if they have a "valid since" in the future, a "valid until" in the past, or reached the maximum ' . 'amount of allowed visits)', - // Deprecated. Shlink 4.0.0 should change default value to `true` - false, ); } } diff --git a/src/Config/Option/RabbitMq/RabbitMqPortConfigOption.php b/src/Config/Option/RabbitMq/RabbitMqPortConfigOption.php index 0481881..27b3895 100644 --- a/src/Config/Option/RabbitMq/RabbitMqPortConfigOption.php +++ b/src/Config/Option/RabbitMq/RabbitMqPortConfigOption.php @@ -4,13 +4,11 @@ namespace Shlinkio\Shlink\Installer\Config\Option\RabbitMq; -use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidatorsTrait; +use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidator; use Symfony\Component\Console\Style\StyleInterface; class RabbitMqPortConfigOption extends AbstractRabbitMqEnabledConfigOption { - use ConfigOptionsValidatorsTrait; - public function getEnvVar(): string { return 'RABBITMQ_PORT'; @@ -22,7 +20,7 @@ public function ask(StyleInterface $io, array $currentOptions): int return (int) $io->ask( 'RabbitMQ port', $useSsl ? '5671' : '5672', - fn (mixed $value) => $this->validateNumberBetween($value, 1, 65535), + fn (mixed $value) => ConfigOptionsValidator::validateNumberBetween($value, 1, 65535), ); } diff --git a/src/Config/Option/Redirect/BaseUrlRedirectConfigOption.php b/src/Config/Option/Redirect/BaseUrlRedirectConfigOption.php index 471f64e..b152b32 100644 --- a/src/Config/Option/Redirect/BaseUrlRedirectConfigOption.php +++ b/src/Config/Option/Redirect/BaseUrlRedirectConfigOption.php @@ -5,13 +5,11 @@ namespace Shlinkio\Shlink\Installer\Config\Option\Redirect; use Shlinkio\Shlink\Installer\Config\Option\BaseConfigOption; -use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidatorsTrait; +use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidator; use Symfony\Component\Console\Style\StyleInterface; class BaseUrlRedirectConfigOption extends BaseConfigOption { - use ConfigOptionsValidatorsTrait; - public function getEnvVar(): string { return 'DEFAULT_BASE_URL_REDIRECT'; @@ -23,7 +21,7 @@ public function ask(StyleInterface $io, array $currentOptions): ?string 'Custom URL to redirect to when a user hits Shlink\'s base URL (If no value is provided, the ' . 'user will see a default "404 not found" page)', null, - [$this, 'validateUrl'], + ConfigOptionsValidator::validateUrl(...), ); } } diff --git a/src/Config/Option/Redirect/InvalidShortUrlRedirectConfigOption.php b/src/Config/Option/Redirect/InvalidShortUrlRedirectConfigOption.php index 1357e90..2e98d14 100644 --- a/src/Config/Option/Redirect/InvalidShortUrlRedirectConfigOption.php +++ b/src/Config/Option/Redirect/InvalidShortUrlRedirectConfigOption.php @@ -5,13 +5,11 @@ namespace Shlinkio\Shlink\Installer\Config\Option\Redirect; use Shlinkio\Shlink\Installer\Config\Option\BaseConfigOption; -use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidatorsTrait; +use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidator; use Symfony\Component\Console\Style\StyleInterface; class InvalidShortUrlRedirectConfigOption extends BaseConfigOption { - use ConfigOptionsValidatorsTrait; - public function getEnvVar(): string { return 'DEFAULT_INVALID_SHORT_URL_REDIRECT'; @@ -23,7 +21,7 @@ public function ask(StyleInterface $io, array $currentOptions): ?string 'Custom URL to redirect to when a user hits an invalid short URL (If no value is provided, the ' . 'user will see a default "404 not found" page)', null, - [$this, 'validateUrl'], + ConfigOptionsValidator::validateUrl(...), ); } } diff --git a/src/Config/Option/Redirect/Regular404RedirectConfigOption.php b/src/Config/Option/Redirect/Regular404RedirectConfigOption.php index 6ef7f6a..0098a81 100644 --- a/src/Config/Option/Redirect/Regular404RedirectConfigOption.php +++ b/src/Config/Option/Redirect/Regular404RedirectConfigOption.php @@ -5,13 +5,11 @@ namespace Shlinkio\Shlink\Installer\Config\Option\Redirect; use Shlinkio\Shlink\Installer\Config\Option\BaseConfigOption; -use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidatorsTrait; +use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidator; use Symfony\Component\Console\Style\StyleInterface; class Regular404RedirectConfigOption extends BaseConfigOption { - use ConfigOptionsValidatorsTrait; - public function getEnvVar(): string { return 'DEFAULT_REGULAR_404_REDIRECT'; @@ -23,7 +21,7 @@ public function ask(StyleInterface $io, array $currentOptions): ?string 'Custom URL to redirect to when a user hits a not found URL other than an invalid short URL ' . '(If no value is provided, the user will see a default "404 not found" page)', null, - [$this, 'validateUrl'], + ConfigOptionsValidator::validateUrl(...), ); } } diff --git a/src/Config/Option/Redis/RedisDecodeCredentialsConfigOption.php b/src/Config/Option/Redis/RedisDecodeCredentialsConfigOption.php deleted file mode 100644 index 29a799f..0000000 --- a/src/Config/Option/Redis/RedisDecodeCredentialsConfigOption.php +++ /dev/null @@ -1,38 +0,0 @@ -confirm( - 'Do you want redis credentials to be URL-decoded? ' - . '(If you provided servers with URL-encoded credentials, this should be "yes")', - false, - ); - } - - public function getDependentOption(): string - { - return RedisServersConfigOption::class; - } -} diff --git a/src/Config/Option/Server/RuntimeConfigOption.php b/src/Config/Option/Server/RuntimeConfigOption.php index f57fc73..316932b 100644 --- a/src/Config/Option/Server/RuntimeConfigOption.php +++ b/src/Config/Option/Server/RuntimeConfigOption.php @@ -15,7 +15,6 @@ class RuntimeConfigOption extends BaseConfigOption public const ENV_VAR = 'RUNTIME'; private const RUNTIMES = [ 'RoadRunner' => RuntimeType::ASYNC, - 'Openswoole' => RuntimeType::ASYNC, 'Classic web server (Nginx, Apache, etc)' => RuntimeType::REGULAR, ]; diff --git a/src/Config/Option/UrlShortener/AutoResolveTitlesConfigOption.php b/src/Config/Option/UrlShortener/AutoResolveTitlesConfigOption.php index 7781de5..c8c9553 100644 --- a/src/Config/Option/UrlShortener/AutoResolveTitlesConfigOption.php +++ b/src/Config/Option/UrlShortener/AutoResolveTitlesConfigOption.php @@ -19,7 +19,6 @@ public function ask(StyleInterface $io, array $currentOptions): bool return $io->confirm( 'Do you want Shlink to resolve the short URL title based on the long URL\'s title tag (if any)? ' . 'Otherwise, it will be kept empty unless explicitly provided.', - false, ); } } diff --git a/src/Config/Option/UrlShortener/RedirectCacheLifeTimeConfigOption.php b/src/Config/Option/UrlShortener/RedirectCacheLifeTimeConfigOption.php index a34f389..6bb08c1 100644 --- a/src/Config/Option/UrlShortener/RedirectCacheLifeTimeConfigOption.php +++ b/src/Config/Option/UrlShortener/RedirectCacheLifeTimeConfigOption.php @@ -6,14 +6,12 @@ use Shlinkio\Shlink\Installer\Config\Option\BaseConfigOption; use Shlinkio\Shlink\Installer\Config\Option\DependentConfigOptionInterface; -use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidatorsTrait; +use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidator; use Shlinkio\Shlink\Installer\Util\ArrayUtils; use Symfony\Component\Console\Style\StyleInterface; class RedirectCacheLifeTimeConfigOption extends BaseConfigOption implements DependentConfigOptionInterface { - use ConfigOptionsValidatorsTrait; - public function getEnvVar(): string { return 'REDIRECT_CACHE_LIFETIME'; @@ -35,7 +33,7 @@ public function ask(StyleInterface $io, array $currentOptions): int return $io->ask( 'How long (in seconds) do you want your redirects to be cached by visitors?', '30', - [$this, 'validatePositiveNumber'], + ConfigOptionsValidator::validatePositiveNumber(...), ); } diff --git a/src/Config/Option/UrlShortener/ShortCodeLengthOption.php b/src/Config/Option/UrlShortener/ShortCodeLengthOption.php index 90c237f..8d44f0c 100644 --- a/src/Config/Option/UrlShortener/ShortCodeLengthOption.php +++ b/src/Config/Option/UrlShortener/ShortCodeLengthOption.php @@ -5,13 +5,11 @@ namespace Shlinkio\Shlink\Installer\Config\Option\UrlShortener; use Shlinkio\Shlink\Installer\Config\Option\BaseConfigOption; -use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidatorsTrait; +use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidator; use Symfony\Component\Console\Style\StyleInterface; class ShortCodeLengthOption extends BaseConfigOption { - use ConfigOptionsValidatorsTrait; - public function getEnvVar(): string { return 'DEFAULT_SHORT_CODES_LENGTH'; @@ -23,7 +21,7 @@ public function ask(StyleInterface $io, array $currentOptions): int 'What is the default length you want generated short codes to have? (You will still be able to override ' . 'this on every created short URL)', '5', - fn ($value) => $this->validateNumberGreaterThan($value, 4), + fn ($value) => ConfigOptionsValidator::validateNumberGreaterThan($value, 4), ); } } diff --git a/src/Config/Option/UrlShortener/ShortUrlModeConfigOption.php b/src/Config/Option/UrlShortener/ShortUrlModeConfigOption.php index 873d87b..579cc60 100644 --- a/src/Config/Option/UrlShortener/ShortUrlModeConfigOption.php +++ b/src/Config/Option/UrlShortener/ShortUrlModeConfigOption.php @@ -5,12 +5,11 @@ namespace Shlinkio\Shlink\Installer\Config\Option\UrlShortener; use Shlinkio\Shlink\Installer\Config\Option\BaseConfigOption; -use Shlinkio\Shlink\Installer\Config\Option\ConfigOptionMigratorInterface; use Symfony\Component\Console\Style\StyleInterface; use const PHP_EOL; -class ShortUrlModeConfigOption extends BaseConfigOption implements ConfigOptionMigratorInterface +class ShortUrlModeConfigOption extends BaseConfigOption { private const MODES = [ 'strict' => 'Short codes and custom slugs will be matched in a case-sensitive way ("foo" !== "FOO"). ' @@ -37,9 +36,4 @@ public function ask(StyleInterface $io, array $currentOptions): string 'strict', ); } - - public function tryToMigrateValue(mixed $currentValue): mixed - { - return $currentValue === 'loosely' ? 'loose' : $currentValue; - } } diff --git a/src/Config/Option/Visit/OrphanVisitsWebhooksConfigOption.php b/src/Config/Option/Visit/OrphanVisitsWebhooksConfigOption.php deleted file mode 100644 index e5cb647..0000000 --- a/src/Config/Option/Visit/OrphanVisitsWebhooksConfigOption.php +++ /dev/null @@ -1,35 +0,0 @@ -confirm('Do you want to also notify the webhooks when an orphan visit occurs?', false); - } - - public function getDependentOption(): string - { - return VisitsWebhooksConfigOption::class; - } -} diff --git a/src/Config/Option/Visit/VisitsThresholdConfigOption.php b/src/Config/Option/Visit/VisitsThresholdConfigOption.php index 4d5d78e..ca699f1 100644 --- a/src/Config/Option/Visit/VisitsThresholdConfigOption.php +++ b/src/Config/Option/Visit/VisitsThresholdConfigOption.php @@ -5,13 +5,11 @@ namespace Shlinkio\Shlink\Installer\Config\Option\Visit; use Shlinkio\Shlink\Installer\Config\Option\BaseConfigOption; -use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidatorsTrait; +use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidator; use Symfony\Component\Console\Style\StyleInterface; class VisitsThresholdConfigOption extends BaseConfigOption { - use ConfigOptionsValidatorsTrait; - public function getEnvVar(): string { return 'DELETE_SHORT_URL_THRESHOLD'; @@ -23,7 +21,7 @@ public function ask(StyleInterface $io, array $currentOptions): ?int 'What is the amount of visits from which the system will not allow short URLs to be deleted? Leave empty ' . 'to always allow deleting short URLs, no matter what', null, - [$this, 'validateOptionalPositiveNumber'], + ConfigOptionsValidator::validateOptionalPositiveNumber(...), ); return $result === null ? null : (int) $result; diff --git a/src/Config/Option/Visit/VisitsWebhooksConfigOption.php b/src/Config/Option/Visit/VisitsWebhooksConfigOption.php deleted file mode 100644 index 6e322d5..0000000 --- a/src/Config/Option/Visit/VisitsWebhooksConfigOption.php +++ /dev/null @@ -1,34 +0,0 @@ -ask( - 'Provide a comma-separated list of webhook URLs which will receive POST notifications when short URLs ' - . 'receive visits.', - null, - [$this, 'splitAndValidateMultipleUrls'], - )); - } -} diff --git a/src/Config/Option/Worker/AbstractWorkerNumConfigOption.php b/src/Config/Option/Worker/AbstractWorkerNumConfigOption.php deleted file mode 100644 index f995994..0000000 --- a/src/Config/Option/Worker/AbstractWorkerNumConfigOption.php +++ /dev/null @@ -1,30 +0,0 @@ -ask( - $this->getQuestionToAsk(), - '16', - fn ($value) => $this->validateNumberGreaterThan($value, $this->getMinimumValue()), - ); - } - - protected function getMinimumValue(): int - { - return 1; - } - - abstract protected function getQuestionToAsk(): string; -} diff --git a/src/Config/Option/Worker/TaskWorkerNumConfigOption.php b/src/Config/Option/Worker/TaskWorkerNumConfigOption.php deleted file mode 100644 index dff3e16..0000000 --- a/src/Config/Option/Worker/TaskWorkerNumConfigOption.php +++ /dev/null @@ -1,23 +0,0 @@ -validateUrl(...), $splitUrls); - } - - public function validateOptionalPositiveNumber(mixed $value): ?int - { - return $value === null ? $value : $this->validatePositiveNumber($value); + return $value === null ? $value : self::validatePositiveNumber($value); } - public function validatePositiveNumber(mixed $value): int + public static function validatePositiveNumber(mixed $value): int { - return $this->validateNumberGreaterThan($value, 1); + return self::validateNumberGreaterThan($value, 1); } - public function validateNumberGreaterThan(mixed $value, int $min): int + public static function validateNumberGreaterThan(mixed $value, int $min): int { $intValue = (int) $value; if (! is_numeric($value) || $intValue < $min) { @@ -62,7 +53,7 @@ public function validateNumberGreaterThan(mixed $value, int $min): int return $intValue; } - public function validateNumberBetween(mixed $value, int $min, int $max): int + public static function validateNumberBetween(mixed $value, int $min, int $max): int { $intValue = (int) $value; if (! is_numeric($value) || $intValue < $min || $intValue > $max) { @@ -73,4 +64,24 @@ public function validateNumberBetween(mixed $value, int $min, int $max): int return $intValue; } + + public static function validateHexColor(string $color): string + { + $onlyDigitsColor = ltrim($color, '#'); + $digitsLength = strlen($onlyDigitsColor); + + if ($digitsLength !== 3 && $digitsLength !== 6) { + throw new InvalidConfigOptionException( + 'Provided value must have 3 or 6 characters, and be optionally preceded by the # character', + ); + } + + if (! ctype_xdigit($onlyDigitsColor)) { + throw new InvalidConfigOptionException( + 'Provided value must be the hexadecimal number representation of a color', + ); + } + + return $color; + } } diff --git a/src/Util/Utils.php b/src/Util/Utils.php index 4f24049..860d5c2 100644 --- a/src/Util/Utils.php +++ b/src/Util/Utils.php @@ -9,7 +9,6 @@ use Shlinkio\Shlink\Installer\Config\Util\DatabaseDriver; use function array_filter; -use function array_map; use function array_reduce; use function ctype_upper; use function explode; @@ -17,17 +16,11 @@ use function is_array; use function is_bool; use function is_numeric; -use function trim; use const ARRAY_FILTER_USE_KEY; class Utils { - public static function commaSeparatedToList(string $list): array - { - return array_map(static fn (string $value) => trim($value), explode(',', $list)); - } - public static function normalizeAndKeepEnvVarKeys(array $array): array { $dbEnvVar = DatabaseDriverConfigOption::ENV_VAR; diff --git a/test/Config/Option/QrCode/DefaultBgColorConfigOptionTest.php b/test/Config/Option/QrCode/DefaultBgColorConfigOptionTest.php new file mode 100644 index 0000000..9c08892 --- /dev/null +++ b/test/Config/Option/QrCode/DefaultBgColorConfigOptionTest.php @@ -0,0 +1,42 @@ +configOption = new DefaultBgColorConfigOption(); + } + + #[Test] + public function returnsExpectedEnvVar(): void + { + self::assertEquals('DEFAULT_QR_CODE_BG_COLOR', $this->configOption->getEnvVar()); + } + + #[Test] + public function expectedQuestionIsAsked(): void + { + $expectedAnswer = 'aaa'; + $io = $this->createMock(StyleInterface::class); + $io->expects($this->once())->method('ask')->with( + 'What\'s the default background color for generated QR codes', + '#FFFFFF', + $this->anything(), + )->willReturn($expectedAnswer); + + $answer = $this->configOption->ask($io, []); + + self::assertEquals($expectedAnswer, $answer); + } +} diff --git a/test/Config/Option/Worker/WebWorkerNumConfigOptionTest.php b/test/Config/Option/QrCode/DefaultColorConfigOptionTest.php similarity index 56% rename from test/Config/Option/Worker/WebWorkerNumConfigOptionTest.php rename to test/Config/Option/QrCode/DefaultColorConfigOptionTest.php index 97db8b8..dacbb29 100644 --- a/test/Config/Option/Worker/WebWorkerNumConfigOptionTest.php +++ b/test/Config/Option/QrCode/DefaultColorConfigOptionTest.php @@ -2,36 +2,36 @@ declare(strict_types=1); -namespace ShlinkioTest\Shlink\Installer\Config\Option\Worker; +namespace ShlinkioTest\Shlink\Installer\Config\Option\QrCode; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; -use Shlinkio\Shlink\Installer\Config\Option\Worker\WebWorkerNumConfigOption; +use Shlinkio\Shlink\Installer\Config\Option\QrCode\DefaultColorConfigOption; use Symfony\Component\Console\Style\StyleInterface; -class WebWorkerNumConfigOptionTest extends TestCase +class DefaultColorConfigOptionTest extends TestCase { - private WebWorkerNumConfigOption $configOption; + private DefaultColorConfigOption $configOption; public function setUp(): void { - $this->configOption = new WebWorkerNumConfigOption(); + $this->configOption = new DefaultColorConfigOption(); } #[Test] public function returnsExpectedEnvVar(): void { - self::assertEquals('WEB_WORKER_NUM', $this->configOption->getEnvVar()); + self::assertEquals('DEFAULT_QR_CODE_COLOR', $this->configOption->getEnvVar()); } #[Test] public function expectedQuestionIsAsked(): void { - $expectedAnswer = 16; + $expectedAnswer = 'aaa'; $io = $this->createMock(StyleInterface::class); $io->expects($this->once())->method('ask')->with( - 'How many concurrent requests do you want Shlink to be able to serve?', - '16', + 'What\'s the default foreground color for generated QR codes', + '#000000', $this->anything(), )->willReturn($expectedAnswer); diff --git a/test/Config/Option/QrCode/DefaultLogoUrlConfigOptionTest.php b/test/Config/Option/QrCode/DefaultLogoUrlConfigOptionTest.php new file mode 100644 index 0000000..ff6f51e --- /dev/null +++ b/test/Config/Option/QrCode/DefaultLogoUrlConfigOptionTest.php @@ -0,0 +1,42 @@ +configOption = new DefaultLogoUrlConfigOption(); + } + + #[Test] + public function returnsExpectedEnvVar(): void + { + self::assertEquals('DEFAULT_QR_CODE_LOGO_URL', $this->configOption->getEnvVar()); + } + + #[Test] + public function expectedQuestionIsAsked(): void + { + $expectedAnswer = 'the_answer'; + $io = $this->createMock(StyleInterface::class); + $io->expects($this->once())->method('ask')->with( + 'Provide a URL for a logo to be placed inside the QR code (leave empty to use no logo)', + null, + $this->anything(), + )->willReturn($expectedAnswer); + + $answer = $this->configOption->ask($io, []); + + self::assertEquals($expectedAnswer, $answer); + } +} diff --git a/test/Config/Option/QrCode/EnabledForDisabledShortUrlsConfigOptionTest.php b/test/Config/Option/QrCode/EnabledForDisabledShortUrlsConfigOptionTest.php index f9cbe2d..2d64a9b 100644 --- a/test/Config/Option/QrCode/EnabledForDisabledShortUrlsConfigOptionTest.php +++ b/test/Config/Option/QrCode/EnabledForDisabledShortUrlsConfigOptionTest.php @@ -32,7 +32,6 @@ public function expectedQuestionIsAsked(): void 'Should Shlink be able to generate QR codes for short URLs which are not enabled? (Short URLs are not ' . 'enabled if they have a "valid since" in the future, a "valid until" in the past, or reached the maximum ' . 'amount of allowed visits)', - false, )->willReturn(true); $answer = $this->configOption->ask($io, []); diff --git a/test/Config/Option/Redis/RedisDecodeCredentialsConfigOptionTest.php b/test/Config/Option/Redis/RedisDecodeCredentialsConfigOptionTest.php deleted file mode 100644 index 35bdf8b..0000000 --- a/test/Config/Option/Redis/RedisDecodeCredentialsConfigOptionTest.php +++ /dev/null @@ -1,62 +0,0 @@ -configOption = new RedisDecodeCredentialsConfigOption(); - } - - #[Test] - public function returnsExpectedConfig(): void - { - self::assertEquals('REDIS_DECODE_CREDENTIALS', $this->configOption->getEnvVar()); - } - - #[Test] - public function expectedQuestionIsAsked(): void - { - $io = $this->createMock(StyleInterface::class); - $io->expects($this->once())->method('confirm')->with( - 'Do you want redis credentials to be URL-decoded? ' - . '(If you provided servers with URL-encoded credentials, this should be "yes")', - false, - )->willReturn(true); - - $answer = $this->configOption->ask($io, []); - - self::assertEquals(true, $answer); - } - - #[Test, DataProvider('provideCurrentOptions')] - public function shouldBeCalledOnlyIfItDoesNotYetExist(array $currentOptions, bool $expected): void - { - self::assertEquals($expected, $this->configOption->shouldBeAsked($currentOptions)); - } - - public static function provideCurrentOptions(): iterable - { - yield 'not exists in config' => [[], false]; - yield 'exists in config' => [['REDIS_DECODE_CREDENTIALS' => true], false]; - yield 'redis enabled in config' => [[RedisServersConfigOption::ENV_VAR => 'bar'], true]; - } - - #[Test] - public function dependsOnRedisServer(): void - { - self::assertEquals(RedisServersConfigOption::class, $this->configOption->getDependentOption()); - } -} diff --git a/test/Config/Option/Server/RuntimeConfigOptionTest.php b/test/Config/Option/Server/RuntimeConfigOptionTest.php index 88caed6..156c863 100644 --- a/test/Config/Option/Server/RuntimeConfigOptionTest.php +++ b/test/Config/Option/Server/RuntimeConfigOptionTest.php @@ -35,7 +35,6 @@ public function expectedQuestionIsAsked(string $answer, RuntimeType $expectedRun . 'follow-up questions)', [ 'RoadRunner', - 'Openswoole', 'Classic web server (Nginx, Apache, etc)', ], 'RoadRunner', @@ -49,7 +48,6 @@ public function expectedQuestionIsAsked(string $answer, RuntimeType $expectedRun public static function provideRuntimes(): iterable { yield 'RoadRunner' => ['RoadRunner', RuntimeType::ASYNC]; - yield 'Openswoole' => ['Openswoole', RuntimeType::ASYNC]; yield 'Classic web server' => ['Classic web server (Nginx, Apache, etc)', RuntimeType::REGULAR]; } } diff --git a/test/Config/Option/UrlShortener/AutoResolveTitlesConfigOptionTest.php b/test/Config/Option/UrlShortener/AutoResolveTitlesConfigOptionTest.php index 6330e69..45b38d4 100644 --- a/test/Config/Option/UrlShortener/AutoResolveTitlesConfigOptionTest.php +++ b/test/Config/Option/UrlShortener/AutoResolveTitlesConfigOptionTest.php @@ -31,7 +31,6 @@ public function expectedQuestionIsAsked(): void $io->expects($this->once())->method('confirm')->with( 'Do you want Shlink to resolve the short URL title based on the long URL\'s title tag (if any)? ' . 'Otherwise, it will be kept empty unless explicitly provided.', - false, )->willReturn(true); $answer = $this->configOption->ask($io, []); diff --git a/test/Config/Option/UrlShortener/ShortUrlModeConfigOptionTest.php b/test/Config/Option/UrlShortener/ShortUrlModeConfigOptionTest.php index 4218faf..89361f9 100644 --- a/test/Config/Option/UrlShortener/ShortUrlModeConfigOptionTest.php +++ b/test/Config/Option/UrlShortener/ShortUrlModeConfigOptionTest.php @@ -51,17 +51,4 @@ public static function provideChoices(): iterable yield 'strict' => ['strict']; yield 'loose' => ['loose']; } - - #[Test, DataProvider('provideMigrationValues')] - public function deprecatedValueIsProperlyMigrated(string $existingValue, string $expectedResult): void - { - self::assertEquals($expectedResult, $this->configOption->tryToMigrateValue($existingValue)); - } - - public static function provideMigrationValues(): iterable - { - yield 'strict' => ['strict', 'strict']; - yield 'loose' => ['loose', 'loose']; - yield 'loosely' => ['loosely', 'loose']; - } } diff --git a/test/Config/Option/Visit/OrphanVisitsWebhooksConfigOptionTest.php b/test/Config/Option/Visit/OrphanVisitsWebhooksConfigOptionTest.php deleted file mode 100644 index 15e4cc4..0000000 --- a/test/Config/Option/Visit/OrphanVisitsWebhooksConfigOptionTest.php +++ /dev/null @@ -1,69 +0,0 @@ -configOption = new OrphanVisitsWebhooksConfigOption(); - } - - #[Test] - public function returnsExpectedEnvVar(): void - { - self::assertEquals('NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS', $this->configOption->getEnvVar()); - } - - #[Test] - public function returnsExpectedDependantOption(): void - { - self::assertEquals(VisitsWebhooksConfigOption::class, $this->configOption->getDependentOption()); - } - - #[Test] - public function expectedQuestionIsAsked(): void - { - $expectedAnswer = true; - $io = $this->createMock(StyleInterface::class); - $io->expects($this->once())->method('confirm')->with( - 'Do you want to also notify the webhooks when an orphan visit occurs?', - false, - )->willReturn($expectedAnswer); - - $answer = $this->configOption->ask($io, []); - - self::assertEquals($expectedAnswer, $answer); - } - - #[Test, DataProvider('provideCurrentOptions')] - public function shouldBeAskedOnlyWhenTheListOfWebhooksIsNotEmpty( - array $currentOptions, - bool $expected, - ): void { - self::assertEquals($expected, $this->configOption->shouldBeAsked([ - RuntimeConfigOption::ENV_VAR => RuntimeType::ASYNC->value, - ...$currentOptions, - ])); - } - - public static function provideCurrentOptions(): iterable - { - yield 'without config' => [[], false]; - yield 'without webhooks' => [[VisitsWebhooksConfigOption::ENV_VAR => []], false]; - yield 'with webhooks' => [[VisitsWebhooksConfigOption::ENV_VAR => ['foo', 'bar']], true]; - } -} diff --git a/test/Config/Option/Visit/VisitsWebhooksConfigOptionTest.php b/test/Config/Option/Visit/VisitsWebhooksConfigOptionTest.php deleted file mode 100644 index 0ff37e2..0000000 --- a/test/Config/Option/Visit/VisitsWebhooksConfigOptionTest.php +++ /dev/null @@ -1,44 +0,0 @@ -configOption = new VisitsWebhooksConfigOption(); - } - - #[Test] - public function returnsExpectedEnvVar(): void - { - self::assertEquals('VISITS_WEBHOOKS', $this->configOption->getEnvVar()); - } - - #[Test] - public function expectedQuestionIsAsked(): void - { - $urls = ['foo', 'bar']; - $expectedAnswer = 'foo,bar'; - $io = $this->createMock(StyleInterface::class); - $io->expects($this->once())->method('ask')->with( - 'Provide a comma-separated list of webhook URLs which will receive POST notifications when short URLs ' - . 'receive visits.', - null, - $this->anything(), - )->willReturn($urls); - - $answer = $this->configOption->ask($io, []); - - self::assertEquals($expectedAnswer, $answer); - } -} diff --git a/test/Config/Option/Worker/TaskWorkerNumConfigOptionTest.php b/test/Config/Option/Worker/TaskWorkerNumConfigOptionTest.php deleted file mode 100644 index 2d679ce..0000000 --- a/test/Config/Option/Worker/TaskWorkerNumConfigOptionTest.php +++ /dev/null @@ -1,118 +0,0 @@ -configOption = new TaskWorkerNumConfigOption(); - } - - #[Test] - public function returnsExpectedEnvVar(): void - { - self::assertEquals('TASK_WORKER_NUM', $this->configOption->getEnvVar()); - } - - #[Test, DataProvider('provideValidValues')] - public function expectedQuestionIsAsked(int $expectedAnswer): void - { - $io = $this->createMock(StyleInterface::class); - $io->expects($this->once())->method('ask')->with( - 'How many concurrent background tasks do you want Shlink to be able to execute?', - '16', - $this->callback(function (callable $arg) use ($expectedAnswer) { - $arg($expectedAnswer); - return true; - }), - )->willReturn($expectedAnswer); - - $answer = $this->configOption->ask($io, []); - - self::assertEquals($expectedAnswer, $answer); - } - - public static function provideValidValues(): iterable - { - yield [4]; - yield [10]; - yield [16]; - } - - #[Test, DataProvider('provideInvalidValues')] - public function throwsAnErrorWhenProvidedValueDoesNotMeetTheMinimum( - mixed $expectedAnswer, - string $expectedMessage, - ): void { - $io = $this->createMock(StyleInterface::class); - $io->expects($this->once())->method('ask')->with( - 'How many concurrent background tasks do you want Shlink to be able to execute?', - '16', - $this->callback(function (callable $arg) use ($expectedAnswer, $expectedMessage) { - try { - $arg($expectedAnswer); - } catch (Throwable $e) { - Assert::assertInstanceOf(InvalidConfigOptionException::class, $e); - Assert::assertEquals($expectedMessage, $e->getMessage()); - } - - return true; - }), - )->willReturn(1); - - $this->configOption->ask($io, []); - } - - public static function provideInvalidValues(): iterable - { - yield '3' => [3, 'Provided value "3" is invalid. Expected a number greater or equal than 4']; - yield '2' => [2, 'Provided value "2" is invalid. Expected a number greater or equal than 4']; - yield '1' => [1, 'Provided value "1" is invalid. Expected a number greater or equal than 4']; - yield 'negative' => [-10, 'Provided value "-10" is invalid. Expected a number greater or equal than 4']; - yield 'string' => [ - 'not a number', - 'Provided value "not a number" is invalid. Expected a number greater or equal than 4', - ]; - } - - #[Test, DataProvider('provideCurrentOptions')] - public function shouldBeAskedWhenNotPresentAndSwooleIsInstalled( - array $currentOptions, - bool $expected, - ): void { - self::assertEquals($expected, $this->configOption->shouldBeAsked($currentOptions)); - } - - public static function provideCurrentOptions(): iterable - { - yield 'without runtime' => [[], false]; - yield 'with async runtime and no config' => [[RuntimeConfigOption::ENV_VAR => RuntimeType::ASYNC->value], true]; - yield 'with regular runtime and no config' => [[ - RuntimeConfigOption::ENV_VAR => RuntimeType::REGULAR->value, - ], false]; - yield 'with async runtime and config' => [[ - RuntimeConfigOption::ENV_VAR => RuntimeType::ASYNC->value, - 'TASK_WORKER_NUM' => 16, - ], false]; - yield 'with regular runtime and config' => [[ - RuntimeConfigOption::ENV_VAR => RuntimeType::REGULAR->value, - 'TASK_WORKER_NUM' => 16, - ], false]; - } -} diff --git a/test/Config/Util/ConfigOptionsValidatorsTraitTest.php b/test/Config/Util/ConfigOptionsValidatorTest.php similarity index 58% rename from test/Config/Util/ConfigOptionsValidatorsTraitTest.php rename to test/Config/Util/ConfigOptionsValidatorTest.php index 083dfe4..84c153d 100644 --- a/test/Config/Util/ConfigOptionsValidatorsTraitTest.php +++ b/test/Config/Util/ConfigOptionsValidatorTest.php @@ -7,59 +7,25 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; -use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidatorsTrait; +use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidator; use Shlinkio\Shlink\Installer\Exception\InvalidConfigOptionException; -class ConfigOptionsValidatorsTraitTest extends TestCase +class ConfigOptionsValidatorTest extends TestCase { - use ConfigOptionsValidatorsTrait; - - #[Test, DataProvider('provideValidUrls')] - public function urlsAreProperlySplitAndValidated(?string $urls, array $expectedResult): void - { - $result = $this->splitAndValidateMultipleUrls($urls); - self::assertEquals($expectedResult, $result); - } - - public static function provideValidUrls(): iterable - { - yield 'no urls' => [null, []]; - yield 'single url' => ['https://foo.com/bar', ['https://foo.com/bar']]; - yield 'multiple urls' => ['https://foo.com/bar,http://bar.io/foo/bar', [ - 'https://foo.com/bar', - 'http://bar.io/foo/bar', - ]]; - } - - #[Test, DataProvider('provideInvalidUrls')] - public function splitUrlsFailWhenProvidedValueIsNotValidUrl(string $urls): void - { - $this->expectException(InvalidConfigOptionException::class); - $this->splitAndValidateMultipleUrls($urls); - } - - public static function provideInvalidUrls(): iterable - { - yield 'single invalid url' => ['invalid']; - yield 'first invalid url' => ['invalid,http://bar.io/foo/bar']; - yield 'last invalid url' => ['http://bar.io/foo/bar,invalid']; - yield 'middle invalid url' => ['http://bar.io/foo/bar,invalid,https://foo.com/bar']; - } - #[Test] public function throwsAnExceptionIfInvalidUrlIsProvided(): void { $this->expectException(InvalidConfigOptionException::class); $this->expectExceptionMessage('Provided value "something" is not a valid URL'); - $this->validateUrl('something'); + ConfigOptionsValidator::validateUrl('something'); } #[Test, DataProvider('provideInvalidValues')] public function validateNumberGreaterThanThrowsExceptionWhenProvidedValueIsInvalid(array $args): void { $this->expectException(InvalidConfigOptionException::class); - $this->validateNumberGreaterThan(...$args); + ConfigOptionsValidator::validateNumberGreaterThan(...$args); } public static function provideInvalidValues(): iterable @@ -77,7 +43,7 @@ public static function provideInvalidValues(): iterable #[Test, DataProvider('providePositiveNumbers')] public function validatePositiveNumberCastsToIntWhenProvidedValueIsValid(mixed $value, int $expected): void { - self::assertEquals($expected, $this->validatePositiveNumber($value)); + self::assertEquals($expected, ConfigOptionsValidator::validatePositiveNumber($value)); } public static function providePositiveNumbers(): iterable @@ -91,7 +57,7 @@ public static function providePositiveNumbers(): iterable #[Test, DataProvider('provideOptionalPositiveNumbers')] public function validateOptionalPositiveNumberCastsToIntWhenProvidedValueIsValid(mixed $value, ?int $expected): void { - self::assertEquals($expected, $this->validateOptionalPositiveNumber($value)); + self::assertEquals($expected, ConfigOptionsValidator::validateOptionalPositiveNumber($value)); } public static function provideOptionalPositiveNumbers(): iterable @@ -107,7 +73,7 @@ public function validateNumberBetweenThrowsExceptionWhenProvidedValueIsInvalid( int $max, ): void { $this->expectException(InvalidConfigOptionException::class); - $this->validateNumberBetween($value, $min, $max); + ConfigOptionsValidator::validateNumberBetween($value, $min, $max); } public static function provideInvalidNumbersBetween(): iterable @@ -131,7 +97,7 @@ public function validateNumberBetweenCastsToIntWhenProvidedValueIsValid( int $max, int $expected, ): void { - self::assertEquals($expected, $this->validateNumberBetween($value, $min, $max)); + self::assertEquals($expected, ConfigOptionsValidator::validateNumberBetween($value, $min, $max)); } public static function provideValidNumbersBetween(): iterable @@ -143,4 +109,43 @@ public static function provideValidNumbersBetween(): iterable yield 'last as string' => ['55', 20, 55, 55]; yield 'last as int' => [55, 20, 55, 55]; } + + #[Test, DataProvider('provideInvalidColors')] + public function validateHexColorThrowsForInvalidValues(string $color, string $expectedMessage): void + { + $this->expectException(InvalidConfigOptionException::class); + $this->expectExceptionMessage($expectedMessage); + + ConfigOptionsValidator::validateHexColor($color); + } + + public static function provideInvalidColors(): iterable + { + yield ['11', 'Provided value must have 3 or 6 characters, and be optionally preceded by the # character']; + yield ['1111', 'Provided value must have 3 or 6 characters, and be optionally preceded by the # character']; + yield ['11111', 'Provided value must have 3 or 6 characters, and be optionally preceded by the # character']; + yield ['1111111', 'Provided value must have 3 or 6 characters, and be optionally preceded by the # character']; + yield ['#11', 'Provided value must have 3 or 6 characters, and be optionally preceded by the # character']; + yield ['#1111', 'Provided value must have 3 or 6 characters, and be optionally preceded by the # character']; + yield ['#11111', 'Provided value must have 3 or 6 characters, and be optionally preceded by the # character']; + yield ['#1111111', 'Provided value must have 3 or 6 characters, and be optionally preceded by the # character']; + yield ['foo', 'Provided value must be the hexadecimal number representation of a color']; + yield ['foobar', 'Provided value must be the hexadecimal number representation of a color']; + yield ['#foo', 'Provided value must be the hexadecimal number representation of a color']; + yield ['#foobar', 'Provided value must be the hexadecimal number representation of a color']; + } + + #[Test, DataProvider('provideValidColors')] + public function validateHexColorReturnsOriginalValueWhenValid(string $color): void + { + self::assertSame($color, ConfigOptionsValidator::validateHexColor($color)); + } + + public static function provideValidColors(): iterable + { + yield ['#111']; + yield ['111']; + yield ['#aaaaaa']; + yield ['aaa']; + } } diff --git a/test/Service/ShlinkAssetsHandlerTest.php b/test/Service/ShlinkAssetsHandlerTest.php index 70f8138..9886245 100644 --- a/test/Service/ShlinkAssetsHandlerTest.php +++ b/test/Service/ShlinkAssetsHandlerTest.php @@ -40,7 +40,7 @@ public function cachedConfigIsDeletedIfExists(bool $appExists, bool $routesExist ]); $this->filesystem->expects($this->exactly($expectedRemoveCalls))->method('remove')->with( $this->stringContains('data/cache'), - )->willReturn(null); + ); $this->assetsHandler->dropCachedConfigIfAny($this->io); } diff --git a/test/Util/UtilsTest.php b/test/Util/UtilsTest.php index ee1a069..9f5b7e2 100644 --- a/test/Util/UtilsTest.php +++ b/test/Util/UtilsTest.php @@ -61,17 +61,4 @@ public static function provideEnvVars(): iterable [DatabaseDriverConfigOption::ENV_VAR => 'mysql'], ]; } - - #[Test, DataProvider('provideCommaSeparatedLists')] - public function commaSeparatedToListReturnsExpectedValue(string $list, array $expectedResult): void - { - self::assertEquals($expectedResult, Utils::commaSeparatedToList($list)); - } - - public static function provideCommaSeparatedLists(): iterable - { - yield 'single item' => ['foo', ['foo']]; - yield 'multiple items' => ['foo,bar bar,baz', ['foo', 'bar bar', 'baz']]; - yield 'extra spaces' => [' foo , bar , baz ', ['foo', 'bar', 'baz']]; - } }