audit-2026-09-21.md 里的后端条目落地。每条都配了回归守卫,并逐条做过变异验证 (注入原 bug → 确认按正确理由变红 → 还原 → 变绿)。 安全 - CSRF 只加同源 Origin/Referer 守卫(csrf.go),不动模板 - 会话 cookie 的 Secure 按请求判断;两步验证两闸(loginGuard + 验证码一次性) - 跳转收口到 safeInternalPath;clientIP 只认 XFF 最后一段 - GPX 按属主圈定且查 RowsAffected;下载名走 downloadDisposition - 上传口统一 MaxBytesReader;落盘后的失败路径清掉半成品 数据一致性 - 区域判定两端各过一遍再 AND(apiTrack 与 movementsWhere 对齐) - Filter.Verdict 全域一致(下推到 visitsWhere);聚合刷新必须返回 error - 附属数据改挂坐标键,Go 与 SQL 两套表达式逐位一致 - 脏备份的伪时间/越界坐标不再进领域模型;rows 循环统一 drainRows - 合并与导出:writeRawRow 上抛错误、悬空引用清理、原生列补全 性能 - placeCols 相关子查询带 dataset_id 前导列;findSourceArchive 先按大小筛 - 批量勾选改 ANY($3::bigint[]);fillupCache 加上限;同一查询不再查两遍 另:新增「全新库」冒烟测试 —— 空库跑完 Migrate 再用应用侧读查询真打一遍, 迁移块排到依赖表之前这种错只有新库会炸,老库毫无症状。
77 lines
3.0 KiB
Go
77 lines
3.0 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"rond-with-you/internal/stats"
|
|
)
|
|
|
|
// D6 对账:同一套区域筛选下,轨迹图层的谓词(照 apiTrack 的写法,用 regionSQL 拼
|
|
// pf / pt 两段 AND)与里程统计用的 movementsWhere 必须选出同一批位移。
|
|
//
|
|
// 曾经一个是「两端都要命中」、一个是「任一端命中」:白名单下同一段里程在图层里
|
|
// 画不出来、却算进了总里程(黑名单下两种写法恰好等价,所以这个洞只在白名单露出来)。
|
|
// 判据用真实的两份实现做 A/B,而不是各写一遍 SQL 互相比对——那样只是把漂移抄了两遍。
|
|
func TestTrackAndMileageAgreeOnRegionFilter(t *testing.T) {
|
|
f := newLoginFixture(t)
|
|
pg, ctx := f.pg, context.Background()
|
|
|
|
// 独立数据集、且 is_active 保持默认 false:统计夹具是「WHERE is_active LIMIT 1」,
|
|
// 置 true 会把它选走,串到别人的用例上(本轮在 ingest 那边刚踩过一次)。
|
|
var dsID int64
|
|
if err := pg.QueryRowContext(ctx, `INSERT INTO datasets (user_id, original_name, sha256, status)
|
|
VALUES ($1,'d6-region-consistency','','done') RETURNING id`, f.uid).Scan(&dsID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { pg.Exec(`DELETE FROM datasets WHERE id=$1`, dsID) })
|
|
|
|
const inRegion, outRegion = "测试省甲", "测试省乙"
|
|
place := func(src int, prov string) int64 {
|
|
t.Helper()
|
|
var id int64
|
|
if err := pg.QueryRowContext(ctx, `INSERT INTO places
|
|
(dataset_id, src_pk, name, province, city, lat, lon)
|
|
VALUES ($1,$2,$3,$4,$5,24.0,109.0) RETURNING id`,
|
|
dsID, src, "p", prov, prov).Scan(&id); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return id
|
|
}
|
|
in1, out, in2 := place(1, inRegion), place(2, outRegion), place(3, inRegion)
|
|
|
|
move := func(src int, from, to int64, km float64) {
|
|
t.Helper()
|
|
if _, err := pg.ExecContext(ctx, `INSERT INTO movements
|
|
(dataset_id, user_id, src_pk, from_place_id, to_place_id, distance_km, started_at)
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7)`, dsID, f.uid, src, from, to, km, time.Now()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
move(1, in1, out, 10) // 一端在区域外:白名单下两边都不该算
|
|
move(2, in1, in2, 20) // 两端都在区域内
|
|
|
|
args := []any{dsID}
|
|
regSQL := regionSQL("pf", []string{inRegion}, false, &args) +
|
|
regionSQL("pt", []string{inRegion}, false, &args)
|
|
var trackKm float64
|
|
if err := pg.QueryRowContext(ctx, `SELECT COALESCE(sum(m.distance_km),0) FROM movements m
|
|
JOIN places pf ON pf.id = m.from_place_id AND pf.lat IS NOT NULL
|
|
JOIN places pt ON pt.id = m.to_place_id AND pt.lat IS NOT NULL
|
|
WHERE m.dataset_id = $1`+regSQL, args...).Scan(&trackKm); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ov, err := (&stats.DB{DB: pg}).Overview(ctx, stats.Filter{DatasetID: dsID, Regions: []string{inRegion}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if trackKm != 20 {
|
|
t.Fatalf("前提不成立:图层侧手写查询应得 20km,实际 %.2f", trackKm)
|
|
}
|
|
if ov.DistanceKm != trackKm {
|
|
t.Errorf("轨迹图层与里程统计口径不一致:图层 %.2f km,统计 %.2f km", trackKm, ov.DistanceKm)
|
|
}
|
|
}
|