diff --git a/composer.json b/composer.json index 7a43af0..3e5d728 100644 --- a/composer.json +++ b/composer.json @@ -48,6 +48,7 @@ }, "require-dev": { "pestphp/pest": "^1.21", - "maximebf/debugbar": "^1.18" + "maximebf/debugbar": "^1.18", + "pestphp/pest-plugin-mock": "^1.0" } } diff --git a/composer.lock b/composer.lock index 307c37e..5f14d1d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5f371188cdd07e6642ae99b22a0738a1", + "content-hash": "cad54da11d2078295810812dfd09bddd", "packages": [ { "name": "abraham/twitteroauth", @@ -5019,6 +5019,57 @@ }, "time": "2020-10-16T08:27:54+00:00" }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + }, + "time": "2020-07-09T08:09:16+00:00" + }, { "name": "maximebf/debugbar", "version": "v1.18.0", @@ -5085,6 +5136,78 @@ }, "time": "2021-12-27T18:49:48+00:00" }, + { + "name": "mockery/mockery", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac", + "reference": "c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": "^7.3 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "issues": "https://github.com/mockery/mockery/issues", + "source": "https://github.com/mockery/mockery/tree/1.5.0" + }, + "time": "2022-01-20T13:18:17+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.11.0", @@ -5417,6 +5540,79 @@ ], "time": "2021-01-03T15:53:42+00:00" }, + { + "name": "pestphp/pest-plugin-mock", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-mock.git", + "reference": "c2dbca6cb85a870f2e666d01aca0a3fdca7d063c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-mock/zipball/c2dbca6cb85a870f2e666d01aca0a3fdca7d063c", + "reference": "c2dbca6cb85a870f2e666d01aca0a3fdca7d063c", + "shasum": "" + }, + "require": { + "mockery/mockery": "^1.4", + "pestphp/pest": "^1.0", + "pestphp/pest-plugin": "^1.0", + "php": "^8.0" + }, + "require-dev": { + "pestphp/pest-dev-tools": "dev-master" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Mock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Adds mocking capabilities to Pest or PHPUnit", + "keywords": [ + "framework", + "mock", + "mocking", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "issues": "https://github.com/pestphp/pest-plugin-mock/issues", + "source": "https://github.com/pestphp/pest-plugin-mock/tree/v1.0.3" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2021-06-10T18:04:11+00:00" + }, { "name": "phar-io/manifest", "version": "2.0.3", diff --git a/db/seeds/UserSeeder.php b/db/seeds/UserSeeder.php index 91e2052..897977c 100644 --- a/db/seeds/UserSeeder.php +++ b/db/seeds/UserSeeder.php @@ -1,6 +1,4 @@ makePartial(); + $githubAuthentication->shouldReceive( 'getUsersWhere')->andReturn(new Collection(['testuser'])); + + $githubAuthentication->setEmail('testuser@website.test'); + expect($githubAuthentication->authenticate() instanceof Result)->toBeTrue(); + expect($githubAuthentication->authenticate()->isValid())->toBeTrue(); +}); diff --git a/tests/Unit/Auth/Adapter/LinkedinAuthenticationTest.php b/tests/Unit/Auth/Adapter/LinkedinAuthenticationTest.php new file mode 100644 index 0000000..740aff5 --- /dev/null +++ b/tests/Unit/Auth/Adapter/LinkedinAuthenticationTest.php @@ -0,0 +1,16 @@ +makePartial(); + $linkedinAuthentication->shouldReceive( 'getUsersWhere')->andReturn(new Collection(['testuser'])); + + $linkedinAuthentication->setEmail('testuser@site.test'); + expect($linkedinAuthentication->authenticate() instanceof Result)->toBeTrue(); + expect($linkedinAuthentication->authenticate()->isValid())->toBeTrue(); +}); diff --git a/tests/Unit/Auth/Adapter/TwitterAuthenticationTest.php b/tests/Unit/Auth/Adapter/TwitterAuthenticationTest.php new file mode 100644 index 0000000..5b7b843 --- /dev/null +++ b/tests/Unit/Auth/Adapter/TwitterAuthenticationTest.php @@ -0,0 +1,16 @@ +makePartial(); + $twitterAuth->shouldReceive( 'getUsersWhere')->andReturn(new Collection(['testuser'])); + + $twitterAuth->setUsername('testuser'); + expect($twitterAuth->authenticate() instanceof Result)->toBeTrue(); + expect($twitterAuth->authenticate()->isValid())->toBeTrue(); +}); diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php deleted file mode 100644 index 61cd84c..0000000 --- a/tests/Unit/ExampleTest.php +++ /dev/null @@ -1,5 +0,0 @@ -toBeTrue(); -}); diff --git a/tests/Unit/Support/SessionTest.php b/tests/Unit/Support/SessionTest.php new file mode 100644 index 0000000..255606b --- /dev/null +++ b/tests/Unit/Support/SessionTest.php @@ -0,0 +1,19 @@ +expect( + getAttribute: function ($attribute) { + // We only return a Helper instance if 'session' is passed + return ($attribute === 'session') ? new Helper : false; + } + ); + + $session = Session::from($request); + + expect($session instanceof Helper)->toBeTrue(); +}); diff --git a/tests/Unit/Support/UserFactoryTest.php b/tests/Unit/Support/UserFactoryTest.php new file mode 100644 index 0000000..a70352d --- /dev/null +++ b/tests/Unit/Support/UserFactoryTest.php @@ -0,0 +1,60 @@ +expect( + getId: fn () => 1, + getEmail: fn () => 'my@email.test', + getFirstName: fn () => 'User', + getLastName: fn () => 'Name' + ); + + $user = UserFactory::fromOauthResourceOwner($resourceOwner, 'linkedin'); + + expect($user['id'] === 1)->toBeTrue(); + expect($user['email'] === 'my@email.test')->toBeTrue(); + expect($user['first_name'] === 'User')->toBeTrue(); + expect($user['last_name'] === 'Name')->toBeTrue(); + expect($user['username'] === 'User Name')->toBeTrue(); + expect($user['full_name'] === 'User Name')->toBeTrue(); +}); + +test('UserFactory creates user correctly when passed GithubResourceOwner', function () { + $resourceOwner = mock(\League\OAuth2\Client\Provider\GithubResourceOwner::class) + ->expect( + getId: fn () => 1, + getNickName: fn () => 'username', + getEmail: fn () => 'my@email.test', + getName: fn () => 'User Name' + ); + + $user = UserFactory::fromOauthResourceOwner($resourceOwner, 'github'); + + expect($user['id'] === 1)->toBeTrue(); + expect($user['email'] === 'my@email.test')->toBeTrue(); + expect($user['first_name'] === 'User')->toBeTrue(); + expect($user['last_name'] === 'Name')->toBeTrue(); + expect($user['username'] === 'username')->toBeTrue(); + expect($user['full_name'] === 'User Name')->toBeTrue(); +}); + +test('UserFactory creates user correctly when passed TwitterResourceOwner', function () { + $resourceOwner = mock(\Noesis\OAuth2\Client\Provider\TwitterResourceOwner::class) + ->expect( + getId: fn () => 1, + getUsername: fn () => 'username', + getEmail: fn () => 'my@email.test', + getName: fn () => 'User Name' + ); + + $user = UserFactory::fromOauthResourceOwner($resourceOwner, 'twitter'); + + expect($user['id'] === 1)->toBeTrue(); + expect($user['email'] === 'my@email.test')->toBeTrue(); + expect($user['first_name'] === 'User')->toBeTrue(); + expect($user['last_name'] === 'Name')->toBeTrue(); + expect($user['username'] === 'username')->toBeTrue(); + expect($user['full_name'] === 'User Name')->toBeTrue(); +}); diff --git a/tests/Unit/Support/UserTest.php b/tests/Unit/Support/UserTest.php new file mode 100644 index 0000000..de51308 --- /dev/null +++ b/tests/Unit/Support/UserTest.php @@ -0,0 +1,14 @@ +expect( + getAttribute: fn ($attribute) => (object) ['attribute' => $attribute] + ); + + $user = User::from($request); + + expect($user->attribute === 'User')->toBeTrue(); +}); diff --git a/tests/Unit/TwitterTest.php b/tests/Unit/TwitterTest.php deleted file mode 100644 index 0390f67..0000000 --- a/tests/Unit/TwitterTest.php +++ /dev/null @@ -1,181 +0,0 @@ -provider = new TwitterProvider([ - 'clientId' => 'mock_client_id', - 'clientSecret' => 'mock_secret', - 'redirectUri' => 'none', - 'pkceVerifier' => 'ENuF7brJJNM5v-dEROtJf.Uee3kTO-GqNQ33fyuY33oixZXo9Vxiomml8-~3ulU9xu4xr_rj1weIer9UYu1JEzK_ZuDUtXe-zHi_2b6Eu41c~HEhzIlV6_QOQWeuvlyh', - ]); - } - - /** - * @link https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code - */ - public function testSmipleAuthorizationUrl(): void - { - $url = $this->provider->getAuthorizationUrl(); - $uri = parse_url($url); - parse_str($uri['query'], $query); - - self::assertArrayHasKey('response_type', $query); - self::assertArrayHasKey('client_id', $query); - self::assertArrayHasKey('redirect_uri', $query); - self::assertArrayHasKey('state', $query); - self::assertArrayHasKey('code_challenge', $query); - self::assertArrayHasKey('code_challenge_method', $query); - - self::assertEquals('code', $query['response_type']); - self::assertEquals('mock_client_id', $query['client_id']); - self::assertEquals('none', $query['redirect_uri']); - self::assertEquals('Q7tD_xw-1L6mtr1RgNQ6-ZHCqA2mRg8_5_OqERLrJtE', $query['code_challenge']); - self::assertEquals('S256', $query['code_challenge_method']); - - self::assertStringContainsString('tweet.read', $query['scope']); - self::assertStringContainsString('users.read', $query['scope']); - self::assertStringContainsString('offline.access', $query['scope']); - - self::assertNotEmpty($this->provider->getState()); - } - - public function testBaseAccessTokenUrl(): void - { - $url = $this->provider->getBaseAccessTokenUrl([]); - $uri = parse_url($url); - - self::assertEquals('/2/oauth2/token', $uri['path']); - } - - public function testResourceOwnerDetailsUrl(): void - { - $token = $this->mockAccessToken(); - - $url = $this->provider->getResourceOwnerDetailsUrl($token); - - self::assertEquals('https://api.twitter.com/2/users/me', $url); - } - - public function testUserData(): void - { - // Mock - $response = [ - "data" => [ - "id" => "1132750396936589312", - "name" => "Smolblog", - "username" => "_smolblog", - ] - ]; - - $token = $this->mockAccessToken(); - - $provider = Phony::partialMock(TwitterProvider::class); - $provider->fetchResourceOwnerDetails->returns($response); - $google = $provider->get(); - - // Execute - $user = $google->getResourceOwner($token); - - // Verify - Phony::inOrder( - $provider->fetchResourceOwnerDetails->called() - ); - - self::assertInstanceOf(ResourceOwnerInterface::class, $user); - - self::assertEquals(1132750396936589312, $user->getId()); - self::assertEquals('Smolblog', $user->getName()); - self::assertEquals('_smolblog', $user->getUsername()); - - $user = $user->toArray(); - - self::assertArrayHasKey('id', $user); - self::assertArrayHasKey('name', $user); - self::assertArrayHasKey('username', $user); - } - - public function testErrorResponse(): void - { - // Mock - $error_json = '{ - "title": "Unauthorized", - "type": "about:blank", - "status": 401, - "detail": "Unauthorized" - }'; - - $stream = Phony::mock('GuzzleHttp\Psr7\Stream'); - $stream->__toString->returns($error_json); - - $response = Phony::mock('GuzzleHttp\Psr7\Response'); - $response->getHeader->returns(['application/json']); - $response->getBody->returns($stream); - - $provider = Phony::partialMock(TwitterProvider::class); - $provider->getResponse->returns($response); - - $google = $provider->get(); - - $token = $this->mockAccessToken(); - - // Expect - $this->expectException(IdentityProviderException::class); - - // Execute - $user = $google->getResourceOwner($token); - - // Verify - Phony::inOrder( - $provider->getResponse->calledWith($this->instanceOf('GuzzleHttp\Psr7\Request')), - $response->getHeader->called(), - $response->getBody->called() - ); - } - - public function testVerifierGeneration(): void { - $verifier = $this->provider->generatePkceVerifier(); - $match_result = preg_match('/^[A-Za-z0-9\-._~]{43,128}$/', $verifier); - - self::assertEquals(1, $match_result); - } - - public function testChallengeGeneration(): void { - $tests = [ - 'g0sseWY2Gp772L_Xu7T1tHkeqRGAOk_9JnU9gFYCmKkVbkFUHu5izyZEivpxDsZU-r40geolIbX64zEvQ7Y4SOYwKL9drG9OF2g1kTB.PJ7nHPbVLFJFL-ziSv6KclSK' - => 'hzRLCtPmWN3w_EVqGW19ARrMaXZBwYrpnTMkelrYIv4', - 'd_O4i_N0nDZdsjl6JGE.vYoIi-Yr8lXcEYWUKXbjwojf8VtMaTmOSwJJYQ5n5NYz2BrdKSQFkLei3sSzP0dygP8vUkH3rP-dEBl9l5rvFAUXtjsTXUusxwRTisOUPe~Y' - => 'Lk5oLe4qImaZKgQbT4ICB9rfD5Hy4ozjydlCP_9nPlo', - 'H5MmPYr8-j.GHXGzaN.Ck8LFh-kmeK_Q6xgUZfOSYkYJHKObUJgtP0xcLCkAySnMBQ~-L-RUUfdNr7r2kT1-9Mpabf5wmoBbPRft.T8HFUiyuVCd4KcX2wRGfc1evspn' - => 'e5KT8_NuYwqcBGkdv3t1Wk-QnbozLkjSaFXKfvDp0nU', - 'D4R-xl8r_6slynxksZhCSbwj5fDB2Hdk8ZzfdW8iWqqbOx7A0oP_XCffIatxBR~J0JYAddxcpIBshuNOTxwUTXhm~24OZWAzmnn-s5FOnOK~mnetlfvDeH6cjhHg~H0-' - => 'NA7eMVS9lXYsvSWA1T2wFXfxNK8Yx-RttVo9iwmQ2FM', - 'Fk0SY30MvDDXCfwO8TiHz0cFADb3sP8-DqCDysiH7iY4NI_sVHW8Bbyl1sypVY61m4fGv4VzEX.ASdir4BRfcD..I70mINH~_L-g0_Y9xLXD9Di0fYu0psevbxm0yh~w' - => 'VPKX0gnLeTzjM-UJ5Mc5ZR5VGQzh8ukr_RbFzbfYJ30', - ]; - - foreach ($tests as $verifier => $expected) { - self::assertEquals($expected, $this->provider->generatePkceChallenge($verifier)); - } - } - - private function mockAccessToken(): AccessToken - { - return new AccessToken([ - 'access_token' => 'mock_access_token', - ]); - } -} \ No newline at end of file