package alert import ( "strings" "testing" ) func goodAlert() *Alert { return &Alert{ CompanyID: "acme-001", SourceID: "prom-prod", Severity: SeverityCritical, Category: "storage", Title: "Disk full on db-prod-03", Body: "92% used", Data: map[string]string{"host": "db-prod-03", "used_pct": "92"}, DedupeKey: "disk:db-prod-03:full", } } func TestValidate_OK(t *testing.T) { if err := goodAlert().Validate(); err != nil { t.Fatalf("expected nil, got %v", err) } } func TestValidate_BadCompanyID(t *testing.T) { a := goodAlert() a.CompanyID = "ACME-001" // uppercase not allowed if err := a.Validate(); err == nil { t.Fatal("expected error for uppercase company_id") } } func TestValidate_UnknownSeverity(t *testing.T) { a := goodAlert() a.Severity = "emergency" err := a.Validate() if err == nil { t.Fatal("expected error for unknown severity") } if !IsInvalid(err) { t.Fatalf("expected *ErrInvalid, got %T", err) } if !strings.Contains(err.Error(), "severity") { t.Fatalf("expected severity reason, got %v", err) } } func TestValidate_DedupeKeyTooLong(t *testing.T) { a := goodAlert() a.DedupeKey = strings.Repeat("a", 129) if err := a.Validate(); err == nil { t.Fatal("expected error for long dedupe_key") } } func TestValidate_DataKeyTooLong(t *testing.T) { a := goodAlert() a.Data = map[string]string{strings.Repeat("k", 65): "v"} if err := a.Validate(); err == nil { t.Fatal("expected error for long data key") } } func TestValidate_TooManyDataKeys(t *testing.T) { a := goodAlert() a.Data = make(map[string]string, MaxDataKeys+1) for i := 0; i < MaxDataKeys+1; i++ { a.Data[shortKey(i)] = "v" } if err := a.Validate(); err == nil { t.Fatal("expected error for too many data keys") } } func TestInminentColapseBypass(t *testing.T) { if !SeverityInminentColapse.InminentColapseBypass() { t.Fatal("inminent_colapse must bypass quiet hours") } if SeverityCritical.InminentColapseBypass() { t.Fatal("critical must NOT bypass quiet hours") } } func TestNewID_UniqueAndSortable(t *testing.T) { a := NewID() b := NewID() if a == b { t.Fatalf("ids collided: %s", a) } if len(a) != 26 { t.Fatalf("id length expected 26, got %d (%q)", len(a), a) } } func shortKey(i int) string { const alphabet = "abcdefghijklmnopqrstuvwxyz" if i < len(alphabet) { return string(alphabet[i]) } return shortKey(i/len(alphabet)) + string(alphabet[i%len(alphabet)]) } func TestSeverityRank(t *testing.T) { cases := []struct { sev Severity want int }{ {SeverityInfo, 0}, {SeverityWarning, 1}, {SeverityCritical, 2}, {SeverityInminentColapse, 3}, {Severity("bogus"), -1}, } for _, tc := range cases { if got := tc.sev.Rank(); got != tc.want { t.Errorf("Severity(%q).Rank() = %d, want %d", tc.sev, got, tc.want) } } } func TestMinSeverityRank(t *testing.T) { if MinSeverityRank("") != 0 { t.Error("empty min_severity should default to rank 0 (info and above)") } if MinSeverityRank("critical") != 2 { t.Error("critical should be rank 2") } if MinSeverityRank("inminent_colapse") != 3 { t.Error("inminent_colapse should be rank 3") } }