package banview import ( "testing" "time" ) func viewAt(clk *time.Time) *View { v := New() v.now = func() time.Time { return *clk } return v } func TestIngestRecentDropsExpired(t *testing.T) { clk := time.Date(2026, 6, 21, 12, 0, 0, 0, time.UTC) v := viewAt(&clk) v.Ingest([]Ban{ {IP: "1.1.1.1", Reason: "tripwire", Since: clk, Expires: clk.Add(time.Hour)}, {IP: "2.2.2.2", Reason: "rejections", Since: clk.Add(-2 * time.Hour), Expires: clk.Add(-time.Hour)}, // expired {IP: "", Reason: "x", Since: clk, Expires: clk.Add(time.Hour)}, // empty IP }) got := v.Recent() if len(got) != 1 || got[0].IP != "1.1.1.1" || got[0].Reason != "tripwire" { t.Fatalf("Recent = %+v, want one live ban for 1.1.1.1", got) } } func TestIngestReplaces(t *testing.T) { clk := time.Date(2026, 6, 21, 12, 0, 0, 0, time.UTC) v := viewAt(&clk) v.Ingest([]Ban{{IP: "1.1.1.1", Since: clk, Expires: clk.Add(time.Hour)}}) v.Ingest([]Ban{{IP: "2.2.2.2", Since: clk, Expires: clk.Add(time.Hour)}}) got := v.Recent() if len(got) != 1 || got[0].IP != "2.2.2.2" { t.Fatalf("Recent = %+v, want only the latest report (2.2.2.2)", got) } } func TestRecentOrdersBySince(t *testing.T) { clk := time.Date(2026, 6, 21, 12, 0, 0, 0, time.UTC) v := viewAt(&clk) v.Ingest([]Ban{ {IP: "old", Since: clk.Add(-10 * time.Minute), Expires: clk.Add(time.Hour)}, {IP: "new", Since: clk.Add(-1 * time.Minute), Expires: clk.Add(time.Hour)}, }) got := v.Recent() if len(got) != 2 || got[0].IP != "new" || got[1].IP != "old" { t.Fatalf("Recent order = %+v, want most recent first", got) } } func TestUnbanRoundTrip(t *testing.T) { clk := time.Date(2026, 6, 21, 12, 0, 0, 0, time.UTC) v := viewAt(&clk) v.RequestUnban("3.3.3.3") v.RequestUnban("") // ignored drained := v.DrainUnbans() if len(drained) != 1 || drained[0] != "3.3.3.3" { t.Fatalf("DrainUnbans = %v, want [3.3.3.3]", drained) } if again := v.DrainUnbans(); again != nil { t.Fatalf("second DrainUnbans = %v, want nil (cleared)", again) } }