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) } }