package handler import ( "os" "regexp" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // --------------------------------------------------------------------------- // 覆盖目标:审计 M-4 的路由 wiring 静态检查 —— // `/api/product/fetchInitialCredentials` 必须挂载 `serverCtx.JwtAuth` 中间件, // 且必须位于 /api/product 前缀组内,不能被错放到其他无鉴权/错前缀块中。 // // 为什么这条 wiring 需要独立钉死: // 1. routes.go 由 goctl 生成,回归时若有人用 `goctl api go -api ... -dir .` // 覆写此文件,可能把新路由吞掉或挪到无 JwtAuth 包裹的块(例如误标 // `@handler` 在 pub 组)。静态检查能最早拦截。 // 2. M-4 的安全假设是"只有超级管理员能消费 ticket"。RequireSuperAdmin 依赖 // JwtAuth 中间件写入 UserDetails;若 JwtAuth 被去掉,handler 自身仅能回 // 401(无 ctx),**但任何持有前端伪造 UserDetails 注入方式**的攻击者都会 // 直接绕过 —— wiring 锚点确保这条防线永远在最外层。 // --------------------------------------------------------------------------- // TC-0967: routes.go 必须把 /product/fetchInitialCredentials 挂到 JwtAuth 包裹块, // 并且处于 /api/product 前缀下(而不是 /api 或其他无超管审查的位置)。 func TestRoutes_FetchInitialCredentialsJwtAuthWired(t *testing.T) { raw, err := os.ReadFile("./routes.go") require.NoError(t, err, "必须能读到 internal/handler/routes.go") src := string(raw) // go-zero 生成的 AddRoutes 块结构: // server.AddRoutes( // rest.WithMiddlewares([]rest.Middleware{serverCtx.XxxMiddleware}, []rest.Route{ // {...Path: "/a"...}, // {...Path: "/fetchInitialCredentials"...}, // ... // }...), // rest.WithPrefix("/api/product"), // ) // 我们需要: // 1. 定位到 /fetchInitialCredentials 所在的 AddRoutes 块整段; // 2. 从块里摘出 rest.Middleware{...} 列表做字符串断言; // 3. 从块里摘出 rest.WithPrefix("...") 的 prefix 做断言。 // 简单起见,按"向上/向下扩展"的方式提取:以 "server.AddRoutes(" 为起点、往下到首个 // "rest.WithPrefix(\"...\")" 为止的整段。 addRoutesBlockRe := regexp.MustCompile( `(?s)server\.AddRoutes\(\s*rest\.WithMiddlewares\(\s*\[\]rest\.Middleware\{([^}]*)\}[\s\S]*?"/fetchInitialCredentials"[\s\S]*?rest\.WithPrefix\("([^"]+)"\)`, ) m := addRoutesBlockRe.FindStringSubmatch(src) require.NotEmpty(t, m, "routes.go 中 /fetchInitialCredentials 必须位于 server.AddRoutes(rest.WithMiddlewares(...), rest.WithPrefix(...)) 结构块里;未匹配说明路由被剥离或迁移到其他结构") middlewaresList := m[1] prefix := m[2] assert.Contains(t, middlewaresList, "serverCtx.JwtAuth", "M-4:/product/fetchInitialCredentials 必须挂载 JwtAuth 中间件,否则 RequireSuperAdmin 的上下文前置条件不成立;实际中间件列表=%q", middlewaresList) assert.Equal(t, "/api/product", prefix, "M-4:/fetchInitialCredentials 必须位于 /api/product 前缀组下;实际 prefix=%q", prefix) } // TC-0968: 防御性 wiring 检查 —— 绝不允许把 fetchInitialCredentials 挂到任何 // *非 JwtAuth* 的中间件块中。此用例是 TC-0967 的"反证":哪怕有人把 JwtAuth 改名 // 成另一条鉴权中间件但语义错位,也会被这里拦住。 func TestRoutes_FetchInitialCredentialsNotInRateLimitGroup(t *testing.T) { raw, err := os.ReadFile("./routes.go") require.NoError(t, err) src := string(raw) // 检查"限流中间件包裹块内"是否误引入了 fetchInitialCredentials for _, name := range []string{"AdminLoginRateLimit", "ProductLoginRateLimit", "RefreshTokenRateLimit", "SyncRateLimit"} { re := regexp.MustCompile(`(?s)rest\.WithMiddlewares\(\s*\[\]rest\.Middleware\{[^}]*?` + name + `[^}]*?\}[^)]*?"/fetchInitialCredentials"`) assert.False(t, re.MatchString(src), "M-4:/fetchInitialCredentials 绝不能被挂到 %s 中间件组(会绕过 JwtAuth / RequireSuperAdmin)", name) } }