diff --git a/internal/web/frontend_fixes_test.go b/internal/web/frontend_fixes_test.go new file mode 100644 index 0000000..94ff565 --- /dev/null +++ b/internal/web/frontend_fixes_test.go @@ -0,0 +1,200 @@ +package web + +import ( + "os" + "strings" + "testing" +) + +// ---------- F5:地图脚本里的 localStorage 访问必须走兜底 ---------- +// +// 隐私模式 / 禁用 Cookie 的浏览器里 localStorage 读写都会抛异常,裸调用会把整段脚本打断 +// (地图白屏、图层不出来,且控制台之外看不出来)。统一走 lsGet/lsSet。 +func TestMapLocalStorageGoesThroughGuard(t *testing.T) { + b, err := staticFS.ReadFile("static/js/rond-map.js") + if err != nil { + t.Fatalf("读取 rond-map.js 失败: %v", err) + } + src := string(b) + if !strings.Contains(src, "function lsGet(k)") || !strings.Contains(src, "function lsSet(k, v)") { + t.Fatal("rond-map.js 里找不到 lsGet/lsSet 兜底(写法变了,这条守卫失效)") + } + if n := strings.Count(src, "window.localStorage."); n != 2 { + t.Errorf("裸 localStorage 访问应只剩 lsGet/lsSet 里那 2 处,实际 %d 处"+ + "(其余一律要走 lsGet/lsSet,否则隐私模式下整段脚本会抛异常)", n) + } +} + +// ---------- F6:配置未就绪时的第二次 preview 不能丢点 ---------- +func TestMapPreviewQueuesPendingPoints(t *testing.T) { + b, err := staticFS.ReadFile("static/js/rond-map.js") + if err != nil { + t.Fatalf("读取 rond-map.js 失败: %v", err) + } + src := string(b) + body := funcBody(src, "preview: function (opts)") + if body == "" { + t.Fatal("在 rond-map.js 里找不到 preview(写法变了,这条守卫失效)") + } + queue := strings.Index(body, "previewQueue[opts.id] = points;") + live := strings.Index(body, "if (live) {") + if queue < 0 || live < 0 || queue > live { + t.Error("preview:必须在 `if (live)` 之前就把本次的点记进 previewQueue," + + "否则配置未就绪时第二次 preview 的点会被静默丢掉") + } + if !strings.Contains(body, "if (previewQueue[opts.id]) points = previewQueue[opts.id];") { + t.Error("preview:配置回调里没把 previewQueue 排队的点取回来画") + } + if strings.Contains(src, "previewPending") { + t.Error("rond-map.js 里不该再有 previewPending(已由 previewQueue 取代)") + } +} + +// ---------- F8:EXIF 全 0 时间不能变成 1899-11-30 ---------- +func TestExifRejectsOutOfRangeDate(t *testing.T) { + b, err := staticFS.ReadFile("static/js/photo-exif.js") + if err != nil { + t.Fatalf("读取 photo-exif.js 失败: %v", err) + } + src := string(b) + guard := strings.Index(src, "Y >= 1900") + mk := strings.Index(src, "res.shot = new Date(Y, Mo - 1, D, H, Mi, S);") + flag := strings.Index(src, "res.hasTime = true;") + if guard < 0 || mk < 0 || flag < 0 || guard > mk || guard > flag { + t.Error("photo-exif.js:拍摄时间必须先过范围校验再赋给 res.shot / hasTime" + + "(否则「0000:00:00」会被 Date 归一成 1899-11-30,被当成到达时间写进库)") + } +} + +// ---------- F9:findExifItemID 读 count 前必须卡住宽度 ---------- +func TestExifItemIDChecksBoundsBeforeRead(t *testing.T) { + b, err := staticFS.ReadFile("static/js/photo-exif.js") + if err != nil { + t.Fatalf("读取 photo-exif.js 失败: %v", err) + } + body := funcBody(string(b), "function findExifItemID(") + if body == "" { + t.Fatal("在 photo-exif.js 里找不到 findExifItemID(写法变了,这条守卫失效)") + } + bounds := strings.Index(body, "p + 2 > u8.length") + read := strings.Index(body, "dv.getUint16(p)") + if bounds < 0 || read < 0 || bounds > read { + t.Error("findExifItemID:读 count 之前必须按 ver 卡住宽度(p+2 / p+4)——" + + "末尾恰好相等(start+4 == length)时旧守卫会放行,越界会抛 RangeError") + } +} + +// ---------- F16:数据编辑页每个筛选表单都要带上全部筛选条件 ---------- +// +// 三个表单各自只放自己那几个控件,其余条件靠隐藏域带着;漏一个,用户一提交 +// 就会把别的列表的筛选悄悄清掉(原来「到访」表单只带 vq+back)。 +func TestEditSearchFormsCarryAllFilters(t *testing.T) { + b, err := tmplFS.ReadFile("templates/admin/edit.html") + if err != nil { + t.Fatalf("读取 edit.html 失败: %v", err) + } + src := string(b) + const open = `