Some checks failed
release-tag / Resolve release metadata (push) Successful in 30s
release-tag / Build knowledge (push) Failing after 4m51s
release-tag / Build control (push) Failing after 5m0s
release-tag / Build agent (push) Failing after 5m0s
release-tag / Build agent-data-init (push) Failing after 5m5s
release-tag / Build neuroforge-worker (push) Failing after 5m7s
release-tag / Build neuroforge (push) Failing after 5m9s
79 lines
3.2 KiB
Go
79 lines
3.2 KiB
Go
package web
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/example/glpi-ai-agent/internal/config"
|
|
"github.com/example/glpi-ai-agent/internal/learning"
|
|
"github.com/example/glpi-ai-agent/internal/model"
|
|
)
|
|
|
|
func TestControlReadAuthIsScopedBearerOnly(t *testing.T) {
|
|
s := &Server{cfg: config.Config{ControlReadToken: "01234567890123456789012345678901"}}
|
|
h := s.controlReadAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) }))
|
|
for _, tc := range []struct {
|
|
name, auth string
|
|
want int
|
|
}{
|
|
{"missing", "", http.StatusUnauthorized},
|
|
{"wrong", "Bearer no", http.StatusUnauthorized},
|
|
{"valid", "Bearer 01234567890123456789012345678901", http.StatusNoContent},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "/api/control/runs", nil)
|
|
if tc.auth != "" {
|
|
req.Header.Set("Authorization", tc.auth)
|
|
}
|
|
rr := httptest.NewRecorder()
|
|
h.ServeHTTP(rr, req)
|
|
if rr.Code != tc.want {
|
|
t.Fatalf("status=%d want=%d", rr.Code, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildRunGraphContainsEvidencePoliciesAndOutcome(t *testing.T) {
|
|
r := model.RunRecord{RunID: "run-1", TicketID: 42, TicketName: "VPN geht nicht", Outcome: "processed", KnowledgeID: "KB-1", ReplyProposed: true, ReplyProposedText: "VPN neu verbinden", AIReplyConfidence: .94,
|
|
ReplyKnowledgeCandidates: []model.KnowledgeCandidateAudit{{ID: "KB-1", Title: "VPN", Source: "internal-kb", Score: .91, RetrievalRank: 1, AutoReply: true}},
|
|
ValidatedOutcomeCandidates: []model.ValidatedOutcomeEvidence{{MemoryID: "m-old", OutcomeID: "o-old", Decision: "accepted", Text: "Adapter reset", Similarity: .82, Source: "glpi.outcome.accepted"}},
|
|
ReplyChecks: []model.RuleCheck{{Code: "evidence", Label: "Evidence ausreichend", Status: "pass", Blocking: true}},
|
|
}
|
|
outs := []learning.TicketOutcome{{ID: "o1", RunID: "run-1", TicketID: 42, Decision: "corrected", ConfirmedReply: "VPN Adapter neu starten", NeuroForgeID: "m1", SyncStatus: "learned"}}
|
|
g := buildRunGraph(r, outs)
|
|
kinds := map[string]bool{}
|
|
for _, n := range g.Nodes {
|
|
kinds[n.Kind] = true
|
|
}
|
|
for _, want := range []string{"ticket", "run", "knowledge", "validated_outcome", "policy_check", "reply", "human_outcome", "memory"} {
|
|
if !kinds[want] {
|
|
t.Fatalf("missing node kind %q in %#v", want, kinds)
|
|
}
|
|
}
|
|
edges := map[string]bool{}
|
|
for _, e := range g.Edges {
|
|
edges[e.Kind] = true
|
|
}
|
|
for _, want := range []string{"retrieved", "experience_evidence", "checked", "proposed_reply", "validated_by", "learned_as"} {
|
|
if !edges[want] {
|
|
t.Fatalf("missing edge kind %q in %#v", want, edges)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildLearningGraphPreservesSupersession(t *testing.T) {
|
|
items := []learning.TicketOutcome{{ID: "new", RunID: "r", TicketID: 7, Decision: "corrected", ConfirmedReply: "new", SupersedesID: "old", SyncStatus: "learned"}, {ID: "old", RunID: "r", TicketID: 7, Decision: "accepted", ConfirmedReply: "old", SyncStatus: "learned"}}
|
|
g := buildLearningGraph(items)
|
|
found := false
|
|
for _, e := range g.Edges {
|
|
if e.Kind == "supersedes" && e.From == "outcome:new" && e.To == "outcome:old" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("supersession edge missing: %+v", g.Edges)
|
|
}
|
|
}
|