Files
hotime/request_trace_test.go
T
hoteas 2cd5d64818 feat(log): 设备级 client_id 追踪,X-Client-Id 头校验后绑定请求级与访问日志
- 与 request_id 同一 UUID 白名单校验,非法/缺失丢弃不自生成
- CORS Allow-Headers 增加 X-Client-Id

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 08:13:01 +08:00

106 lines
3.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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_idgot %q", got)
}
// 非法 → 走服务端自生成分支
if normalizeRequestId("evil\ninjection") != "" {
t.Fatal("非法值应被丢弃并触发服务端自生成")
}
id := shortID(6)
if len(id) != 12 {
t.Fatalf("服务端自生成 request_id 应为 12 位 hexgot %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())
}
}