package middleware import ( "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" ) // setupAdminRoleRouter 构造一个仅含 AdminRoleMiddleware 的 gin 路由用于测试。 // nextCalled 标记后续 handler 是否被调用。 func setupAdminRoleRouter(t *testing.T) (*gin.Engine, *bool) { t.Helper() gin.SetMode(gin.TestMode) r := gin.New() called := false r.Use(AdminRoleMiddleware()) r.Any("/test", func(c *gin.Context) { called = true c.Status(http.StatusOK) }) return r, &called } // parseActionState 解析 ActionState 错误信封,返回 success/code/message。 func parseActionState(t *testing.T, w *httptest.ResponseRecorder) (bool, string, string) { t.Helper() var body struct { Success bool `json:"success"` Error struct { Code string `json:"code"` Message string `json:"message"` } `json:"error"` } if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil { t.Fatalf("解析响应体失败: %v, body=%s", err, w.Body.String()) } return body.Success, body.Error.Code, body.Error.Message } func TestAdminRoleMiddleware_PassesWhenAdminOnly(t *testing.T) { r, called := setupAdminRoleRouter(t) req := httptest.NewRequest(http.MethodGet, "/test", nil) req.Header.Set("x-user-roles", "admin") w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("纯 admin 角色应通过,期望 200,实际 %d", w.Code) } if !*called { t.Fatal("下游 handler 应被调用") } } func TestAdminRoleMiddleware_PassesWhenAdminInList(t *testing.T) { r, called := setupAdminRoleRouter(t) // 多角色列表中包含 admin req := httptest.NewRequest(http.MethodGet, "/test", nil) req.Header.Set("x-user-roles", "teacher,admin") w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("多角色包含 admin 应通过,期望 200,实际 %d", w.Code) } if !*called { t.Fatal("下游 handler 应被调用") } } func TestAdminRoleMiddleware_RejectsWhenMissingRolesHeader(t *testing.T) { r, called := setupAdminRoleRouter(t) req := httptest.NewRequest(http.MethodGet, "/test", nil) // 不设置 x-user-roles 头 w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusForbidden { t.Fatalf("缺失 roles 头期望 403,实际 %d", w.Code) } if *called { t.Fatal("下游 handler 不应被调用") } success, code, _ := parseActionState(t, w) if success { t.Fatal("响应 success 应为 false") } if code != "GW_FORBIDDEN" { t.Fatalf("错误码应为 GW_FORBIDDEN,实际 %s", code) } } func TestAdminRoleMiddleware_RejectsWhenNoAdminRole(t *testing.T) { r, called := setupAdminRoleRouter(t) req := httptest.NewRequest(http.MethodGet, "/test", nil) req.Header.Set("x-user-roles", "teacher") w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusForbidden { t.Fatalf("非 admin 角色期望 403,实际 %d", w.Code) } if *called { t.Fatal("下游 handler 不应被调用") } _, code, msg := parseActionState(t, w) if code != "GW_FORBIDDEN" { t.Fatalf("错误码应为 GW_FORBIDDEN,实际 %s", code) } if msg != "admin role required" { t.Fatalf("错误消息应为 'admin role required',实际 %s", msg) } } func TestAdminRoleMiddleware_RejectsStudentRole(t *testing.T) { r, called := setupAdminRoleRouter(t) req := httptest.NewRequest(http.MethodGet, "/test", nil) req.Header.Set("x-user-roles", "student,parent") w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusForbidden { t.Fatalf("student/parent 角色期望 403,实际 %d", w.Code) } if *called { t.Fatal("下游 handler 不应被调用") } } func TestAdminRoleMiddleware_RejectsEmptyRolesHeader(t *testing.T) { r, called := setupAdminRoleRouter(t) req := httptest.NewRequest(http.MethodGet, "/test", nil) req.Header.Set("x-user-roles", "") w := httptest.NewRecorder() r.ServeHTTP(w, req) // 空字符串会被视为缺失 roles 头 if w.Code != http.StatusForbidden { t.Fatalf("空 roles 头期望 403,实际 %d", w.Code) } if *called { t.Fatal("下游 handler 不应被调用") } } func TestAdminRoleMiddleware_CaseSensitive(t *testing.T) { r, called := setupAdminRoleRouter(t) // "Admin"(大写)不应通过(大小写敏感) req := httptest.NewRequest(http.MethodGet, "/test", nil) req.Header.Set("x-user-roles", "Admin") w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusForbidden { t.Fatalf("'Admin'(大写)不应通过,期望 403,实际 %d", w.Code) } if *called { t.Fatal("下游 handler 不应被调用") } } func TestHasAdminRole_Variants(t *testing.T) { cases := []struct { input string want bool }{ {"admin", true}, {"teacher,admin", true}, {"admin,teacher", true}, {" teacher , admin ", true}, // 含空格 {"teacher", false}, {"student,parent", false}, {"", false}, {"Admin", false}, // 大小写敏感 {"administrator", false}, {"admin-role", false}, } for _, c := range cases { got := hasAdminRole(c.input) if got != c.want { t.Errorf("hasAdminRole(%q) = %v, want %v", c.input, got, c.want) } } }