package backup import ( "os" "path/filepath" "sort" "testing" "time" ) func TestApplyRetentionByCount(t *testing.T) { dir := t.TempDir() projDir := filepath.Join(dir, "testapp") os.MkdirAll(projDir, 0755) for i := 0; i < 5; i++ { name := "testapp-20250101-00000" + string(rune('1'+i)) + ".tar.gz" f, err := os.Create(filepath.Join(projDir, name)) if err != nil { t.Fatal(err) } f.Close() mt := time.Now().Add(-time.Duration(5-i) * time.Hour) os.Chtimes(f.Name(), mt, mt) } removed, err := ApplyRetention(dir, "testapp", RetentionPolicy{Count: 3}) if err != nil { t.Fatal(err) } if removed != 2 { t.Errorf("expected 2 removed, got %d", removed) } entries, _ := os.ReadDir(projDir) if len(entries) != 3 { t.Errorf("expected 3 remaining, got %d", len(entries)) } } func TestApplyRetentionByDays(t *testing.T) { dir := t.TempDir() projDir := filepath.Join(dir, "testapp") os.MkdirAll(projDir, 0755) oldTime := time.Now().AddDate(0, 0, -10) recentTime := time.Now().Add(-1 * time.Hour) f1, _ := os.Create(filepath.Join(projDir, "testapp-old.tar.gz")) f1.Close() os.Chtimes(f1.Name(), oldTime, oldTime) f2, _ := os.Create(filepath.Join(projDir, "testapp-recent.tar.gz")) f2.Close() os.Chtimes(f2.Name(), recentTime, recentTime) removed, err := ApplyRetention(dir, "testapp", RetentionPolicy{Count: 0, Days: 7}) if err != nil { t.Fatal(err) } if removed != 1 { t.Errorf("expected 1 removed, got %d", removed) } entries, _ := os.ReadDir(projDir) if len(entries) != 1 { t.Errorf("expected 1 remaining, got %d", len(entries)) } } func TestApplyRetentionIgnoresNonBackupFiles(t *testing.T) { dir := t.TempDir() projDir := filepath.Join(dir, "testapp") os.MkdirAll(projDir, 0755) keepNames := []string{"README.txt", "otherapp-20250101.tar.gz", "testapp-note.txt"} for _, name := range keepNames { f, err := os.Create(filepath.Join(projDir, name)) if err != nil { t.Fatal(err) } f.Close() } for i := 0; i < 3; i++ { name := "testapp-20250101-00000" + string(rune('1'+i)) + ".tar.gz" f, err := os.Create(filepath.Join(projDir, name)) if err != nil { t.Fatal(err) } f.Close() mt := time.Now().Add(-time.Duration(3-i) * time.Hour) os.Chtimes(f.Name(), mt, mt) } removed, err := ApplyRetention(dir, "testapp", RetentionPolicy{Count: 1}) if err != nil { t.Fatal(err) } if removed != 2 { t.Errorf("expected 2 removed, got %d", removed) } for _, name := range keepNames { if _, err := os.Stat(filepath.Join(projDir, name)); err != nil { t.Fatalf("non-backup file %s should remain: %v", name, err) } } } func TestApplyRetentionEmptyDir(t *testing.T) { dir := t.TempDir() removed, err := ApplyRetention(dir, "nonexistent", RetentionPolicy{Count: 7}) if err != nil { t.Fatal(err) } if removed != 0 { t.Errorf("expected 0 removed, got %d", removed) } } func TestApplyRetentionSortOrder(t *testing.T) { dir := t.TempDir() projDir := filepath.Join(dir, "testapp") os.MkdirAll(projDir, 0755) times := []time.Time{ time.Now().Add(-5 * time.Hour), time.Now().Add(-1 * time.Hour), time.Now().Add(-3 * time.Hour), } for i, mt := range times { name := "testapp-2025010" + string(rune('1'+i)) + "-000001.tar.gz" f, _ := os.Create(filepath.Join(projDir, name)) f.Close() os.Chtimes(f.Name(), mt, mt) } removed, err := ApplyRetention(dir, "testapp", RetentionPolicy{Count: 2}) if err != nil { t.Fatal(err) } if removed != 1 { t.Fatalf("expected 1 removed, got %d", removed) } entries, _ := os.ReadDir(projDir) names := make([]string, len(entries)) for i, e := range entries { names[i] = e.Name() } sort.Strings(names) expected := []string{"testapp-20250102-000001.tar.gz", "testapp-20250103-000001.tar.gz"} if len(names) != len(expected) { t.Fatalf("expected %v, got %v", expected, names) } for i := range expected { if expected[i] != names[i] { t.Errorf("expected[%d]=%s, got %s", i, expected[i], names[i]) } } }