From 2cfe61dcf5515fdd4bc3a26dc412511cdfea6390 Mon Sep 17 00:00:00 2001 From: Alessio Date: Sat, 24 Oct 2020 21:10:33 +0200 Subject: [PATCH 01/13] Test for IntegractWithAdditionalAttributes --- ...ditionalAttributesDefinedViaMethodTest.php | 69 +++++++++++++++++++ ...tionalAttributesDefinedViaPropertyTest.php | 50 ++++++++++++++ .../InteractsWithAdditionalAttributesTest.php | 46 +++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 tests/Unit/InteractsWithAdditionalAttributesDefinedViaMethodTest.php create mode 100644 tests/Unit/InteractsWithAdditionalAttributesDefinedViaPropertyTest.php create mode 100644 tests/Unit/InteractsWithAdditionalAttributesTest.php diff --git a/tests/Unit/InteractsWithAdditionalAttributesDefinedViaMethodTest.php b/tests/Unit/InteractsWithAdditionalAttributesDefinedViaMethodTest.php new file mode 100644 index 0000000..af39aa5 --- /dev/null +++ b/tests/Unit/InteractsWithAdditionalAttributesDefinedViaMethodTest.php @@ -0,0 +1,69 @@ +redirectAttributes(); + + $this->assertEquals(['attribute'], $attributes); + } + + public function test_attributes_are_saved() + { + Session::shouldReceive('put')->once()->with('_oot.identities.attributes', '{"attribute":"value"}'); + + $request = Request::create('http://localhost', 'GET', [ + 'attribute' => 'value' + ]); + $request->setLaravelSession(Session::getFacadeRoot()); + + $this->pushAttributes($request); + } + + public function test_attributes_are_retrieved() + { + Session::shouldReceive('previousUrl')->andReturnNull(); + + Session::shouldReceive('pull')->once() + ->with('_oot.identities.attributes') + ->andReturn('{"attribute":"http://localhost/previous"}'); + + $request = Request::create('http://localhost/callback'); + $request->setLaravelSession(Session::getFacadeRoot()); + + $data = $this->pullAttributes($request); + + $this->assertEquals(['attribute' => 'http://localhost/previous'], $data); + } + + public function test_attributes_not_retrieved_if_nothing_saved() + { + Session::shouldReceive('previousUrl')->andReturnNull(); + + Session::shouldReceive('pull')->once() + ->with('_oot.identities.attributes') + ->andReturn(null); + + $request = Request::create('http://localhost/callback'); + $request->setLaravelSession(Session::getFacadeRoot()); + + $data = $this->pullAttributes($request); + + $this->assertEquals([], $data); + } +} diff --git a/tests/Unit/InteractsWithAdditionalAttributesDefinedViaPropertyTest.php b/tests/Unit/InteractsWithAdditionalAttributesDefinedViaPropertyTest.php new file mode 100644 index 0000000..35d6c5f --- /dev/null +++ b/tests/Unit/InteractsWithAdditionalAttributesDefinedViaPropertyTest.php @@ -0,0 +1,50 @@ +redirectAttributes(); + + $this->assertEquals(['attribute'], $attributes); + } + + public function test_attributes_are_saved() + { + Session::shouldReceive('put')->once()->with('_oot.identities.attributes', '{"attribute":"value"}'); + + $request = Request::create('http://localhost', 'GET', [ + 'attribute' => 'value' + ]); + $request->setLaravelSession(Session::getFacadeRoot()); + + $this->pushAttributes($request); + } + + public function test_attributes_are_retrieved() + { + Session::shouldReceive('previousUrl')->andReturnNull(); + + Session::shouldReceive('pull')->once() + ->with('_oot.identities.attributes') + ->andReturn('{"attribute":"http://localhost/previous"}'); + + $request = Request::create('http://localhost/callback'); + $request->setLaravelSession(Session::getFacadeRoot()); + + $data = $this->pullAttributes($request); + + $this->assertEquals(['attribute' => 'http://localhost/previous'], $data); + } +} diff --git a/tests/Unit/InteractsWithAdditionalAttributesTest.php b/tests/Unit/InteractsWithAdditionalAttributesTest.php new file mode 100644 index 0000000..1b70a86 --- /dev/null +++ b/tests/Unit/InteractsWithAdditionalAttributesTest.php @@ -0,0 +1,46 @@ +redirectAttributes(); + + $this->assertEquals([], $attributes); + } + + public function test_nothing_is_saved() + { + Session::shouldReceive('put')->never(); + + $request = Request::create('http://localhost', 'GET', [ + 'attribute' => 'value' + ]); + $request->setLaravelSession(Session::getFacadeRoot()); + + $this->pushAttributes($request); + } + + public function test_nothing_is_retrieved() + { + Session::shouldReceive('previousUrl')->andReturnNull(); + + Session::shouldReceive('pull')->never(); + + $request = Request::create('http://localhost/callback'); + $request->setLaravelSession(Session::getFacadeRoot()); + + $data = $this->pullAttributes($request); + + $this->assertEquals([], $data); + } +} From bb743bb11a3958984ca3932a558abb227d471717 Mon Sep 17 00:00:00 2001 From: Alessio Date: Sun, 8 Nov 2020 20:07:17 +0100 Subject: [PATCH 02/13] Test identity traits --- src/Support/FindIdentity.php | 4 + tests/TestCase.php | 17 ++++ tests/Unit/IdentityTraitTest.php | 135 +++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 tests/Unit/IdentityTraitTest.php diff --git a/src/Support/FindIdentity.php b/src/Support/FindIdentity.php index 3499531..e77ba9d 100644 --- a/src/Support/FindIdentity.php +++ b/src/Support/FindIdentity.php @@ -9,6 +9,10 @@ trait FindIdentity { /** + * Search a user given its identity in the third party provider + * + * @param \Laravel\Socialite\Contracts\User $identity Third party identity provided by Laravel Socialite + * @param string $provider The identity provider name * @return \Illuminate\Contracts\Auth\Authenticatable|null */ protected function findUserFromIdentity($identity, $provider) diff --git a/tests/TestCase.php b/tests/TestCase.php index ae21652..5e05844 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -7,6 +7,7 @@ use Illuminate\Events\Dispatcher; use Laravel\Socialite\SocialiteServiceProvider; use Orchestra\Testbench\TestCase as BaseTestCase; +use Oneofftech\Identities\Facades\Identity as IdentityFacade; use Oneofftech\Identities\Providers\IdentitiesServiceProvider; use SocialiteProviders\Dropbox\DropboxExtendSocialite; use SocialiteProviders\GitLab\GitLabExtendSocialite; @@ -22,6 +23,8 @@ public function setUp(): void { parent::setUp(); + $this->setUpDatabase($this->app); + $this->activateSocialiteExtensions(); } @@ -72,6 +75,20 @@ protected function getPackageProviders($app) ]; } + /** + * @param \Illuminate\Foundation\Application $app + */ + protected function setUpDatabase($app) + { + $this->loadLaravelMigrations(); + + $this->loadMigrationsFrom(__DIR__.'/../stubs/migrations'); + + IdentityFacade::useNamespace("App"); + IdentityFacade::useIdentityModel("App\\Identity"); + IdentityFacade::useUserModel("App\\User"); + } + protected function activateSocialiteExtensions() { $socialiteWasCalled = $this->app->make(SocialiteWasCalled::class); diff --git a/tests/Unit/IdentityTraitTest.php b/tests/Unit/IdentityTraitTest.php new file mode 100644 index 0000000..517eaf7 --- /dev/null +++ b/tests/Unit/IdentityTraitTest.php @@ -0,0 +1,135 @@ +forceFill([ + 'email' => 'user@local.local', + 'name' => 'User', + 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', + 'remember_token' => Str::random(10), + ])->save(); + }); + + $identity = $user->identities()->create([ + 'provider'=> 'social', + 'provider_id'=> 'aaaa', + 'token'=> 'tttt', + 'refresh_token'=> null, + 'expires_at'=> null, + 'registration' => true, + ]); + + $this->assertInstanceOf(Identity::class, $identity); + $this->assertNotNull($identity->getKey()); + $this->assertEquals('social', $identity->provider); + $this->assertEquals('aaaa', $identity->provider_id); + $this->assertEquals('tttt', $identity->token); + $this->assertNull($identity->refresh_token); + $this->assertNull($identity->expires_at); + $this->assertTrue($identity->registration); + $this->assertTrue($identity->user->is($user)); + } + + public function test_find_identity_trait() + { + IdentityFacade::useNamespace("Tests\\Fixtures\\"); + IdentityFacade::useIdentityModel("Tests\\Fixtures\\Identity"); + IdentityFacade::useUserModel("Tests\\Fixtures\\User"); + + $expectedUser = tap((new User), function ($u) { + $u->forceFill([ + 'email' => 'user@local.local', + 'name' => 'User', + 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', + 'remember_token' => Str::random(10), + ])->save(); + + $u->identities()->create([ + 'provider'=> 'social', + 'provider_id'=> IdentityCrypt::hash('P1'), + 'token'=> 'tttt', + 'refresh_token'=> null, + 'expires_at'=> null, + 'registration' => true, + ]); + }); + + $user = $this->findUserFromIdentity(new class implements SocialiteUser { + public function getId() + { + return 'P1'; + } + public function getNickname() + { + return null; + } + public function getName() + { + return null; + } + public function getEmail() + { + return null; + } + public function getAvatar() + { + return null; + } + }, 'social'); + + $this->assertTrue($expectedUser->is($user)); + } + + public function test_find_identity_trait_return_null_when_not_found() + { + IdentityFacade::useNamespace("Tests\\Fixtures\\"); + IdentityFacade::useIdentityModel("Tests\\Fixtures\\Identity"); + IdentityFacade::useUserModel("Tests\\Fixtures\\User"); + + $user = $this->findUserFromIdentity(new class implements SocialiteUser { + public function getId() + { + return 'P1'; + } + public function getNickname() + { + return null; + } + public function getName() + { + return null; + } + public function getEmail() + { + return null; + } + public function getAvatar() + { + return null; + } + }, 'social'); + + $this->assertNull($user); + } +} From eb2e6e4aa27adfc99557d1eb285dfe15647b6bdf Mon Sep 17 00:00:00 2001 From: Alessio Date: Sun, 8 Nov 2020 20:07:57 +0100 Subject: [PATCH 03/13] Use identity key if configured then fallback to app.key --- src/Providers/IdentitiesServiceProvider.php | 4 +++- tests/TestCase.php | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Providers/IdentitiesServiceProvider.php b/src/Providers/IdentitiesServiceProvider.php index 9d3800d..b725ab3 100644 --- a/src/Providers/IdentitiesServiceProvider.php +++ b/src/Providers/IdentitiesServiceProvider.php @@ -38,7 +38,9 @@ public function register() $config = $app->make('config')->get('identities'); $app_config = $app->make('config')->get('app'); - if (Str::startsWith($key = $config['key'], 'base64:')) { + $key = $config['key'] ?? $app_config['key']; + + if (Str::startsWith($key, 'base64:')) { $key = base64_decode(substr($key, 7)); } diff --git a/tests/TestCase.php b/tests/TestCase.php index 5e05844..e6952ed 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -58,9 +58,9 @@ protected function getEnvironmentSetUp($app) ]); $key = Str::random(32); - $app['config']->set('app.key', $key); + $app['config']->set('app.key', 'base64:'.base64_encode($key)); $app['config']->set('app.cipher', 'AES-256-CBC'); - $app['config']->set('identities.key', $key); + $app['config']->set('identities.key', 'base64:'.base64_encode($key)); } /** From a8351ebb1b3cb5fc21b26fa5958689b253be6cf9 Mon Sep 17 00:00:00 2001 From: Alessio Date: Sun, 8 Nov 2020 20:08:14 +0100 Subject: [PATCH 04/13] Test default driver --- tests/Unit/IdentityServiceProviderTest.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Unit/IdentityServiceProviderTest.php b/tests/Unit/IdentityServiceProviderTest.php index 8531855..7421d1d 100644 --- a/tests/Unit/IdentityServiceProviderTest.php +++ b/tests/Unit/IdentityServiceProviderTest.php @@ -11,12 +11,22 @@ use Illuminate\Foundation\Testing\DatabaseMigrations; use InvalidArgumentException; use Laravel\Socialite\Two\FacebookProvider; +use Oneofftech\Identities\Providers\IdentitiesServiceProvider; use SocialiteProviders\GitLab\Provider as GitlabSocialiteProvider; class IdentityServiceProviderTest extends TestCase { use DatabaseMigrations; + public function test_default_driver_cannot_not_configured() + { + $factory = $this->app->make(IdentitiesManager::class); + + $this->expectException(InvalidArgumentException::class); + + $factory->redirect(); + } + public function test_it_can_instantiate_the_gitlab_driver() { $factory = $this->app->make(IdentitiesManager::class); @@ -101,4 +111,11 @@ public function test_facade_return_manager_instance() { $this->assertInstanceOf(IdentitiesManager::class, Identity::getFacadeRoot()); } + + public function test_provider_lists_provided_services() + { + $provides = (new IdentitiesServiceProvider($this->app))->provides(); + + $this->assertEquals([IdentitiesManager::class], $provides); + } } From 5a1328575ba4a4a772f533784702442e9b562711 Mon Sep 17 00:00:00 2001 From: Alessio Date: Sun, 8 Nov 2020 20:17:33 +0100 Subject: [PATCH 05/13] Test find user by id --- src/Facades/Identity.php | 3 ++- tests/Unit/IdentityTraitTest.php | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Facades/Identity.php b/src/Facades/Identity.php index 8796a4d..87e4eab 100644 --- a/src/Facades/Identity.php +++ b/src/Facades/Identity.php @@ -93,7 +93,8 @@ public static function events() */ public static function findUserByIdOrFail(string $id) { - return static::newUserModel()->where('id', $id)->firstOrFail(); + $model = static::newUserModel(); + return $model->where($model->getKeyName(), $id)->firstOrFail(); } /** diff --git a/tests/Unit/IdentityTraitTest.php b/tests/Unit/IdentityTraitTest.php index 517eaf7..4c0c8a4 100644 --- a/tests/Unit/IdentityTraitTest.php +++ b/tests/Unit/IdentityTraitTest.php @@ -132,4 +132,24 @@ public function getAvatar() $this->assertNull($user); } + + public function test_find_user() + { + IdentityFacade::useNamespace("Tests\\Fixtures\\"); + IdentityFacade::useIdentityModel("Tests\\Fixtures\\Identity"); + IdentityFacade::useUserModel("Tests\\Fixtures\\User"); + + $expectedUser = tap((new User), function ($u) { + $u->forceFill([ + 'email' => 'user@local.local', + 'name' => 'User', + 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', + 'remember_token' => Str::random(10), + ])->save(); + }); + + $user = IdentityFacade::findUserByIdOrFail($expectedUser->getKey()); + + $this->assertTrue($expectedUser->is($user)); + } } From 3b374c13cd88b6ddec3501115307df657b00ea5e Mon Sep 17 00:00:00 2001 From: Alessio Date: Sun, 8 Nov 2020 21:54:45 +0100 Subject: [PATCH 06/13] Base test for ui:identities command --- .../ScaffoldAuthenticationControllersTest.php | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/Unit/ScaffoldAuthenticationControllersTest.php diff --git a/tests/Unit/ScaffoldAuthenticationControllersTest.php b/tests/Unit/ScaffoldAuthenticationControllersTest.php new file mode 100644 index 0000000..a8ada24 --- /dev/null +++ b/tests/Unit/ScaffoldAuthenticationControllersTest.php @@ -0,0 +1,48 @@ + true]); + + $this->assertEquals(0, $exit); + + $this->assertStringContainsString('\Oneofftech\Identities\Facades\Identity::routes();', file_get_contents($webRoutesFile)); + + $files = [ + base_path('database/migrations/2020_08_09_115707_create_identities_table.php'), + app_path('Identity.php'), + app_path('Http/Controllers/Identities/Auth/ConnectController.php'), + app_path('Http/Controllers/Identities/Auth/LoginController.php'), + app_path('Http/Controllers/Identities/Auth/RegisterController.php'), + ]; + + foreach ($files as $file) { + $exists = File::exists($file); + + $this->assertTrue($exists, "Expected [$file] do not exists."); + + if ($exists) { + unlink($file); + } + } + } +} From 3dd03ca05daf77960b838a5eef8f3bf9261c307a Mon Sep 17 00:00:00 2001 From: Alessio Date: Mon, 9 Nov 2020 21:09:22 +0100 Subject: [PATCH 07/13] Add redirect to provider tests --- tests/TestCase.php | 15 ++++-- tests/Unit/ConnectControllerTest.php | 58 +++++++++++++++++++++++ tests/Unit/LoginControllerTest.php | 27 +++++++++++ tests/Unit/RegistrationControllerTest.php | 27 +++++++++++ 4 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 tests/Unit/ConnectControllerTest.php create mode 100644 tests/Unit/LoginControllerTest.php create mode 100644 tests/Unit/RegistrationControllerTest.php diff --git a/tests/TestCase.php b/tests/TestCase.php index e6952ed..ba096f6 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -23,7 +23,9 @@ public function setUp(): void { parent::setUp(); - $this->setUpDatabase($this->app); + $this->setUpDatabase(); + + $this->setUpSession($this->app); $this->activateSocialiteExtensions(); } @@ -75,10 +77,7 @@ protected function getPackageProviders($app) ]; } - /** - * @param \Illuminate\Foundation\Application $app - */ - protected function setUpDatabase($app) + protected function setUpDatabase() { $this->loadLaravelMigrations(); @@ -89,6 +88,12 @@ protected function setUpDatabase($app) IdentityFacade::useUserModel("App\\User"); } + protected function setUpSession() + { + $kernel = $this->app->make('Illuminate\Contracts\Http\Kernel'); + $kernel->pushMiddleware('Illuminate\Session\Middleware\StartSession'); + } + protected function activateSocialiteExtensions() { $socialiteWasCalled = $this->app->make(SocialiteWasCalled::class); diff --git a/tests/Unit/ConnectControllerTest.php b/tests/Unit/ConnectControllerTest.php new file mode 100644 index 0000000..2e46e5b --- /dev/null +++ b/tests/Unit/ConnectControllerTest.php @@ -0,0 +1,58 @@ +app->make('router'); + + $router->get('login', function () { + })->name('login'); + + $this->withoutExceptionHandling(); + + $this->expectException(AuthenticationException::class); + $this->expectExceptionMessage('Unauthenticated'); + + $this->get(route('oneofftech::connect.provider', ['provider' => 'gitlab'])); + } + + public function test_redirect_to_provider() + { + IdentityFacade::useNamespace('Tests\\Fixtures'); + IdentityFacade::routes(); + + $user = tap((new User()), function ($u) { + $u->forceFill([ + 'email' => 'user@local.local', + 'name' => 'User', + 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', + 'remember_token' => Str::random(10), + ])->save(); + }); + + $response = $this->actingAs($user) + ->get(route('oneofftech::connect.provider', ['provider' => 'gitlab'])); + + $response->assertRedirect(); + + $location = urldecode($response->headers->get('Location')); + + $this->assertStringContainsString('gitlab.com', $location); + $this->assertStringContainsString(route('oneofftech::connect.callback', ['provider' => 'gitlab']), $location); + } +} diff --git a/tests/Unit/LoginControllerTest.php b/tests/Unit/LoginControllerTest.php new file mode 100644 index 0000000..3458228 --- /dev/null +++ b/tests/Unit/LoginControllerTest.php @@ -0,0 +1,27 @@ +get(route('oneofftech::login.provider', ['provider' => 'gitlab'])); + + $response->assertRedirect(); + + $location = urldecode($response->headers->get('Location')); + + $this->assertStringContainsString('gitlab.com', $location); + $this->assertStringContainsString(route('oneofftech::login.callback', ['provider' => 'gitlab']), $location); + } +} diff --git a/tests/Unit/RegistrationControllerTest.php b/tests/Unit/RegistrationControllerTest.php new file mode 100644 index 0000000..82e4b6b --- /dev/null +++ b/tests/Unit/RegistrationControllerTest.php @@ -0,0 +1,27 @@ +get(route('oneofftech::register.provider', ['provider' => 'gitlab'])); + + $response->assertRedirect(); + + $location = urldecode($response->headers->get('Location')); + + $this->assertStringContainsString('gitlab.com', $location); + $this->assertStringContainsString(route('oneofftech::register.callback', ['provider' => 'gitlab']), $location); + } +} From 88cca0073ba6de00139587e1993aeef69c690b5e Mon Sep 17 00:00:00 2001 From: Alessio Date: Tue, 10 Nov 2020 23:07:25 +0100 Subject: [PATCH 08/13] Registration test --- tests/Fixtures/User.php | 6 +++ tests/Unit/RegistrationControllerTest.php | 52 +++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/tests/Fixtures/User.php b/tests/Fixtures/User.php index f538aa3..2c062da 100644 --- a/tests/Fixtures/User.php +++ b/tests/Fixtures/User.php @@ -8,4 +8,10 @@ class User extends Authenticatable { use WithIdentities; + + protected $fillable = [ + 'name', + 'email', + 'password', + ]; } diff --git a/tests/Unit/RegistrationControllerTest.php b/tests/Unit/RegistrationControllerTest.php index 82e4b6b..ecc6040 100644 --- a/tests/Unit/RegistrationControllerTest.php +++ b/tests/Unit/RegistrationControllerTest.php @@ -4,7 +4,12 @@ use Tests\TestCase; use Illuminate\Foundation\Testing\RefreshDatabase; +use SocialiteProviders\GitLab\Provider; +use Mockery; use Oneofftech\Identities\Facades\Identity as IdentityFacade; +use Oneofftech\Identities\Facades\IdentityCrypt; +use SocialiteProviders\Manager\OAuth2\User as OauthUser; +use Tests\Fixtures\User; class RegistrationControllerTest extends TestCase { @@ -24,4 +29,51 @@ public function test_redirect_to_provider() $this->assertStringContainsString('gitlab.com', $location); $this->assertStringContainsString(route('oneofftech::register.callback', ['provider' => 'gitlab']), $location); } + + public function test_callback() + { + IdentityFacade::useIdentityModel('Tests\\Fixtures\\Identity'); + IdentityFacade::useNamespace('Tests\\Fixtures'); + IdentityFacade::routes(); + + $this->withoutExceptionHandling(); + + $driverMock = Mockery::mock(Provider::class)->makePartial(); + + $oauthFakeUser = (new OauthUser())->map([ + 'id' => 'U1', + 'nickname' => 'User', + 'name' => 'User', + 'email' => 'user@local.com', + 'avatar' => 'https://gitlab.com', + 'token' => 'T1', + ]); + + $driverMock->shouldReceive('user')->andReturn($oauthFakeUser); + + $driverMock->shouldReceive('redirectUrl')->andReturn($driverMock); + + IdentityFacade::shouldReceive('driver')->with('gitlab')->andReturn($driverMock); + + $response = $this->get(route('oneofftech::register.callback', ['provider' => 'gitlab'])); + + $response->assertRedirect('http://localhost/home'); + + $user = User::first(); + + $this->assertNotNull($user); + $this->assertEquals('User', $user->name); + $this->assertEquals('user@local.com', $user->email); + + $linkedIdentities = $user->identities; + + $this->assertNotNull($linkedIdentities); + $this->assertEquals(1, $linkedIdentities->count()); + + $firstIdentity = $linkedIdentities->first(); + + $this->assertEquals(IdentityCrypt::hash('U1'), $firstIdentity->provider_id); + $this->assertEquals('gitlab', $firstIdentity->provider); + $this->assertNotNull($firstIdentity->token); + } } From 0f660ddad5e72d8aba1e9261f5e38317670daad0 Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 12 Nov 2020 20:48:42 +0100 Subject: [PATCH 09/13] Complete user registration tests --- tests/Fixtures/Concern/UseTestFixtures.php | 16 +++++++++ tests/TestCase.php | 27 +++++++++++++++ tests/Unit/RegistrationControllerTest.php | 39 ++++++++++++++++++---- 3 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 tests/Fixtures/Concern/UseTestFixtures.php diff --git a/tests/Fixtures/Concern/UseTestFixtures.php b/tests/Fixtures/Concern/UseTestFixtures.php new file mode 100644 index 0000000..3c2ad7b --- /dev/null +++ b/tests/Fixtures/Concern/UseTestFixtures.php @@ -0,0 +1,16 @@ +activateSocialiteExtensions(); } + protected function setUpTraits() + { + parent::setUpTraits(); + + $uses = \array_flip(\class_uses_recursive(static::class)); + + if (isset($uses[UseTestFixtures::class])) { + $this->useTestFixtures(); + } + + return $uses; + } + /** * Define environment setup. * @@ -119,4 +134,16 @@ public function assertListenerIsAttachedToEvent($listener, $event) $this->assertTrue(false, sprintf('Event %s does not have the %s listener attached to it', $event, $listener)); } + + public function createUser($data = []) + { + return tap((new User), function ($u) use ($data) { + $u->forceFill(array_merge([ + 'email' => 'user@local.com', + 'name' => 'User', + 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', + 'remember_token' => Str::random(10), + ], $data))->save(); + }); + } } diff --git a/tests/Unit/RegistrationControllerTest.php b/tests/Unit/RegistrationControllerTest.php index ecc6040..0bc7587 100644 --- a/tests/Unit/RegistrationControllerTest.php +++ b/tests/Unit/RegistrationControllerTest.php @@ -10,16 +10,15 @@ use Oneofftech\Identities\Facades\IdentityCrypt; use SocialiteProviders\Manager\OAuth2\User as OauthUser; use Tests\Fixtures\User; +use Illuminate\Validation\ValidationException; +use Tests\Fixtures\Concern\UseTestFixtures; class RegistrationControllerTest extends TestCase { - use RefreshDatabase; + use RefreshDatabase, UseTestFixtures; public function test_redirect_to_provider() { - IdentityFacade::useNamespace('Tests\\Fixtures'); - IdentityFacade::routes(); - $response = $this->get(route('oneofftech::register.provider', ['provider' => 'gitlab'])); $response->assertRedirect(); @@ -30,11 +29,9 @@ public function test_redirect_to_provider() $this->assertStringContainsString(route('oneofftech::register.callback', ['provider' => 'gitlab']), $location); } - public function test_callback() + public function test_user_can_be_registered() { IdentityFacade::useIdentityModel('Tests\\Fixtures\\Identity'); - IdentityFacade::useNamespace('Tests\\Fixtures'); - IdentityFacade::routes(); $this->withoutExceptionHandling(); @@ -76,4 +73,32 @@ public function test_callback() $this->assertEquals('gitlab', $firstIdentity->provider); $this->assertNotNull($firstIdentity->token); } + + public function test_user_cannot_register_twice() + { + $this->createUser(); + + $this->withoutExceptionHandling(); + + $driverMock = Mockery::mock(Provider::class)->makePartial(); + + $oauthFakeUser = (new OauthUser())->map([ + 'id' => 'U1', + 'nickname' => 'User', + 'name' => 'User', + 'email' => 'user@local.com', + 'avatar' => 'https://gitlab.com', + 'token' => 'T1', + ]); + + $driverMock->shouldReceive('user')->andReturn($oauthFakeUser); + + $driverMock->shouldReceive('redirectUrl')->andReturn($driverMock); + + IdentityFacade::shouldReceive('driver')->with('gitlab')->andReturn($driverMock); + + $this->expectException(ValidationException::class); + + $this->get(route('oneofftech::register.callback', ['provider' => 'gitlab'])); + } } From 90823a17ac87f4cbb45f85707f18ad2e3803cba8 Mon Sep 17 00:00:00 2001 From: Alessio Date: Thu, 12 Nov 2020 21:08:57 +0100 Subject: [PATCH 10/13] Code style --- tests/Unit/RegistrationControllerTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Unit/RegistrationControllerTest.php b/tests/Unit/RegistrationControllerTest.php index 0bc7587..c6021a9 100644 --- a/tests/Unit/RegistrationControllerTest.php +++ b/tests/Unit/RegistrationControllerTest.php @@ -2,16 +2,16 @@ namespace Tests\Unit; +use Mockery; use Tests\TestCase; -use Illuminate\Foundation\Testing\RefreshDatabase; +use Tests\Fixtures\User; use SocialiteProviders\GitLab\Provider; -use Mockery; -use Oneofftech\Identities\Facades\Identity as IdentityFacade; +use Tests\Fixtures\Concern\UseTestFixtures; +use Illuminate\Validation\ValidationException; use Oneofftech\Identities\Facades\IdentityCrypt; +use Illuminate\Foundation\Testing\RefreshDatabase; +use Oneofftech\Identities\Facades\Identity as IdentityFacade; use SocialiteProviders\Manager\OAuth2\User as OauthUser; -use Tests\Fixtures\User; -use Illuminate\Validation\ValidationException; -use Tests\Fixtures\Concern\UseTestFixtures; class RegistrationControllerTest extends TestCase { From 7f53ab687b8d9cb6a71842cb6908c0cf903335f0 Mon Sep 17 00:00:00 2001 From: Alessio Date: Fri, 13 Nov 2020 10:37:48 +0100 Subject: [PATCH 11/13] Focus on encrypt/decrypt strings --- tests/Unit/EncrypterTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Unit/EncrypterTest.php b/tests/Unit/EncrypterTest.php index 02d8dfc..a3841d1 100644 --- a/tests/Unit/EncrypterTest.php +++ b/tests/Unit/EncrypterTest.php @@ -20,9 +20,9 @@ public function test_value_encrypted_with_old_key_can_be_decrypted() { $encrypter = new Encrypter(config('identities.old_key'), 'AES-256-CBC'); - $encryptedWithOldKey = $encrypter->encrypt('test'); + $encryptedWithOldKey = $encrypter->encryptString('test'); - $decrypted = IdentityCrypt::decrypt($encryptedWithOldKey); + $decrypted = IdentityCrypt::decryptString($encryptedWithOldKey); $this->assertEquals('test', $decrypted); } @@ -33,18 +33,18 @@ public function test_value_encrypted_with_old_base64_key_can_be_decrypted() $this->app['config']->set('identities.old_key', 'base64:'.base64_encode($key)); $encrypter = new Encrypter($key, 'AES-256-CBC'); - $encryptedWithOldKey = $encrypter->encrypt('test'); + $encryptedWithOldKey = $encrypter->encryptString('test'); - $decrypted = IdentityCrypt::decrypt($encryptedWithOldKey); + $decrypted = IdentityCrypt::decryptString($encryptedWithOldKey); $this->assertEquals('test', $decrypted); } public function test_value_can_be_encrypted() { - $encrypted = IdentityCrypt::encrypt('test'); + $encrypted = IdentityCrypt::encryptString('test'); - $decrypted = IdentityCrypt::decrypt($encrypted); + $decrypted = IdentityCrypt::decryptString($encrypted); $this->assertEquals('test', $decrypted); } From e097d6d95ab087a54320e51dea6b999fb6d433d3 Mon Sep 17 00:00:00 2001 From: Alessio Date: Fri, 13 Nov 2020 10:40:01 +0100 Subject: [PATCH 12/13] Login tests --- tests/Unit/LoginControllerTest.php | 90 +++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/tests/Unit/LoginControllerTest.php b/tests/Unit/LoginControllerTest.php index 3458228..75d91a7 100644 --- a/tests/Unit/LoginControllerTest.php +++ b/tests/Unit/LoginControllerTest.php @@ -2,13 +2,19 @@ namespace Tests\Unit; +use Mockery; use Tests\TestCase; +use SocialiteProviders\GitLab\Provider; +use Tests\Fixtures\Concern\UseTestFixtures; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Validation\ValidationException; +use SocialiteProviders\Manager\OAuth2\User as OauthUser; use Oneofftech\Identities\Facades\Identity as IdentityFacade; +use Oneofftech\Identities\Facades\IdentityCrypt; class LoginControllerTest extends TestCase { - use RefreshDatabase; + use RefreshDatabase, UseTestFixtures; public function test_redirect_to_provider() { @@ -24,4 +30,86 @@ public function test_redirect_to_provider() $this->assertStringContainsString('gitlab.com', $location); $this->assertStringContainsString(route('oneofftech::login.callback', ['provider' => 'gitlab']), $location); } + + public function test_user_login() + { + $this->useTestFixtures(); + + $user = $this->createUser(); + + $identity = $user->identities()->create([ + 'provider'=> 'gitlab', + 'provider_id'=> IdentityCrypt::hash('U1'), + 'token'=> 'T1', + 'refresh_token'=> null, + 'expires_at'=> null, + 'registration' => true, + ]); + + $this->withoutExceptionHandling(); + + $driverMock = Mockery::mock(Provider::class)->makePartial(); + + $oauthFakeUser = (new OauthUser())->map([ + 'id' => 'U1', + 'nickname' => 'User', + 'name' => 'User', + 'email' => 'user@local.com', + 'avatar' => 'https://gitlab.com', + 'token' => 'T1', + ]); + + $driverMock->shouldReceive('user')->andReturn($oauthFakeUser); + + $driverMock->shouldReceive('redirectUrl')->andReturn($driverMock); + + IdentityFacade::shouldReceive('driver')->with('gitlab')->andReturn($driverMock); + + $response = $this->get(route('oneofftech::login.callback', ['provider' => 'gitlab'])); + + $response->assertRedirect('http://localhost/home'); + + $this->assertAuthenticatedAs($user); + } + + public function test_user_login_denied_if_identity_cannot_be_found() + { + $this->useTestFixtures(); + + $user = $this->createUser(); + + $identity = $user->identities()->create([ + 'provider'=> 'facebook', + 'provider_id'=> IdentityCrypt::hash('U2'), + 'token'=> 'T1', + 'refresh_token'=> null, + 'expires_at'=> null, + 'registration' => true, + ]); + + $this->withoutExceptionHandling(); + + $driverMock = Mockery::mock(Provider::class)->makePartial(); + + $oauthFakeUser = (new OauthUser())->map([ + 'id' => 'U1', + 'nickname' => 'User', + 'name' => 'User', + 'email' => 'user@local.com', + 'avatar' => 'https://gitlab.com', + 'token' => 'T1', + ]); + + $driverMock->shouldReceive('user')->andReturn($oauthFakeUser); + + $driverMock->shouldReceive('redirectUrl')->andReturn($driverMock); + + IdentityFacade::shouldReceive('driver')->with('gitlab')->andReturn($driverMock); + + $this->expectException(ValidationException::class); + + $response = $this->get(route('oneofftech::login.callback', ['provider' => 'gitlab'])); + + $this->assertGuest(); + } } From e8f0e2f21a12d6b5fec0f764f6e5fca6c4acb948 Mon Sep 17 00:00:00 2001 From: Alessio Date: Fri, 13 Nov 2020 10:40:42 +0100 Subject: [PATCH 13/13] Connect identity test --- tests/Unit/ConnectControllerTest.php | 109 ++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 4 deletions(-) diff --git a/tests/Unit/ConnectControllerTest.php b/tests/Unit/ConnectControllerTest.php index 2e46e5b..9e9e4d1 100644 --- a/tests/Unit/ConnectControllerTest.php +++ b/tests/Unit/ConnectControllerTest.php @@ -2,16 +2,22 @@ namespace Tests\Unit; -use Illuminate\Auth\AuthenticationException; +use Mockery; +use Carbon\Carbon; use Tests\TestCase; -use Illuminate\Foundation\Testing\RefreshDatabase; -use Oneofftech\Identities\Facades\Identity as IdentityFacade; use Tests\Fixtures\User; use Illuminate\Support\Str; +use SocialiteProviders\GitLab\Provider; +use Tests\Fixtures\Concern\UseTestFixtures; +use Illuminate\Auth\AuthenticationException; +use Oneofftech\Identities\Facades\IdentityCrypt; +use Illuminate\Foundation\Testing\RefreshDatabase; +use SocialiteProviders\Manager\OAuth2\User as OauthUser; +use Oneofftech\Identities\Facades\Identity as IdentityFacade; class ConnectControllerTest extends TestCase { - use RefreshDatabase; + use RefreshDatabase, UseTestFixtures; public function test_redirect_to_provider_require_authentication() { @@ -55,4 +61,99 @@ public function test_redirect_to_provider() $this->assertStringContainsString('gitlab.com', $location); $this->assertStringContainsString(route('oneofftech::connect.callback', ['provider' => 'gitlab']), $location); } + + public function test_connect_creates_identity() + { + $this->useTestFixtures(); + + $user = $this->createUser(); + + $this->withoutExceptionHandling(); + + $driverMock = Mockery::mock(Provider::class)->makePartial(); + + Carbon::setTestNow(Carbon::create(2020, 11, 12, 10, 20)); + + $oauthFakeUser = (new OauthUser())->map([ + 'id' => 'U1', + 'nickname' => 'User', + 'name' => 'User', + 'email' => 'user@local.com', + 'avatar' => 'https://gitlab.com', + 'token' => 'T2', + 'refreshToken' => 'RT2', + 'expiresIn' => Carbon::SECONDS_PER_MINUTE, + ]); + + $driverMock->shouldReceive('user')->andReturn($oauthFakeUser); + + $driverMock->shouldReceive('redirectUrl')->andReturn($driverMock); + + IdentityFacade::shouldReceive('driver')->with('gitlab')->andReturn($driverMock); + + $response = $this->actingAs($user) + ->get(route('oneofftech::connect.callback', ['provider' => 'gitlab'])); + + $response->assertRedirect('http://localhost/home'); + + $updatedIdentity = $user->identities->first(); + + $this->assertEquals(IdentityCrypt::hash('U1'), $updatedIdentity->provider_id); + $this->assertEquals('gitlab', $updatedIdentity->provider); + $this->assertEquals(Carbon::create(2020, 11, 12, 10, 21), $updatedIdentity->expires_at); + $this->assertEquals('T2', IdentityCrypt::decryptString($updatedIdentity->token)); + $this->assertEquals('RT2', IdentityCrypt::decryptString($updatedIdentity->refresh_token)); + } + + public function test_connect_updates_existing_identity() + { + $this->useTestFixtures(); + + $user = $this->createUser(); + + $identity = $user->identities()->create([ + 'provider'=> 'gitlab', + 'provider_id'=> IdentityCrypt::hash('U1'), + 'token'=> 'T1', + 'refresh_token'=> null, + 'expires_at'=> null, + 'registration' => true, + ]); + + $this->withoutExceptionHandling(); + + $driverMock = Mockery::mock(Provider::class)->makePartial(); + + Carbon::setTestNow(Carbon::create(2020, 11, 12, 10, 20)); + + $oauthFakeUser = (new OauthUser())->map([ + 'id' => 'U1', + 'nickname' => 'User', + 'name' => 'User', + 'email' => 'user@local.com', + 'avatar' => 'https://gitlab.com', + 'token' => 'T2', + 'refreshToken' => 'RT2', + 'expiresIn' => Carbon::SECONDS_PER_MINUTE, + ]); + + $driverMock->shouldReceive('user')->andReturn($oauthFakeUser); + + $driverMock->shouldReceive('redirectUrl')->andReturn($driverMock); + + IdentityFacade::shouldReceive('driver')->with('gitlab')->andReturn($driverMock); + + $response = $this->actingAs($user) + ->get(route('oneofftech::connect.callback', ['provider' => 'gitlab'])); + + $response->assertRedirect('http://localhost/home'); + + $updatedIdentity = $user->identities->first(); + + $this->assertEquals($identity->provider_id, $updatedIdentity->provider_id); + $this->assertEquals('gitlab', $updatedIdentity->provider); + $this->assertEquals(Carbon::create(2020, 11, 12, 10, 21), $updatedIdentity->expires_at); + $this->assertEquals('T2', IdentityCrypt::decryptString($updatedIdentity->token)); + $this->assertEquals('RT2', IdentityCrypt::decryptString($updatedIdentity->refresh_token)); + } }