package alerts import "testing" // TestDedupeKey pins the stable dedupe key construction. The key is the type prefix joined to the subject id, // and the org scoping comes from the UNIQUE (organization_id, dedupe_key) constraint, so the same event // always yields the same key (the basis for the idempotent UPSERT) and two different subjects never collide. func TestDedupeKey(t *testing.T) { cases := []struct { alertType string subjectID string want string }{ {TypeClearanceExpiring, "11111111-1111-1111-1111-111111111111", "clearance_expiring:11111111-1111-1111-1111-111111111111"}, {TypeRecompeteWinnable, "22222222-2222-2222-2222-222222222222", "recompete_winnable:22222222-2222-2222-2222-222222222222"}, {TypeGapFillable, "33333333-3333-3333-3333-333333333333", "gap_fillable:33333333-3333-3333-3333-333333333333"}, } for _, c := range cases { if got := dedupeKey(c.alertType, c.subjectID); got != c.want { t.Errorf("dedupeKey(%q, %q) = %q, want %q", c.alertType, c.subjectID, got, c.want) } } // The same event is byte stable: a re sweep recomputes the identical key, which is what makes the UPSERT // idempotent. a := dedupeKey(TypeClearanceExpiring, "abc") b := dedupeKey(TypeClearanceExpiring, "abc") if a != b { t.Errorf("dedupeKey must be stable for the same event, got %q then %q", a, b) } // Two different subjects never collide, so two candidates each get their own alert row. if dedupeKey(TypeClearanceExpiring, "abc") == dedupeKey(TypeClearanceExpiring, "def") { t.Error("different subjects must yield different dedupe keys") } // Two different types over the same subject never collide either. if dedupeKey(TypeGapFillable, "abc") == dedupeKey(TypeClearanceExpiring, "abc") { t.Error("different alert types must yield different dedupe keys for the same subject") } } // TestIsValidType pins the closed set of alert types the DB CHECK enforces. func TestIsValidType(t *testing.T) { for _, ty := range []string{TypeRecompeteWinnable, TypeClearanceExpiring, TypeGapFillable} { if !IsValidType(ty) { t.Errorf("%q must be a valid alert type", ty) } } for _, ty := range []string{"", "recompete", "RECOMPETE_WINNABLE", "gap", "clearance_expiring "} { if IsValidType(ty) { t.Errorf("%q must NOT be a valid alert type", ty) } } } // TestIsValidSeverity pins the closed set of severities the DB CHECK enforces. func TestIsValidSeverity(t *testing.T) { for _, s := range []string{SeverityInfo, SeverityWatch, SeverityUrgent} { if !IsValidSeverity(s) { t.Errorf("%q must be a valid severity", s) } } for _, s := range []string{"", "low", "URGENT", "critical"} { if IsValidSeverity(s) { t.Errorf("%q must NOT be a valid severity", s) } } } // TestIsValidStatus pins the closed set of statuses the DB CHECK enforces and the UI PATCH validates against. func TestIsValidStatus(t *testing.T) { for _, s := range []string{StatusUnread, StatusRead, StatusDismissed} { if !IsValidStatus(s) { t.Errorf("%q must be a valid status", s) } } for _, s := range []string{"", "open", "archived", "READ", "dismissed "} { if IsValidStatus(s) { t.Errorf("%q must NOT be a valid status", s) } } } // TestSweepSummaryTotal pins the total as the sum of the three per type counts. func TestSweepSummaryTotal(t *testing.T) { s := SweepSummary{ClearanceExpiring: 2, RecompeteWinnable: 3, GapFillable: 4} if s.Total() != 9 { t.Errorf("Total() = %d, want 9", s.Total()) } if (SweepSummary{}).Total() != 0 { t.Errorf("an empty summary must total 0") } }