Public source validation / validate (push) Failing after 3m8s
87 lines
4.5 KiB
Go
87 lines
4.5 KiB
Go
package incident
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/itworx/pulse/internal/database"
|
|
)
|
|
|
|
func TestIncidentRepositoryPostgreSQL(t *testing.T) {
|
|
dsn := os.Getenv("PULSE_TEST_DATABASE_URL")
|
|
if dsn == "" {
|
|
t.Skip("PULSE_TEST_DATABASE_URL is not set")
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
|
|
defer cancel()
|
|
pool, err := database.NewPool(ctx, database.Config{URL: dsn, MaxConns: 12, MinConns: 1})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer pool.Close()
|
|
if err := database.Migrate(ctx, pool); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
repository, err := NewRepository(pool)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
run := fmt.Sprintf("%012x", time.Now().UnixNano()&0xffffffffffff)
|
|
ruleID := "00000000-0000-4000-8000-" + run
|
|
versionID := fmt.Sprintf("00000000-0000-4001-8000-%012x", (time.Now().UnixNano()+1)&0xffffffffffff)
|
|
alertID := fmt.Sprintf("00000000-0000-4002-8000-%012x", (time.Now().UnixNano()+2)&0xffffffffffff)
|
|
entityID := fmt.Sprintf("00000000-0000-4003-8000-%012x", (time.Now().UnixNano()+3)&0xffffffffffff)
|
|
incidentID := fmt.Sprintf("00000000-0000-4004-8000-%012x", (time.Now().UnixNano()+4)&0xffffffffffff)
|
|
_, _ = pool.Exec(ctx, `DELETE FROM incidents WHERE id=$1; DELETE FROM alert_instances WHERE id=$2; DELETE FROM alert_rule_versions WHERE id=$3; DELETE FROM alert_rules WHERE id=$4; DELETE FROM entities WHERE id=$5`, incidentID, alertID, versionID, ruleID, entityID)
|
|
if _, err := pool.Exec(ctx, `INSERT INTO entities (id,entity_type,canonical_name,display_name,first_seen_at) VALUES ($1,'host',$2,$3,now())`, entityID, "m8-09-host-"+run, "M8-09 host "+run); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := pool.Exec(ctx, `INSERT INTO alert_rules
|
|
(id,schema_version,name,severity,condition,evaluation_interval_seconds,pending_seconds,resolve_seconds,unknown_behavior,message)
|
|
VALUES ($1,1,'M8-09 rule','critical','{}',60,0,0,'become-unknown','{}')`, ruleID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `INSERT INTO alert_rule_versions (id,rule_id,version_number,document) VALUES ($1,$2,1,'{}')`, versionID, ruleID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `UPDATE alert_rules SET current_version_id=$1 WHERE id=$2`, versionID, ruleID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `INSERT INTO alert_instances (id,rule_id,rule_version_id,fingerprint,entity_id,current_state,last_evaluated_at) VALUES ($1,$2,$3,$4,$5,'firing',now())`, alertID, ruleID, versionID, "m8-09-fingerprint-"+run, entityID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
_, _ = pool.Exec(ctx, `DELETE FROM incidents WHERE id=$1; DELETE FROM alert_instances WHERE id=$2; DELETE FROM alert_rule_versions WHERE id=$3; DELETE FROM alert_rules WHERE id=$4; DELETE FROM entities WHERE id=$5`, incidentID, alertID, versionID, ruleID, entityID)
|
|
}()
|
|
|
|
now := time.Now().UTC().Truncate(time.Microsecond)
|
|
candidate := Candidate{CorrelationKey: "dependency:" + entityID, Title: "Host unreachable", Summary: "primary dependency outage", Severity: SeverityCritical, StartedAt: now, Confidence: 1, CorrelationMethod: "primary_dependency", Signals: []Signal{{AlertID: alertID, EntityID: entityID, RuleName: "Host unreachable", Severity: SeverityCritical, State: "firing", DependencyOutage: true, ObservedAt: now}}}
|
|
created, first, err := repository.UpsertCandidate(ctx, candidate)
|
|
if err != nil || !first || len(created.Alerts) != 1 || len(created.Entities) != 1 {
|
|
t.Fatalf("created=%+v first=%v err=%v", created, first, err)
|
|
}
|
|
repeated, first, err := repository.UpsertCandidate(ctx, candidate)
|
|
if err != nil || first || repeated.ID != created.ID || len(repeated.Alerts) != 1 {
|
|
t.Fatalf("repeated=%+v first=%v err=%v", repeated, first, err)
|
|
}
|
|
association, duplicate, err := repository.AssociateAlert(ctx, created.ID, alertID, "Operator confirmed common dependency.", 1, "operator-1")
|
|
if err != nil || duplicate || !association.Manual {
|
|
t.Fatalf("association=%+v duplicate=%v err=%v", association, duplicate, err)
|
|
}
|
|
if _, duplicate, err := repository.AssociateAlert(ctx, created.ID, alertID, "Operator confirmed common dependency.", 1, "operator-1"); err != nil || !duplicate {
|
|
t.Fatalf("idempotent association duplicate=%v err=%v", duplicate, err)
|
|
}
|
|
if _, err := repository.UpdateStatus(ctx, created.ID, StatusResolved, 2, now.Add(time.Minute)); !errors.Is(err, ErrConflict) {
|
|
t.Fatalf("stale status error=%v", err)
|
|
}
|
|
item, err := repository.Get(ctx, created.ID)
|
|
if err != nil || len(item.Alerts) != 1 || !item.Alerts[0].Manual {
|
|
t.Fatalf("get incident=%+v err=%v", item, err)
|
|
}
|
|
}
|