备份(.rondbackup)与迷雾(.fwss)各加一层「先看差异再动手」: - 迷雾:上传先出差异页,说清叠加能补多少、整体替换会丢多少,并在真实底图上 用绿/灰/红标出会新增 / 不变 / 会丢的区域 - 备份:与当前数据集按 UUID 逐条比对(rond 每次导出都会重排 Z_PK,编号认不出人), 新增合入、冲突逐条选、仅站内有逐条勾选删除,全在一个事务里执行; 合入的行写回 entity_raw,保证再传同一份备份时差异归零 - 地点按坐标对齐:站点的「补全地址」会把地点改名,拿名称当键会让一整个地点的 到访全变成假冲突 - 附属数据(备注/结论/访客票/图片)从 dataset_id+src_pk 换成坐标键,换数据集不再失联; 访客票没有 dataset_id,回填按活跃数据集认领 - 修正 contentHash 拿 Z_PK 当指纹的隐患,以及两处「src_pk 跨快照稳定」的错误注释 导出格式(.rondbackup / .fwss)保持原样,往返测试通过。
77 lines
2.9 KiB
Go
77 lines
2.9 KiB
Go
package rond
|
|
|
|
import "testing"
|
|
|
|
func TestBuildMergePlanPicks(t *testing.T) {
|
|
vd := VisitDiff{
|
|
Added: []Visit{{SrcPK: 900, Arrival: tp("2026-09-09 10:00")}},
|
|
Removed: []StoredVisit{{SrcPK: 5, UUID: uuidA}},
|
|
Changed: []VisitConflict{
|
|
{Backup: Visit{SrcPK: 901, Arrival: tp("2026-09-01 12:00")}, DB: StoredVisit{SrcPK: 1, UUID: uuidA}},
|
|
{Backup: Visit{SrcPK: 902, Arrival: tp("2026-09-02 12:00")}, DB: StoredVisit{SrcPK: 2, UUID: uuidB}},
|
|
},
|
|
}
|
|
pd := PlaceDiff{Added: []Location{{SrcPK: 3, Name: "新地点"}}}
|
|
ad := ActivityDiff{Added: []Activity{{SrcPK: 8, Name: "散步"}}}
|
|
|
|
p := BuildMergePlan(vd, pd, ad, Choices{
|
|
AddVisits: true,
|
|
AddPlaces: true,
|
|
AddActs: false,
|
|
VisitClash: map[string]string{uuidA: PickBackup, uuidB: PickDB},
|
|
})
|
|
if len(p.AddVisits) != 1 || p.AddVisits[0].SrcPK != 900 {
|
|
t.Fatalf("新增到访应合入一条: %+v", p.AddVisits)
|
|
}
|
|
if len(p.UpdVisits) != 1 || p.UpdVisits[0].UUID != uuidA {
|
|
t.Fatalf("只有选了「用实机」的那条才该更新: %+v", p.UpdVisits)
|
|
}
|
|
if len(p.AddPlaces) != 1 {
|
|
t.Fatalf("勾了「并进来」就该带上新增地点: %d", len(p.AddPlaces))
|
|
}
|
|
// 即使没单独勾活动,合入到访时也要把它可能挂着的「库里还没有的活动」带上,
|
|
// 否则那条到访的 activity_id 无从映射,只能整条丢掉。
|
|
if len(p.AddActs) != 1 || p.AddActs[0].Name != "散步" {
|
|
t.Fatalf("合入到访应连带它需要的活动: %+v", p.AddActs)
|
|
}
|
|
// 「仅站内有」永远不进计划:删除是单独的事,绝不顺手做
|
|
if len(p.UpdPlaces)+len(p.UpdActs) != 0 {
|
|
t.Fatal("不该有地点/活动更新")
|
|
}
|
|
}
|
|
|
|
// 没提交的键、或提交了怪值,一律当成「保留站内」——不误伤。
|
|
func TestBuildMergePlanDefaultsToKeepDB(t *testing.T) {
|
|
vd := VisitDiff{Changed: []VisitConflict{
|
|
{Backup: Visit{SrcPK: 901}, DB: StoredVisit{SrcPK: 1, UUID: uuidA}},
|
|
}}
|
|
p := BuildMergePlan(vd, PlaceDiff{}, ActivityDiff{}, Choices{VisitClash: map[string]string{uuidA: "wat"}})
|
|
if len(p.UpdVisits) != 0 {
|
|
t.Fatalf("怪值不该被当成「用实机」: %+v", p.UpdVisits)
|
|
}
|
|
if !p.Empty() {
|
|
t.Fatal("什么都没选,计划应当是空的")
|
|
}
|
|
}
|
|
|
|
// 冲突选了「跳过」也不动。
|
|
func TestBuildMergePlanSkip(t *testing.T) {
|
|
vd := VisitDiff{Changed: []VisitConflict{
|
|
{Backup: Visit{SrcPK: 901}, DB: StoredVisit{SrcPK: 1, UUID: uuidA}},
|
|
}}
|
|
p := BuildMergePlan(vd, PlaceDiff{}, ActivityDiff{}, Choices{VisitClash: map[string]string{uuidA: PickSkip}})
|
|
if !p.Empty() {
|
|
t.Fatalf("跳过就不该产生改动: %+v", p)
|
|
}
|
|
}
|
|
|
|
// 活动改了名也要能用 UUID 当键对得上。
|
|
func TestActivityKeyPrefersUUID(t *testing.T) {
|
|
if got := ActivityKey(StoredActivity{SrcPK: 1, Name: "上班", UUID: uuidC}); got != uuidC {
|
|
t.Fatalf("有 UUID 就用 UUID,实际 %q", got)
|
|
}
|
|
if got := ActivityKey(StoredActivity{SrcPK: 1, Name: "上班"}); got != "n:上班" {
|
|
t.Fatalf("没有 UUID 退回名称,实际 %q", got)
|
|
}
|
|
}
|