package engine import ( "strings" "testing" "github.com/local/glpi-neural-brain/internal/config" "github.com/local/glpi-neural-brain/internal/model" ) func TestArticleContentToDraftFormatsConceptWithoutInventedSteps(t *testing.T) { content := model.KnowledgeArticleContent{ Title: "Btrfs-Snapshots und ZFS-History in einer Timeline einordnen", ProblemDescription: "Bei der forensischen Timeline-Analyse müssen Dateisystemartefakte mit unterschiedlicher Semantik korrekt eingeordnet werden.", Scope: "Gilt für die vergleichende Analyse von Btrfs- und ZFS-Artefakten.", KeyPoints: []string{ "Ein Snapshot beschreibt einen referenzierten Dateisystemzustand und nicht automatisch eine vollständige Ereignishistorie.", "Zeitstempel müssen mit Artefakttyp, Erzeugungsmechanismus und Datenquelle dokumentiert werden.", }, DecisionCriteria: []string{ "Für eine Ereignistimeline sind nur Zeitangaben geeignet, deren Herkunft und Semantik nachvollziehbar sind.", "Snapshot-Zeitpunkte dürfen nicht ohne zusätzliche Belege als Zeitpunkt jeder enthaltenen Dateiänderung interpretiert werden.", }, } draft := articleContentToDraft(content, []string{"S1", "S2"}, "concept") if strings.TrimSpace(draft.Answer) == "" { t.Fatal("concept article must have a useful answer without artificial solution steps") } if !strings.Contains(draft.Answer, "## Kernaussagen") || !strings.Contains(draft.Answer, "## Einordnung und Abgrenzung") { t.Fatalf("concept sections missing: %s", draft.Answer) } if strings.Contains(draft.Answer, "1. ") { t.Fatalf("concept article contains invented numbered steps: %s", draft.Answer) } } func TestArticleContentToDraftKeepsOperationalStepsNumbered(t *testing.T) { content := model.KnowledgeArticleContent{ Title: "Audit Logging prüfen", ProblemDescription: "Audit-Ereignisse fehlen in der zentralen Protokollierung.", SolutionSteps: []string{ "Aktivieren Sie die zentrale Audit-Protokollierung.", "Erzeugen Sie ein dokumentiertes Testereignis.", }, } draft := articleContentToDraft(content, []string{"S1"}, "troubleshooting") if !strings.Contains(draft.Answer, "1. Aktivieren") || !strings.Contains(draft.Answer, "2. Erzeugen") { t.Fatalf("operational steps were not numbered: %s", draft.Answer) } } func TestNormalizeArticleContentCleansConceptFields(t *testing.T) { content := normalizeArticleContent(model.KnowledgeArticleContent{ KeyPoints: []string{" 1. Erster Punkt ", "Erster Punkt"}, DecisionCriteria: []string{" - Kriterium A ", ""}, }) if len(content.KeyPoints) != 1 || content.KeyPoints[0] != "Erster Punkt" { t.Fatalf("unexpected key points: %#v", content.KeyPoints) } if len(content.DecisionCriteria) != 1 || content.DecisionCriteria[0] != "Kriterium A" { t.Fatalf("unexpected decision criteria: %#v", content.DecisionCriteria) } } func TestArticleQualityContextCarriesArticleType(t *testing.T) { e := &Engine{} contextValue := e.articleQualityContext(model.KnowledgeArticleDraft{Title: "Vergleich", Text: "Beschreibung", Answer: "Kernaussagen"}, "concept", nil, nil) if !strings.Contains(contextValue, "ARTIKELTYP: concept") { t.Fatalf("article type missing from quality context: %s", contextValue) } } func TestArticleDraftContextCarriesArticleType(t *testing.T) { e := &Engine{Cfg: config.Config{MaxContextChars: 4000}} contextValue := e.articleDraftContext(nil, model.ArticlePlanDecision{ArticleType: "decision_guide", Action: "create"}, model.KnowledgeBrief{}, nil) if !strings.Contains(contextValue, "ARTIKELTYP: decision_guide") { t.Fatalf("article type missing from draft context: %s", contextValue) } }