package handler_test import ( "net/http" "testing" "github.com/stretchr/testify/assert" ) // ============================================================================= // SSOHandler Tests - Single Sign-On // ============================================================================= // TestSSOHandler_Authorize_CodeFlow 验证授权码流程 func TestSSOHandler_Authorize_CodeFlow(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() // Register and login user registerUser(server.URL, "ssouser", "sso@test.com", "Pass123!") token := getToken(server.URL, "ssouser", "Pass123!") assert.NotEmpty(t, token) // Request authorization with code flow resp, _ := doGet(server.URL+"/api/v1/sso/authorize?client_id=test-client&redirect_uri=http://localhost/callback&response_type=code&state=xyz", token) defer resp.Body.Close() // SSO may return various status codes based on configuration assert.True(t, resp.StatusCode == http.StatusFound || resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusInternalServerError || resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusUnauthorized, "should handle authorize request, got %d", resp.StatusCode) } // TestSSOHandler_Authorize_TokenFlow 验证隐式授权流程 func TestSSOHandler_Authorize_TokenFlow(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "ssouser2", "sso2@test.com", "Pass123!") token := getToken(server.URL, "ssouser2", "Pass123!") assert.NotEmpty(t, token) // Request authorization with token flow resp, _ := doGet(server.URL+"/api/v1/sso/authorize?client_id=test-client&redirect_uri=http://localhost/callback&response_type=token&state=abc", token) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusFound || resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusInternalServerError || resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusUnauthorized, "should handle token flow, got %d", resp.StatusCode) } // TestSSOHandler_Authorize_MissingParams 验证缺少参数 func TestSSOHandler_Authorize_MissingParams(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "ssouser3", "sso3@test.com", "Pass123!") token := getToken(server.URL, "ssouser3", "Pass123!") // Missing params - handler may enforce or not based on config resp1, _ := doGet(server.URL+"/api/v1/sso/authorize?redirect_uri=http://localhost&response_type=code", token) defer resp1.Body.Close() assert.True(t, resp1.StatusCode >= http.StatusBadRequest || resp1.StatusCode == http.StatusOK, "should handle missing client_id, got %d", resp1.StatusCode) // Missing redirect_uri resp2, _ := doGet(server.URL+"/api/v1/sso/authorize?client_id=test&response_type=code", token) defer resp2.Body.Close() assert.True(t, resp2.StatusCode >= http.StatusBadRequest || resp2.StatusCode == http.StatusOK, "should handle missing redirect_uri, got %d", resp2.StatusCode) // Missing response_type resp3, _ := doGet(server.URL+"/api/v1/sso/authorize?client_id=test&redirect_uri=http://localhost", token) defer resp3.Body.Close() assert.True(t, resp3.StatusCode >= http.StatusBadRequest || resp3.StatusCode == http.StatusOK, "should handle missing response_type, got %d", resp3.StatusCode) } // TestSSOHandler_Authorize_InvalidResponseType 验证无效响应类型 func TestSSOHandler_Authorize_InvalidResponseType(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "ssouser4", "sso4@test.com", "Pass123!") token := getToken(server.URL, "ssouser4", "Pass123!") resp, _ := doGet(server.URL+"/api/v1/sso/authorize?client_id=test&redirect_uri=http://localhost&response_type=invalid", token) defer resp.Body.Close() assert.True(t, resp.StatusCode >= http.StatusBadRequest || resp.StatusCode == http.StatusOK, "should handle invalid response_type, got %d", resp.StatusCode) } // TestSSOHandler_Authorize_Unauthorized 验证未认证用户 func TestSSOHandler_Authorize_Unauthorized(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() // No authentication token resp, _ := doGet(server.URL+"/api/v1/sso/authorize?client_id=test&redirect_uri=http://localhost&response_type=code", "") defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusBadRequest, "should handle unauthorized request, got %d", resp.StatusCode) } // TestSSOHandler_Token_Success 验证获取 Token func TestSSOHandler_Token_Success(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() // Try to exchange code for token using doPost helper resp, _ := doPost(server.URL+"/api/v1/sso/token", "", map[string]interface{}{ "grant_type": "authorization_code", "code": "invalid-code", "client_id": "test", "client_secret": "secret", "redirect_uri": "http://localhost", }) defer resp.Body.Close() // Handler may accept or reject based on SSO config assert.True(t, resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusOK, "should handle token request, got %d", resp.StatusCode) } // TestSSOHandler_Token_MissingParams 验证缺少 Token 参数 func TestSSOHandler_Token_MissingParams(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() // Missing client_id resp1, _ := doPost(server.URL+"/api/v1/sso/token", "", map[string]interface{}{ "grant_type": "authorization_code", "code": "test", "client_secret": "secret", }) defer resp1.Body.Close() assert.True(t, resp1.StatusCode >= http.StatusBadRequest || resp1.StatusCode == http.StatusOK, "should handle missing client_id, got %d", resp1.StatusCode) // Missing client_secret resp2, _ := doPost(server.URL+"/api/v1/sso/token", "", map[string]interface{}{ "grant_type": "authorization_code", "code": "test", "client_id": "test", }) defer resp2.Body.Close() assert.True(t, resp2.StatusCode >= http.StatusBadRequest || resp2.StatusCode == http.StatusOK, "should handle missing client_secret, got %d", resp2.StatusCode) // Missing grant_type resp3, _ := doPost(server.URL+"/api/v1/sso/token", "", map[string]interface{}{ "client_id": "test", "client_secret": "secret", "code": "test", }) defer resp3.Body.Close() assert.True(t, resp3.StatusCode >= http.StatusBadRequest || resp3.StatusCode == http.StatusOK, "should handle missing grant_type, got %d", resp3.StatusCode) } // TestSSOHandler_Token_InvalidGrantType 验证无效授权类型 func TestSSOHandler_Token_InvalidGrantType(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() resp, _ := doPost(server.URL+"/api/v1/sso/token", "", map[string]interface{}{ "grant_type": "invalid_grant", "client_id": "test", "client_secret": "secret", "code": "test", }) defer resp.Body.Close() assert.True(t, resp.StatusCode >= http.StatusBadRequest || resp.StatusCode == http.StatusOK, "should handle invalid grant_type, got %d", resp.StatusCode) } // TestSSOHandler_Introspect_Success 验证 Token 验证 func TestSSOHandler_Introspect_Success(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() // Introspect invalid token resp, body := doPost(server.URL+"/api/v1/sso/introspect", "", map[string]interface{}{ "token": "invalid-token", }) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusBadRequest, "should return introspect response, got %d: %s", resp.StatusCode, body) } // TestSSOHandler_Introspect_MissingToken 验证缺少 Token func TestSSOHandler_Introspect_MissingToken(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() resp, _ := doPost(server.URL+"/api/v1/sso/introspect", "", map[string]interface{}{ "token": "", }) defer resp.Body.Close() assert.True(t, resp.StatusCode >= http.StatusBadRequest || resp.StatusCode == http.StatusOK, "should handle missing token, got %d", resp.StatusCode) } // TestSSOHandler_Revoke_Success 验证 Token 撤销 func TestSSOHandler_Revoke_Success(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() resp, body := doPost(server.URL+"/api/v1/sso/revoke", "", map[string]interface{}{ "token": "some-token", }) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusBadRequest, "should handle revoke request, got %d: %s", resp.StatusCode, body) } // TestSSOHandler_Revoke_MissingToken 验证缺少 Token func TestSSOHandler_Revoke_MissingToken(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() resp, _ := doPost(server.URL+"/api/v1/sso/revoke", "", map[string]interface{}{ "token": "", }) defer resp.Body.Close() assert.True(t, resp.StatusCode >= http.StatusBadRequest || resp.StatusCode == http.StatusOK, "should handle missing token, got %d", resp.StatusCode) } // TestSSOHandler_UserInfo_Success 验证获取用户信息 func TestSSOHandler_UserInfo_Success(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "ssouser5", "sso5@test.com", "Pass123!") token := getToken(server.URL, "ssouser5", "Pass123!") assert.NotEmpty(t, token) resp, body := doGet(server.URL+"/api/v1/sso/userinfo", token) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden, "should handle userinfo request, got %d: %s", resp.StatusCode, body) } // TestSSOHandler_UserInfo_Unauthorized 验证未认证访问 func TestSSOHandler_UserInfo_Unauthorized(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() resp, _ := doGet(server.URL+"/api/v1/sso/userinfo", "") defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusBadRequest, "should handle unauthorized request, got %d", resp.StatusCode) } // TestSSOHandler_FullFlow_Authorization 验证完整授权流程 func TestSSOHandler_FullFlow_Authorization(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() // Register and login registerUser(server.URL, "flowuser", "flow@test.com", "Pass123!") token := getToken(server.URL, "flowuser", "Pass123!") assert.NotEmpty(t, token) // Step 1: Authorize (get code or redirect) authResp, _ := doGet(server.URL+"/api/v1/sso/authorize?client_id=test&redirect_uri=http://localhost&response_type=code&scope=profile", token) defer authResp.Body.Close() // Step 2: Check response - SSO may return redirect or direct response based on config assert.True(t, authResp.StatusCode == http.StatusFound || authResp.StatusCode == http.StatusOK || authResp.StatusCode == http.StatusBadRequest || authResp.StatusCode == http.StatusInternalServerError, "should handle authorization, got %d", authResp.StatusCode) if authResp.StatusCode == http.StatusFound { location := authResp.Header.Get("Location") assert.Contains(t, location, "localhost") t.Logf("Redirected to: %s", location) } } // TestSSOHandler_ClientCredentials_Validation 验证客户端凭证验证 func TestSSOHandler_ClientCredentials_Validation(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() // Try with invalid client credentials resp, _ := doPost(server.URL+"/api/v1/sso/token", "", map[string]interface{}{ "grant_type": "authorization_code", "code": "test-code", "client_id": "invalid-client", "client_secret": "wrong-secret", "redirect_uri": "http://localhost", }) defer resp.Body.Close() // May accept or reject based on SSO configuration assert.True(t, resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusOK, "should handle client credentials, got %d", resp.StatusCode) } // TestSSOHandler_Scope_Handling 验证 Scope 处理 func TestSSOHandler_Scope_Handling(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "scopeuser", "scope@test.com", "Pass123!") token := getToken(server.URL, "scopeuser", "Pass123!") // Request with scope resp, _ := doGet(server.URL+"/api/v1/sso/authorize?client_id=test&redirect_uri=http://localhost&response_type=code&scope=profile+email", token) defer resp.Body.Close() // Should handle scope parameter assert.True(t, resp.StatusCode == http.StatusFound || resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusInternalServerError || resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusUnauthorized, "should handle scope parameter, got %d", resp.StatusCode) } // TestSSOHandler_State_Preservation 验证 State 参数保持 func TestSSOHandler_State_Preservation(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "stateuser", "state@test.com", "Pass123!") token := getToken(server.URL, "stateuser", "Pass123!") // Request with state parameter resp, _ := doGet(server.URL+"/api/v1/sso/authorize?client_id=test&redirect_uri=http://localhost&response_type=code&state=my-state-value", token) defer resp.Body.Close() // If redirected, state should be preserved in callback if resp.StatusCode == http.StatusFound { location := resp.Header.Get("Location") // State should be included in redirect URL t.Logf("Redirect location: %s", location) } }