package middleware import ( "net/http" "net/http/httptest" "strings" "testing" ) // 客户端超长 request_id 会被下游当作幂等键缓存,必须作废并换成服务端生成的 id(P2-6)。 func TestRequestIDClampsOversizedHeader(t *testing.T) { var ( ctxID string handlerID string ) h := RequestID(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctxID = GetRequestID(r.Context()) handlerID = r.Header.Get(HeaderRequestID) })) req := httptest.NewRequest(http.MethodPost, "/api/teams", nil) req.Header.Set(HeaderRequestID, strings.Repeat("x", maxRequestIDLen+1)) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if ctxID == "" || len(ctxID) > maxRequestIDLen { t.Fatalf("超长 id 应换成服务端生成的短 id,得到 %q", ctxID) } if handlerID != ctxID { t.Fatalf("下游读到的 request_id 应与 ctx 一致,ctx=%q header=%q", ctxID, handlerID) } if got := rec.Header().Get(HeaderRequestID); got != ctxID { t.Fatalf("响应头 request_id 应为 %q,得到 %q", ctxID, got) } } // 合理长度的客户端 id 仍应原样复用,保证幂等回放。 func TestRequestIDKeepsNormalHeader(t *testing.T) { var ctxID string h := RequestID(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctxID = GetRequestID(r.Context()) })) req := httptest.NewRequest(http.MethodPost, "/api/teams", nil) req.Header.Set(HeaderRequestID, "abc-123") h.ServeHTTP(httptest.NewRecorder(), req) if ctxID != "abc-123" { t.Fatalf("正常 id 应原样复用,得到 %q", ctxID) } } // 登录是 guest 请求,CSRF Token 校验被跳过,靠同源校验挡住 login CSRF(P2-6)。 func TestCSRFBlocksCrossOriginLogin(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) h := CSRF(nil, next) req := httptest.NewRequest(http.MethodPost, "/api/auth/login", nil) req.Host = "scoreboard.local" req.Header.Set("Origin", "http://evil.example") rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusForbidden { t.Fatalf("跨站登录应 403,得到 %d", rec.Code) } req = httptest.NewRequest(http.MethodPost, "/api/auth/login", nil) req.Host = "scoreboard.local" req.Header.Set("Origin", "http://scoreboard.local") rec = httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("同源登录应放行,得到 %d", rec.Code) } } // 同源判断:两个头都没有时放行脚本调用,Origin: null 与解析失败一律拒绝。 func TestSameOrigin(t *testing.T) { cases := []struct { name string origin string referer string want bool }{ {"无来源头", "", "", true}, {"同源 Origin", "http://scoreboard.local", "", true}, {"跨站 Origin", "http://evil.example", "", false}, {"Origin 为 null", "null", "", false}, {"同源 Referer", "", "http://scoreboard.local/admin", true}, {"跨站 Referer", "", "http://evil.example/x", false}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/api/auth/login", nil) req.Host = "scoreboard.local" if tc.origin != "" { req.Header.Set("Origin", tc.origin) } if tc.referer != "" { req.Header.Set("Referer", tc.referer) } if got := sameOrigin(req); got != tc.want { t.Fatalf("sameOrigin = %v,期望 %v", got, tc.want) } }) } }