diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c7b4d0e..ea5b271 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,4 +1,5 @@ -name: Full CI process +name: 'CI' + on: push: branches: @@ -8,92 +9,154 @@ on: - main jobs: - test: - name: PHP ${{ matrix.PHP_VERSION }} - runs-on: ubuntu-18.04 + cs-fixer: + name: 'PHP CS Fixer' + + runs-on: 'ubuntu-latest' + strategy: - fail-fast: false matrix: - include: - - PHP_VERSION: '7.3' - SYMFONY_REQUIRE: '^4.4' - - PHP_VERSION: '7.3' - SYMFONY_REQUIRE: '^5.0' - - PHP_VERSION: '7.4' - SYMFONY_REQUIRE: '^5.2' - - PHP_VERSION: '8.0' - - PHP_VERSION: '8.0' - SYMFONY_REQUIRE: '^6.0' + php-version: + - '8.2' steps: - # —— Setup Github actions 🐙 ————————————————————————————————————————————— - # https://github.com/actions/checkout (official) - - name: Checkout - uses: actions/checkout@v2 + name: 'Check out' + uses: 'actions/checkout@v2' - # https://github.com/shivammathur/setup-php (community) - - name: Setup PHP, extensions and composer with shivammathur/setup-php - uses: shivammathur/setup-php@v2 + name: 'Set up PHP' + uses: 'shivammathur/setup-php@v2' with: - php-version: ${{ matrix.PHP_VERSION }} - extensions: mbstring, ctype, iconv, bcmath, filter, json - coverage: none - env: - update: true + php-version: '${{ matrix.php-version }}' + coverage: 'none' - # —— Composer 🧙‍️ ————————————————————————————————————————————————————————— - - name: Install Composer dependencies - env: - SYMFONY_REQUIRE: ${{ matrix.SYMFONY_REQUIRE }} - SYMFONY_PHPUNIT_DISABLE_RESULT_CACHE: 1 - run: | - git config --global author.name Sebastiaan Stok - git config --global author.email s.stok@rollerscapes.net - git config --global user.name Sebastiaan Stok - git config --global user.email s.stok@rollerscapes.net + name: 'Get Composer cache directory' + id: 'composer-cache' + run: 'echo "::set-output name=cache-dir::$(composer config cache-files-dir)"' + + - + name: 'Cache dependencies' + uses: 'actions/cache@v2' + with: + path: '${{ steps.composer-cache.outputs.cache-dir }}' + key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" + restore-keys: 'php-${{ matrix.php-version }}-composer-locked-' - rm -f composer.lock - composer config --no-plugins allow-plugins.symfony/flex true - composer global require symfony/flex - composer install --no-progress --no-interaction --no-suggest --optimize-autoloader --ansi + - + name: 'Install dependencies' + run: 'composer update --no-progress --prefer-stable' - ## —— Tests ✅ ——————————————————————————————————————————————————————————— - - name: Run Tests - run: | - make test - lint: - name: PHP-QA - runs-on: ubuntu-latest + name: 'Check the code style' + run: 'make cs-full' + + phpstan: + name: 'PhpStan' + + runs-on: 'ubuntu-latest' + strategy: - fail-fast: false + matrix: + php-version: + - '8.2' + steps: - - name: Checkout - uses: actions/checkout@v2 + name: 'Check out' + uses: 'actions/checkout@v2' + + - + name: 'Set up PHP' + uses: 'shivammathur/setup-php@v2' + with: + php-version: '${{ matrix.php-version }}' + coverage: 'none' + + - + name: 'Get Composer cache directory' + id: 'composer-cache' + run: 'echo "::set-output name=cache-dir::$(composer config cache-files-dir)"' - # https://github.com/shivammathur/setup-php (community) - - name: Setup PHP, extensions and composer with shivammathur/setup-php - uses: shivammathur/setup-php@v2 + name: 'Cache dependencies' + uses: 'actions/cache@v2' with: - php-version: '7.4' - extensions: mbstring, ctype, iconv, bcmath, filter, json - coverage: none + path: '${{ steps.composer-cache.outputs.cache-dir }}' + key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" + restore-keys: 'php-${{ matrix.php-version }}-composer-locked-' + + - + name: 'Install dependencies' + run: 'composer update --no-progress --prefer-stable' - # —— Composer 🧙‍️ ————————————————————————————————————————————————————————— - - name: Install Composer dependencies + name: 'Run PhpStan' + run: 'vendor/bin/phpstan analyze --no-progress' + + tests: + name: 'PHPUnit' + + runs-on: 'ubuntu-latest' + + strategy: + matrix: + include: + - + php-version: '8.2' + composer-options: '--prefer-stable' + symfony-version: '6.3' + - + php-version: '8.2' + composer-options: '--prefer-stable' + symfony-version: '^6.4' + + - + php-version: '8.2' + composer-options: '--prefer-stable' + symfony-version: '^7.0' + + steps: + - + name: 'Check out' + uses: 'actions/checkout@v2' + + - + name: 'Set up PHP' + uses: 'shivammathur/setup-php@v2' + with: + php-version: '${{ matrix.php-version }}' + coverage: 'none' + + - + name: 'Get Composer cache directory' + id: 'composer-cache' + run: 'echo "::set-output name=cache-dir::$(composer config cache-files-dir)"' + + - + name: 'Cache dependencies' + uses: 'actions/cache@v2' + with: + path: '${{ steps.composer-cache.outputs.cache-dir }}' + key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" + restore-keys: 'php-${{ matrix.php-version }}-composer-locked-' + + - + name: 'Install dependencies' + env: + COMPOSER_OPTIONS: '${{ matrix.composer-options }}' + SYMFONY_REQUIRE: '${{ matrix.symfony-version }}' run: | - rm -f composer.lock - composer config --no-plugins allow-plugins.symfony/flex true - composer global require symfony/flex - composer install --no-progress --no-interaction --no-suggest --optimize-autoloader --ansi + composer global config --no-plugins allow-plugins.symfony/flex true + composer global require --no-progress --no-scripts --no-plugins symfony/flex + composer update --no-progress $COMPOSER_OPTIONS + + - + name: 'Install PHPUnit' + run: 'vendor/bin/simple-phpunit install' - - name: Run PHP-QA + name: 'Run tests' run: | - make cs-full + vendor/bin/simple-phpunit diff --git a/Makefile b/Makefile index 52d526c..eea7565 100644 --- a/Makefile +++ b/Makefile @@ -1,30 +1,27 @@ -QA_DOCKER_IMAGE=jakzal/phpqa:1.59.1-php7.4-alpine +QA_DOCKER_IMAGE=jakzal/phpqa:1.92.1-php8.2-alpine QA_DOCKER_COMMAND=docker run --init -t --rm --user "$(shell id -u):$(shell id -g)" --volume /tmp/tmp-phpqa-$(shell id -u):/tmp --volume "$(shell pwd):/project" --workdir /project ${QA_DOCKER_IMAGE} dist: install cs-full phpstan test-full -lint: install security-check cs-full phpstan +lint: install cs-full phpstan install: composer install --no-progress --no-interaction --no-suggest --optimize-autoloader --prefer-dist --ansi test: - ./vendor/bin/phpunit --verbose + vendor/bin/simple-phpunit --verbose # Linting tools -security-check: ensure - sh -c "${QA_DOCKER_COMMAND} security-checker security:check ./composer.lock" - phpstan: ensure - sh -c "${QA_DOCKER_COMMAND} phpstan analyse --configuration phpstan.neon" + vendor/bin/phpstan analyse cs: ensure sh -c "${QA_DOCKER_COMMAND} php-cs-fixer fix -vvv --diff" cs-full: ensure - sh -c "${QA_DOCKER_COMMAND} php-cs-fixer fix -vvv --using-cache=false --diff" + sh -c "${QA_DOCKER_COMMAND} php-cs-fixer fix -vvv --using-cache=no --diff" cs-full-check: ensure - sh -c "${QA_DOCKER_COMMAND} php-cs-fixer fix -vvv --using-cache=false --diff --dry-run" + sh -c "${QA_DOCKER_COMMAND} php-cs-fixer fix -vvv --using-cache=no --diff --dry-run" ensure: mkdir -p ${HOME}/.composer /tmp/tmp-phpqa-$(shell id -u) diff --git a/README.md b/README.md index 29eb1dd..9b16ada 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -RollerworksPasswordStrength Validator +Rollerworks PasswordStrengthValidator ===================================== This package provides various password strength validators for the [Symfony Validator @@ -25,7 +25,7 @@ for you. ## Requirements -You need at least PHP 5.6 or PHP 7.0, mbstring is recommended but not required. +You need at least PHP PHP 8.2 and Symfony 6, mbstring is recommended but not required. ## Basic Usage diff --git a/UPGRADE.md b/UPGRADE.md index dbd6b8b..d774a9a 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -11,6 +11,8 @@ UPGRADE * The PwnedPassword validator was removed in favor of the Symfon [NotCompromisedPassword](https://symfony.com/doc/current/reference/constraints/NotCompromisedPassword.html) validator. +* Support for Symfony 4 and 5 was removed, PHP 8.2 and Symfony 6.0 is now the minimum required version. + ## Upgrade from 1.6 to 1.7 * The blacklist validator was deprecated in favor of the [PasswordCommonList Validator](https://github.com/rollerworks/password-common-list). diff --git a/composer.json b/composer.json index 5a026c1..334e623 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,13 @@ { "name": "rollerworks/password-strength-validator", "description": "Password-strength validator for Symfony", - "keywords": ["password", "validator", "symfony"], - "type": "library", "license": "MIT", + "type": "library", + "keywords": [ + "password", + "validator", + "symfony" + ], "authors": [ { "name": "Sebastiaan Stok", @@ -15,42 +19,40 @@ } ], "require": { - "php": ">=7.3", - "symfony/deprecation-contracts": "^2.4 || ^3.0", + "php": ">=8.2", + "symfony/config": "^6.0 || ^7.0", "symfony/polyfill-mbstring": "^1.5.0", - "symfony/translation": "^4.4 || ^5.0 || ^6.0", - "symfony/validator": "^4.4 || ^5.0 || ^6.0" + "symfony/translation": "^6.0 || ^7.0", + "symfony/validator": "^6.0 || ^7.0" }, "require-dev": { - "phpspec/prophecy": "^1.10.3", - "phpspec/prophecy-phpunit": "^2.0", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-symfony": "^1.2", "phpunit/phpunit": "^9.5", - "psr/log": "^1.0 || ^2.0", - "symfony/config": "^4.4 || ^5.0 || ^6.0", - "symfony/phpunit-bridge": "^5.3 || ^6.0" + "symfony/phpunit-bridge": "^6.0 || ^7.0" }, + "minimum-stability": "dev", + "prefer-stable": true, "autoload": { "psr-4": { "Rollerworks\\Component\\PasswordStrength\\": "src/" }, - "exclude-from-classmap": ["test/"] + "exclude-from-classmap": [ + "test/" + ] }, "autoload-dev": { "psr-4": { "Rollerworks\\Component\\PasswordStrength\\Tests\\": "tests/" } }, + "config": { + "sort-packages": true + }, "extra": { "branch-alias": { "dev-main": "2.0-dev" } - }, - "config": { - "preferred-install": { - "*": "dist" - }, - "sort-packages": true - }, - "minimum-stability": "dev", - "prefer-stable": true + } } diff --git a/phpstan.neon b/phpstan.neon index c9138f7..ce8d418 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,15 +1,12 @@ includes: - - /tools/.composer/vendor-bin/phpstan/vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-symfony/extension.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon parameters: - level: 5 + level: 8 paths: - ./src + - ./tests - ignoreErrors: - #- '#__construct\(\) does not call parent constructor from .+#' - #- '#Access to an undefined property Symfony\\Component\\Validator\\Constraint\:\:#' - - "#Casting to string something that's already string#" - - # Tests - - '#Call to an undefined method Prophecy\\Prophecy\\ObjectProphecy::[a-zA-Z0-9_]+\(\)#' + #ignoreErrors: diff --git a/src/Validator/Constraints/PasswordRequirements.php b/src/Validator/Constraints/PasswordRequirements.php index 85459ca..5fe36d1 100644 --- a/src/Validator/Constraints/PasswordRequirements.php +++ b/src/Validator/Constraints/PasswordRequirements.php @@ -11,42 +11,42 @@ namespace Rollerworks\Component\PasswordStrength\Validator\Constraints; -use Attribute; use Symfony\Component\Validator\Constraint; /** * @Annotation + * * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) */ -#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class PasswordRequirements extends Constraint { - public $tooShortMessage = 'Your password must be at least {{length}} characters long.'; - public $missingLettersMessage = 'Your password must include at least one letter.'; - public $requireCaseDiffMessage = 'Your password must include both upper and lower case letters.'; - public $missingNumbersMessage = 'Your password must include at least one number.'; - public $missingSpecialCharacterMessage = 'Your password must contain at least one special character.'; + public string $tooShortMessage = 'Your password must be at least {{length}} characters long.'; + public string $missingLettersMessage = 'Your password must include at least one letter.'; + public string $requireCaseDiffMessage = 'Your password must include both upper and lower case letters.'; + public string $missingNumbersMessage = 'Your password must include at least one number.'; + public string $missingSpecialCharacterMessage = 'Your password must contain at least one special character.'; - public $minLength = 6; - public $requireLetters = true; - public $requireCaseDiff = false; - public $requireNumbers = false; - public $requireSpecialCharacter = false; + public int $minLength = 6; + public bool $requireLetters = true; + public bool $requireCaseDiff = false; + public bool $requireNumbers = false; + public bool $requireSpecialCharacter = false; public function __construct( - $options = null, - ?array $groups = null, - $payload = null, - ?int $minLength = null, - ?bool $requireLetters = null, - ?bool $requireCaseDiff = null, - ?bool $requireNumbers = null, - ?bool $requireSpecialCharacter = null, - ?string $tooShortMessage = null, - ?string $missingLettersMessage = null, - ?string $requireCaseDiffMessage = null, - ?string $missingNumbersMessage = null, - ?string $missingSpecialCharacterMessage = null + mixed $options = null, + array $groups = null, + mixed $payload = null, + int $minLength = null, + bool $requireLetters = null, + bool $requireCaseDiff = null, + bool $requireNumbers = null, + bool $requireSpecialCharacter = null, + string $tooShortMessage = null, + string $missingLettersMessage = null, + string $requireCaseDiffMessage = null, + string $missingNumbersMessage = null, + string $missingSpecialCharacterMessage = null ) { parent::__construct($options ?? [], $groups, $payload); diff --git a/src/Validator/Constraints/PasswordRequirementsValidator.php b/src/Validator/Constraints/PasswordRequirementsValidator.php index 68a9198..9c88846 100644 --- a/src/Validator/Constraints/PasswordRequirementsValidator.php +++ b/src/Validator/Constraints/PasswordRequirementsValidator.php @@ -17,20 +17,22 @@ class PasswordRequirementsValidator extends ConstraintValidator { - /** - * @param string|null $value - * @param PasswordRequirements|Constraint $constraint - */ - public function validate($value, Constraint $constraint) + public function validate(mixed $value, Constraint $constraint): void { if ($value === null || $value === '') { return; } - if (! is_scalar($value) && ! (\is_object($value) && method_exists($value, '__toString'))) { + if (! $constraint instanceof PasswordRequirements) { + throw new UnexpectedTypeException($constraint, PasswordRequirements::class); + } + + if (! \is_scalar($value) && ! (\is_object($value) && method_exists($value, '__toString'))) { throw new UnexpectedTypeException($value, 'string'); } + $value = (string) $value; + if ($constraint->minLength > 0 && (mb_strlen($value) < $constraint->minLength)) { $this->context->buildViolation($constraint->tooShortMessage) ->setParameters(['{{length}}' => $constraint->minLength]) diff --git a/src/Validator/Constraints/PasswordStrength.php b/src/Validator/Constraints/PasswordStrength.php index c9d8123..63a5d34 100644 --- a/src/Validator/Constraints/PasswordStrength.php +++ b/src/Validator/Constraints/PasswordStrength.php @@ -11,27 +11,27 @@ namespace Rollerworks\Component\PasswordStrength\Validator\Constraints; -use Attribute; use Symfony\Component\Validator\Constraint; /** * @Annotation + * * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) */ -#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class PasswordStrength extends Constraint { - public $tooShortMessage = 'Your password must be at least {{length}} characters long.'; - public $message = 'password_too_weak'; - public $minLength = 6; - public $minStrength; - public $unicodeEquality = false; + public string $tooShortMessage = 'Your password must be at least {{length}} characters long.'; + public string $message = 'password_too_weak'; + public int $minLength = 6; + public int $minStrength; + public bool $unicodeEquality = false; public function __construct( - $options = null, + mixed $options = null, array $groups = null, - $payload = null, - ?int $minStrength = null, + mixed $payload = null, + int $minStrength = null, int $minLength = null, bool $unicodeEquality = null, string $message = null, @@ -39,7 +39,7 @@ public function __construct( ) { $finalOptions = []; - if (is_array($options)) { + if (\is_array($options)) { $finalOptions = $options; } else { $finalOptions['minStrength'] = $options; @@ -50,7 +50,7 @@ public function __construct( $finalOptions['minStrength'] = $minStrength; } - parent::__construct($finalOptions ?? [], $groups, $payload); + parent::__construct($finalOptions, $groups, $payload); $this->minLength = $minLength ?? $this->minLength; $this->unicodeEquality = $unicodeEquality ?? $this->unicodeEquality; diff --git a/src/Validator/Constraints/PasswordStrengthValidator.php b/src/Validator/Constraints/PasswordStrengthValidator.php index 82463cd..db63287 100644 --- a/src/Validator/Constraints/PasswordStrengthValidator.php +++ b/src/Validator/Constraints/PasswordStrengthValidator.php @@ -13,7 +13,6 @@ use Symfony\Component\Translation\Loader\XliffFileLoader; use Symfony\Component\Translation\Translator; -use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; @@ -38,8 +37,12 @@ */ class PasswordStrengthValidator extends ConstraintValidator { - private $translator; - private static $levelToLabel = [ + private TranslatorInterface $translator; + + /** + * @var array + */ + private static array $levelToLabel = [ 1 => 'very_weak', 2 => 'weak', 3 => 'medium', @@ -47,12 +50,8 @@ class PasswordStrengthValidator extends ConstraintValidator 5 => 'very_strong', ]; - public function __construct($translator = null) + public function __construct(TranslatorInterface $translator = null) { - if ($translator !== null && ! $translator instanceof LegacyTranslatorInterface && ! $translator instanceof TranslatorInterface) { - throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator))); - } - // If translator is missing create a new translator. // With the 'en' locale and 'validators' domain. if ($translator === null) { @@ -64,22 +63,22 @@ public function __construct($translator = null) $this->translator = $translator; } - /** - * @param string|null $password - * @param PasswordStrength|Constraint $constraint - */ - public function validate($password, Constraint $constraint) + public function validate(mixed $value, Constraint $constraint): void { - if ($password === null || $password === '') { + if ($value === null || $value === '') { return; } - if (! is_scalar($password) && ! (\is_object($password) && method_exists($password, '__toString'))) { - throw new UnexpectedTypeException($password, 'string'); + if (! $constraint instanceof PasswordStrength) { + throw new UnexpectedTypeException($constraint, PasswordStrength::class); } - $password = (string) $password; - $passLength = mb_strlen($password); + if (! \is_scalar($value) && ! (\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedTypeException($value, 'string'); + } + + $value = (string) $value; + $passLength = mb_strlen($value); if ($passLength < $constraint->minLength) { $this->context->buildViolation($constraint->tooShortMessage) @@ -93,9 +92,9 @@ public function validate($password, Constraint $constraint) $tips = []; if ($constraint->unicodeEquality) { - $passwordStrength = $this->calculateStrengthUnicode($password, $tips); + $passwordStrength = $this->calculateStrengthUnicode($value, $tips); } else { - $passwordStrength = $this->calculateStrength($password, $tips); + $passwordStrength = $this->calculateStrength($value, $tips); } if ($passLength > 12) { @@ -122,15 +121,15 @@ public function validate($password, Constraint $constraint) } } - /** - * @internal - */ - public function translateTips($tip) + private function translateTips(string $tip): string { return $this->translator->trans(/* @Ignore */ 'rollerworks_password.tip.' . $tip, [], 'validators'); } - private function calculateStrength($password, &$tips) + /** + * @param array $tips + */ + private function calculateStrength(string $password, array &$tips): int { $passwordStrength = 0; @@ -163,7 +162,10 @@ private function calculateStrength($password, &$tips) return $passwordStrength; } - private function calculateStrengthUnicode($password, &$tips) + /** + * @param array $tips + */ + private function calculateStrengthUnicode(string $password, array &$tips): int { $passwordStrength = 0; diff --git a/tests/Validator/PasswordRequirementsValidatorTest.php b/tests/Validator/PasswordRequirementsValidatorTest.php index 5aefa13..9e0eb40 100644 --- a/tests/Validator/PasswordRequirementsValidatorTest.php +++ b/tests/Validator/PasswordRequirementsValidatorTest.php @@ -13,53 +13,32 @@ use Rollerworks\Component\PasswordStrength\Validator\Constraints\PasswordRequirements; use Rollerworks\Component\PasswordStrength\Validator\Constraints\PasswordRequirementsValidator; +use Symfony\Component\Validator\ConstraintValidatorInterface; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; use Symfony\Component\Validator\Test\ConstraintViolationAssertion; /** * @internal + * + * @template-extends ConstraintValidatorTestCase */ final class PasswordRequirementsValidatorTest extends ConstraintValidatorTestCase { - public function getMock($originalClassName, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = false, $callOriginalMethods = false, $proxyTarget = null) - { - if (\func_num_args() === 1 && preg_match('/^Symfony\\\\Component\\\\([a-z]+\\\\)+[a-z]+Interface$/i', $originalClassName)) { - return $this->getMockBuilder($originalClassName)->getMock(); - } - - return parent::getMock( - $originalClassName, - $methods, - $arguments, - $mockClassName, - $callOriginalConstructor, - $callOriginalClone, - $callAutoload, - $cloneArguments, - $callOriginalMethods, - $proxyTarget - ); - } - - protected function createValidator() + protected function createValidator(): ConstraintValidatorInterface { return new PasswordRequirementsValidator(); } - /** - * @test - */ - public function null_is_valid() + /** @test */ + public function null_is_valid(): void { $this->validator->validate(null, new PasswordRequirements()); $this->assertNoViolation(); } - /** - * @test - */ - public function empty_is_valid() + /** @test */ + public function empty_is_valid(): void { $this->validator->validate('', new PasswordRequirements()); @@ -67,13 +46,11 @@ public function empty_is_valid() } /** - * @dataProvider provideValidConstraints - * - * @param string $value + * @dataProvider provideValid_value_constraintsCases * * @test */ - public function valid_value_constraints($value, PasswordRequirements $constraint) + public function valid_value_constraints(string $value, PasswordRequirements $constraint): void { $this->value = $value; @@ -83,13 +60,13 @@ public function valid_value_constraints($value, PasswordRequirements $constraint } /** - * @dataProvider provideViolationConstraints - * - * @param string $value + * @dataProvider provideViolation_value_constraintsCases * * @test + * + * @param array}> $violations */ - public function violation_value_constraints($value, PasswordRequirements $constraint, array $violations = []) + public function violation_value_constraints(string $value, PasswordRequirements $constraint, array $violations = []): void { $this->value = $value; /** @var ConstraintViolationAssertion $constraintViolationAssertion */ @@ -97,6 +74,9 @@ public function violation_value_constraints($value, PasswordRequirements $constr $this->validator->validate($value, $constraint); + /** + * @var array $violation + */ foreach ($violations as $i => $violation) { if ($i === 0) { $constraintViolationAssertion = $this->buildViolation($violation[0]) @@ -116,7 +96,10 @@ public function violation_value_constraints($value, PasswordRequirements $constr } } - public function provideValidConstraints() + /** + * @return iterable + */ + public static function provideValid_value_constraintsCases(): iterable { return [ ['test', new PasswordRequirements(['minLength' => 3])], @@ -136,7 +119,10 @@ public function provideValidConstraints() ]; } - public function provideViolationConstraints() + /** + * @return iterable}> + */ + public static function provideViolation_value_constraintsCases(): iterable { $constraint = new PasswordRequirements(); diff --git a/tests/Validator/PasswordStrengthTest.php b/tests/Validator/PasswordStrengthTest.php index 293c895..1a82025 100644 --- a/tests/Validator/PasswordStrengthTest.php +++ b/tests/Validator/PasswordStrengthTest.php @@ -14,16 +14,19 @@ use Rollerworks\Component\PasswordStrength\Validator\Constraints\PasswordStrength; use Rollerworks\Component\PasswordStrength\Validator\Constraints\PasswordStrengthValidator; use Symfony\Component\Translation\Translator; +use Symfony\Component\Validator\ConstraintValidatorInterface; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; /** * @internal + * + * @template-extends ConstraintValidatorTestCase */ final class PasswordStrengthTest extends ConstraintValidatorTestCase { /** - * @var array + * @var array */ private static $levelToLabel = [ 1 => 'very_weak', @@ -33,7 +36,7 @@ final class PasswordStrengthTest extends ConstraintValidatorTestCase 5 => 'very_strong', ]; - protected function createValidator() + protected function createValidator(): ConstraintValidatorInterface { return new PasswordStrengthValidator(new Translator('en')); } @@ -54,37 +57,34 @@ public function constraints_options_are_properly_resolved(): void self::assertEquals(3, $constraint->minStrength); } - /** - * @test - */ - public function null_is_valid() + /** @test */ + public function null_is_valid(): void { $this->validator->validate(null, new PasswordStrength(6)); $this->assertNoViolation(); } - /** - * @test - */ - public function empty_is_valid() + /** @test */ + public function empty_is_valid(): void { $this->validator->validate('', new PasswordStrength(6)); $this->assertNoViolation(); } - /** - * @test - */ - public function expects_string_compatible_type() + /** @test */ + public function expects_string_compatible_type(): void { $this->expectException(UnexpectedTypeException::class); $this->validator->validate(new \stdClass(), new PasswordStrength(5)); } - public function getWeakPasswords() + /** + * @return iterable + */ + public function provideWeak_passwords_will_not_passCases(): iterable { $pre = 'rollerworks_password.tip.'; @@ -112,7 +112,10 @@ public function getWeakPasswords() ]; } - public function getWeakPasswordsUnicode() + /** + * @return iterable + */ + public function provideWeak_passwords_with_unicode_will_not_passCases(): iterable { $pre = 'rollerworks_password.tip.'; @@ -146,7 +149,10 @@ public function getWeakPasswordsUnicode() ]; } - public static function getStrongPasswords() + /** + * @return iterable + */ + public static function provideStrongPasswords(): iterable { return [ ['Foobar!55!'], @@ -156,7 +162,10 @@ public static function getStrongPasswords() ]; } - public static function getVeryStrongPasswords() + /** + * @return iterable + */ + public static function provideStrong_passwords_will_passCases(): iterable { return [ ['Foobar$55_4&F'], @@ -164,10 +173,8 @@ public static function getVeryStrongPasswords() ]; } - /** - * @test - */ - public function short_password_will_not_pass() + /** @test */ + public function short_password_will_not_pass(): void { $constraint = new PasswordStrength(['minStrength' => 5, 'minLength' => 6]); @@ -183,10 +190,8 @@ public function short_password_will_not_pass() ; } - /** - * @test - */ - public function short_password_in_multi_byte_will_not_pass() + /** @test */ + public function short_password_in_multi_byte_will_not_pass(): void { $constraint = new PasswordStrength(['minStrength' => 5, 'minLength' => 7]); @@ -203,11 +208,11 @@ public function short_password_in_multi_byte_will_not_pass() } /** - * @dataProvider getWeakPasswords + * @dataProvider provideWeak_passwords_will_not_passCases * * @test */ - public function weak_passwords_will_not_pass($minStrength, $value, $currentStrength, $tips = '') + public function weak_passwords_will_not_pass(int $minStrength, string $value, int $currentStrength, string $tips = ''): void { $constraint = new PasswordStrength(['minStrength' => $minStrength, 'minLength' => 6]); @@ -227,11 +232,11 @@ public function weak_passwords_will_not_pass($minStrength, $value, $currentStren } /** - * @dataProvider getWeakPasswordsUnicode + * @dataProvider provideWeak_passwords_with_unicode_will_not_passCases * * @test */ - public function weak_passwords_with_unicode_will_not_pass($minStrength, $value, $currentStrength, $tips = '') + public function weak_passwords_with_unicode_will_not_pass(int $minStrength, string $value, int $currentStrength, string $tips = ''): void { $constraint = new PasswordStrength(['minStrength' => $minStrength, 'minLength' => 6, 'unicodeEquality' => true]); @@ -251,11 +256,11 @@ public function weak_passwords_with_unicode_will_not_pass($minStrength, $value, } /** - * @dataProvider getVeryStrongPasswords + * @dataProvider provideStrong_passwords_will_passCases * * @test */ - public function strong_passwords_will_pass($value) + public function strong_passwords_will_pass(string $value): void { $constraint = new PasswordStrength(5); @@ -264,20 +269,16 @@ public function strong_passwords_will_pass($value) $this->assertNoViolation(); } - /** - * @test - */ - public function constraint_get_default_option() + /** @test */ + public function constraint_get_default_option(): void { $constraint = new PasswordStrength(5); self::assertEquals(5, $constraint->minStrength); } - /** - * @test - */ - public function parameters_are_translated_when_translator_is_missing() + /** @test */ + public function parameters_are_translated_when_translator_is_missing(): void { $this->validator = new PasswordStrengthValidator(); $this->validator->initialize($this->context);