Files
BackupX/server/internal/backup/retention/differential_test.go
Wu Qing 90b58d58d6 feat(backup): 新增差异备份(differential)模式 (#88)
文件备份新增差异模式:仅打包自上次全量以来的变更并记录删除,恢复自动按全量+差异链还原。含基线解析、链式恢复、保留链保护与本机文件任务校验;清单/比对/删除/往返/保留保护单测全覆盖。
2026-05-27 19:03:40 +08:00

56 lines
2.0 KiB
Go

package retention
import (
"testing"
"backupx/server/internal/model"
)
func retentionRecIDs(records []model.BackupRecord) []uint {
ids := make([]uint, 0, len(records))
for _, r := range records {
ids = append(ids, r.ID)
}
return ids
}
// 基线全量仍被「不在删除集合中的差异」依赖 → 必须保留,否则差异无法恢复。
func TestProtectDifferentialBasesKeepsBaseWithSurvivingDiff(t *testing.T) {
all := []model.BackupRecord{
{ID: 1, BackupKind: model.BackupKindFull},
{ID: 2, BackupKind: model.BackupKindDifferential, BaseRecordID: 1},
}
candidates := []model.BackupRecord{{ID: 1, BackupKind: model.BackupKindFull}}
if got := protectDifferentialBases(all, candidates); len(got) != 0 {
t.Fatalf("base with surviving diff must be protected, got %v", retentionRecIDs(got))
}
}
// 基线全量与其全部差异都在删除集合中 → 可一并删除(无残留差异失去基线)。
func TestProtectDifferentialBasesDeletesBaseWhenDiffAlsoDeleted(t *testing.T) {
all := []model.BackupRecord{
{ID: 1, BackupKind: model.BackupKindFull},
{ID: 2, BackupKind: model.BackupKindDifferential, BaseRecordID: 1},
}
candidates := []model.BackupRecord{
{ID: 1, BackupKind: model.BackupKindFull},
{ID: 2, BackupKind: model.BackupKindDifferential, BaseRecordID: 1},
}
if got := protectDifferentialBases(all, candidates); len(got) != 2 {
t.Fatalf("base+diff both expired should both be deleted, got %v", retentionRecIDs(got))
}
}
// 无差异备份时原样透传(不影响既有全量保留逻辑)。
func TestProtectDifferentialBasesNoDiffsPassThrough(t *testing.T) {
all := []model.BackupRecord{
{ID: 1, BackupKind: model.BackupKindFull},
{ID: 2, BackupKind: model.BackupKindFull},
}
candidates := []model.BackupRecord{{ID: 1, BackupKind: model.BackupKindFull}}
got := protectDifferentialBases(all, candidates)
if len(got) != 1 || got[0].ID != 1 {
t.Fatalf("no diffs should pass through unchanged, got %v", retentionRecIDs(got))
}
}