启动模板自检 + 站长导航放行 + 统计脚本关口收紧

排查:/admin/settings 渲染到一半就断
- 现象:页面在「统计脚本」那张卡片处截断,没有保存按钮、没有 </form></html>,而 HTTP 仍是 200。
- 根因:跑的是旧二进制(改动前编译),磁盘上的模板却是新的。debug=true 下 pageSet()
  每次渲染都从磁盘重解析模板,于是新 settings.html 引用了旧 setting.Settings 里没有的
  TrackEnabled —— 执行期报错,而 render 只把错误写进日志,客户端只看到半张页面。
- 判据:strings <二进制> | grep -c cleanTrackScript(旧 0 / 新 2)。别用 strings | grep
  TrackScript —— config 里本来就有这个名字,旧二进制也命中,是个假信号。

启动自检(防复发)
- template_smoke.go:pageProbe 给 24 个页面各配一份最小可用数据,New() 里全部渲染一遍,
  任一页失败就拒绝启动。pageProbe 与 pageFiles 双向校验,新加页面必须补探针,
  漏配按失败处理(不是省事)。
- smokeOne 用 recover 兜住方法里的 panic,报「哪一页坏了」而不是崩栈。
- 实测:往 settings.html 插一个不存在的字段 → 启动即报
  「模板自检未通过…admin/settings: can't evaluate field …」,退出码 1。
- 门禁 template_smoke_test.go:一条跑全量自检,一条变异验证(探针换成字段更少的类型
  必须报错且点名该页)。

站长导航被页面开关误伤
- guardPage 只拦访客(站长始终能访问全部公开页),但导航写的是 {{if .Settings.HasPage "x"}},
  没有 IsAdmin 分支 ⇒ 页面开关一关,站长自己也只剩「主页」一个入口 —— 能访问却点不到。
  改成 {{if or .IsAdmin (.Settings.HasPage "x")}}(map/places/timeline/stats/report 五处)。
- 门禁 nav_visibility_test.go:三向不变量(站长仍有全部入口 / 访客一个都没有 /
  全开放时访客也看得到),做过变异验证。
- 访客侧「只有主页」是按后台配置应有的状态,没有动数据。

统计脚本关口收紧(cleanTrackScript)
- src= 只在 <script …> 开标签内部找。原来对整段做 strings.Index,脚本体里一句
  `img.src=u` 就被命中、整段被判危险而清空 —— 页面照样提示「设置已保存」,站长看不出没挂上。
- 开闭标签从「只拒 shut > open」改成严格配平:末尾未闭合的 <script> 会把页面收尾整段吞进脚本。
- 不再静默:签名改成 (script, warn),说明码经 ?warn= 回显成人话;判为危险时整段不采纳、
  库里原脚本与开关都不动(把站长手上好用的脚本一并清掉更糟)。
- 门禁 TestCleanTrackScript 重写:逐条边界 + 定义性不变量(采纳了非空结果时,
  里面开闭标签数必定相等)+ TestTrackWarnMessage(未识别码不得回显)。变异验证做过。
- 文案订正:settings.html 的「conf/app.ini 的 track_script 会作为这里的默认值」有歧义 ——
  它不回填文本框,而是「这个框留空时、注入那一刻才回落到 ini」。

其余
- bin/ 目录删除(15 个 exe / 412MB),二进制只留仓库根 rond.exe;
  技能 rond-web-verify 与 memory details 十八里 bin/rond.exe 的写法同步改掉。
- 验收:./scripts/verify.sh 全绿且 0 skip(PASS=346 FAIL=0 SKIP=0)。
This commit is contained in:
tamakyi
2026-10-09 11:06:41 +08:00
parent 4015ddf373
commit d4e4257a23
12 changed files with 566 additions and 77 deletions
+77
View File
@@ -0,0 +1,77 @@
package web
import (
"html/template"
"path/filepath"
"strings"
"testing"
"rond-with-you/internal/config"
)
// TestSmokeRenderAllPages 把启动自检搬进 go test:每个页面都要能用「最小可用数据」渲染到底。
//
// 比 TestPageTemplatesParse 更强的地方在于**执行**:模板能解析不代表渲染得完,
// 「字段不存在 / 对着 nil 解引用」只在执行期暴露,而线上表现是「HTTP 200 + 半张页面」。
// 2026-10-09 的 /admin/settings 截断就是这么来的(新模板引用了旧结构体里没有的字段)。
func TestSmokeRenderAllPages(t *testing.T) {
srv := &Server{cfg: &config.Config{}}
pages := make(map[string]pageSet, len(pageFiles))
for key, def := range pageFiles {
tpl, err := template.New(filepath.Base(def.files[0])).Funcs(templateFuncs()).
ParseFS(tmplFS, def.files...)
if err != nil {
t.Fatalf("解析模板 %s 失败: %v", key, err)
}
pages[key] = pageSet{t: tpl, entry: def.entry}
}
srv.pages = pages
if err := srv.smokeRender(); err != nil {
t.Fatal(err)
}
// 定义性不变量:pageFiles 里每加一个页面,就必须在 pageProbe 里配一份自检数据,
// 否则那一页会悄悄逃过自检(smokeRender 会把缺项报成失败,这里再独立钉一次)。
for key := range pageFiles {
if _, ok := pageProbe[key]; !ok {
t.Errorf("页面 %s 没有自检数据(pageProbe 缺项)——新加页面时要一起补", key)
}
}
for key := range pageProbe {
if _, ok := pageFiles[key]; !ok {
t.Errorf("pageProbe 里的 %s 不是已注册页面(改名后忘了同步?)", key)
}
}
}
// TestSmokeRenderCatchesFieldDrift 变异验证:把页面数据换成**字段更少**的类型时必须报错。
//
// 光断言「当前代码自检通过」证明不了自检有效——它可能只是什么都没检查。
// 这里用 Page 顶替 SettingsData(模板引用的 .Modules / .S3Info 都不在 Page 上),
// 自检必须红,且错误信息里要能看到是哪一页。
func TestSmokeRenderCatchesFieldDrift(t *testing.T) {
srv := &Server{cfg: &config.Config{}}
pages := make(map[string]pageSet, len(pageFiles))
for key, def := range pageFiles {
tpl, err := template.New(filepath.Base(def.files[0])).Funcs(templateFuncs()).
ParseFS(tmplFS, def.files...)
if err != nil {
t.Fatalf("解析模板 %s 失败: %v", key, err)
}
pages[key] = pageSet{t: tpl, entry: def.entry}
}
srv.pages = pages
orig := pageProbe["admin/settings"]
pageProbe["admin/settings"] = func() any { return probePage("admin") }
t.Cleanup(func() { pageProbe["admin/settings"] = orig })
err := srv.smokeRender()
if err == nil {
t.Fatal("页面数据缺字段时自检必须报错,否则它什么也挡不住")
}
if !strings.Contains(err.Error(), "admin/settings") {
t.Errorf("自检报错要指名是哪个页面,实际为:%v", err)
}
}