- 抽出 rond.MatchPlaces(stored, backup) PlaceMatch:坐标优先、退到「同名且相距 ≤1km」、 多值索引 + 认领即标记已用;DiffPlaces 与 web.mergePlaces 共用它。 - mergePlaces 原来是单槽位 byCoord/byName 自己再配一遍:同坐标/同名的多条备份地点全并到同一行 站内地点,到访被挂到错的分店(实测「茶颜悦色」两家分店相隔 8.3km,到访却指着同一条)。 现在直接用配对结果,再叠加 plan.AddPlaces 的新插入。 - loadStoredPlaces 与 mergePlaces 的地点查询都加 ORDER BY id:候选多于一个时靠顺序定谁配谁, 两侧必须一致,否则配对会飘。 - 删掉只写不读的 PlacePatch.CoordKey、ActivityPatch.Key(容易被误当成查找键)。 - 用例:TestMatchPlacesKeepsDuplicateNameGroupsDistinct(同一坐标/邻近同名都要一条配一条)、 TestMergePlacesMapsSameNameLocationsOneToOne(真库 + 真事务,两条同名备份地点必须映射到两条不同 地点);两处都做了变异验证(认领不标记已用 / 恢复单槽位映射 → 对应用例变红)。 - 探针库复测(从 dump 恢复、不动本地真库,49 组冲突全「用实机」):到访挂错地点(>1km)0/953 条、 地点被搬动 >1km 0 个;合并后再比对三类全 0(到访 0/47/0、地点 0/4/0、活动 0/0/0), 再点合并「按当前的选择没有要写入的改动」。全量验收 293 PASS / 0 FAIL / 0 SKIP。 已知取舍:勾「用实机」但没勾「并进来」时,若该到访在实机上的地点站内没有、且按 1km 规则也配不上, place_id 会被置空(旧行为是挂到一条远方的同名地点上——那正是这次要去掉的错配)。
86 lines
3.7 KiB
Go
86 lines
3.7 KiB
Go
package rond
|
|
|
|
import "testing"
|
|
|
|
// 地点/活动冲突此前一条用例都没有:只造过「新增」,所以 placePick / activityPick
|
|
// 与 UpdPlaces / UpdActs 分支始终没被执行过。差异页线上确实会提交这两类选择。
|
|
func TestBuildMergePlanPlaceClash(t *testing.T) {
|
|
keyA := PlaceCoordKey(30.123456, 120.654321)
|
|
keyB := PlaceCoordKey(31.000001, 121.000002)
|
|
keyC := PlaceCoordKey(32.000003, 122.000004)
|
|
pd := PlaceDiff{Changed: []PlaceConflict{
|
|
{Backup: Location{SrcPK: 11, Name: "备份名A"}, DB: StoredPlace{SrcPK: 1, Key: keyA, Name: "站内名A"}},
|
|
{Backup: Location{SrcPK: 12, Name: "备份名B"}, DB: StoredPlace{SrcPK: 2, Key: keyB, Name: "站内名B"}},
|
|
{Backup: Location{SrcPK: 13, Name: "备份名C"}, DB: StoredPlace{SrcPK: 3, Key: keyC, Name: "站内名C"}},
|
|
}}
|
|
p := BuildMergePlan(VisitDiff{}, pd, ActivityDiff{}, Choices{
|
|
PlaceClash: map[string]string{keyA: PickBackup, keyB: PickDB},
|
|
})
|
|
if len(p.UpdPlaces) != 1 {
|
|
t.Fatalf("只有选了「用实机」的地点该更新: %+v", p.UpdPlaces)
|
|
}
|
|
// 带的是备份侧那条的整份值;「改库里的哪一行」由配对(MatchPlaces)决定,计划里不再存键
|
|
if p.UpdPlaces[0].L.Name != "备份名A" {
|
|
t.Fatalf("应带备份侧的值: %+v", p.UpdPlaces[0])
|
|
}
|
|
if len(p.UpdVisits)+len(p.AddVisits)+len(p.UpdActs)+len(p.AddActs) != 0 {
|
|
t.Fatal("没勾新增就不该带出到访/活动改动")
|
|
}
|
|
}
|
|
|
|
func TestBuildMergePlanActivityClash(t *testing.T) {
|
|
ad := ActivityDiff{Changed: []ActivityConflict{
|
|
{Backup: Activity{SrcPK: 21, Name: "散步"}, DB: StoredActivity{SrcPK: 3, Name: "走路", UUID: uuidC}},
|
|
{Backup: Activity{SrcPK: 22, Name: "跑步"}, DB: StoredActivity{SrcPK: 4, Name: "跑步"}},
|
|
{Backup: Activity{SrcPK: 23, Name: "游泳"}, DB: StoredActivity{SrcPK: 5, Name: "游泳", UUID: uuidA}},
|
|
}}
|
|
p := BuildMergePlan(VisitDiff{}, PlaceDiff{}, ad, Choices{
|
|
ActClash: map[string]string{uuidC: PickBackup, "n:跑步": PickSkip},
|
|
})
|
|
if len(p.UpdActs) != 1 {
|
|
t.Fatalf("只有选了「用实机」的活动该更新: %+v", p.UpdActs)
|
|
}
|
|
// 改名也能对上:选择是按「UUID(没有才退回名称)」提交的,配对早就发生在比对阶段
|
|
if p.UpdActs[0].A.Name != "散步" {
|
|
t.Fatalf("应带备份侧的值: %+v", p.UpdActs[0])
|
|
}
|
|
}
|
|
|
|
// 删除是逐条勾选的事:没勾的「仅站内有」绝不顺手删掉,勾了的才进计划。
|
|
func TestBuildMergePlanDeletesOnlyWhenChecked(t *testing.T) {
|
|
vd := VisitDiff{Removed: []StoredVisit{{SrcPK: 51}, {SrcPK: 52}}}
|
|
pd := PlaceDiff{Removed: []StoredPlace{{SrcPK: 31}, {SrcPK: 32}}}
|
|
ad := ActivityDiff{Removed: []StoredActivity{{SrcPK: 41}, {SrcPK: 42}}}
|
|
p := BuildMergePlan(vd, pd, ad, Choices{
|
|
DelVisits: map[int]bool{52: true},
|
|
DelPlaces: map[int]bool{31: true},
|
|
DelActs: map[int]bool{42: true},
|
|
})
|
|
if len(p.DelVisits) != 1 || p.DelVisits[0].SrcPK != 52 {
|
|
t.Fatalf("只该删勾了的那条到访: %+v", p.DelVisits)
|
|
}
|
|
if len(p.DelPlaces) != 1 || p.DelPlaces[0].SrcPK != 31 {
|
|
t.Fatalf("只该删勾了的那个地点: %+v", p.DelPlaces)
|
|
}
|
|
if len(p.DelActs) != 1 || p.DelActs[0].SrcPK != 42 {
|
|
t.Fatalf("只该删勾了的那个活动: %+v", p.DelActs)
|
|
}
|
|
}
|
|
|
|
func TestAppendMissingDedupesBySrcPK(t *testing.T) {
|
|
places := appendMissingPlaces(
|
|
[]Location{{SrcPK: 1, Name: "甲"}},
|
|
[]Location{{SrcPK: 1, Name: "甲"}, {SrcPK: 2, Name: "乙"}},
|
|
)
|
|
if len(places) != 2 || places[1].SrcPK != 2 {
|
|
t.Fatalf("同 src_pk 不该重复加入: %+v", places)
|
|
}
|
|
acts := appendMissingActs(
|
|
[]Activity{{SrcPK: 7, Name: "通勤"}},
|
|
[]Activity{{SrcPK: 7, Name: "通勤"}, {SrcPK: 8, Name: "散步"}},
|
|
)
|
|
if len(acts) != 2 || acts[1].SrcPK != 8 {
|
|
t.Fatalf("同 src_pk 不该重复加入: %+v", acts)
|
|
}
|
|
}
|