diff --git a/CHANGELOG.md b/CHANGELOG.md index f9dab1f..d7dab69 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* +* Add QR code options for foreground color, background color and logo URL. ### Changed * Update dependencies diff --git a/config/config.php b/config/config.php index dd40f9f..9d78865 100644 --- a/config/config.php +++ b/config/config.php @@ -89,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, ], @@ -182,6 +185,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/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/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/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/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/Worker/AbstractWorkerNumConfigOption.php b/src/Config/Option/Worker/AbstractWorkerNumConfigOption.php index f995994..3129533 100644 --- a/src/Config/Option/Worker/AbstractWorkerNumConfigOption.php +++ b/src/Config/Option/Worker/AbstractWorkerNumConfigOption.php @@ -5,19 +5,17 @@ namespace Shlinkio\Shlink\Installer\Config\Option\Worker; use Shlinkio\Shlink\Installer\Config\Option\Server\AbstractAsyncRuntimeDependentConfigOption; -use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidatorsTrait; +use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidator; use Symfony\Component\Console\Style\StyleInterface; abstract class AbstractWorkerNumConfigOption extends AbstractAsyncRuntimeDependentConfigOption { - use ConfigOptionsValidatorsTrait; - public function ask(StyleInterface $io, array $currentOptions): int { return $io->ask( $this->getQuestionToAsk(), '16', - fn ($value) => $this->validateNumberGreaterThan($value, $this->getMinimumValue()), + fn ($value) => ConfigOptionsValidator::validateNumberGreaterThan($value, $this->getMinimumValue()), ); } diff --git a/src/Config/Util/ConfigOptionsValidatorsTrait.php b/src/Config/Util/ConfigOptionsValidator.php similarity index 53% rename from src/Config/Util/ConfigOptionsValidatorsTrait.php rename to src/Config/Util/ConfigOptionsValidator.php index 940ed61..fb06fbd 100644 --- a/src/Config/Util/ConfigOptionsValidatorsTrait.php +++ b/src/Config/Util/ConfigOptionsValidator.php @@ -5,16 +5,17 @@ namespace Shlinkio\Shlink\Installer\Config\Util; use Shlinkio\Shlink\Installer\Exception\InvalidConfigOptionException; -use Shlinkio\Shlink\Installer\Util\Utils; -use function array_map; +use function ctype_xdigit; use function is_numeric; +use function ltrim; use function preg_match; use function sprintf; +use function strlen; -trait ConfigOptionsValidatorsTrait +class ConfigOptionsValidator { - public function validateUrl(?string $value): ?string + public static function validateUrl(?string $value): ?string { $httpUrlRegexp = '/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}' @@ -30,27 +31,17 @@ public function validateUrl(?string $value): ?string return $value; } - public function splitAndValidateMultipleUrls(?string $urls): array + public static function validateOptionalPositiveNumber(mixed $value): ?int { - if ($urls === null) { - return []; - } - - $splitUrls = Utils::commaSeparatedToList($urls); - return array_map($this->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/QrCode/DefaultColorConfigOptionTest.php b/test/Config/Option/QrCode/DefaultColorConfigOptionTest.php new file mode 100644 index 0000000..dacbb29 --- /dev/null +++ b/test/Config/Option/QrCode/DefaultColorConfigOptionTest.php @@ -0,0 +1,42 @@ +configOption = new DefaultColorConfigOption(); + } + + #[Test] + public function returnsExpectedEnvVar(): void + { + self::assertEquals('DEFAULT_QR_CODE_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 foreground color for generated QR codes', + '#000000', + $this->anything(), + )->willReturn($expectedAnswer); + + $answer = $this->configOption->ask($io, []); + + self::assertEquals($expectedAnswer, $answer); + } +} 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/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/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']]; - } }