Public source validation / validate (push) Failing after 3m8s
168 lines
5.5 KiB
Go
168 lines
5.5 KiB
Go
package alertdefaults
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/itworx/pulse/internal/alert"
|
|
"github.com/itworx/pulse/internal/metriccatalog"
|
|
)
|
|
|
|
type fakeStore struct {
|
|
rules map[string]alert.Rule
|
|
adds int
|
|
}
|
|
|
|
func (store *fakeStore) Create(_ context.Context, _ string, document alert.Document, _ string) (alert.Rule, alert.Version, error) {
|
|
if _, exists := store.rules[document.ID]; exists {
|
|
return alert.Rule{}, alert.Version{}, alert.ErrConflict
|
|
}
|
|
store.adds++
|
|
rule := alert.Rule{Document: document}
|
|
store.rules[document.ID] = rule
|
|
return rule, alert.Version{RuleID: document.ID, VersionNumber: 1}, nil
|
|
}
|
|
|
|
func (store *fakeStore) Update(_ context.Context, id, _ string, expected int64, document alert.Document, _ string) (alert.Rule, error) {
|
|
rule, exists := store.rules[id]
|
|
if !exists {
|
|
return alert.Rule{}, alert.ErrNotFound
|
|
}
|
|
if rule.Revision != expected {
|
|
return alert.Rule{}, alert.ErrConflict
|
|
}
|
|
rule.Document = document
|
|
rule.Revision++
|
|
store.rules[id] = rule
|
|
return rule, nil
|
|
}
|
|
|
|
func (store *fakeStore) Get(_ context.Context, id string) (alert.Rule, error) {
|
|
rule, exists := store.rules[id]
|
|
if !exists {
|
|
return alert.Rule{}, alert.ErrNotFound
|
|
}
|
|
return rule, nil
|
|
}
|
|
|
|
func TestLoadValidatesImplementationOwnedDefaults(t *testing.T) {
|
|
registry, err := metriccatalog.DefaultRegistry()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rules, err := Load(registry)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(rules) < 4 {
|
|
t.Fatalf("default rule count=%d, want at least 4", len(rules))
|
|
}
|
|
}
|
|
|
|
func TestSeedIsIdempotentAndDoesNotOverwriteExistingRule(t *testing.T) {
|
|
registry, err := metriccatalog.DefaultRegistry()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rules, err := Load(registry)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
custom := rules[0]
|
|
custom.Name = "Aangepaste naam"
|
|
store := &fakeStore{rules: map[string]alert.Rule{custom.ID: {Document: custom}}}
|
|
first, err := Seed(context.Background(), store, registry, "system-defaults")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if first.Added != len(rules)-1 || first.Existing != 1 || store.adds != len(rules)-1 {
|
|
t.Fatalf("first seed report=%+v adds=%d", first, store.adds)
|
|
}
|
|
second, err := Seed(context.Background(), store, registry, "system-defaults")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if second.Added != 0 || second.Existing != len(rules) {
|
|
t.Fatalf("second seed report=%+v", second)
|
|
}
|
|
loaded, err := store.Get(context.Background(), custom.ID)
|
|
if err != nil || loaded.Name != "Aangepaste naam" {
|
|
t.Fatalf("custom rule was overwritten: rule=%+v err=%v", loaded, err)
|
|
}
|
|
}
|
|
func TestDefaultRulesReduceStormNoise(t *testing.T) {
|
|
registry, err := metriccatalog.DefaultRegistry()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rules, err := Load(registry)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var serviceRule alert.Document
|
|
for _, rule := range rules {
|
|
if rule.Name == "Service is niet bereikbaar" {
|
|
serviceRule = rule
|
|
}
|
|
}
|
|
if serviceRule.ID == "" {
|
|
t.Fatal("service default rule is missing")
|
|
}
|
|
at := time.Date(2026, time.January, 4, 12, 0, 0, 0, time.UTC)
|
|
signals := make([]alert.Signal, 0, 5)
|
|
for i := 0; i < 5; i++ {
|
|
signals = append(signals, alert.Signal{InstanceID: "service-" + string(rune('a'+i)), RuleID: serviceRule.ID, RuleVersionID: "version-1", EntityID: "entity-" + string(rune('a'+i)), Severity: serviceRule.Severity, State: alert.StateFiring, Fingerprint: "fingerprint-" + string(rune('a'+i)), EvaluationKey: "evaluation-1", ObservedAt: at, Labels: map[string]string{"host": "pulse", "application": "media", "service": "svc-" + string(rune('a'+i))}, GroupBy: serviceRule.GroupBy, SuppressWhen: serviceRule.SuppressWhen})
|
|
}
|
|
deduplicated, err := alert.DeduplicateSignals(append(signals, signals[0]))
|
|
if err != nil || len(deduplicated) != len(signals) {
|
|
t.Fatalf("deduplicated=%d err=%v", len(deduplicated), err)
|
|
}
|
|
groups, err := alert.GroupSignals(deduplicated)
|
|
if err != nil || len(groups) != len(signals) {
|
|
t.Fatalf("groups=%+v err=%v", groups, err)
|
|
}
|
|
decision, err := alert.EvaluateSuppression(signals[0], []alert.Cause{{Key: "dns.unavailable", State: alert.StateFiring, Confirmed: true, ObservedAt: at}})
|
|
if err != nil || !decision.Suppressed || decision.CauseKey != "dns.unavailable" {
|
|
t.Fatalf("suppression=%+v err=%v", decision, err)
|
|
}
|
|
}
|
|
|
|
func TestSeedRefreshesUntouchedDefaultButKeepsOperatorEdits(t *testing.T) {
|
|
registry, err := metriccatalog.DefaultRegistry()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rules, err := Load(registry)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stale := rules[0]
|
|
stale.PendingSeconds = stale.PendingSeconds + 60
|
|
edited := rules[1]
|
|
edited.Name = "Door operator aangepast"
|
|
store := &fakeStore{rules: map[string]alert.Rule{
|
|
stale.ID: {Document: stale, Revision: 1}, // seeded, never edited -> refreshed
|
|
edited.ID: {Document: edited, Revision: 2}, // edited by an operator -> untouched
|
|
}}
|
|
report, err := Seed(context.Background(), store, registry, "system-defaults")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if report.Refreshed != 1 || report.Existing != 1 || report.Added != len(rules)-2 {
|
|
t.Fatalf("report=%+v", report)
|
|
}
|
|
refreshed, _ := store.Get(context.Background(), stale.ID)
|
|
if refreshed.PendingSeconds != rules[0].PendingSeconds || refreshed.Revision != 2 {
|
|
t.Fatalf("stale default not refreshed: %+v", refreshed)
|
|
}
|
|
kept, _ := store.Get(context.Background(), edited.ID)
|
|
if kept.Name != "Door operator aangepast" || kept.Revision != 2 {
|
|
t.Fatalf("operator edit overwritten: %+v", kept)
|
|
}
|
|
again, err := Seed(context.Background(), store, registry, "system-defaults")
|
|
if err != nil || again.Refreshed != 0 || again.Added != 0 {
|
|
t.Fatalf("second seed not idempotent: %+v err=%v", again, err)
|
|
}
|
|
}
|