Files
glpi-ai-agent/internal/agent/policy_test.go
T
jbergner 1c12e3ed60
release-tag / release-image (push) Successful in 1m41s
Update
2026-07-27 21:27:42 +02:00

123 lines
4.7 KiB
Go

package agent
import (
"strings"
"testing"
"github.com/example/glpi-ai-agent/internal/model"
)
func productionTestPolicy() Policy {
return NewPolicy(true, true, .9, .97, .88, []string{"internal-kb", "vendor-docs"}, []string{"internal-kb"}, "de-DE", "formal", "Guten Tag,", "Mit freundlichen Grüßen", "IT-Service", true, true, .2)
}
func approvedHit(source, language, style string) []model.KnowledgeHit {
return []model.KnowledgeHit{{Doc: model.KnowledgeDoc{ID: "KB1", Answer: "Bitte starten Sie den VPN-Client neu.", AutoReply: true, MinScore: .9, Categories: []int64{2}, Source: source, Language: language, CommunicationStyle: style}, Score: .95}}
}
func replyDecision() model.Decision {
var d model.Decision
d.Reply.Allowed = true
d.Reply.Confidence = .99
d.Reply.KnowledgeID = "KB1"
d.Category.ID = 2
d.Category.Confidence = .99
return d
}
func TestPolicyAutoReplyUsesApprovedKnowledge(t *testing.T) {
r, err := productionTestPolicy().Evaluate(model.Ticket{CategoryID: 1}, replyDecision(), []model.Category{{ID: 1}, {ID: 2}}, approvedHit("internal-kb", "de-DE", "formal"), model.ContextSnapshot{})
if err != nil {
t.Fatal(err)
}
if !r.Reply || !r.ChangeCategory {
t.Fatalf("unexpected result: %+v", r)
}
for _, expected := range []string{"Guten Tag,", "Bitte starten Sie", "Mit freundlichen Grüßen", "IT-Service"} {
if !strings.Contains(r.ReplyText, expected) {
t.Fatalf("reply missing %q: %q", expected, r.ReplyText)
}
}
}
func TestPolicyRejectsSourceNotAllowedForAutoReply(t *testing.T) {
r, err := productionTestPolicy().Evaluate(model.Ticket{CategoryID: 1}, replyDecision(), []model.Category{{ID: 1}, {ID: 2}}, approvedHit("vendor-docs", "de-DE", "formal"), model.ContextSnapshot{})
if err != nil {
t.Fatal(err)
}
if r.Reply {
t.Fatalf("vendor-docs must not auto-reply: %+v", r)
}
}
func TestPolicyRejectsWrongLanguageOrStyle(t *testing.T) {
p := productionTestPolicy()
for _, tc := range []struct{ language, style string }{{"en-US", "formal"}, {"de-DE", "informal"}} {
r, err := p.Evaluate(model.Ticket{CategoryID: 1}, replyDecision(), []model.Category{{ID: 1}, {ID: 2}}, approvedHit("internal-kb", tc.language, tc.style), model.ContextSnapshot{})
if err != nil {
t.Fatal(err)
}
if r.Reply {
t.Fatalf("unexpected reply for %s/%s", tc.language, tc.style)
}
}
}
func TestPolicyRejectsUnknownCategoryWithoutFailingRun(t *testing.T) {
var d model.Decision
d.Category.ID = 99
d.Category.Confidence = 1
p := NewPolicy(true, false, .9, .9, .8, []string{"internal-kb"}, nil, "de-DE", "formal", "", "", "", true, true, .2)
r, err := p.Evaluate(model.Ticket{CategoryID: 1}, d, []model.Category{{ID: 1}}, nil, model.ContextSnapshot{})
if err != nil {
t.Fatal(err)
}
if r.ChangeCategory || r.CategoryDecision != "category_unknown" {
t.Fatalf("unexpected result: %+v", r)
}
}
func TestPolicyCategoryDecisionIsDeterministic(t *testing.T) {
p := NewPolicy(true, false, .9, .9, .8, []string{"internal-kb"}, nil, "de-DE", "formal", "", "", "", true, true, .2)
var d model.Decision
d.Category.ID = 2
d.Category.Confidence = .89
r, err := p.Evaluate(model.Ticket{CategoryID: 1}, d, []model.Category{{ID: 1, Name: "Alt"}, {ID: 2, Name: "Active Directory"}}, nil, model.ContextSnapshot{})
if err != nil {
t.Fatal(err)
}
if r.ChangeCategory || r.CategoryDecision != "category_confidence_below_threshold" || r.CategoryRecommendationName != "Active Directory" {
t.Fatalf("unexpected result: %+v", r)
}
d.Category.Confidence = .91
r, err = p.Evaluate(model.Ticket{CategoryID: 1}, d, []model.Category{{ID: 1, Name: "Alt"}, {ID: 2, Name: "Active Directory"}}, nil, model.ContextSnapshot{})
if err != nil {
t.Fatal(err)
}
if !r.ChangeCategory || r.CategoryID != 2 || r.CategoryDecision != "category_accepted" {
t.Fatalf("unexpected accepted result: %+v", r)
}
}
func TestPolicyBlocksAutoReplyOnRelevantIncident(t *testing.T) {
ctx := model.ContextSnapshot{MajorIncidents: []model.MajorIncidentContext{{ID: 77, Name: "VPN Ausfall", Relevance: .8}}}
r, err := productionTestPolicy().Evaluate(model.Ticket{CategoryID: 1}, replyDecision(), []model.Category{{ID: 1}, {ID: 2}}, approvedHit("internal-kb", "de-DE", "formal"), ctx)
if err != nil {
t.Fatal(err)
}
if r.Reply || r.ReplyDecision != "reply_relevant_incident" {
t.Fatalf("unexpected: %+v", r)
}
}
func TestPolicyBlocksAutoReplyOnIncompleteContext(t *testing.T) {
ctx := model.ContextSnapshot{Incomplete: true, Warnings: []string{"uptime_kuma: timeout"}}
r, err := productionTestPolicy().Evaluate(model.Ticket{CategoryID: 1}, replyDecision(), []model.Category{{ID: 1}, {ID: 2}}, approvedHit("internal-kb", "de-DE", "formal"), ctx)
if err != nil {
t.Fatal(err)
}
if r.Reply || r.ReplyDecision != "reply_context_incomplete" {
t.Fatalf("unexpected: %+v", r)
}
}