package middleware import ( "context" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "lijiaoqiao/supply-api/internal/middleware" ) // TestScopeAuth_CheckScope_SuperAdminHasAllScopes 测试超级管理员拥有所有Scope func TestScopeAuth_CheckScope_SuperAdminHasAllScopes(t *testing.T) { // arrange // 创建超级管理员token claims claims := &IAMTokenClaims{ SubjectID: "user:1", Role: "super_admin", Scope: []string{"*"}, // 通配符Scope代表所有权限 TenantID: 0, } ctx := WithIAMClaims(context.Background(), claims) // act hasScope := CheckScope(ctx, "platform:read") hasScope2 := CheckScope(ctx, "supply:account:write") hasScope3 := CheckScope(ctx, "consumer:apikey:create") // assert assert.True(t, hasScope, "super_admin should have platform:read") assert.True(t, hasScope2, "super_admin should have supply:account:write") assert.True(t, hasScope3, "super_admin should have consumer:apikey:create") } // TestScopeAuth_CheckScope_ViewerHasReadOnly 测试Viewer只有只读权限 func TestScopeAuth_CheckScope_ViewerHasReadOnly(t *testing.T) { // arrange claims := &IAMTokenClaims{ SubjectID: "user:2", Role: "viewer", Scope: []string{"platform:read", "tenant:read", "billing:read"}, TenantID: 1, } ctx := WithIAMClaims(context.Background(), claims) // act & assert assert.True(t, CheckScope(ctx, "platform:read"), "viewer should have platform:read") assert.True(t, CheckScope(ctx, "tenant:read"), "viewer should have tenant:read") assert.True(t, CheckScope(ctx, "billing:read"), "viewer should have billing:read") assert.False(t, CheckScope(ctx, "platform:write"), "viewer should NOT have platform:write") assert.False(t, CheckScope(ctx, "tenant:write"), "viewer should NOT have tenant:write") assert.False(t, CheckScope(ctx, "supply:account:write"), "viewer should NOT have supply:account:write") } // TestScopeAuth_CheckScope_Denied 测试Scope被拒绝 func TestScopeAuth_CheckScope_Denied(t *testing.T) { // arrange claims := &IAMTokenClaims{ SubjectID: "user:3", Role: "viewer", Scope: []string{"platform:read"}, TenantID: 1, } ctx := WithIAMClaims(context.Background(), claims) // act & assert assert.False(t, CheckScope(ctx, "platform:write"), "viewer should NOT have platform:write") assert.False(t, CheckScope(ctx, "supply:account:write"), "viewer should NOT have supply:account:write") } // TestScopeAuth_CheckScope_MissingTokenClaims 测试缺少Token Claims func TestScopeAuth_CheckScope_MissingTokenClaims(t *testing.T) { // arrange ctx := context.Background() // 没有token claims // act hasScope := CheckScope(ctx, "platform:read") // assert assert.False(t, hasScope, "should return false when token claims are missing") } // TestScopeAuth_CheckScope_EmptyScope 测试空Scope要求 func TestScopeAuth_CheckScope_EmptyScope(t *testing.T) { // arrange claims := &IAMTokenClaims{ SubjectID: "user:4", Role: "viewer", Scope: []string{"platform:read"}, TenantID: 1, } ctx := WithIAMClaims(context.Background(), claims) // act hasEmptyScope := CheckScope(ctx, "") // assert - 空scope应该拒绝访问(安全修复) assert.False(t, hasEmptyScope, "empty scope should DENY access (security fix)") } // TestScopeAuth_CheckMultipleScopes 测试检查多个Scope(需要全部满足) func TestScopeAuth_CheckMultipleScopes(t *testing.T) { // arrange claims := &IAMTokenClaims{ SubjectID: "user:5", Role: "operator", Scope: []string{"platform:read", "platform:write", "tenant:read", "tenant:write"}, TenantID: 1, } ctx := WithIAMClaims(context.Background(), claims) // act & assert assert.True(t, CheckAllScopes(ctx, []string{"platform:read", "platform:write"}), "operator should have both read and write") assert.True(t, CheckAllScopes(ctx, []string{"tenant:read", "tenant:write"}), "operator should have both tenant scopes") assert.False(t, CheckAllScopes(ctx, []string{"platform:read", "platform:admin"}), "operator should NOT have platform:admin") } // TestScopeAuth_CheckAnyScope 测试检查多个Scope(只需满足其一) func TestScopeAuth_CheckAnyScope(t *testing.T) { // arrange claims := &IAMTokenClaims{ SubjectID: "user:6", Role: "viewer", Scope: []string{"platform:read"}, TenantID: 1, } ctx := WithIAMClaims(context.Background(), claims) // act & assert assert.True(t, CheckAnyScope(ctx, []string{"platform:read", "platform:write"}), "should pass with one matching scope") assert.False(t, CheckAnyScope(ctx, []string{"platform:write", "platform:admin"}), "should fail when no scopes match") assert.True(t, CheckAnyScope(ctx, []string{}), "empty scope list should pass") } // TestScopeAuth_GetIAMTokenClaims 测试从Context获取IAMTokenClaims func TestScopeAuth_GetIAMTokenClaims(t *testing.T) { // arrange claims := &IAMTokenClaims{ SubjectID: "user:7", Role: "org_admin", Scope: []string{"platform:read", "platform:write"}, TenantID: 1, } ctx := WithIAMClaims(context.Background(), claims) // act retrievedClaims := GetIAMTokenClaims(ctx) // assert assert.NotNil(t, retrievedClaims) assert.Equal(t, claims.SubjectID, retrievedClaims.SubjectID) assert.Equal(t, claims.Role, retrievedClaims.Role) assert.Equal(t, claims.Scope, retrievedClaims.Scope) } // TestScopeAuth_GetIAMTokenClaims_Missing 测试获取不存在的IAMTokenClaims func TestScopeAuth_GetIAMTokenClaims_Missing(t *testing.T) { // arrange ctx := context.Background() // act retrievedClaims := GetIAMTokenClaims(ctx) // assert assert.Nil(t, retrievedClaims) } // TestScopeAuth_HasRole 测试用户角色检查 func TestScopeAuth_HasRole(t *testing.T) { // arrange claims := &IAMTokenClaims{ SubjectID: "user:8", Role: "operator", Scope: []string{"platform:read"}, TenantID: 1, } ctx := WithIAMClaims(context.Background(), claims) // act & assert assert.True(t, HasRole(ctx, "operator")) assert.False(t, HasRole(ctx, "viewer")) assert.False(t, HasRole(ctx, "admin")) } // TestScopeAuth_HasRole_MissingClaims 测试缺少Claims时的角色检查 func TestScopeAuth_HasRole_MissingClaims(t *testing.T) { // arrange ctx := context.Background() // act & assert assert.False(t, HasRole(ctx, "operator")) } // TestScopeRoleAuthzMiddleware_WithScope 测试带Scope要求的中间件 func TestScopeRoleAuthzMiddleware_WithScope(t *testing.T) { // arrange scopeAuth := NewScopeAuthMiddleware() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`{"status":"ok"}`)) }) // 创建一个带scope验证的handler wrappedHandler := scopeAuth.RequireScope("platform:write")(handler) // 创建一个带有token claims的请求 claims := &IAMTokenClaims{ SubjectID: "user:9", Role: "operator", Scope: []string{"platform:read", "platform:write"}, TenantID: 1, } req := httptest.NewRequest("GET", "/test", nil) req = req.WithContext(WithIAMClaims(req.Context(), claims)) // act rec := httptest.NewRecorder() wrappedHandler.ServeHTTP(rec, req) // assert assert.Equal(t, http.StatusOK, rec.Code) } // TestScopeRoleAuthzMiddleware_Denied 测试Scope不足时中间件拒绝 func TestScopeRoleAuthzMiddleware_Denied(t *testing.T) { // arrange scopeAuth := NewScopeAuthMiddleware() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) wrappedHandler := scopeAuth.RequireScope("platform:admin")(handler) claims := &IAMTokenClaims{ SubjectID: "user:10", Role: "viewer", Scope: []string{"platform:read"}, // viewer没有platform:admin TenantID: 1, } req := httptest.NewRequest("GET", "/test", nil) req = req.WithContext(WithIAMClaims(req.Context(), claims)) // act rec := httptest.NewRecorder() wrappedHandler.ServeHTTP(rec, req) // assert assert.Equal(t, http.StatusForbidden, rec.Code) } // TestScopeRoleAuthzMiddleware_MissingClaims 测试缺少Claims时中间件拒绝 func TestScopeRoleAuthzMiddleware_MissingClaims(t *testing.T) { // arrange scopeAuth := NewScopeAuthMiddleware() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) wrappedHandler := scopeAuth.RequireScope("platform:read")(handler) req := httptest.NewRequest("GET", "/test", nil) // 不设置token claims // act rec := httptest.NewRecorder() wrappedHandler.ServeHTTP(rec, req) // assert assert.Equal(t, http.StatusUnauthorized, rec.Code) } // TestScopeRoleAuthzMiddleware_RequireAllScopes 测试要求所有Scope的中间件 func TestScopeRoleAuthzMiddleware_RequireAllScopes(t *testing.T) { // arrange scopeAuth := NewScopeAuthMiddleware() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) wrappedHandler := scopeAuth.RequireAllScopes([]string{"platform:read", "tenant:read"})(handler) claims := &IAMTokenClaims{ SubjectID: "user:11", Role: "operator", Scope: []string{"platform:read", "platform:write", "tenant:read"}, TenantID: 1, } req := httptest.NewRequest("GET", "/test", nil) req = req.WithContext(WithIAMClaims(req.Context(), claims)) // act rec := httptest.NewRecorder() wrappedHandler.ServeHTTP(rec, req) // assert assert.Equal(t, http.StatusOK, rec.Code) } // TestScopeRoleAuthzMiddleware_RequireAllScopes_Denied 测试要求所有Scope但不足时拒绝 func TestScopeRoleAuthzMiddleware_RequireAllScopes_Denied(t *testing.T) { // arrange scopeAuth := NewScopeAuthMiddleware() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) wrappedHandler := scopeAuth.RequireAllScopes([]string{"platform:read", "platform:admin"})(handler) claims := &IAMTokenClaims{ SubjectID: "user:12", Role: "viewer", Scope: []string{"platform:read"}, // viewer没有platform:admin TenantID: 1, } req := httptest.NewRequest("GET", "/test", nil) req = req.WithContext(WithIAMClaims(req.Context(), claims)) // act rec := httptest.NewRecorder() wrappedHandler.ServeHTTP(rec, req) // assert assert.Equal(t, http.StatusForbidden, rec.Code) } // TestScopeAuth_HasRoleLevel 测试角色层级检查 func TestScopeAuth_HasRoleLevel(t *testing.T) { // arrange testCases := []struct { role string minLevel int expected bool }{ {"super_admin", 50, true}, {"super_admin", 100, true}, {"org_admin", 50, true}, {"org_admin", 60, false}, {"operator", 30, true}, {"operator", 40, false}, {"viewer", 10, true}, {"viewer", 20, false}, } for _, tc := range testCases { claims := &IAMTokenClaims{ SubjectID: "user:test", Role: tc.role, Scope: []string{}, TenantID: 1, } ctx := WithIAMClaims(context.Background(), claims) // act result := HasRoleLevel(ctx, tc.minLevel) // assert assert.Equal(t, tc.expected, result, "role=%s, minLevel=%d", tc.role, tc.minLevel) } } // TestGetRoleLevel 测试获取角色层级 func TestGetRoleLevel(t *testing.T) { testCases := []struct { role string expected int }{ {"super_admin", 100}, {"org_admin", 50}, {"supply_admin", 40}, {"operator", 30}, {"developer", 20}, {"viewer", 10}, {"unknown_role", 0}, } for _, tc := range testCases { // act level := GetRoleLevel(tc.role) // assert assert.Equal(t, tc.expected, level, "role=%s", tc.role) } } // TestScopeAuth_WithIAMClaims 测试设置IAM Claims到Context func TestScopeAuth_WithIAMClaims(t *testing.T) { // arrange claims := &IAMTokenClaims{ SubjectID: "user:13", Role: "org_admin", Scope: []string{"platform:read"}, TenantID: 1, } // act ctx := WithIAMClaims(context.Background(), claims) retrievedClaims := GetIAMTokenClaims(ctx) // assert assert.NotNil(t, retrievedClaims) assert.Equal(t, claims.SubjectID, retrievedClaims.SubjectID) assert.Equal(t, claims.Role, retrievedClaims.Role) } // TestGetClaimsFromLegacy 测试从原有TokenClaims转换 func TestGetClaimsFromLegacy(t *testing.T) { // arrange legacyClaims := &middleware.TokenClaims{ SubjectID: "user:14", Role: "viewer", Scope: []string{"platform:read"}, TenantID: 1, } // act iamClaims := GetClaimsFromLegacy(legacyClaims) // assert assert.NotNil(t, iamClaims) assert.Equal(t, legacyClaims.SubjectID, iamClaims.SubjectID) assert.Equal(t, legacyClaims.Role, iamClaims.Role) assert.Equal(t, legacyClaims.Scope, iamClaims.Scope) assert.Equal(t, legacyClaims.TenantID, iamClaims.TenantID) } // P0-01: 测试WithIAMClaims存储指针,返回有效指针而非悬空指针 // 问题:GetIAMTokenClaims返回指向栈帧的指针,函数返回后指针无效 // 修复:改为存储和获取指针,返回有效堆内存指针 func TestP0_01_WithIAMClaims_ReturnsValidPointer(t *testing.T) { // arrange - 创建一个claims并存储到context originalClaims := &IAMTokenClaims{ SubjectID: "user:p0test1", Role: "operator", Scope: []string{"platform:read"}, TenantID: 100, } ctx := WithIAMClaims(context.Background(), originalClaims) // act - 从context获取claims(获取的应该是有效指针) retrievedClaims := GetIAMTokenClaims(ctx) // assert - 返回的应该是有效指针,指向与原始claims相同的内存 assert.NotNil(t, retrievedClaims, "retrieved claims should not be nil") assert.Equal(t, originalClaims, retrievedClaims, "should return same pointer as stored") assert.Equal(t, "user:p0test1", retrievedClaims.SubjectID, "SubjectID should match") assert.Equal(t, "operator", retrievedClaims.Role, "Role should match") // 验证修改原始对象后,retrievedClaims能看到变化(因为共享指针) originalClaims.Role = "super_admin" assert.Equal(t, "super_admin", retrievedClaims.Role, "retrieved claims should see modification") } // P0-01: 测试GetIAMTokenClaims在context返回后仍然有效 func TestP0_01_GetIAMTokenClaims_PointerValidAfterReturn(t *testing.T) { // arrange claims := &IAMTokenClaims{ SubjectID: "user:ptrtest", Role: "viewer", Scope: []string{"platform:read"}, TenantID: 1, } // act - 存储到context ctx := WithIAMClaims(context.Background(), claims) // 在函数外获取claims(模拟中间件在请求处理中访问) retrievedClaims := GetIAMTokenClaims(ctx) // assert - 应该返回有效指针而不是nil或无效指针 assert.NotNil(t, retrievedClaims) assert.Equal(t, claims, retrievedClaims, "should return exact same pointer") assert.Equal(t, "user:ptrtest", retrievedClaims.SubjectID) } // P0-02: 测试writeAuthError写入响应体 func TestP0_02_writeAuthError_WritesResponseBody(t *testing.T) { // arrange rec := httptest.NewRecorder() // act - 调用writeAuthError writeAuthError(rec, http.StatusUnauthorized, "AUTH_CONTEXT_MISSING", "authentication context is missing") // assert - 响应体应该包含错误信息 body := rec.Body.String() assert.NotEmpty(t, body, "response body should not be empty") // 验证响应体包含错误码和消息 assert.Contains(t, body, "AUTH_CONTEXT_MISSING", "body should contain error code") assert.Contains(t, body, "authentication context is missing", "body should contain error message") assert.Equal(t, http.StatusUnauthorized, rec.Code, "status code should match") assert.Equal(t, "application/json", rec.Header().Get("Content-Type"), "content type should be JSON") } // P0-02: 测试writeAuthError在Forbidden状态下也写入响应体 func TestP0_02_writeAuthError_ForbiddenWritesBody(t *testing.T) { // arrange rec := httptest.NewRecorder() // act writeAuthError(rec, http.StatusForbidden, "AUTH_SCOPE_DENIED", "required scope is not granted") // assert body := rec.Body.String() assert.NotEmpty(t, body, "response body should not be empty for Forbidden status") assert.Contains(t, body, "AUTH_SCOPE_DENIED") assert.Contains(t, body, "required scope is not granted") } // HIGH-01: CheckScope空scope应该拒绝访问(而不应该绕过权限检查) func TestHIGH01_CheckScope_EmptyScopeShouldDenyAccess(t *testing.T) { // arrange claims := &IAMTokenClaims{ SubjectID: "user:high01", Role: "viewer", Scope: []string{"platform:read"}, TenantID: 1, } ctx := WithIAMClaims(context.Background(), claims) // act - 空scope要求应该拒绝访问(安全修复) hasEmptyScope := CheckScope(ctx, "") // assert - 空scope应该返回false,拒绝访问 assert.False(t, hasEmptyScope, "empty scope should DENY access (security fix)") } // MED-01: RequireAnyScope当requiredScopes为空时应该拒绝访问 func TestMED01_RequireAnyScope_EmptyScopesShouldDenyAccess(t *testing.T) { // arrange scopeAuth := NewScopeAuthMiddleware() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) // 传入空的requiredScopes wrappedHandler := scopeAuth.RequireAnyScope([]string{})(handler) claims := &IAMTokenClaims{ SubjectID: "user:med01", Role: "viewer", Scope: []string{"platform:read"}, TenantID: 1, } req := httptest.NewRequest("GET", "/test", nil) req = req.WithContext(WithIAMClaims(req.Context(), claims)) // act rec := httptest.NewRecorder() wrappedHandler.ServeHTTP(rec, req) // assert - 空scope列表应该拒绝访问(安全修复) assert.Equal(t, http.StatusForbidden, rec.Code, "empty required scopes should DENY access (security fix)") }