package engine import ( "testing" "github.com/local/glpi-neural-brain/internal/config" "github.com/local/glpi-neural-brain/internal/model" ) func TestDetectArticleFreshnessNeedStaticTopicDoesNotForceWeb(t *testing.T) { plan := model.ArticlePlanDecision{ExpectedValue: "Least Privilege in Windows-Netzwerken konfigurieren", ArticleType: "how_to"} relation := model.RelationDecision{TopicLabel: "Least Privilege", Keywords: []string{"Windows", "Berechtigungen"}} got := detectArticleFreshnessNeed(plan, relation, []articleSource{{Node: model.Node{Label: "Least Privilege Grundlagen"}}}) if got.Required { t.Fatalf("static configuration topic must not force web research: %+v", got) } } func TestDetectArticleFreshnessNeedCurrentVersionForcesWeb(t *testing.T) { plan := model.ArticlePlanDecision{ExpectedValue: "Aktuell unterstützte Kubernetes Versionen und Supportstatus"} got := detectArticleFreshnessNeed(plan, model.RelationDecision{}, nil) if !got.Required || len(got.Queries) == 0 { t.Fatalf("freshness-sensitive topic should trigger focused web research: %+v", got) } } func TestEffectiveArticleResearchStrategyAutoFollowsProcessingMode(t *testing.T) { e := &Engine{Cfg: config.Config{ArticleResearchStrategy: "auto"}} e.runtime.ProcessingMode = "clustered" if got := e.effectiveArticleResearchStrategy(); got != "adaptive" { t.Fatalf("clustered auto strategy=%q", got) } e.runtime.ProcessingMode = "precise" if got := e.effectiveArticleResearchStrategy(); got != "always" { t.Fatalf("precise auto strategy=%q", got) } } func TestNormalizeArticleContentClearsUnusedResearchQueries(t *testing.T) { got := normalizeArticleContent(model.KnowledgeArticleContent{ResearchNeeded: false, ResearchQueries: []string{"current version"}, ResearchReason: "not needed"}) if len(got.ResearchQueries) != 0 { t.Fatalf("unused author research queries must be cleared: %#v", got.ResearchQueries) } } func TestArticleSourceFingerprintChangesWithContent(t *testing.T) { plan := model.ArticlePlanDecision{Action: "update", TargetArticleID: "target", ArticleType: "how_to"} a := []articleSource{{Node: model.Node{ID: "A"}, Content: "old content"}} b := []articleSource{{Node: model.Node{ID: "A"}, Content: "new content"}} if articleSourceFingerprint(a, plan, nil, "test-pipeline") == articleSourceFingerprint(b, plan, nil, "test-pipeline") { t.Fatal("source fingerprint must change when source content changes") } } func TestReviewReferenceUsesExactReviewerSubset(t *testing.T) { full := []model.ResearchResult{ {Title: "weak", URL: "https://a.example/weak", Relevance: .1, SourceQualityScore: .1}, {Title: "best", URL: "https://b.example/best", Relevance: .99, SourceQualityScore: .99, Fetched: true}, {Title: "other", URL: "https://c.example/other", Relevance: .8, SourceQualityScore: .8, Fetched: true}, } subset := selectReviewEvidence(full, 2) if len(subset) != 2 || subset[0].URL != "https://b.example/best" { t.Fatalf("unexpected reviewer subset: %#v", subset) } grounded := reviewedResearchEvidence(subset, []model.ArticleClaimReview{{Claim: "x", Verdict: "supported", SourceRefs: []string{"R1"}}}) if len(grounded) != 1 || grounded[0].URL != "https://b.example/best" { t.Fatalf("R1 must resolve against the exact reviewer subset, got %#v", grounded) } } func TestClusterPendingArticleCandidatesGroupsSharedSeed(t *testing.T) { shared := model.Node{ID: "shared", Label: "Backup Repository", Categories: []string{"Backup"}} a := &pendingArticleCandidate{Seeds: []model.Node{shared, {ID: "a", Label: "Ransomware Schutz"}}, Relation: model.RelationDecision{TopicLabel: "Backup Hardening"}} b := &pendingArticleCandidate{Seeds: []model.Node{shared, {ID: "b", Label: "Immutable Backup"}}, Relation: model.RelationDecision{TopicLabel: "Backup Resilience"}} clusters := clusterPendingArticleCandidates([]*pendingArticleCandidate{a, b}) if len(clusters) != 1 || len(clusters[0]) != 2 { t.Fatalf("shared seed should produce one article cluster: %#v", clusters) } } func TestArticleWorkFingerprintChangesWhenGroundedResearchChanges(t *testing.T) { sources := []articleSource{{Node: model.Node{ID: "A"}, Content: "stable internal"}} relation := model.RelationDecision{RelationType: "same_topic", TopicLabel: "Backup Hardening", Keywords: []string{"backup"}} a := articleWorkFingerprint(sources, relation, []model.ResearchResult{{URL: "https://example.test/a", Content: "old external evidence"}}, "test-pipeline") b := articleWorkFingerprint(sources, relation, []model.ResearchResult{{URL: "https://example.test/a", Content: "new external evidence"}}, "test-pipeline") if a == b { t.Fatal("new grounded research must invalidate the work fingerprint") } }