package scrub import ( "testing" "time" "gitea.perlbach24.de/scripte/nexarch/archive/internal/reconcile" ) var now = time.Date(2026, 8, 29, 12, 0, 0, 0, time.UTC) // TestSample_PrioritizesNeverScrubbedAndOldest ist der Nachweis fuer das // GoBD-Akzeptanzkriterium: nie geprueft ODER am laengsten nicht geprueft // kommt zuerst, nicht bloss alphabetisch nach StorageKey. func TestSample_PrioritizesNeverScrubbedAndOldest(t *testing.T) { existing := []reconcile.Finding{ {StorageKey: "documents/a/revisions/r1"}, // vor 1 tag geprueft {StorageKey: "documents/b/revisions/r1"}, // nie geprueft {StorageKey: "documents/c/revisions/r1"}, // vor 30 tagen geprueft (aeltest) } lastScrubbed := map[string]time.Time{ "documents/a/revisions/r1": now.Add(-24 * time.Hour), "documents/c/revisions/r1": now.Add(-30 * 24 * time.Hour), } got := Sample(existing, lastScrubbed, time.Hour, 2, now) if len(got) != 2 { t.Fatalf("erwartet 2 kandidaten, habe %d: %+v", len(got), got) } // "nie geprueft" (b) zaehlt als aeltestmoeglich, kommt vor "vor 30 tagen" (c). if got[0].StorageKey != "documents/b/revisions/r1" || got[1].StorageKey != "documents/c/revisions/r1" { t.Fatalf("falsche prioritaet, want [b, c], habe %+v", got) } } // TestSample_RespectsCooldown ist der Nachweis, dass kuerzlich gepruefte // Objekte NICHT erneut ausgewaehlt werden — sonst wuerde dieselbe Gruppe // dauernd gescrubbt (genau der Fehler, den die Alt-Priorisierung // verhindern soll). func TestSample_RespectsCooldown(t *testing.T) { existing := []reconcile.Finding{ {StorageKey: "documents/a/revisions/r1"}, {StorageKey: "documents/b/revisions/r1"}, } lastScrubbed := map[string]time.Time{ "documents/a/revisions/r1": now.Add(-1 * time.Hour), // innerhalb cooldown } got := Sample(existing, lastScrubbed, 24*time.Hour, 10, now) if len(got) != 1 || got[0].StorageKey != "documents/b/revisions/r1" { t.Fatalf("erwartet nur b (a innerhalb cooldown), habe %+v", got) } } // TestSample_LimitsToSampleSize ist der Nachweis, dass die // Stichprobengroesse tatsaechlich begrenzt (kein Voll-Scrub jeden Lauf). func TestSample_LimitsToSampleSize(t *testing.T) { existing := []reconcile.Finding{ {StorageKey: "documents/a/revisions/r1"}, {StorageKey: "documents/b/revisions/r1"}, {StorageKey: "documents/c/revisions/r1"}, } got := Sample(existing, map[string]time.Time{}, time.Hour, 1, now) if len(got) != 1 { t.Fatalf("erwartet genau 1 kandidat, habe %d", len(got)) } } // TestSample_DeterministicForIdenticalInput ist der Nachweis, dass zwei // Laeufe mit identischer Eingabe dieselbe Reihenfolge liefern (kein // Zufall im Sampling). func TestSample_DeterministicForIdenticalInput(t *testing.T) { existing := []reconcile.Finding{ {StorageKey: "documents/a/revisions/r1"}, {StorageKey: "documents/b/revisions/r1"}, {StorageKey: "documents/c/revisions/r1"}, } lastScrubbed := map[string]time.Time{} first := Sample(existing, lastScrubbed, time.Hour, 2, now) second := Sample(existing, lastScrubbed, time.Hour, 2, now) if len(first) != len(second) { t.Fatal("unterschiedliche anzahl zwischen zwei laeufen mit identischer eingabe") } for i := range first { if first[i].StorageKey != second[i].StorageKey { t.Fatalf("reihenfolge nicht deterministisch: lauf1=%+v lauf2=%+v", first, second) } } }