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

80 lines
2.3 KiB
Go

package backup
import (
"reflect"
"testing"
)
func TestEncodeDecodeManifestRoundTrip(t *testing.T) {
m := Manifest{Entries: []ManifestEntry{
{Path: "src/a.txt", Size: 10, ModTimeNs: 100, Mode: 0o644},
{Path: "src", Size: 0, ModTimeNs: 50, Mode: 0o755, IsDir: true},
}}
data, err := EncodeManifest(m)
if err != nil {
t.Fatalf("EncodeManifest: %v", err)
}
got, err := DecodeManifest(data)
if err != nil {
t.Fatalf("DecodeManifest: %v", err)
}
if !reflect.DeepEqual(got, m) {
t.Fatalf("roundtrip mismatch:\n got %#v\nwant %#v", got, m)
}
}
func TestDecodeManifestEmpty(t *testing.T) {
got, err := DecodeManifest(nil)
if err != nil {
t.Fatalf("DecodeManifest(nil): %v", err)
}
if len(got.Entries) != 0 {
t.Fatalf("expected empty manifest, got %#v", got)
}
}
func TestChangedSince(t *testing.T) {
base := Manifest{Entries: []ManifestEntry{
{Path: "a.txt", Size: 10, ModTimeNs: 100},
{Path: "dir", IsDir: true, ModTimeNs: 100},
}}.index()
cases := []struct {
name string
cur ManifestEntry
want bool
}{
{"unchanged file", ManifestEntry{Path: "a.txt", Size: 10, ModTimeNs: 100}, false},
{"size changed", ManifestEntry{Path: "a.txt", Size: 11, ModTimeNs: 100}, true},
{"mtime changed", ManifestEntry{Path: "a.txt", Size: 10, ModTimeNs: 200}, true},
{"new file", ManifestEntry{Path: "b.txt", Size: 1, ModTimeNs: 1}, true},
{"existing dir skipped", ManifestEntry{Path: "dir", IsDir: true, ModTimeNs: 999}, false},
{"new dir included", ManifestEntry{Path: "newdir", IsDir: true, ModTimeNs: 1}, true},
}
for _, tc := range cases {
if got := changedSince(base, tc.cur); got != tc.want {
t.Errorf("%s: changedSince=%v want %v", tc.name, got, tc.want)
}
}
}
func TestDeletedPaths(t *testing.T) {
base := Manifest{Entries: []ManifestEntry{
{Path: "a"}, {Path: "b"}, {Path: "c"},
}}.index()
seen := map[string]struct{}{"a": {}, "c": {}}
got := deletedPaths(base, seen)
want := []string{"b"}
if !reflect.DeepEqual(got, want) {
t.Fatalf("deletedPaths=%v want %v", got, want)
}
}
func TestDeletedPathsNoneWhenAllSeen(t *testing.T) {
base := Manifest{Entries: []ManifestEntry{{Path: "a"}, {Path: "b"}}}.index()
seen := map[string]struct{}{"a": {}, "b": {}}
if got := deletedPaths(base, seen); len(got) != 0 {
t.Fatalf("expected no deletions, got %v", got)
}
}