package db import ( "path/filepath" "testing" "time" ) func openTestDB(t *testing.T) *DB { t.Helper() database, err := Open(filepath.Join(t.TempDir(), "test.db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { database.Close() }) return database } // TestBlacklistIPEscalation confirms repeat offenses double the block duration up to the cap. func TestBlacklistIPEscalation(t *testing.T) { d := openTestDB(t) const ip = "203.0.113.7" wantHours := []int{12, 24, 48, 96, 168, 168} // caps at 168 (7 days) for i, want := range wantHours { if err := d.BlacklistIP(ip, "test", 12, 168); err != nil { t.Fatalf("offense %d: %v", i+1, err) } list, err := d.ListBlacklist() if err != nil { t.Fatal(err) } if len(list) != 1 { t.Fatalf("offense %d: expected 1 entry, got %d", i+1, len(list)) } e := list[0] if e.OffenseCount != i+1 { t.Errorf("offense %d: OffenseCount = %d, want %d", i+1, e.OffenseCount, i+1) } gotHours := e.ExpiresAt.Sub(e.BlacklistedAt).Hours() if diff := gotHours - float64(want); diff < -1 || diff > 1 { t.Errorf("offense %d: duration = %.1fh, want ~%dh", i+1, gotHours, want) } } } // TestCountFailedAuthAttemptsByIPMatchesCurrentTimestamp guards against the exact // SQLite time.Time/CURRENT_TIMESTAMP format mismatch already found once in // CountRecentFailedAttempts: a row inserted via CURRENT_TIMESTAMP must be found by a // since-cutoff comparison using a Go-side time.Time a moment earlier. func TestCountFailedAuthAttemptsByIPMatchesCurrentTimestamp(t *testing.T) { d := openTestDB(t) const ip = "198.51.100.9" since := time.Now().Add(-1 * time.Minute) if err := d.LogAuthAttempt("sender", "someone@example.com", ip, false, "bad password"); err != nil { t.Fatal(err) } n, err := d.CountFailedAuthAttemptsByIP(ip, since) if err != nil { t.Fatal(err) } if n != 1 { t.Fatalf("CountFailedAuthAttemptsByIP = %d, want 1 (CURRENT_TIMESTAMP/time.Time format mismatch?)", n) } blacklisted, err := d.IsIPBlacklisted(ip) if err != nil { t.Fatal(err) } if blacklisted { t.Fatal("IP should not be blacklisted yet") } } func TestIPAbuseWhitelist(t *testing.T) { d := openTestDB(t) const ip = "192.0.2.55" whitelisted, err := d.IsIPAbuseWhitelisted(ip) if err != nil { t.Fatal(err) } if whitelisted { t.Fatal("should not be whitelisted before AddAbuseWhitelist") } if err := d.AddAbuseWhitelist(ip, "trusted scanner"); err != nil { t.Fatal(err) } whitelisted, err = d.IsIPAbuseWhitelisted(ip) if err != nil { t.Fatal(err) } if !whitelisted { t.Fatal("should be whitelisted after AddAbuseWhitelist") } }