package store import ( "testing" "time" ) // R-901: a deleted customer's events and notification_log rows go 1 year after the deletion — and nothing else goes. // RED-PROOF: return early from PruneDeletedCustomerAudit (the pre-R-901 state: nothing prunes notification_log) → // the c-old rows survive → this FAILS on the first assertion. func TestR901_DeletedCustomerAuditGoesAfterOneYear(t *testing.T) { s := newTestStore(t) now := time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC) ts := func(d time.Duration) string { return now.Add(-d).Format("2006-01-02 15:04:05") } day := 24 * time.Hour journal := func(cid, legs string, completedAgo time.Duration) { if _, err := s.db.Exec(`INSERT INTO customer_resets (customer_id, started_at, completed_at, legs_json) VALUES (?, ?, ?, ?)`, cid, ts(completedAgo+time.Minute), ts(completedAgo), legs); err != nil { t.Fatal(err) } } row := func(cid string, ago time.Duration) { if _, err := s.db.Exec(`INSERT INTO events (customer_id, event_type, severity, message, created_at) VALUES (?, 'x', 'info', 'm', ?)`, cid, ts(ago)); err != nil { t.Fatal(err) } if _, err := s.db.Exec(`INSERT INTO notification_log (customer_id, event_type, severity, message, status, created_at) VALUES (?, 'x', 'info', 'm', 'sent', ?)`, cid, ts(ago)); err != nil { t.Fatal(err) } } deleted := `{"hosts":"ok","residue":"ok","customer_delete":"ok"}` journal("c-old", deleted, 400*day) // deleted 400 days ago → its audit rows go journal("c-recent", deleted, 100*day) // deleted 100 days ago → kept journal("c-reset", `{"hetzner":"ok","pbs":"ok","db_purge":"ok"}`, 400*day) // a RESET, not a deletion → kept row("c-old", 500*day) row("c-old", 401*day) row("c-old", 10*day) // the id re-used AFTER the deletion → kept row("c-recent", 200*day) row("c-reset", 500*day) row("c-live", 900*day) // never deleted → kept (events' own 90-day prune is not this function's business) ev, nl, err := s.PruneDeletedCustomerAudit(now, DeletedCustomerAuditKeep) if err != nil { t.Fatal(err) } if ev != 2 || nl != 2 { t.Fatalf("deleted events=%d notification_log=%d, want 2 and 2 (c-old's two rows before its deletion)", ev, nl) } count := func(table, cid string) int { var n int if err := s.db.QueryRow(`SELECT COUNT(*) FROM `+table+` WHERE customer_id = ?`, cid).Scan(&n); err != nil { t.Fatal(err) } return n } for _, c := range []struct { cid string want int }{{"c-old", 1}, {"c-recent", 1}, {"c-reset", 1}, {"c-live", 1}} { for _, tb := range []string{"events", "notification_log"} { if got := count(tb, c.cid); got != c.want { t.Errorf("%s rows for %s = %d, want %d", tb, c.cid, got, c.want) } } } // The journal row (provenance) stays. var j int _ = s.db.QueryRow(`SELECT COUNT(*) FROM customer_resets WHERE customer_id = 'c-old'`).Scan(&j) if j != 1 { t.Fatalf("the deletion journal row must stay, got %d", j) } // Idempotent. if ev, nl, _ := s.PruneDeletedCustomerAudit(now, DeletedCustomerAuditKeep); ev+nl != 0 { t.Fatalf("a second run deleted %d+%d rows, want 0", ev, nl) } }