Files
glpi-neural-brain/internal/engine/article_adaptive_test.go
2026-08-07 21:55:54 +02:00

90 lines
4.6 KiB
Go

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")
}
}