Skip to content

Commit 335c556

Browse files
authored
Merge pull request #14 from chrisw-dev/copilot/extend-oauth-error-scenarios
Fix: Default error_scenario.enabled to true when configured
2 parents 361403a + 617ce92 commit 335c556

File tree

3 files changed

+132
-14
lines changed

3 files changed

+132
-14
lines changed

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,11 +376,14 @@ Allows test code to dynamically configure the responses returned by the OAuth2 e
376376
"error_scenario": {
377377
"endpoint": "token",
378378
"error": "invalid_grant",
379-
"error_description": "Custom error for testing"
379+
"error_description": "Custom error for testing",
380+
"enabled": true
380381
}
381382
}
382383
```
383384

385+
**Note**: The `enabled` field is optional and defaults to `true` when `endpoint` and `error` are provided. To explicitly disable an error scenario, set `"enabled": false`.
386+
384387
**Response**:
385388

386389
```json
@@ -392,6 +395,30 @@ Allows test code to dynamically configure the responses returned by the OAuth2 e
392395

393396
**IMPORTANT**: To update user information that will be returned by the `/userinfo` endpoint, you must include the user profile data inside the `tokens.user_info` object, not in the top-level `user_info` field. The top-level `user_info` field updates a different user object that is not used by the `/userinfo` endpoint.
394397

398+
**Error Scenario Configuration**:
399+
400+
The `error_scenario` object supports the following OAuth2 error codes for testing:
401+
402+
**Authorize endpoint errors:**
403+
- `access_denied` - User denied access
404+
- `unauthorized_client` - Client not authorized for this grant type
405+
- `invalid_scope` - Requested scope is invalid or unknown
406+
- `temporarily_unavailable` - Server is temporarily unavailable
407+
- `invalid_request` - Request is missing a required parameter or malformed
408+
- `unsupported_response_type` - Response type is not supported
409+
- `server_error` - Internal server error occurred
410+
411+
**Token endpoint errors:**
412+
- `invalid_grant` - Invalid authorization code or credentials
413+
- `invalid_client` - Client authentication failed
414+
- `unsupported_grant_type` - Grant type is not supported
415+
- `invalid_request` - Request is malformed
416+
417+
**Userinfo endpoint errors:**
418+
- `invalid_token` - Access token is invalid or expired
419+
- `insufficient_scope` - Token lacks required scope
420+
- `server_error` - Internal server error occurred
421+
395422
This enables testing scenarios like:
396423

397424
- Testing how your application handles different user profiles

