package export import ( "os" "path/filepath" "testing" "time" ) func TestBackupAndRetention(t *testing.T) { db := openEventDB(t) seedEvent(t, db) dir := t.TempDir() base := time.Date(2026, 9, 9, 10, 0, 0, 0, time.Local) info, err := Backup(db, BackupOptions{Dir: dir, Prefix: "event", Reason: "publish", Keep: 3, Now: base}) if err != nil { t.Fatalf("备份: %v", err) } if info.Size == 0 || info.SHA256 == "" { t.Errorf("备份信息不完整: %+v", info) } if info.Reason != "publish" { t.Errorf("备份原因 = %q,期望 publish", info.Reason) } if _, err := os.Stat(info.Path); err != nil { t.Fatalf("备份文件不存在: %v", err) } // 快照必须可打开,且数据与源库一致。 bdb := openSQLite(t, info.Path) if got := countRows(t, bdb, "teams"); got != 2 { t.Errorf("备份内 teams = %d,期望 2", got) } if got := countRows(t, bdb, "score_ledger"); got != 2 { t.Errorf("备份内 score_ledger = %d,期望 2", got) } _ = bdb.Close() // 再生成 5 份,Keep=3 应只留最近 3 份。 for i := 1; i <= 5; i++ { if _, err := Backup(db, BackupOptions{ Dir: dir, Prefix: "event", Reason: "auto", Keep: 3, Now: base.Add(time.Duration(i) * time.Minute), }); err != nil { t.Fatalf("第 %d 次备份: %v", i, err) } } list, err := ListBackups(dir, "event") if err != nil { t.Fatalf("列出备份: %v", err) } if len(list) != 3 { t.Fatalf("保留份数 = %d,期望 3", len(list)) } if list[0].Reason != "auto" { t.Errorf("最新备份原因 = %q,期望 auto", list[0].Reason) } latest, ok, err := LatestBackup(dir, "event") if err != nil || !ok { t.Fatalf("最近备份: ok=%v err=%v", ok, err) } if !latest.CreatedAt.Equal(base.Add(5 * time.Minute)) { t.Errorf("最近备份时间 = %s,期望 %s", latest.CreatedAt, base.Add(5*time.Minute)) } if _, err := PruneBackups(dir, "event", 1); err != nil { t.Fatalf("手动清理: %v", err) } list, err = ListBackups(dir, "event") if err != nil { t.Fatalf("清理后列出备份: %v", err) } if len(list) != 1 { t.Errorf("清理后份数 = %d,期望 1", len(list)) } } func TestBackupBeforePublishAndUniqueNames(t *testing.T) { db := openEventDB(t) seedEvent(t, db) dir := t.TempDir() base := time.Date(2026, 9, 9, 11, 0, 0, 0, time.Local) a, err := Backup(db, BackupOptions{Dir: dir, Prefix: "event", Now: base, SkipPrune: true}) if err != nil { t.Fatalf("备份 A: %v", err) } b, err := Backup(db, BackupOptions{Dir: dir, Prefix: "event", Now: base, SkipPrune: true}) if err != nil { t.Fatalf("备份 B: %v", err) } if a.Path == b.Path { t.Errorf("同一秒的两份备份重名: %s", a.Path) } info, err := BackupBeforePublish(db, BackupOptions{Dir: dir, Prefix: "event", Keep: 20, Now: base.Add(time.Hour)}) if err != nil { t.Fatalf("发布前备份: %v", err) } if info.Reason != "publish" { t.Errorf("发布前备份原因 = %q,期望 publish", info.Reason) } if filepath.Dir(info.Path) != dir { t.Errorf("备份目录 = %q,期望 %q", filepath.Dir(info.Path), dir) } } func TestListBackupsMissingDir(t *testing.T) { list, err := ListBackups(filepath.Join(t.TempDir(), "不存在"), "event") if err != nil { t.Fatalf("目录不存在不应报错: %v", err) } if len(list) != 0 { t.Errorf("备份数 = %d,期望 0", len(list)) } if _, ok, err := LatestBackup(filepath.Join(t.TempDir(), "不存在"), "event"); err != nil || ok { t.Errorf("空目录的最近备份: ok=%v err=%v", ok, err) } }