This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
package alert
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func signal(id, rule, evaluation string, state State, labels map[string]string) Signal {
|
||||
return Signal{InstanceID: id, RuleID: rule, RuleVersionID: "version-1", Severity: SeverityDegraded, State: state, EvaluationKey: evaluation, ObservedAt: time.Date(2026, time.January, 1, 12, 0, 0, 0, time.UTC), Labels: labels, GroupBy: []string{"host", "application"}, SuppressWhen: []string{"host.unreachable", "dns.failure", "source.unavailable"}}
|
||||
}
|
||||
|
||||
func TestBuildFingerprintIsStableAndIncludesRuleBehavior(t *testing.T) {
|
||||
first, err := BuildFingerprint("rule-1", "version-1", "entity-1", map[string]string{"application": "media", "host": "pulse"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
second, err := BuildFingerprint("rule-1", "version-1", "entity-1", map[string]string{"host": "pulse", "application": "media"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if first != second || len(first) != 64 {
|
||||
t.Fatalf("fingerprint instability: %q %q", first, second)
|
||||
}
|
||||
changedVersion, err := BuildFingerprint("rule-1", "version-2", "entity-1", map[string]string{"host": "pulse", "application": "media"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if changedVersion == first {
|
||||
t.Fatal("rule version did not affect fingerprint")
|
||||
}
|
||||
tooMany := make(map[string]string, MaxFingerprintLabels+1)
|
||||
for i := 0; i <= MaxFingerprintLabels; i++ {
|
||||
tooMany["label"+string(rune('a'+i))] = "value"
|
||||
}
|
||||
if _, err := BuildFingerprint("rule-1", "version-1", "entity-1", tooMany); !errors.Is(err, ErrInvalidAlertIdentity) {
|
||||
t.Fatalf("too many labels error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupAndDeduplicateSignalsAreDeterministic(t *testing.T) {
|
||||
inputs := []Signal{
|
||||
signal("instance-b", "rule-1", "slot-1", StateFiring, map[string]string{"host": "pulse", "application": "media"}),
|
||||
signal("instance-a", "rule-1", "slot-1", StateFiring, map[string]string{"host": "pulse", "application": "media"}),
|
||||
signal("instance-a", "rule-1", "slot-1", StateFiring, map[string]string{"host": "pulse", "application": "media"}),
|
||||
signal("instance-c", "rule-1", "slot-1", StateFiring, map[string]string{"host": "other", "application": "media"}),
|
||||
}
|
||||
deduplicated, err := DeduplicateSignals(inputs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(deduplicated) != 3 {
|
||||
t.Fatalf("deduplicated signals = %d, want 3", len(deduplicated))
|
||||
}
|
||||
groups, err := GroupSignals(deduplicated)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(groups) != 2 || len(groups[0].Signals) != 1 || len(groups[1].Signals) != 2 {
|
||||
t.Fatalf("unexpected groups: %#v", groups)
|
||||
}
|
||||
if groups[0].Key > groups[1].Key {
|
||||
t.Fatal("groups are not sorted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSuppressionScenariosRemainInspectablyBounded(t *testing.T) {
|
||||
base := signal("instance-1", "rule-service", "slot-1", StateFiring, map[string]string{"host": "pulse", "application": "web"})
|
||||
tests := []struct {
|
||||
name string
|
||||
cause Cause
|
||||
want bool
|
||||
reason string
|
||||
}{
|
||||
{name: "host outage", cause: Cause{Key: "host.unreachable", State: StateFiring, Confirmed: true}, want: true, reason: "dependency_failure"},
|
||||
{name: "dns outage", cause: Cause{Key: "dns.failure", State: StateAcknowledged, Confidence: .9}, want: true, reason: "dependency_failure"},
|
||||
{name: "source outage", cause: Cause{Key: "source.unavailable", State: StateUnknown, Confirmed: true}, want: true, reason: "source_outage"},
|
||||
{name: "low confidence", cause: Cause{Key: "host.unreachable", State: StateFiring, Confidence: .5}, want: false, reason: "no_active_suppression_cause"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
decision, err := EvaluateSuppression(base, []Cause{test.cause})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if decision.Suppressed != test.want || decision.Reason != test.reason {
|
||||
t.Fatalf("decision = %#v, want suppressed=%v reason=%s", decision, test.want, test.reason)
|
||||
}
|
||||
})
|
||||
}
|
||||
resolved := base
|
||||
resolved.State = StateResolved
|
||||
decision, err := EvaluateSuppression(resolved, []Cause{{Key: "host.unreachable", State: StateFiring, Confirmed: true}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if decision.Suppressed {
|
||||
t.Fatal("resolved alert was suppressed")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user