internal/handlers/config.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ type ConfigRequest struct {
3434

3535
// ErrorScenario defines an error condition to simulate
3636
type ErrorScenario struct {
37-
Enabled bool `json:"enabled"` // Whether the error scenario is enabled
38-
Endpoint string `json:"endpoint"` // Which endpoint should return an error (authorize, token, userinfo)
39-
Error string `json:"error"` // OAuth2 error code
37+
Enabled *bool `json:"enabled,omitempty"` // Whether the error scenario is enabled (defaults to true if not specified)
38+
Endpoint string `json:"endpoint"` // Which endpoint should return an error (authorize, token, userinfo)
39+
Error string `json:"error"` // OAuth2 error code
4040
ErrorDescription string `json:"error_description,omitempty"`
4141
}
4242

@@ -109,9 +109,17 @@ func (h *ConfigHandler) storeTokenConfig(tokenConfig map[string]interface{}) {
109109

110110
// storeErrorScenario saves error scenario configuration to the store
111111
func (h *ConfigHandler) storeErrorScenario(scenario ErrorScenario) {
112+
// Default enabled to true when an error scenario is being configured
113+
// If Enabled is nil (not provided), default to true
114+
// If Enabled is explicitly set (true or false), use that value
115+
enabled := true
116+
if scenario.Enabled != nil {
117+
enabled = *scenario.Enabled
118+
}
119+
112120
// Convert from handlers.ErrorScenario to types.ErrorScenario
113121
storeScenario := types.ErrorScenario{
114-
Enabled: scenario.Enabled,
122+
Enabled: enabled,
115123
Endpoint: scenario.Endpoint,
116124
StatusCode: determineStatusCode(scenario.Error),
117125
ErrorCode: scenario.Error,

internal/handlers/config_test.go

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ func newMockStore() *mockStore {
2828
}
2929
}
3030

31+
// Helper function to create a bool pointer
32+
func boolPtr(b bool) *bool {
33+
return &b
34+
}
35+
3136
func (s *mockStore) StoreAuthCode(code string, request *models.AuthRequest) {
3237
s.authCodes[code] = request
3338
}
@@ -255,7 +260,7 @@ func TestConfigHandler_UpdateErrorScenario(t *testing.T) {
255260
{
256261
name: "invalid_request",
257262
errorScenario: ErrorScenario{
258-
Enabled: true,
263+
Enabled: boolPtr(true),
259264
Endpoint: "token",
260265
Error: "invalid_request",
261266
ErrorDescription: "Test invalid request",
@@ -265,7 +270,7 @@ func TestConfigHandler_UpdateErrorScenario(t *testing.T) {
265270
{
266271
name: "invalid_client",
267272
errorScenario: ErrorScenario{
268-
Enabled: true,
273+
Enabled: boolPtr(true),
269274
Endpoint: "token",
270275
Error: "invalid_client",
271276
ErrorDescription: "Test invalid client",
@@ -275,7 +280,7 @@ func TestConfigHandler_UpdateErrorScenario(t *testing.T) {
275280
{
276281
name: "server_error",
277282
errorScenario: ErrorScenario{
278-
Enabled: true,
283+
Enabled: boolPtr(true),
279284
Endpoint: "userinfo",
280285
Error: "server_error",
281286
ErrorDescription: "Test server error",
@@ -285,7 +290,7 @@ func TestConfigHandler_UpdateErrorScenario(t *testing.T) {
285290
{
286291
name: "unknown_error",
287292
errorScenario: ErrorScenario{
288-
Enabled: true,
293+
Enabled: boolPtr(true),
289294
Endpoint: "authorize",
290295
Error: "unknown_error",
291296
ErrorDescription: "Test unknown error",
@@ -295,7 +300,7 @@ func TestConfigHandler_UpdateErrorScenario(t *testing.T) {
295300
{
296301
name: "unsupported_response_type",
297302
errorScenario: ErrorScenario{
298-
Enabled: true,
303+
Enabled: boolPtr(true),
299304
Endpoint: "authorize",
300305
Error: "unsupported_response_type",
301306
ErrorDescription: "Response type not supported",
@@ -305,7 +310,7 @@ func TestConfigHandler_UpdateErrorScenario(t *testing.T) {
305310
{
306311
name: "temporarily_unavailable",
307312
errorScenario: ErrorScenario{
308-
Enabled: true,
313+
Enabled: boolPtr(true),
309314
Endpoint: "authorize",
310315
Error: "temporarily_unavailable",
311316
ErrorDescription: "Server is under maintenance",
@@ -340,8 +345,9 @@ func TestConfigHandler_UpdateErrorScenario(t *testing.T) {
340345
}
341346

342347
// Check the fields were stored correctly
343-
if scenario.Enabled != tc.errorScenario.Enabled {
344-
t.Errorf("wrong enabled status stored: got %v want %v", scenario.Enabled, tc.errorScenario.Enabled)
348+
expectedEnabled := tc.errorScenario.Enabled != nil && *tc.errorScenario.Enabled
349+
if scenario.Enabled != expectedEnabled {
350+
t.Errorf("wrong enabled status stored: got %v want %v", scenario.Enabled, expectedEnabled)
345351
}
346352
if scenario.Endpoint != tc.errorScenario.Endpoint {
347353
t.Errorf("wrong endpoint stored: got %v want %v", scenario.Endpoint, tc.errorScenario.Endpoint)
@@ -376,7 +382,7 @@ func TestConfigHandler_CombinedUpdate(t *testing.T) {
376382
"expires_in": 2400,
377383
},
378384
ErrorScenario: &ErrorScenario{
379-
Enabled: true,
385+
Enabled: boolPtr(true),
380386
Endpoint: "token",
381387
Error: "invalid_grant",
382388
ErrorDescription: "Combined test error",
@@ -476,3 +482,80 @@ func TestDetermineStatusCode(t *testing.T) {
476482
})
477483
}
478484
}
485+
486+
func TestConfigHandler_ErrorScenarioDefaultEnabled(t *testing.T) {
487+
// Setup
488+
mockStore := newMockStore()
489+
defaultUser := models.NewDefaultUser()
490+
handler := NewConfigHandler(mockStore, defaultUser)
491+
492+
// Test case where enabled field is not set (nil pointer)
493+
// Should default to true when endpoint and error are provided
494+
configReq := ConfigRequest{
495+
ErrorScenario: &ErrorScenario{
496+
// Enabled field not set (nil), will default to true
497+
Endpoint: "authorize",
498+
Error: "unauthorized_client",
499+
ErrorDescription: "Client not authorized",
500+
},
501+
}
502+
reqBody, _ := json.Marshal(configReq)
503+
req := httptest.NewRequest("POST", "/config", bytes.NewBuffer(reqBody))
504+
rr := httptest.NewRecorder()
505+
506+
// Call handler
507+
handler.ServeHTTP(rr, req)
508+
509+
// Check response code
510+
if status := rr.Code; status != http.StatusOK {
511+
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
512+
}
513+
514+
// Verify error scenario was stored and ENABLED by default
515+
scenario, exists := mockStore.GetErrorScenario("authorize")
516+
if !exists {
517+
t.Errorf("error scenario not found for authorize endpoint")
518+
return
519+
}
520+
521+
if !scenario.Enabled {
522+
t.Errorf("error scenario should be enabled by default when endpoint and error are provided, got enabled=%v", scenario.Enabled)
523+
}
524+
if scenario.ErrorCode != "unauthorized_client" {
525+
t.Errorf("wrong error code stored: got %v want %v", scenario.ErrorCode, "unauthorized_client")
526+
}
527+
}
528+
529+
func TestConfigHandler_ErrorScenarioExplicitlyDisabled(t *testing.T) {
530+
// Setup
531+
mockStore := newMockStore()
532+
defaultUser := models.NewDefaultUser()
533+
handler := NewConfigHandler(mockStore, defaultUser)
534+
535+
// Test case where enabled is explicitly set to false
536+
configReq := ConfigRequest{
537+
ErrorScenario: &ErrorScenario{
538+
Enabled: boolPtr(false), // Explicitly disabled
539+
Endpoint: "authorize",
540+
Error: "access_denied",
541+
ErrorDescription: "Should not be active",
542+
},
543+
}
544+
reqBody, _ := json.Marshal(configReq)
545+
req := httptest.NewRequest("POST", "/config", bytes.NewBuffer(reqBody))
546+
rr := httptest.NewRecorder()
547+
548+
// Call handler
549+
handler.ServeHTTP(rr, req)
550+
551+
// Check response code
552+
if status := rr.Code; status != http.StatusOK {
553+
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
554+
}
555+
556+
// Verify error scenario is stored but NOT enabled
557+
scenario, exists := mockStore.GetErrorScenario("authorize")
558+
if exists {
559+
t.Errorf("error scenario should not be returned when disabled, but got: %+v", scenario)
560+
}
561+
}

0 commit comments

Comments
 (0)