package engine import ( "crypto/sha256" "encoding/hex" "encoding/json" "os" "path/filepath" "testing" "time" "github.com/local/glpi-neural-brain/internal/config" "github.com/local/glpi-neural-brain/internal/model" ) func TestLoadResearchEvidenceValidatesPortableRecord(t *testing.T) { root := t.TempDir() result := model.ResearchResult{ Title: "Audit logging documentation", URL: "https://docs.example.test/audit", Content: "Ausführlicher und fachlich relevanter Volltext für die spätere Wiederverwendung.", Fetched: true, Relevant: true, Relevance: .91, SourceQualityScore: .88, } hash := sha256.Sum256([]byte(result.URL + "\x00" + result.Content)) record := researchEvidenceRecord{SchemaVersion: researchEvidenceSchemaVersion, SavedAt: time.Now().UTC(), ContentSHA256: hex.EncodeToString(hash[:]), Result: result} data, err := json.Marshal(record) if err != nil { t.Fatal(err) } relPath := filepath.Join("research-evidence", "test.json") if err := os.MkdirAll(filepath.Join(root, "research-evidence"), 0o750); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(root, relPath), data, 0o640); err != nil { t.Fatal(err) } e := &Engine{Cfg: config.Config{DataDir: root}} loaded, err := e.loadResearchEvidence(filepath.ToSlash(relPath)) if err != nil { t.Fatalf("load evidence: %v", err) } if loaded.Result.URL != result.URL || loaded.Result.Content != result.Content { t.Fatalf("unexpected loaded record: %+v", loaded) } } func TestLoadResearchEvidenceRejectsTraversalAndTampering(t *testing.T) { root := t.TempDir() e := &Engine{Cfg: config.Config{DataDir: root}} if _, err := e.loadResearchEvidence("../outside.json"); err == nil { t.Fatal("path traversal must be rejected") } result := model.ResearchResult{URL: "https://docs.example.test/audit", Content: "content", Fetched: true, Relevant: true} record := researchEvidenceRecord{SchemaVersion: researchEvidenceSchemaVersion, SavedAt: time.Now().UTC(), ContentSHA256: "bad", Result: result} data, _ := json.Marshal(record) if err := os.MkdirAll(filepath.Join(root, "research-evidence"), 0o750); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(root, "research-evidence", "bad.json"), data, 0o640); err != nil { t.Fatal(err) } if _, err := e.loadResearchEvidence("research-evidence/bad.json"); err == nil { t.Fatal("checksum mismatch must be rejected") } } func TestResearchResultNodeReferencesEvidenceWithoutEmbeddingFullPageInMetadata(t *testing.T) { result := model.ResearchResult{Title: "Documentation", URL: "https://docs.example.test/a", Content: "full page content", Fetched: true, Relevant: true} node := researchResultNode("id", result, "research-evidence/id.json", "abc", []string{"IT-Security"}) if node.Metadata["evidence_path"] != "research-evidence/id.json" || node.Metadata["content_sha256"] != "abc" { t.Fatalf("evidence reference missing: %+v", node.Metadata) } if _, exists := node.Metadata["content"]; exists { t.Fatal("full page content must not be copied into graph metadata") } } func TestLoadResearchEvidenceCanReuseInMemoryQueuedRecord(t *testing.T) { root := t.TempDir() result := model.ResearchResult{URL: "https://docs.example.test/live", Content: "queued full content", Fetched: true, Relevant: true} hash := sha256.Sum256([]byte(result.URL + "\x00" + result.Content)) record := researchEvidenceRecord{SchemaVersion: researchEvidenceSchemaVersion, SavedAt: time.Now().UTC(), ContentSHA256: hex.EncodeToString(hash[:]), Result: result} e := &Engine{Cfg: config.Config{DataDir: root}} e.cacheResearchEvidence("research-evidence/live.json", record) loaded, err := e.loadResearchEvidence("research-evidence/live.json") if err != nil { t.Fatalf("load cached evidence before disk flush: %v", err) } if loaded.Result.Content != result.Content { t.Fatalf("unexpected cached evidence: %+v", loaded) } } func TestMetadataStringTreatsMissingAndNilAsEmpty(t *testing.T) { cases := []map[string]any{ nil, {}, {"evidence_path": nil}, {"evidence_path": ""}, {"evidence_path": "null"}, } for _, metadata := range cases { if got := metadataString(metadata, "evidence_path"); got != "" { t.Fatalf("expected empty metadata string, got %q from %#v", got, metadata) } } if got := metadataString(map[string]any{"evidence_path": " research-evidence/x.json "}, "evidence_path"); got != "research-evidence/x.json" { t.Fatalf("unexpected normalized metadata string %q", got) } }