diff --git a/src/GraphQL/Mutations/VerifyEmail.php b/src/GraphQL/Mutations/VerifyEmail.php index cabd4d8..f85dab5 100644 --- a/src/GraphQL/Mutations/VerifyEmail.php +++ b/src/GraphQL/Mutations/VerifyEmail.php @@ -44,7 +44,7 @@ public function __invoke($_, array $args): array $user = $userProvider->retrieveById($args['id']); if (! $user instanceof MustVerifyEmail) { - throw new RuntimeException('User not instance of MustVerifyEmail'); + throw new RuntimeException('"' . get_class($user) . '" must implement "' . MustVerifyEmail::class . '".'); } $this->emailVerificationService->verify($user, (string) $args['hash']); diff --git a/tests/Integration/GraphQL/Mutations/VerifyEmailTest.php b/tests/Integration/GraphQL/Mutations/VerifyEmailTest.php new file mode 100644 index 0000000..32e5a27 --- /dev/null +++ b/tests/Integration/GraphQL/Mutations/VerifyEmailTest.php @@ -0,0 +1,77 @@ +app['config']->set('auth.providers.users.model', UserMustVerifyEmail::class); + + /** @var UserMustVerifyEmail $user */ + $user = UserMustVerifyEmail::factory()->create([ + 'id' => 123, + 'email_verified_at' => null, + ]); + + $user->sendEmailVerificationNotification(); + + $this->graphQL(/** @lang GraphQL */ ' + mutation { + verifyEmail(input: { + id: 123, + hash: "' . sha1($user->getEmailForVerification()) . '" + }) { + status + } + } + ')->assertJson([ + 'data' => [ + 'verifyEmail' => [ + 'status' => 'VERIFIED', + ], + ], + ]); + + $user->refresh(); + + static::assertNotNull($user->getAttribute('email_verified_at')); + } + + /** + * @test + */ + public function it_returns_an_error_if_the_hash_is_incorrect(): void + { + $this->app['config']->set('auth.providers.users.model', UserMustVerifyEmail::class); + + UserMustVerifyEmail::factory()->create([ + 'id' => 123, + 'email_verified_at' => null, + ]); + + $response = $this->graphQL(/** @lang GraphQL */ ' + mutation { + verifyEmail(input: { + id: 123, + hash: "foobar" + }) { + status + } + } + '); + + $this->assertGraphQLErrorMessage($response, 'The provided id and hash are incorrect.'); + } +} diff --git a/tests/Traits/MocksUserProvider.php b/tests/Traits/MocksUserProvider.php new file mode 100644 index 0000000..40dc66a --- /dev/null +++ b/tests/Traits/MocksUserProvider.php @@ -0,0 +1,50 @@ +shouldReceive('createUserProvider') + ->with('sanctum-provider') + ->andReturn($userProvider) + ->getMock(); + + return $authManager; + } + + /** + * @return Config|MockInterface + */ + protected function mockConfig() + { + /** @var Config|MockInterface $config */ + $config = Mockery::mock(Config::class) + ->shouldReceive('get') + ->with('lighthouse-sanctum.provider') + ->andReturn('sanctum-provider') + ->getMock(); + + return $config; + } + + /** + * @return UserProvider|MockInterface + */ + abstract protected function mockUserProvider(?User $user); +} diff --git a/tests/Unit/GraphQL/Mutations/VerifyEmailTest.php b/tests/Unit/GraphQL/Mutations/VerifyEmailTest.php new file mode 100644 index 0000000..9b45a2b --- /dev/null +++ b/tests/Unit/GraphQL/Mutations/VerifyEmailTest.php @@ -0,0 +1,115 @@ +shouldReceive('markEmailAsVerified') + ->getMock(); + + /** @var EmailVerificationServiceInterface|MockInterface $verificationService */ + $verificationService = Mockery::mock(EmailVerificationServiceInterface::class) + ->shouldReceive('verify') + ->with($user, '1234567890') + ->getMock(); + + $userProvider = $this->mockUserProvider($user); + + $mutation = new VerifyEmail( + $this->mockAuthManager($userProvider), + $this->mockConfig(), + $verificationService, + ); + + $result = $mutation(null, [ + 'id' => 123, + 'hash' => '1234567890', + ]); + + static::assertIsArray($result); + static::assertCount(1, $result); + static::assertTrue(EmailVerificationStatus::VERIFIED()->is($result['status'])); + } + + /** + * @test + */ + public function it_throws_an_exception_if_the_user_provider_is_not_found(): void + { + static::expectException(RuntimeException::class); + static::expectExceptionMessage('User provider not found.'); + + $mutation = new VerifyEmail( + $this->mockAuthManager(null), + $this->mockConfig(), + Mockery::mock(EmailVerificationServiceInterface::class), + ); + + $mutation(null, [ + 'id' => 123, + 'hash' => '1234567890', + ]); + } + + /** + * @test + */ + public function it_throws_an_exception_if_the_user_does_not_implement_the_must_verify_email_interface(): void + { + $user = Mockery::mock(User::class); + + static::expectException(RuntimeException::class); + static::expectExceptionMessage('"' . get_class($user) . '" must implement "Illuminate\Contracts\Auth\MustVerifyEmail".'); + + $userProvider = $this->mockUserProvider($user); + + $mutation = new VerifyEmail( + $this->mockAuthManager($userProvider), + $this->mockConfig(), + Mockery::mock(EmailVerificationServiceInterface::class), + ); + + $mutation(null, [ + 'id' => 123, + 'hash' => '1234567890', + ]); + } + + /** + * @return UserProvider|MockInterface + */ + protected function mockUserProvider(?User $user) + { + /** @var UserProvider|MockInterface $userProvider */ + $userProvider = Mockery::mock(UserProvider::class) + ->shouldReceive('retrieveById') + ->with(123) + ->andReturn($user) + ->getMock(); + + return $userProvider; + } +} diff --git a/tests/stubs/Users/UserHasApiTokens.php b/tests/stubs/Users/UserHasApiTokens.php index b1e1126..5fffd98 100644 --- a/tests/stubs/Users/UserHasApiTokens.php +++ b/tests/stubs/Users/UserHasApiTokens.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User; +use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\Contracts\HasApiTokens as HasApiTokensContract; use Laravel\Sanctum\HasApiTokens; @@ -14,6 +15,7 @@ class UserHasApiTokens extends User implements HasApiTokensContract { use HasApiTokens; use HasFactory; + use Notifiable; protected $table = 'users';