package state import ( "strings" "testing" "time" ) func TestPlanPruneOlderThan(t *testing.T) { now := time.Date(2026, 6, 8, 12, 0, 0, 0, time.UTC) olderThan := 48 * time.Hour plan := PlanPrune([]PruneCandidate{ {Path: "old.txt", UpdatedAt: now.Add(-72 * time.Hour)}, {Path: "fresh.txt", UpdatedAt: now.Add(-24 * time.Hour)}, }, PrunePlanOptions{Now: now, OlderThan: &olderThan}) if got, want := pruneCandidatePaths(plan.Pruned), "old.txt"; got != want { t.Fatalf("pruned = %q, want %q", got, want) } if got, want := pruneCandidatePaths(plan.Preserved), "fresh.txt"; got != want { t.Fatalf("preserved = %q, want %q", got, want) } } func TestPlanPruneKeepLatest(t *testing.T) { now := time.Date(2026, 6, 8, 12, 0, 0, 0, time.UTC) keepLatest := 2 plan := PlanPrune([]PruneCandidate{ {Path: "old.txt", UpdatedAt: now.Add(-72 * time.Hour)}, {Path: "new.txt", UpdatedAt: now.Add(-1 * time.Hour)}, {Path: "middle.txt", UpdatedAt: now.Add(-24 * time.Hour)}, }, PrunePlanOptions{KeepLatest: &keepLatest}) if got, want := pruneCandidatePaths(plan.Pruned), "old.txt"; got != want { t.Fatalf("pruned = %q, want %q", got, want) } if got, want := pruneCandidatePaths(plan.Preserved), "new.txt,middle.txt"; got != want { t.Fatalf("preserved = %q, want %q", got, want) } } func TestPlanPruneCombinedPolicyPreservesLatestBeforeAgeCheck(t *testing.T) { now := time.Date(2026, 6, 8, 12, 0, 0, 0, time.UTC) olderThan := 48 * time.Hour keepLatest := 1 plan := PlanPrune([]PruneCandidate{ {Path: "oldest.txt", UpdatedAt: now.Add(-96 * time.Hour)}, {Path: "old.txt", UpdatedAt: now.Add(-72 * time.Hour)}, {Path: "fresh.txt", UpdatedAt: now.Add(-24 * time.Hour)}, }, PrunePlanOptions{Now: now, OlderThan: &olderThan, KeepLatest: &keepLatest}) if got, want := pruneCandidatePaths(plan.Pruned), "oldest.txt,old.txt"; got != want { t.Fatalf("pruned = %q, want %q", got, want) } if got, want := pruneCandidatePaths(plan.Preserved), "fresh.txt"; got != want { t.Fatalf("preserved = %q, want %q", got, want) } } func TestPlanPruneDeterministicTieBreaking(t *testing.T) { now := time.Date(2026, 6, 8, 12, 0, 0, 0, time.UTC) keepLatest := 1 plan := PlanPrune([]PruneCandidate{ {Path: "b.txt", UpdatedAt: now}, {Path: "a.txt", UpdatedAt: now}, {Path: "c.txt", UpdatedAt: now.Add(-time.Hour)}, }, PrunePlanOptions{KeepLatest: &keepLatest}) if got, want := pruneCandidatePaths(plan.Preserved), "a.txt"; got != want { t.Fatalf("preserved = %q, want %q", got, want) } if got, want := pruneCandidatePaths(plan.Pruned), "c.txt,b.txt"; got != want { t.Fatalf("pruned = %q, want %q", got, want) } } func pruneCandidatePaths(candidates []PruneCandidate) string { paths := make([]string, 0, len(candidates)) for _, candidate := range candidates { paths = append(paths, candidate.Path) } return strings.Join(paths, ",") }