92cf2921dd
Closes HIF-3 (Stage 1 P1 defects from QA smoke report) F2: admin_action_log.ip / user_agent 恒为空 - 根因:requestMeta 从 ctx 读裸字符串 key,全库无 writer 塞 - 修复:新增 AdminMetaMiddleware 用 typed constant.CtxKeyIP / CtxKeyUserAgent - 挂载:lottery admin 组末尾(不 gate access) - 回归护栏:UsesTypedKey + IgnoresBareStringKeys 双向断言 typed key,防止未来退化回裸字符串 F3: qa/lottery/stage1_curl.sh Bearer 前缀 - 删除两处 Bearer 前缀 + 加注释说明 ppanel AuthMiddleware 不 strip 单测 5 用例全绿;scope 严格限于 lottery admin 组,其它路径零改动;无 DB 变更。 Merged: architect review 后,将触发 staging 二次部署 — 期望这次能一并解决 shanshanzhong147 手工 SSH 后 Lottery.Enable=true 未生效的问题(若真是 mount/restart 未正确 pick up)。
82 lines
2.3 KiB
Go
82 lines
2.3 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/perfect-panel/server/pkg/constant"
|
|
)
|
|
|
|
// TestAdminMetaMiddleware_PopulatesCtx asserts the middleware pins ClientIP
|
|
// and User-Agent onto the request context under the typed constant keys, so
|
|
// downstream audit writers can pick them up without gin.Context threading.
|
|
func TestAdminMetaMiddleware_PopulatesCtx(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
var (
|
|
gotIP string
|
|
gotUA string
|
|
)
|
|
r := gin.New()
|
|
r.Use(AdminMetaMiddleware())
|
|
r.GET("/ping", func(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
gotIP, _ = ctx.Value(constant.CtxKeyIP).(string)
|
|
gotUA, _ = ctx.Value(constant.CtxKeyUserAgent).(string)
|
|
c.String(http.StatusOK, "ok")
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/ping", nil)
|
|
req.RemoteAddr = "10.99.99.7:54321"
|
|
req.Header.Set("User-Agent", "qa-audit-probe")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200", w.Code)
|
|
}
|
|
if gotUA != "qa-audit-probe" {
|
|
t.Fatalf("user_agent = %q, want %q", gotUA, "qa-audit-probe")
|
|
}
|
|
// Gin resolves ClientIP() from RemoteAddr when no forwarded headers are
|
|
// trusted. It strips the port, so we assert the exact host we set.
|
|
if gotIP != "10.99.99.7" {
|
|
t.Fatalf("ip = %q, want %q", gotIP, "10.99.99.7")
|
|
}
|
|
}
|
|
|
|
// TestAdminMetaMiddleware_UsesTypedKey guards against the regression that
|
|
// motivated PR D: reader and writer must share the typed CtxKey, not a bare
|
|
// string. A ctx.Value("ip") lookup (bare string) MUST miss even though the
|
|
// typed CtxKey "ip" is present.
|
|
func TestAdminMetaMiddleware_UsesTypedKey(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
var (
|
|
typedIP string
|
|
bareStrIP any
|
|
)
|
|
r := gin.New()
|
|
r.Use(AdminMetaMiddleware())
|
|
r.GET("/ping", func(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
typedIP, _ = ctx.Value(constant.CtxKeyIP).(string)
|
|
bareStrIP = ctx.Value("ip") // bare string key — must MISS
|
|
c.String(http.StatusOK, "ok")
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/ping", nil)
|
|
req.RemoteAddr = "10.1.2.3:80"
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if typedIP == "" {
|
|
t.Fatalf("typed CtxKeyIP lookup must succeed")
|
|
}
|
|
if bareStrIP != nil {
|
|
t.Fatalf("bare-string \"ip\" lookup MUST miss (got %v); F2 regression risk", bareStrIP)
|
|
}
|
|
}
|