From 7ef421ea81c2c3be78da145cce7957f505469799 Mon Sep 17 00:00:00 2001 From: Amirsobhan Nafariyeh Date: Mon, 15 Jan 2024 22:29:49 +0330 Subject: [PATCH 1/7] test: CodeGenerator.php --- .../OneTimePassword/Support/CodeGenerator.php | 15 ++--------- tests/Unit/ExampleTest.php | 26 ++++++++++++++++--- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/Infrastructure/OneTimePassword/Support/CodeGenerator.php b/src/Infrastructure/OneTimePassword/Support/CodeGenerator.php index 1dbd42b..fe48df8 100644 --- a/src/Infrastructure/OneTimePassword/Support/CodeGenerator.php +++ b/src/Infrastructure/OneTimePassword/Support/CodeGenerator.php @@ -9,20 +9,9 @@ class CodeGenerator implements GeneratorInterface { - protected readonly int $length; - protected readonly OneTimePasswordCodeType $type; - - public function __construct(Repository $configRepository) + public function __construct(protected readonly OneTimePasswordCodeType $type = OneTimePasswordCodeType::DIGIT, protected readonly int $length = 6, ) { - /** - * @phpstan-ignore-next-line - */ - $this->length = $configRepository->get('auth_pro.one_time_password.code.length', 6); - - /** - * @phpstan-ignore-next-line - */ - $this->type = $configRepository->get('auth_pro.one_time_password.code.type', OneTimePasswordCodeType::DIGIT); + // } public function generate(int $length = null): string diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php index 61cd84c..0405301 100644 --- a/tests/Unit/ExampleTest.php +++ b/tests/Unit/ExampleTest.php @@ -1,5 +1,25 @@ toBeTrue(); -}); +use LaravelAuthPro\Infrastructure\OneTimePassword\Enum\OneTimePasswordCodeType; +use LaravelAuthPro\Infrastructure\OneTimePassword\Support\CodeGenerator; + +test('code generator generates random code', function (OneTimePasswordCodeType $type) { + $generator = new CodeGenerator($type); + + $values = collect(range(1, 16)) + ->map(fn(int $i) => $i * 2) + ->mapWithKeys(fn(int $length) => [$length => $generator->generate($length)]); + + expect($values) + ->sequence( + fn($code, $length) => $code->toHaveLength((int)$length->value), + fn($code) => match ($type) { + OneTimePasswordCodeType::DIGIT => $code->toBeDigits(), + OneTimePasswordCodeType::ALPHA => $code->toBeAlpha(), + } + ); +}) + ->with([ + 'digits' => [OneTimePasswordCodeType::DIGIT], + 'alpha' => [OneTimePasswordCodeType::ALPHA], + ]); From 0ee282d86bec9e410b231db1fd3ebe610049d640 Mon Sep 17 00:00:00 2001 From: a1383n Date: Mon, 15 Jan 2024 19:00:37 +0000 Subject: [PATCH 2/7] style: Fix code style issues --- .../OneTimePassword/Support/CodeGenerator.php | 3 +-- tests/Unit/ExampleTest.php | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Infrastructure/OneTimePassword/Support/CodeGenerator.php b/src/Infrastructure/OneTimePassword/Support/CodeGenerator.php index fe48df8..3b78af8 100644 --- a/src/Infrastructure/OneTimePassword/Support/CodeGenerator.php +++ b/src/Infrastructure/OneTimePassword/Support/CodeGenerator.php @@ -2,14 +2,13 @@ namespace LaravelAuthPro\Infrastructure\OneTimePassword\Support; -use Illuminate\Contracts\Config\Repository; use Illuminate\Support\Str; use LaravelAuthPro\Contracts\Base\GeneratorInterface; use LaravelAuthPro\Infrastructure\OneTimePassword\Enum\OneTimePasswordCodeType; class CodeGenerator implements GeneratorInterface { - public function __construct(protected readonly OneTimePasswordCodeType $type = OneTimePasswordCodeType::DIGIT, protected readonly int $length = 6, ) + public function __construct(protected readonly OneTimePasswordCodeType $type = OneTimePasswordCodeType::DIGIT, protected readonly int $length = 6) { // } diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php index 0405301..913eaf9 100644 --- a/tests/Unit/ExampleTest.php +++ b/tests/Unit/ExampleTest.php @@ -7,13 +7,13 @@ $generator = new CodeGenerator($type); $values = collect(range(1, 16)) - ->map(fn(int $i) => $i * 2) - ->mapWithKeys(fn(int $length) => [$length => $generator->generate($length)]); + ->map(fn (int $i) => $i * 2) + ->mapWithKeys(fn (int $length) => [$length => $generator->generate($length)]); expect($values) ->sequence( - fn($code, $length) => $code->toHaveLength((int)$length->value), - fn($code) => match ($type) { + fn ($code, $length) => $code->toHaveLength((int)$length->value), + fn ($code) => match ($type) { OneTimePasswordCodeType::DIGIT => $code->toBeDigits(), OneTimePasswordCodeType::ALPHA => $code->toBeAlpha(), } From e0cafdcee01a3b2b4280a64b2f21f31951591037 Mon Sep 17 00:00:00 2001 From: Amirsobhan Nafariyeh Date: Thu, 25 Jan 2024 18:25:30 +0330 Subject: [PATCH 3/7] test: add more test case --- composer.json | 1 + .../Support/TokenGenerator.php | 20 ++---- src/Traits/HasPayload.php | 2 +- tests/Unit/Base/BaseServiceTest.php | 20 ++++++ tests/Unit/Credential/AuthCredentialTest.php | 48 ++++++++++++++ .../Builder/AuthCredentialBuilderTest.php | 66 +++++++++++++++++++ tests/Unit/Credential/EmailCredentialTest.php | 36 ++++++++++ tests/Unit/Credential/PhoneCredentialTest.php | 59 +++++++++++++++++ tests/Unit/ExampleTest.php | 25 ------- tests/Unit/Support/CodeGeneratorTest.php | 61 +++++++++++++++++ 10 files changed, 299 insertions(+), 39 deletions(-) create mode 100644 tests/Unit/Base/BaseServiceTest.php create mode 100644 tests/Unit/Credential/AuthCredentialTest.php create mode 100644 tests/Unit/Credential/Builder/AuthCredentialBuilderTest.php create mode 100644 tests/Unit/Credential/EmailCredentialTest.php create mode 100644 tests/Unit/Credential/PhoneCredentialTest.php delete mode 100644 tests/Unit/ExampleTest.php create mode 100644 tests/Unit/Support/CodeGeneratorTest.php diff --git a/composer.json b/composer.json index ea1174f..e49d165 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.26", + "mockery/mockery": "^1.6", "nunomaduro/larastan": "^2.0", "orchestra/testbench": "^7.0|^8.0", "pestphp/pest": "^1.23|^2.18", diff --git a/src/Infrastructure/OneTimePassword/Support/TokenGenerator.php b/src/Infrastructure/OneTimePassword/Support/TokenGenerator.php index 0073b2f..cce4f2b 100644 --- a/src/Infrastructure/OneTimePassword/Support/TokenGenerator.php +++ b/src/Infrastructure/OneTimePassword/Support/TokenGenerator.php @@ -9,20 +9,9 @@ class TokenGenerator implements GeneratorInterface { - protected readonly int $length; - protected readonly OneTimePasswordTokenType $type; - - public function __construct(Repository $configRepository) + public function __construct( protected readonly OneTimePasswordTokenType $type = OneTimePasswordTokenType::RANDOM_STRING, protected readonly int $length = 8) { - /** - * @phpstan-ignore-next-line - */ - $this->length = $configRepository->get('one_time_password.token.length', 8); - - /** - * @phpstan-ignore-next-line - */ - $this->type = OneTimePasswordTokenType::from($configRepository->get('one_time_password.token.type', 'random_string')); + // } /** @@ -42,6 +31,11 @@ public function generate(int $length = null): string private function generateRandomInt(int $length): int { + if ($length > 18) { + // It's reached PHP_MAX_INT value and will convert to float, but random_int method accept int as min and max. + throw new \RuntimeException('$length is too large. Length should not above 18'); + } + return random_int(10 ** ($length - 1), (10 ** $length) - 1); } } diff --git a/src/Traits/HasPayload.php b/src/Traits/HasPayload.php index c58a130..b0bfc65 100644 --- a/src/Traits/HasPayload.php +++ b/src/Traits/HasPayload.php @@ -11,7 +11,7 @@ trait HasPayload private function fillAttributes(array $payload): void { $staticProperties = array_keys(get_class_vars(static::class)); - $staticProperties = array_diff($staticProperties, array_keys(get_object_vars($this))); +// $staticProperties = array_diff($staticProperties, array_keys(get_object_vars($this))); foreach ($staticProperties as $property) { if (! empty($payload[$property])) { diff --git a/tests/Unit/Base/BaseServiceTest.php b/tests/Unit/Base/BaseServiceTest.php new file mode 100644 index 0000000..6ba84c4 --- /dev/null +++ b/tests/Unit/Base/BaseServiceTest.php @@ -0,0 +1,20 @@ +hasRepository())->toBeTrue(); + + $class = new $class(); + expect($class->hasRepository()) + ->toBeFalse() + ->and(fn() => $class->throwIfRepositoryNotProvided()) + ->toThrow(\InvalidArgumentException::class); + }); +}); diff --git a/tests/Unit/Credential/AuthCredentialTest.php b/tests/Unit/Credential/AuthCredentialTest.php new file mode 100644 index 0000000..c3aa6e6 --- /dev/null +++ b/tests/Unit/Credential/AuthCredentialTest.php @@ -0,0 +1,48 @@ +andReturn([ + \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\EmailCredentialInterface::class, + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class + ]); + + \LaravelAuthPro\AuthPro::shouldReceive('getAuthProvidersMapper')->andReturn([ + \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Providers\EmailProvider::class, + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Providers\PhoneProvider::class + ]); +}); + +describe('test auth credential model class', function () { + it('throw if identifier type not supported', function () { + $identifier = Mockery::mock(\LaravelAuthPro\Contracts\AuthIdentifierInterface::class) + ->shouldReceive('getIdentifierType')->andReturn(\LaravelAuthPro\Enums\AuthIdentifierType::EMAIL) + ->getMock(); + + app()->bind(\LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class, \LaravelAuthPro\Credentials\PhoneCredential::class); + + (new \LaravelAuthPro\Credentials\Builder\AuthCredentialBuilder()) + ->with('phone') + ->as($identifier) + ->by(\LaravelAuthPro\Enums\AuthProviderSignInMethod::PASSWORD) + ->withPayload() + ->build(); + })->throws(\InvalidArgumentException::class, 'Invalid identifier type [EMAIL] in PhoneCredential'); + + it('payload rule must be available according to base interface parents', function () { + expect(\LaravelAuthPro\Credentials\EmailCredential::class)->toImplement(\LaravelAuthPro\Contracts\Credentials\EmailCredentialInterface::class); + + expect(\LaravelAuthPro\Contracts\Credentials\EmailCredentialInterface::class)->toExtend(\LaravelAuthPro\Contracts\Credentials\Base\HasPasswordInterface::class) + ->and(\LaravelAuthPro\Credentials\EmailCredential::class)->toExtend(\LaravelAuthPro\Contracts\Credentials\Base\HasOneTimePasswordInterface::class); + + expect(\LaravelAuthPro\Credentials\EmailCredential::getPayloadRules()) + ->toHaveKeys(['password', 'code', 'token']); + + expect(\LaravelAuthPro\Credentials\PhoneCredential::getPayloadRules()) + ->toHaveKeys(['password', 'code', 'token']); + }); + + it('return correct builder class', function () { + expect(\LaravelAuthPro\Credentials\EmailCredential::getBuilder()) + ->toBeInstanceOf(\LaravelAuthPro\Credentials\Builder\AuthCredentialBuilder::class); + }); +}); diff --git a/tests/Unit/Credential/Builder/AuthCredentialBuilderTest.php b/tests/Unit/Credential/Builder/AuthCredentialBuilderTest.php new file mode 100644 index 0000000..9f18efc --- /dev/null +++ b/tests/Unit/Credential/Builder/AuthCredentialBuilderTest.php @@ -0,0 +1,66 @@ +andReturn([ + \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\EmailCredentialInterface::class, + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class + ]); + + \LaravelAuthPro\AuthPro::shouldReceive('getAuthProvidersMapper')->andReturn([ + \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Providers\EmailProvider::class, + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Providers\PhoneProvider::class + ]); +}); + +describe('test auth credential builder', function () { + it('build email credential class', function () { + $authIdentifier = Mockery::mock(\LaravelAuthPro\Contracts\AuthIdentifierInterface::class) + ->shouldReceive('getIdentifierType')->andReturn(\LaravelAuthPro\Enums\AuthIdentifierType::EMAIL) + ->getMock(); + + app()->bind(\LaravelAuthPro\Contracts\Credentials\EmailCredentialInterface::class, \LaravelAuthPro\Credentials\EmailCredential::class); + + $model = (new \LaravelAuthPro\Credentials\Builder\AuthCredentialBuilder()) + ->with('email') + ->by(\LaravelAuthPro\Enums\AuthProviderSignInMethod::PASSWORD) + ->as($authIdentifier) + ->withPayload(['password' => 'foo', 'token' => 'bar', 'code' => 'test']) + ->build(); + + expect($model)->toBeInstanceOf(\LaravelAuthPro\Contracts\Credentials\EmailCredentialInterface::class) + ->and($model->getIdentifier())->toBe($authIdentifier) + ->and($model->getProviderId())->toBe('email') + ->and($model->getSignInMethod())->toBe(\LaravelAuthPro\Enums\AuthProviderSignInMethod::PASSWORD) + ->and($model->getPassword())->toBe('foo') + ->and($model->getOneTimePasswordToken())->toBe('bar') + ->and($model->getOneTimePassword())->toBe('test'); + }); + + it('build phone credential class', function () { + $authIdentifier = Mockery::mock(\LaravelAuthPro\Contracts\AuthIdentifierInterface::class) + ->shouldReceive('getIdentifierType')->andReturn(\LaravelAuthPro\Enums\AuthIdentifierType::MOBILE) + ->getMock(); + + app()->bind(\LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class, \LaravelAuthPro\Credentials\PhoneCredential::class); + + $model = (new \LaravelAuthPro\Credentials\Builder\AuthCredentialBuilder()) + ->with('phone') + ->by(\LaravelAuthPro\Enums\AuthProviderSignInMethod::PASSWORD) + ->as($authIdentifier) + ->withPayload(['password' => 'foo', 'token' => 'bar', 'code' => 'test']) + ->build(); + + expect($model)->toBeInstanceOf(\LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class) + ->and($model->getIdentifier())->toBe($authIdentifier) + ->and($model->getProviderId())->toBe('phone') + ->and($model->getSignInMethod())->toBe(\LaravelAuthPro\Enums\AuthProviderSignInMethod::PASSWORD) + ->and($model->getPassword())->toBe('foo') + ->and($model->getOneTimePasswordToken())->toBe('bar') + ->and($model->getOneTimePassword())->toBe('test'); + }); + + it('throw error when provider is null', function () { + (new \LaravelAuthPro\Credentials\Builder\AuthCredentialBuilder()) + ->build(); + })->throws(\InvalidArgumentException::class); +}); diff --git a/tests/Unit/Credential/EmailCredentialTest.php b/tests/Unit/Credential/EmailCredentialTest.php new file mode 100644 index 0000000..0eb2a84 --- /dev/null +++ b/tests/Unit/Credential/EmailCredentialTest.php @@ -0,0 +1,36 @@ +flush(); + + \LaravelAuthPro\AuthPro::shouldReceive('getCredentialsMapper')->andReturn([ + \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\EmailCredentialInterface::class, + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class + ]); + + \LaravelAuthPro\AuthPro::shouldReceive('getAuthProvidersMapper')->andReturn([ + \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Providers\EmailProvider::class, + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Providers\PhoneProvider::class + ]); +}); + +describe('test email credential', function () { + it('have correct email according to identifier', function () { + $identifier = Mockery::mock(\LaravelAuthPro\Contracts\AuthIdentifierInterface::class) + ->shouldReceive('getIdentifierValue')->andReturn('someone@example.com') + ->shouldReceive('getIdentifierType')->andReturn(\LaravelAuthPro\Enums\AuthIdentifierType::EMAIL) + ->getMock(); + + app()->bind(\LaravelAuthPro\Contracts\Credentials\EmailCredentialInterface::class, \LaravelAuthPro\Credentials\EmailCredential::class); + + $credential = (new \LaravelAuthPro\Credentials\Builder\AuthCredentialBuilder()) + ->with('email') + ->as($identifier) + ->by(\LaravelAuthPro\Enums\AuthProviderSignInMethod::PASSWORD) + ->withPayload() + ->build(); + + expect($credential)->toBeInstanceOf(\LaravelAuthPro\Credentials\EmailCredential::class) + ->and($credential->getEmail())->toBe('someone@example.com'); + }); +}); diff --git a/tests/Unit/Credential/PhoneCredentialTest.php b/tests/Unit/Credential/PhoneCredentialTest.php new file mode 100644 index 0000000..bcd2262 --- /dev/null +++ b/tests/Unit/Credential/PhoneCredentialTest.php @@ -0,0 +1,59 @@ +flush(); + + \LaravelAuthPro\AuthPro::shouldReceive('getCredentialsMapper')->andReturn([ + \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\EmailCredentialInterface::class, + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class + ]); + + \LaravelAuthPro\AuthPro::shouldReceive('getAuthProvidersMapper')->andReturn([ + \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Providers\EmailProvider::class, + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Providers\PhoneProvider::class + ]); +}); + +describe('test phone credential', function () { + it('have correct phone according to identifier', function () { + $authIdentifier = Mockery::mock(\LaravelAuthPro\Contracts\AuthIdentifierInterface::class) + ->shouldReceive('getIdentifierValue')->andReturn('111222333') + ->shouldReceive('getIdentifierType')->andReturn(\LaravelAuthPro\Enums\AuthIdentifierType::MOBILE) + ->getMock(); + + app()->bind(\LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class, \LaravelAuthPro\Credentials\PhoneCredential::class); + + $credential = (new \LaravelAuthPro\Credentials\Builder\AuthCredentialBuilder()) + ->with('phone') + ->by(\LaravelAuthPro\Enums\AuthProviderSignInMethod::PASSWORD) + ->as($authIdentifier) + ->withPayload(['password' => 'foo', 'token' => 'bar', 'code' => 'test']) + ->build(); + + expect($credential)->toBeInstanceOf(\LaravelAuthPro\Credentials\PhoneCredential::class) + ->and($credential->getPhone())->toBe('111222333'); + }); + + it('load signature from encrypted string', function () { + $signature = new \LaravelAuthPro\AuthSignature('id', 'ip', 'user_id', now()); + + $authIdentifier = Mockery::mock(\LaravelAuthPro\Contracts\AuthIdentifierInterface::class) + ->shouldReceive('getIdentifierValue')->andReturn('111222333') + ->shouldReceive('getIdentifierType')->andReturn(\LaravelAuthPro\Enums\AuthIdentifierType::MOBILE) + ->getMock(); + + + \Illuminate\Support\Facades\Crypt::shouldReceive('encrypt')->andReturn('encrypted_string'); + \Illuminate\Support\Facades\Crypt::shouldReceive('decrypt')->andReturn($signature->toArray()); + + $credential = (new \LaravelAuthPro\Credentials\Builder\AuthCredentialBuilder()) + ->with('phone') + ->by(\LaravelAuthPro\Enums\AuthProviderSignInMethod::PASSWORD) + ->as($authIdentifier) + ->withPayload(['signature' => $signature->__toString()]) + ->build(); + + expect($credential->getSignature()->toArray())->toBe($signature->toArray()); + }); +}); diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php deleted file mode 100644 index 913eaf9..0000000 --- a/tests/Unit/ExampleTest.php +++ /dev/null @@ -1,25 +0,0 @@ -map(fn (int $i) => $i * 2) - ->mapWithKeys(fn (int $length) => [$length => $generator->generate($length)]); - - expect($values) - ->sequence( - fn ($code, $length) => $code->toHaveLength((int)$length->value), - fn ($code) => match ($type) { - OneTimePasswordCodeType::DIGIT => $code->toBeDigits(), - OneTimePasswordCodeType::ALPHA => $code->toBeAlpha(), - } - ); -}) - ->with([ - 'digits' => [OneTimePasswordCodeType::DIGIT], - 'alpha' => [OneTimePasswordCodeType::ALPHA], - ]); diff --git a/tests/Unit/Support/CodeGeneratorTest.php b/tests/Unit/Support/CodeGeneratorTest.php new file mode 100644 index 0000000..cab9e0c --- /dev/null +++ b/tests/Unit/Support/CodeGeneratorTest.php @@ -0,0 +1,61 @@ +map(fn (int $i) => $i * 2) + ->mapWithKeys(fn (int $length) => [$length => $generator->generate($length)]); + + expect($values) + ->sequence( + fn ($code, $length) => $code->toHaveLength((int)$length->value), + fn ($code) => match ($type) { + OneTimePasswordCodeType::DIGIT => $code->toBeDigits(), + OneTimePasswordCodeType::ALPHA => $code->toBeAlpha(), + } + ); + }) + ->with([ + 'digits' => [OneTimePasswordCodeType::DIGIT], + 'alpha' => [OneTimePasswordCodeType::ALPHA], + ]); +}); + +describe('test token generator', function () { + it('generate random token with specified type', function (OneTimePasswordTokenType $type, int $endRange = 16) { + $generator = new TokenGenerator($type); + + $values = collect(range(1, $endRange)) + ->map(fn (int $i) => $i * 2) + ->mapWithKeys(fn (int $length) => [$length => $generator->generate($length)]); + + expect($values) + ->each( + fn ($code, $length) => match ($type) { + OneTimePasswordTokenType::RANDOM_STRING => $code->toBeString() && $code->toHaveLength($length), + OneTimePasswordTokenType::RANDOM_INT => $code->toBeNumeric() && $code->toHaveLength($length), + OneTimePasswordTokenType::ULID => expect(Ulid::isValid($code->value))->toBeTrue(), + OneTimePasswordTokenType::UUID => $code->toBeUuid(), + } + ); + }) + ->with([ + 'string' => [OneTimePasswordTokenType::RANDOM_STRING], + 'integer' => [OneTimePasswordTokenType::RANDOM_INT, 9], + 'ulid' => [OneTimePasswordTokenType::ULID], + 'uuid' => [OneTimePasswordTokenType::UUID], + ]); + + it('throw error when length is too large for integer type', function () { + (new TokenGenerator(OneTimePasswordTokenType::RANDOM_INT, 20)) + ->generate(); + })->throws(\RuntimeException::class); +}); From ad62bbe999e1e9ca5f19b2904c4f43395714bd2b Mon Sep 17 00:00:00 2001 From: a1383n Date: Thu, 25 Jan 2024 14:55:53 +0000 Subject: [PATCH 4/7] style: Fix code style issues --- src/Infrastructure/OneTimePassword/Support/TokenGenerator.php | 3 +-- src/Traits/HasPayload.php | 2 +- tests/Unit/Base/BaseServiceTest.php | 4 ++-- tests/Unit/Credential/AuthCredentialTest.php | 4 ++-- tests/Unit/Credential/Builder/AuthCredentialBuilderTest.php | 4 ++-- tests/Unit/Credential/EmailCredentialTest.php | 4 ++-- tests/Unit/Credential/PhoneCredentialTest.php | 4 ++-- 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Infrastructure/OneTimePassword/Support/TokenGenerator.php b/src/Infrastructure/OneTimePassword/Support/TokenGenerator.php index cce4f2b..105c94c 100644 --- a/src/Infrastructure/OneTimePassword/Support/TokenGenerator.php +++ b/src/Infrastructure/OneTimePassword/Support/TokenGenerator.php @@ -2,14 +2,13 @@ namespace LaravelAuthPro\Infrastructure\OneTimePassword\Support; -use Illuminate\Contracts\Config\Repository; use Illuminate\Support\Str; use LaravelAuthPro\Contracts\Base\GeneratorInterface; use LaravelAuthPro\Infrastructure\OneTimePassword\Enum\OneTimePasswordTokenType; class TokenGenerator implements GeneratorInterface { - public function __construct( protected readonly OneTimePasswordTokenType $type = OneTimePasswordTokenType::RANDOM_STRING, protected readonly int $length = 8) + public function __construct(protected readonly OneTimePasswordTokenType $type = OneTimePasswordTokenType::RANDOM_STRING, protected readonly int $length = 8) { // } diff --git a/src/Traits/HasPayload.php b/src/Traits/HasPayload.php index b0bfc65..71df3f2 100644 --- a/src/Traits/HasPayload.php +++ b/src/Traits/HasPayload.php @@ -11,7 +11,7 @@ trait HasPayload private function fillAttributes(array $payload): void { $staticProperties = array_keys(get_class_vars(static::class)); -// $staticProperties = array_diff($staticProperties, array_keys(get_object_vars($this))); + // $staticProperties = array_diff($staticProperties, array_keys(get_object_vars($this))); foreach ($staticProperties as $property) { if (! empty($payload[$property])) { diff --git a/tests/Unit/Base/BaseServiceTest.php b/tests/Unit/Base/BaseServiceTest.php index 6ba84c4..663ef29 100644 --- a/tests/Unit/Base/BaseServiceTest.php +++ b/tests/Unit/Base/BaseServiceTest.php @@ -4,7 +4,7 @@ it('throw if repository not provided', function () { $repository = Mockery::mock(\LaravelAuthPro\Base\BaseRepository::class); - $class = new class extends \LaravelAuthPro\Base\BaseService { + $class = new class () extends \LaravelAuthPro\Base\BaseService { // }; @@ -14,7 +14,7 @@ $class = new $class(); expect($class->hasRepository()) ->toBeFalse() - ->and(fn() => $class->throwIfRepositoryNotProvided()) + ->and(fn () => $class->throwIfRepositoryNotProvided()) ->toThrow(\InvalidArgumentException::class); }); }); diff --git a/tests/Unit/Credential/AuthCredentialTest.php b/tests/Unit/Credential/AuthCredentialTest.php index c3aa6e6..7859469 100644 --- a/tests/Unit/Credential/AuthCredentialTest.php +++ b/tests/Unit/Credential/AuthCredentialTest.php @@ -3,12 +3,12 @@ beforeAll(function () { \LaravelAuthPro\AuthPro::shouldReceive('getCredentialsMapper')->andReturn([ \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\EmailCredentialInterface::class, - \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class, ]); \LaravelAuthPro\AuthPro::shouldReceive('getAuthProvidersMapper')->andReturn([ \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Providers\EmailProvider::class, - \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Providers\PhoneProvider::class + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Providers\PhoneProvider::class, ]); }); diff --git a/tests/Unit/Credential/Builder/AuthCredentialBuilderTest.php b/tests/Unit/Credential/Builder/AuthCredentialBuilderTest.php index 9f18efc..881f6b4 100644 --- a/tests/Unit/Credential/Builder/AuthCredentialBuilderTest.php +++ b/tests/Unit/Credential/Builder/AuthCredentialBuilderTest.php @@ -3,12 +3,12 @@ beforeAll(function () { \LaravelAuthPro\AuthPro::shouldReceive('getCredentialsMapper')->andReturn([ \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\EmailCredentialInterface::class, - \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class, ]); \LaravelAuthPro\AuthPro::shouldReceive('getAuthProvidersMapper')->andReturn([ \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Providers\EmailProvider::class, - \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Providers\PhoneProvider::class + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Providers\PhoneProvider::class, ]); }); diff --git a/tests/Unit/Credential/EmailCredentialTest.php b/tests/Unit/Credential/EmailCredentialTest.php index 0eb2a84..449f952 100644 --- a/tests/Unit/Credential/EmailCredentialTest.php +++ b/tests/Unit/Credential/EmailCredentialTest.php @@ -5,12 +5,12 @@ \LaravelAuthPro\AuthPro::shouldReceive('getCredentialsMapper')->andReturn([ \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\EmailCredentialInterface::class, - \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class, ]); \LaravelAuthPro\AuthPro::shouldReceive('getAuthProvidersMapper')->andReturn([ \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Providers\EmailProvider::class, - \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Providers\PhoneProvider::class + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Providers\PhoneProvider::class, ]); }); diff --git a/tests/Unit/Credential/PhoneCredentialTest.php b/tests/Unit/Credential/PhoneCredentialTest.php index bcd2262..18eff92 100644 --- a/tests/Unit/Credential/PhoneCredentialTest.php +++ b/tests/Unit/Credential/PhoneCredentialTest.php @@ -6,12 +6,12 @@ \LaravelAuthPro\AuthPro::shouldReceive('getCredentialsMapper')->andReturn([ \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\EmailCredentialInterface::class, - \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Contracts\Credentials\PhoneCredentialInterface::class, ]); \LaravelAuthPro\AuthPro::shouldReceive('getAuthProvidersMapper')->andReturn([ \LaravelAuthPro\Contracts\Providers\EmailProviderInterface::class => \LaravelAuthPro\Providers\EmailProvider::class, - \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Providers\PhoneProvider::class + \LaravelAuthPro\Contracts\Providers\PhoneProviderInterface::class => \LaravelAuthPro\Providers\PhoneProvider::class, ]); }); From 1071bd93720c7f85b6a3dacda08cc8cf3d28ab82 Mon Sep 17 00:00:00 2001 From: Amirsobhan Nafariyeh Date: Fri, 16 Feb 2024 17:21:40 +0330 Subject: [PATCH 5/7] test: add more test case --- .../Exceptions/AuthExceptionTest.php | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/Unit/Contracts/Exceptions/AuthExceptionTest.php diff --git a/tests/Unit/Contracts/Exceptions/AuthExceptionTest.php b/tests/Unit/Contracts/Exceptions/AuthExceptionTest.php new file mode 100644 index 0000000..72bee86 --- /dev/null +++ b/tests/Unit/Contracts/Exceptions/AuthExceptionTest.php @@ -0,0 +1,42 @@ + 'bar']); + + $translatorMock = Mockery::mock(\Illuminate\Translation\Translator::class) + ->shouldReceive('get') + ->andReturn('My Error') + ->getMock(); + + $reposeFactory = Mockery::mock($rf = \Illuminate\Contracts\Routing\ResponseFactory::class) + ->shouldReceive('make') + ->andReturn(new \Illuminate\Http\Response()) + ->getMock(); + + app()['translator'] = $translatorMock; + app()[$rf] = $reposeFactory; + + expect($e->getErrorMessage()) + ->toEqual('my_error') + ->and($e->getCode()) + ->toEqual(500) + ->and($e->report()) + ->toBeFalse() + ->and($e->render(new \Illuminate\Http\Request())) + ->toBeInstanceOf(\Illuminate\Http\Response::class); + }); + + it('custom render function', function () { + $e = new \LaravelAuthPro\Contracts\Exceptions\AuthException('my_error', 500, ['foo' => 'bar']); + + \LaravelAuthPro\Contracts\Exceptions\AuthException::setRenderClosure(function (\LaravelAuthPro\Contracts\Exceptions\AuthException $exception) use ($e) { + expect($exception)->toBe($e); + + return ['error' => $e->getErrorMessage(), 'code' => $e->getCode()]; + }); + + expect($e->render(new \Illuminate\Http\Request())) + ->toBe(['error' => 'my_error', 'code' => 500]); + }); +}); From a334e6b40fdbeb4ed2748bda6fc1b725fe4aa11a Mon Sep 17 00:00:00 2001 From: a1383n Date: Fri, 16 Feb 2024 13:52:00 +0000 Subject: [PATCH 6/7] style: Fix code style issues --- tests/Unit/Contracts/Exceptions/AuthExceptionTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/Contracts/Exceptions/AuthExceptionTest.php b/tests/Unit/Contracts/Exceptions/AuthExceptionTest.php index 72bee86..9e495bb 100644 --- a/tests/Unit/Contracts/Exceptions/AuthExceptionTest.php +++ b/tests/Unit/Contracts/Exceptions/AuthExceptionTest.php @@ -4,7 +4,7 @@ it('create new instance', function () { $e = new \LaravelAuthPro\Contracts\Exceptions\AuthException('my_error', 500, ['foo' => 'bar']); - $translatorMock = Mockery::mock(\Illuminate\Translation\Translator::class) + $translatorMock = Mockery::mock(\Illuminate\Translation\Translator::class) ->shouldReceive('get') ->andReturn('My Error') ->getMock(); From c1b71f8579a589ec239d47aa4809dd18eab989dd Mon Sep 17 00:00:00 2001 From: Amirsobhan Nafariyeh Date: Fri, 16 Feb 2024 17:30:59 +0330 Subject: [PATCH 7/7] fix: phpstan errors --- src/AuthResult.php | 3 +++ .../Repositories/OneTimePasswordVerifierRepository.php | 3 --- src/Infrastructure/OneTimePassword/Support/TokenGenerator.php | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/AuthResult.php b/src/AuthResult.php index 3821f0a..8619da2 100644 --- a/src/AuthResult.php +++ b/src/AuthResult.php @@ -58,6 +58,9 @@ public function getUser(): ?AuthenticatableInterface public function throwIfError(): self { + /** + * @phpstan-ignore-next-line + */ return ! $this->isSuccessful() ? throw $this->getException() : $this; } } diff --git a/src/Infrastructure/OneTimePassword/Repositories/OneTimePasswordVerifierRepository.php b/src/Infrastructure/OneTimePassword/Repositories/OneTimePasswordVerifierRepository.php index a084e32..e55a29e 100644 --- a/src/Infrastructure/OneTimePassword/Repositories/OneTimePasswordVerifierRepository.php +++ b/src/Infrastructure/OneTimePassword/Repositories/OneTimePasswordVerifierRepository.php @@ -29,9 +29,6 @@ public function getFailedAttemptsCount(OneTimePasswordEntityInterface $entity): public function incrementFailAttemptsCount(OneTimePasswordEntityInterface $entity, int $value = 1): int { - /** - * @phpstan-ignore-next-line - */ $value = $this->connection->incr($key = self::getKey($entity->getKey()), $value); $this->connection->expire($key, (int)$entity->getValidInterval()->addDays(1)->totalSeconds); diff --git a/src/Infrastructure/OneTimePassword/Support/TokenGenerator.php b/src/Infrastructure/OneTimePassword/Support/TokenGenerator.php index 105c94c..80ddc84 100644 --- a/src/Infrastructure/OneTimePassword/Support/TokenGenerator.php +++ b/src/Infrastructure/OneTimePassword/Support/TokenGenerator.php @@ -35,6 +35,9 @@ private function generateRandomInt(int $length): int throw new \RuntimeException('$length is too large. Length should not above 18'); } + /** + * @phpstan-ignore-next-line + */ return random_int(10 ** ($length - 1), (10 ** $length) - 1); } }