package hotime import ( "net/http/httptest" "strconv" "strings" "testing" "time" . "code.hoteas.com/golang/hotime/common" ) func TestNormalizeRequestId_Valid(t *testing.T) { cases := []struct{ in, want string }{ {"6f0a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c", "6f0a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c"}, // 大写归一为小写 {"6F0A1B2C-3D4E-4F5A-8B6C-7D8E9F0A1B2C", "6f0a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c"}, } for _, c := range cases { if got := normalizeRequestId(c.in); got != c.want { t.Fatalf("normalizeRequestId(%q)=%q want %q", c.in, got, c.want) } } } func TestNormalizeRequestId_Invalid(t *testing.T) { cases := []string{ "", "abc", // 12 位短 hex(服务端自生成格式,不接受为前端传入 id) "a1b2c3d4e5f6", // 长度对但连字符位置错 "6f0a1b2c3-d4e-4f5a-8b6c-7d8e9f0a1b2c", // 非 hex 字符 "6f0a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1bzz", // 注入尝试:换行/引号 "6f0a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1\n2c", "\"};alert(1);//-4f5a-8b6c-7d8e9f0a1b2c", // 超长 "6f0a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c0", } for _, c := range cases { if got := normalizeRequestId(c); got != "" { t.Fatalf("normalizeRequestId(%q)=%q want empty", c, got) } } } // 共用 request_id 行为:前端带合法 UUID 沿用,非法/缺失服务端自生成 12 位 hex func TestRequestIdSharing(t *testing.T) { // 合法 UUID → 沿用(归一小写) if got := normalizeRequestId("6F0A1B2C-3D4E-4F5A-8B6C-7D8E9F0A1B2C"); got != "6f0a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c" { t.Fatalf("合法 UUID 应沿用为 request_id,got %q", got) } // 非法 → 走服务端自生成分支 if normalizeRequestId("evil\ninjection") != "" { t.Fatal("非法值应被丢弃并触发服务端自生成") } id := shortID(6) if len(id) != 12 { t.Fatalf("服务端自生成 request_id 应为 12 位 hex,got %q", id) } for i := 0; i < len(id); i++ { c := id[i] if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) { t.Fatalf("服务端自生成 request_id 含非 hex 字符: %q", id) } } } // 设备级 client_id:与 request_id 同一 UUID 白名单口径——合法归一小写后绑定,非法/缺失丢弃(不自生成) func TestClientIdHeaderNormalize(t *testing.T) { if got := normalizeRequestId("6F0A1B2C-3D4E-4F5A-8B6C-7D8E9F0A1B2C"); got != "6f0a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c" { t.Fatalf("合法 X-Client-Id 应归一沿用,got %q", got) } for _, bad := range []string{"", "device-001", "a1b2c3d4e5f6", "evil\ninjection"} { if normalizeRequestId(bad) != "" { t.Fatalf("非法 X-Client-Id %q 应被丢弃", bad) } } } func TestViewSetsServerSentMsHeader(t *testing.T) { rec := httptest.NewRecorder() ctx := &Context{Resp: rec, RespData: Map{"status": 0}} before := time.Now().UnixMilli() ctx.View() after := time.Now().UnixMilli() h := rec.Header().Get("X-Server-Sent-Ms") if h == "" { t.Fatal("X-Server-Sent-Ms 未设置") } ms, err := strconv.ParseInt(h, 10, 64) if err != nil { t.Fatalf("X-Server-Sent-Ms=%q 非毫秒时间戳: %v", h, err) } if ms < before || ms > after { t.Fatalf("X-Server-Sent-Ms=%d 不在 [%d,%d] 区间", ms, before, after) } if !strings.Contains(rec.Body.String(), "\"status\":0") { t.Fatalf("响应体异常: %s", rec.Body.String()) } }