// Copyright 2026 tamakyi. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package route import ( "fmt" "net/http/httptest" "testing" "time" "github.com/stretchr/testify/require" ) // TestAccessLogNeverRecordsQuery 是本轮修复的回归测试。 // // 修复前用的是 flamego.Classic(),它自带的 Logger() 记录 RequestURI(含 query), // 于是 ?t=<私密题钥匙> 与 ?code=<改密凭证> 原样进了日志(本地实测: // `path=/_/tblocal/4?t=SECRETTOKEN123`)。拿到这些值可以直接读私密提问或重置密码。 // // 现在要求:不论请求带什么 query,日志里只有 URL.Path。 func TestAccessLogNeverRecordsQuery(t *testing.T) { cases := []struct { name string target string want string }{ {"私密提问 token", "/_/alice/42?t=SECRETTOKEN123", "/_/alice/42"}, {"改密凭证", "/recover/reset?code=SECRETCODE456", "/recover/reset"}, {"探索页搜索词", "/explore?q=%E7%A7%98%E5%AF%86&sort=latest", "/explore"}, {"分页参数", "/explore?page=2", "/explore"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { req := httptest.NewRequest("GET", c.target, nil) fields := accessLogFields(req, 200, 3*time.Millisecond) got, ok := fields["path"].(string) require.True(t, ok, "path 字段必须是字符串") require.Equal(t, c.want, got) require.NotContains(t, got, "?", "path 里不允许出现 query 分隔符") // 兜底:整个字段集合里都不许出现被拦截的凭证 for k, v := range fields { require.NotContains(t, fmt.Sprint(v), "SECRET", "字段 %q 泄露了 query 内容:%v", k, v) } }) } } // TestAccessLogSkipsStaticAssets 保证静态资源的成功请求不进日志(噪音控制), // 但出错时仍然要记 —— 主题图 / 字体 404 正是靠这条排查的。 func TestAccessLogSkipsStaticAssets(t *testing.T) { require.False(t, shouldAccessLog("/static/css/base.css", 200)) require.False(t, shouldAccessLog("/static/css/base.css", 304)) require.True(t, shouldAccessLog("/static/css/base.css", 404), "静态资源 404 必须记录,否则排查不了图裂") require.True(t, shouldAccessLog("/static/css/base.css", 500)) // 业务路径一律记录 require.True(t, shouldAccessLog("/", 200)) require.True(t, shouldAccessLog("/_/alice", 302)) require.True(t, shouldAccessLog("/staticish", 200), "只有 /static/ 前缀才算静态资源") }