Skip to content

Commit cbb4e9f

Browse files
Add tests for login controller (#7)
* Update LoginResponse to implement LoginResponseInterface * Updated test namespace * Overwritten OpenIDConnectClient redirect function to use Laravel abort method * Login tests * Add response test
1 parent 7fdff54 commit cbb4e9f

File tree

5 files changed

+323
-3
lines changed

5 files changed

+323
-3
lines changed

src/Http/Responses/LoginResponse.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44

55
namespace MinVWS\OpenIDConnectLaravel\Http\Responses;
66

7-
use Illuminate\Contracts\Support\Responsable;
87
use Illuminate\Http\JsonResponse;
98
use Illuminate\Http\Request;
109
use Symfony\Component\HttpFoundation\Response;
1110

12-
class LoginResponse implements Responsable
11+
class LoginResponse implements LoginResponseInterface
1312
{
1413
public function __construct(
1514
protected object $userInfo

src/OpenIDConnectClient.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace MinVWS\OpenIDConnectLaravel;
66

7+
use Illuminate\Support\Facades\App;
78
use Illuminate\Support\Facades\Session;
89
use Illuminate\Support\Str;
910
use Jumbojett\OpenIDConnectClient as BaseOpenIDConnectClient;
@@ -108,4 +109,16 @@ protected function getWellKnownConfigValue($param, $default = null): string|arra
108109

109110
return $config->{$param};
110111
}
112+
113+
/**
114+
* Overwrite the redirect method to use Laravel's abort method.
115+
* Sometimes the error 'Cannot modify header information - headers already sent' was thrown.
116+
* By using Laravel's abort method, this error is prevented.
117+
* @param string $url
118+
* @return void
119+
*/
120+
public function redirect($url): void
121+
{
122+
App::abort(302, '', ['Location' => $url]);
123+
}
111124
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MinVWS\OpenIDConnectLaravel\Tests\Feature\Http\Controllers;
6+
7+
use Illuminate\Support\Facades\Http;
8+
use MinVWS\OpenIDConnectLaravel\OpenIDConfiguration\OpenIDConfiguration;
9+
use MinVWS\OpenIDConnectLaravel\OpenIDConfiguration\OpenIDConfigurationLoader;
10+
use MinVWS\OpenIDConnectLaravel\OpenIDConnectClient;
11+
use MinVWS\OpenIDConnectLaravel\Tests\TestCase;
12+
use Mockery;
13+
14+
class LoginControllerTest extends TestCase
15+
{
16+
public function setUp(): void
17+
{
18+
parent::setUp();
19+
20+
// Support for running tests with Laravel 8
21+
if (method_exists(Http::class, 'preventStrayRequests')) {
22+
Http::preventStrayRequests();
23+
}
24+
}
25+
26+
public function testLoginRouteRedirectsToAuthorizeUrlOfProvider(): void
27+
{
28+
$this->mockOpenIDConfigurationLoader();
29+
30+
config()->set('oidc.client_id', 'test-client-id');
31+
32+
$response = $this->get(route('oidc.login'));
33+
$response
34+
->assertStatus(302)
35+
->assertRedirectContains("https://provider.rdobeheer.nl/authorize")
36+
->assertRedirectContains('test-client-id');
37+
}
38+
39+
public function testLoginRouteReturnsUserInfoWitchMockedClient(): void
40+
{
41+
$mockClient = Mockery::mock(OpenIDConnectClient::class);
42+
$mockClient
43+
->shouldReceive('authenticate')
44+
->once();
45+
46+
$mockClient
47+
->shouldReceive('requestUserInfo')
48+
->andReturn((object) [
49+
'sub' => 'test-sub',
50+
'name' => 'test-name',
51+
'email' => 'test-email',
52+
]);
53+
54+
$this->app->instance(OpenIDConnectClient::class, $mockClient);
55+
56+
$response = $this->get(route('oidc.login'));
57+
$response
58+
->assertJson([
59+
'userInfo' => [
60+
'sub' => 'test-sub',
61+
'name' => 'test-name',
62+
'email' => 'test-email',
63+
],
64+
]);
65+
}
66+
67+
protected function mockOpenIDConfigurationLoader(): void
68+
{
69+
$mock = Mockery::mock(OpenIDConfigurationLoader::class);
70+
$mock
71+
->shouldReceive('getConfiguration')
72+
->andReturn($this->exampleOpenIDConfiguration());
73+
74+
$this->app->instance(OpenIDConfigurationLoader::class, $mock);
75+
}
76+
77+
protected function exampleOpenIDConfiguration(): OpenIDConfiguration
78+
{
79+
return new OpenIDConfiguration(
80+
version: "3.0",
81+
tokenEndpointAuthMethodsSupported: ["none"],
82+
claimsParameterSupported: true,
83+
requestParameterSupported: false,
84+
requestUriParameterSupported: true,
85+
requireRequestUriRegistration: false,
86+
grantTypesSupported: ["authorization_code"],
87+
frontchannelLogoutSupported: false,
88+
frontchannelLogoutSessionSupported: false,
89+
backchannelLogoutSupported: false,
90+
backchannelLogoutSessionSupported: false,
91+
issuer: "https://provider.rdobeheer.nl",
92+
authorizationEndpoint: "https://provider.rdobeheer.nl/authorize",
93+
jwksUri: "https://provider.rdobeheer.nl/jwks",
94+
tokenEndpoint: "https://provider.rdobeheer.nl/token",
95+
scopesSupported: ["openid"],
96+
responseTypesSupported: ["code"],
97+
responseModesSupported: ["query"],
98+
subjectTypesSupported: ["pairwise"],
99+
idTokenSigningAlgValuesSupported: ["RS256"],
100+
userinfoEndpoint: "https://provider.rdobeheer.nl/userinfo",
101+
codeChallengeMethodsSupported: ["S256"],
102+
);
103+
}
104+
}

tests/Feature/OpenIDConfigurationLoaderTest.php renamed to tests/Feature/OpenIDConfiguration/OpenIDConfigurationLoaderTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace MinVWS\OpenIDConnectLaravel\Tests\Feature;
5+
namespace MinVWS\OpenIDConnectLaravel\Tests\Feature\OpenIDConfiguration;
66

77
use Illuminate\Support\Facades\Cache;
88
use Illuminate\Support\Facades\Http;
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MinVWS\OpenIDConnectLaravel\Tests\Unit\Http\Controllers;
6+
7+
use Exception;
8+
use Illuminate\Contracts\Support\Responsable;
9+
use Illuminate\Http\Request;
10+
use Jumbojett\OpenIDConnectClientException;
11+
use MinVWS\OpenIDConnectLaravel\Http\Controllers\LoginController;
12+
use MinVWS\OpenIDConnectLaravel\Http\Responses\LoginResponse;
13+
use MinVWS\OpenIDConnectLaravel\Http\Responses\LoginResponseInterface;
14+
use MinVWS\OpenIDConnectLaravel\OpenIDConnectClient;
15+
use MinVWS\OpenIDConnectLaravel\Services\JWE\JweDecryptException;
16+
use MinVWS\OpenIDConnectLaravel\Services\OpenIDConnectExceptionHandler;
17+
use Mockery;
18+
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
19+
use PHPUnit\Framework\TestCase;
20+
21+
class LoginControllerTest extends TestCase
22+
{
23+
use MockeryPHPUnitIntegration;
24+
25+
protected function setUp(): void
26+
{
27+
parent::setUp();
28+
29+
// Bind the LoginResponseInterface to the LoginResponse class
30+
app()->bind(LoginResponseInterface::class, LoginResponse::class);
31+
}
32+
33+
protected function tearDown(): void
34+
{
35+
// Flush so the LoginResponseInterface binding is removed
36+
app()->flush();
37+
38+
parent::tearDown();
39+
}
40+
41+
public function testLoginControllerCanBeCreated(): void
42+
{
43+
$loginController = new LoginController(
44+
new OpenIDConnectClient(),
45+
new OpenIDConnectExceptionHandler(),
46+
);
47+
$this->assertInstanceOf(LoginController::class, $loginController);
48+
}
49+
50+
public function testExceptionHandlerIsCalledWhenAuthenticateThrowsException(): void
51+
{
52+
$mockClient = Mockery::mock(OpenIDConnectClient::class);
53+
$mockClient
54+
->shouldReceive('authenticate')
55+
->andThrow(OpenIDConnectClientException::class);
56+
57+
$mockExceptionHandler = Mockery::mock(OpenIDConnectExceptionHandler::class);
58+
$mockExceptionHandler
59+
->shouldReceive('handleExceptionWhileAuthenticate')
60+
->once();
61+
62+
$loginController = new LoginController(
63+
$mockClient,
64+
$mockExceptionHandler,
65+
);
66+
67+
$loginController->__invoke();
68+
}
69+
70+
public function testExceptionHandlerIsCalledWhenRequestUserInfoDoesNotReturnAnObject(): void
71+
{
72+
$mockClient = Mockery::mock(OpenIDConnectClient::class);
73+
$mockClient->shouldReceive('authenticate')->once();
74+
$mockClient
75+
->shouldReceive('requestUserInfo')
76+
->andReturn('not an object')
77+
->once();
78+
79+
$mockExceptionHandler = Mockery::mock(OpenIDConnectExceptionHandler::class);
80+
$mockExceptionHandler
81+
->shouldReceive('handleExceptionWhileRequestUserInfo')
82+
->withArgs(function (OpenIDConnectClientException $e) {
83+
return $e->getMessage() === 'Received user info is not an object';
84+
})
85+
->once();
86+
87+
$loginController = new LoginController(
88+
$mockClient,
89+
$mockExceptionHandler,
90+
);
91+
92+
$loginController->__invoke();
93+
}
94+
95+
public function testExceptionHandlerIsCalledWhenRequestUserInfoThrowsAnException(): void
96+
{
97+
$mockClient = Mockery::mock(OpenIDConnectClient::class);
98+
$mockClient->shouldReceive('authenticate')->once();
99+
$mockClient
100+
->shouldReceive('requestUserInfo')
101+
->andThrow(OpenIDConnectClientException::class, 'Something went wrong')
102+
->once();
103+
104+
$mockExceptionHandler = Mockery::mock(OpenIDConnectExceptionHandler::class);
105+
$mockExceptionHandler
106+
->shouldReceive('handleExceptionWhileRequestUserInfo')
107+
->withArgs(function (OpenIDConnectClientException $e) {
108+
return $e->getMessage() === 'Something went wrong';
109+
})
110+
->once();
111+
112+
$loginController = new LoginController(
113+
$mockClient,
114+
$mockExceptionHandler,
115+
);
116+
117+
$loginController->__invoke();
118+
}
119+
120+
public function testExceptionHandlerIsCalledWhenRequestUserInfoThrowsAnJweDecryptException(): void
121+
{
122+
$mockClient = Mockery::mock(OpenIDConnectClient::class);
123+
$mockClient->shouldReceive('authenticate')->once();
124+
$mockClient
125+
->shouldReceive('requestUserInfo')
126+
->andThrow(JweDecryptException::class, 'Something went wrong')
127+
->once();
128+
129+
$mockExceptionHandler = Mockery::mock(OpenIDConnectExceptionHandler::class);
130+
$mockExceptionHandler
131+
->shouldReceive('handleException')
132+
->withArgs(function (Exception $e) {
133+
return $e->getMessage() === 'Something went wrong';
134+
})
135+
->once();
136+
137+
$loginController = new LoginController(
138+
$mockClient,
139+
$mockExceptionHandler,
140+
);
141+
142+
$loginController->__invoke();
143+
}
144+
145+
public function testLoginResponseIsReturnedWithUserInfo(): void
146+
{
147+
$mockClient = Mockery::mock(OpenIDConnectClient::class);
148+
$mockClient->shouldReceive('authenticate')->once();
149+
$mockClient
150+
->shouldReceive('requestUserInfo')
151+
->andReturn($this->exampleUserInfo())
152+
->once();
153+
154+
$mockExceptionHandler = Mockery::mock(OpenIDConnectExceptionHandler::class);
155+
156+
$loginController = new LoginController(
157+
$mockClient,
158+
$mockExceptionHandler,
159+
);
160+
161+
$response = $loginController->__invoke();
162+
163+
$this->assertInstanceOf(LoginResponseInterface::class, $response);
164+
$this->assertInstanceOf(Responsable::class, $response);
165+
}
166+
167+
public function testUserInfoIsReturned(): void
168+
{
169+
$mockClient = Mockery::mock(OpenIDConnectClient::class);
170+
$mockClient->shouldReceive('authenticate')->once();
171+
$mockClient
172+
->shouldReceive('requestUserInfo')
173+
->andReturn($this->exampleUserInfo())
174+
->once();
175+
176+
$mockExceptionHandler = Mockery::mock(OpenIDConnectExceptionHandler::class);
177+
178+
$loginController = new LoginController(
179+
$mockClient,
180+
$mockExceptionHandler,
181+
);
182+
183+
$loginResponse = $loginController->__invoke();
184+
$response = $loginResponse->toResponse(Mockery::mock(Request::class));
185+
186+
$this->assertSame(json_encode([
187+
'userInfo' => $this->exampleUserInfo(),
188+
]), $response->getContent());
189+
}
190+
191+
protected function exampleUserInfo(): object
192+
{
193+
return (object) [
194+
'sub' => '1234567890',
195+
'name' => 'John Doe',
196+
'given_name' => 'John',
197+
'family_name' => 'Doe',
198+
'middle_name' => 'Middle',
199+
'nickname' => 'JD',
200+
'preferred_username' => 'johndoe',
201+
'email' => '',
202+
];
203+
}
204+
}

0 commit comments

Comments
 (0)