Files
glpi-ai-agent/internal/state/store_test.go
jbergner f415434111
All checks were successful
release-tag / release-image (push) Successful in 1m36s
Eskalation erweitert
2026-08-02 23:00:39 +02:00

170 lines
5.0 KiB
Go

package state
import (
"github.com/example/glpi-ai-agent/internal/model"
"testing"
"time"
)
func TestStoreRoundTrip(t *testing.T) {
dir := t.TempDir()
s, err := Open(dir, 10)
if err != nil {
t.Fatal(err)
}
r := model.RunRecord{TicketID: 7, SourceVersion: "v1", FinishedAt: time.Now(), Outcome: "processed"}
if err := s.Append(r); err != nil {
t.Fatal(err)
}
if !s.Seen(7, "v1") {
t.Fatal("not seen")
}
s2, err := Open(dir, 10)
if err != nil {
t.Fatal(err)
}
if !s2.Seen(7, "v1") {
t.Fatal("not persisted")
}
}
func TestErrorRunIsRetriedAfterRestart(t *testing.T) {
dir := t.TempDir()
s, err := Open(dir, 10)
if err != nil {
t.Fatal(err)
}
r := model.RunRecord{TicketID: 8, SourceVersion: "v1", FinishedAt: time.Now(), Outcome: "error", Error: "temporary outage"}
if err := s.Append(r); err != nil {
t.Fatal(err)
}
if s.Seen(8, "v1") {
t.Fatal("failed run must remain retryable")
}
s2, err := Open(dir, 10)
if err != nil {
t.Fatal(err)
}
if s2.Seen(8, "v1") {
t.Fatal("failed run became seen after restart")
}
}
func TestAnalysisIndexAndEscalationIdempotencySurviveRestart(t *testing.T) {
dir := t.TempDir()
s, err := Open(dir, 10)
if err != nil {
t.Fatal(err)
}
a := model.AnalysisRun{
AnalysisID: "analysis-1",
ParentRunID: "run-1",
TicketID: 17,
AnalysisType: "escalation",
Action: model.ActionAudit{
Type: "raise_priority",
Executed: true,
Result: "ticket=17;level=2; priority_written",
},
}
r := model.RunRecord{RunID: "run-1", TicketID: 17, Trigger: "scheduled_escalation", SourceVersion: "v1", FinishedAt: time.Now(), Outcome: "processed", Analyses: []model.AnalysisRun{a}}
if err := s.Append(r); err != nil {
t.Fatal(err)
}
if s.Seen(17, "v1") {
t.Fatal("scheduled escalation must not consume the normal ticket version")
}
got, ok := s.FindAnalysis("analysis-1")
if !ok || got.ParentRunID != "run-1" {
t.Fatalf("analysis index result=%+v ok=%v", got, ok)
}
if !s.HasEscalationKey("ticket=17;level=2") {
t.Fatal("executed escalation key not found")
}
s2, err := Open(dir, 10)
if err != nil {
t.Fatal(err)
}
if _, ok := s2.FindAnalysis("analysis-1"); !ok {
t.Fatal("analysis index was not rebuilt after restart")
}
if !s2.HasEscalationKey("ticket=17;level=2") {
t.Fatal("escalation idempotency was not rebuilt after restart")
}
}
func TestLatestTicketRunCanExcludeScheduledRuns(t *testing.T) {
dir := t.TempDir()
s, err := Open(dir, 10)
if err != nil {
t.Fatal(err)
}
base := time.Now()
for _, r := range []model.RunRecord{
{RunID: "normal", TicketID: 5, Trigger: "poll", SourceVersion: "v1", FinishedAt: base, Outcome: "processed"},
{RunID: "scheduled", TicketID: 5, Trigger: "scheduled_escalation", SourceVersion: "v1", FinishedAt: base.Add(time.Second), Outcome: "processed"},
} {
if err := s.Append(r); err != nil {
t.Fatal(err)
}
}
got, ok := s.LatestTicketRun(5, "scheduled_escalation")
if !ok || got.RunID != "normal" {
t.Fatalf("latest non-scheduled run=%+v ok=%v", got, ok)
}
}
func TestMultiActionEscalationKeysSurviveRestart(t *testing.T) {
dir := t.TempDir()
s, err := Open(dir, 10)
if err != nil {
t.Fatal(err)
}
a := model.AnalysisRun{
AnalysisID: "analysis-plan",
ParentRunID: "run-plan",
TicketID: 23,
AnalysisType: "escalation",
Action: model.ActionAudit{
Type: "escalation_plan",
Steps: []model.ActionStepAudit{
{Step: "assign_second_level", Executed: true, Result: "ticket=23;level=2;action=assign_second_level;target=group:42; assigned"},
{Step: "notify_service_owner", Executed: true, Result: "ticket=23;level=2;action=notify_service_owner;target=user:9; notified"},
{Step: "request_manager_review", Executed: false, Result: "ticket=23;level=2;action=request_manager_review;target=group:77; failed"},
},
},
}
r := model.RunRecord{RunID: "run-plan", TicketID: 23, Trigger: "scheduled_escalation", FinishedAt: time.Now(), Outcome: "processed", Analyses: []model.AnalysisRun{a}}
if err := s.Append(r); err != nil {
t.Fatal(err)
}
for _, key := range []string{
"ticket=23;level=2;action=assign_second_level;target=group:42",
"ticket=23;level=2;action=notify_service_owner;target=user:9",
} {
if !s.HasEscalationKey(key) {
t.Fatalf("executed action key %q not found", key)
}
}
if s.HasEscalationKey("ticket=23;level=2;action=request_manager_review;target=group:77") {
t.Fatal("failed action must not become idempotent")
}
s2, err := Open(dir, 10)
if err != nil {
t.Fatal(err)
}
if !s2.HasEscalationKey("ticket=23;level=2;action=assign_second_level;target=group:42") || !s2.HasEscalationKey("ticket=23;level=2;action=notify_service_owner;target=user:9") {
t.Fatal("multi-action keys were not persisted across restart")
}
}
func TestEscalationKeyFromResultKeepsActionAndTarget(t *testing.T) {
got := escalationKeyFromResult("ticket=4; level=3; action=link_major_incident; target=incident:99; linked")
want := "ticket=4;level=3;action=link_major_incident;target=incident:99"
if got != want {
t.Fatalf("key=%q want %q", got, want)
}
}