90 lines
4.6 KiB
Go
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")
|
|
}
|
|
}
|