Files
hotime/request_trace_test.go
T
hoteas d3e71decf4 feat(log): 前后端共用 request_id 与四时间点时钟校准
- X-Request-Id 请求头带合法 UUID 则沿用为 request_id(严格格式白名单防注入),否则自生成 12 位 hex
- 响应头新增 X-Server-Received-Ms / X-Server-Sent-Ms 供前端时钟校准
- CORS Allow-Headers 增加 X-Request-Id,Expose-Headers 显式列出(credentials 下不认 *)

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

94 lines
2.7 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)
}
}
}
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())
}
}