188 lines
5.0 KiB
Go
188 lines
5.0 KiB
Go
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)
|
||
}
|
||
}
|
||
}
|