package middleware import ( "net/http" "testing" ) // 回归:inferAuditFields 不能越界。 // // 线上事故:/api/v1/hosts/2 拆出 4 段(api v1 hosts 2), // 旧实现 `if len>=4 { resource=segments[3]; resourceID=segments[4] }` // 直接 index out of range [4] with length 4 → 中间件 panic → // 该路径上的所有写请求 500 且不落审计。 // 这类 bug 编译期发现不了,必须用表驱动测试把各段数都覆盖一遍。 func TestInferAuditFieldsNoPanic(t *testing.T) { cases := []string{ "/api/v1", // 2 段 "/api/v1/hosts", // 3 段 "/api/v1/hosts/2", // 4 段 ← 原 bug 触发点 "/api/v1/hosts/2/validate", // 5 段 "/api/v1/deployments/1/deploy", // 5 段 "/api/v1/logs/policy-groups/3", // 5 段 "/api/v1/collectors/2/install", // 5 段 "/", // 1 段 "", // 0 段 } for _, path := range cases { func() { defer func() { if r := recover(); r != nil { t.Fatalf("inferAuditFields(%q) panic: %v", path, r) } }() inferAuditFields(http.MethodPost, path) inferAuditFields(http.MethodPut, path) inferAuditFields(http.MethodDelete, path) }() } } // 4 段路径必须能解析出 module 与 resource,且 resourceID 为空(不是越界值) func TestInferAuditFieldsFourSegments(t *testing.T) { module, _, resource, resourceID := inferAuditFields(http.MethodPut, "/api/v1/hosts/2") if module != "hosts" { t.Fatalf("module = %q,期望 hosts", module) } if resource != "2" { // 4 段时第 4 段按位置是 resource t.Fatalf("resource = %q,期望 2", resource) } if resourceID != "" { t.Fatalf("4 段路径不应解析出 resourceID,实际 %q", resourceID) } } // 5 段路径要正确解出 resourceID func TestInferAuditFieldsFiveSegments(t *testing.T) { module, action, resource, resourceID := inferAuditFields(http.MethodPost, "/api/v1/logs/sources/7") if module != "logs" || resource != "sources" || resourceID != "7" { t.Fatalf("解析错误: module=%q resource=%q resourceID=%q", module, resource, resourceID) } if action != "create" { t.Fatalf("action = %q,期望 create", action) } } // 语义化动作不能被位置解析覆盖 func TestInferAuditFieldsSemanticActions(t *testing.T) { cases := map[string]string{ "/api/v1/deployments/1/deploy": "deploy", "/api/v1/deployments/1/stop": "stop", "/api/v1/collectors/2/install": "install", "/api/v1/collectors/2/uninstall": "uninstall", "/api/v1/users/3/reset-password": "reset-password", "/api/v1/hosts/2/validate": "validate", "/api/v1/storage/1/test": "test", } for path, want := range cases { if _, action, _, _ := inferAuditFields(http.MethodPost, path); action != want { t.Fatalf("%s action = %q,期望 %q", path, action, want) } } } // shouldAudit:只读接口不记,但部署/安装类动作必须记 func TestShouldAudit(t *testing.T) { mustAudit := []string{ "/api/v1/deployments/1/deploy", "/api/v1/collectors/2/install", "/api/v1/hosts/2/validate", "/api/v1/storage", } for _, p := range mustAudit { if !shouldAudit(p) { t.Fatalf("%q 应当被审计", p) } } mustSkip := []string{ "/api/v1/logs/query", "/api/v1/logs/stats", "/api/v1/health", "/api/v1/metrics", } for _, p := range mustSkip { if shouldAudit(p) { t.Fatalf("%q 不应被审计(只读/噪音)", p) } } }