189 lines
5.4 KiB
Go
189 lines
5.4 KiB
Go
package store
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func writeDoc(t *testing.T, dir, name string, doc map[string]any) {
|
|
t.Helper()
|
|
b, err := json.MarshalIndent(doc, "", " ")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, name), b, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestLoadSearchSaveAndBulk(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeDoc(t, dir, "a.json", map[string]any{
|
|
"id": "KB-1", "title": "Fehler 0x80070005", "text": "Zugriff verweigert",
|
|
"answer": "Prüfen", "auto_reply": true, "min_score": 0.78,
|
|
"keywords": []string{"Windows", "0x80070005"}, "categories": []string{},
|
|
"language": "de-DE", "communication_style": "formal", "source": "Microsoft Learn",
|
|
"custom_field": "must survive",
|
|
})
|
|
writeDoc(t, dir, "b.json", map[string]any{
|
|
"id": "KB-2", "title": "Setup", "auto_reply": false, "keywords": []string{"Setup"},
|
|
})
|
|
|
|
t.Setenv("BACKUP_DIR", filepath.Join(dir, "backups"))
|
|
s, err := New(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s.Count() != 2 {
|
|
t.Fatalf("count=%d", s.Count())
|
|
}
|
|
|
|
got := s.List(Query{Q: "80070005", Page: 1, PageSize: 10})
|
|
if got.Total != 1 || got.Items[0].ID != "KB-1" {
|
|
t.Fatalf("unexpected list: %+v", got)
|
|
}
|
|
|
|
key := got.Items[0].Key
|
|
doc, _, err := s.Get(key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
doc["answer"] = "Neue Lösung"
|
|
if _, _, err := s.Save(key, doc); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
reloaded, _, err := s.Get(key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if reloaded["custom_field"] != "must survive" {
|
|
t.Fatal("unknown field was lost")
|
|
}
|
|
if reloaded["answer"] != "Neue Lösung" {
|
|
t.Fatalf("answer=%v", reloaded["answer"])
|
|
}
|
|
|
|
yes := true
|
|
patch := BulkPatch{SetAutoReply: &yes, AddKeywords: []string{"Geprüft"}, FindReplace: &FindReplace{
|
|
Fields: []string{"title"}, Find: "setup", Replace: "Upgrade", CaseSensitive: false,
|
|
}}
|
|
all := s.MatchingKeys(Query{Page: 1, PageSize: 100})
|
|
preview, err := s.ApplyBulk(all, patch, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if preview.Changed != 2 {
|
|
t.Fatalf("preview changed=%d", preview.Changed)
|
|
}
|
|
applied, err := s.ApplyBulk(all, patch, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if applied.Changed != 2 || applied.Backup == "" {
|
|
t.Fatalf("applied=%+v", applied)
|
|
}
|
|
|
|
setup := s.List(Query{Q: "upgrade", Page: 1, PageSize: 10})
|
|
if setup.Total != 1 {
|
|
t.Fatalf("upgrade total=%d", setup.Total)
|
|
}
|
|
}
|
|
|
|
func TestExternalChangeIsRejected(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeDoc(t, dir, "a.json", map[string]any{"id": "KB-1", "title": "Original", "auto_reply": true})
|
|
t.Setenv("BACKUP_DIR", filepath.Join(dir, "..", "backups-external"))
|
|
s, err := New(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
item := s.List(Query{Page: 1, PageSize: 10}).Items[0]
|
|
doc, _, err := s.Get(item.Key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Simulate an external writer after the editor indexed the file.
|
|
writeDoc(t, dir, "a.json", map[string]any{"id": "KB-1", "title": "Extern geändert", "auto_reply": true})
|
|
doc["title"] = "Editor geändert"
|
|
if _, _, err := s.Save(item.Key, doc); err == nil {
|
|
t.Fatal("expected external-change conflict")
|
|
}
|
|
}
|
|
|
|
func TestPreferredJSONFieldOrder(t *testing.T) {
|
|
payload, err := marshalDocument(map[string]any{
|
|
"language": "de-DE", "answer": "A", "id": "KB-1", "title": "T", "custom_z": 1, "custom_a": 2,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s := string(payload)
|
|
wantOrder := []string{`"id"`, `"title"`, `"answer"`, `"language"`, `"custom_a"`, `"custom_z"`}
|
|
last := -1
|
|
for _, needle := range wantOrder {
|
|
idx := strings.Index(s, needle)
|
|
if idx <= last {
|
|
t.Fatalf("field %s out of order in %s", needle, s)
|
|
}
|
|
last = idx
|
|
}
|
|
}
|
|
|
|
func TestSearchRanksExactIdentifiersAndBuildsExcerpt(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeDoc(t, dir, "exact.json", map[string]any{
|
|
"id": "KB-0x80070005", "title": "Fehler 0x80070005", "text": "Zugriff verweigert. Prüfe die Berechtigungen.",
|
|
"keywords": []string{"0x80070005", "Access denied"},
|
|
})
|
|
writeDoc(t, dir, "answer.json", map[string]any{
|
|
"id": "KB-2", "title": "Allgemeine Reparatur", "answer": "Diese Anleitung erwähnt 0x80070005 nur als Beispiel.",
|
|
})
|
|
s, err := New(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
result := s.Search(Query{Q: "0x80070005", Page: 1, PageSize: 10})
|
|
if result.Total != 2 {
|
|
t.Fatalf("total=%d", result.Total)
|
|
}
|
|
if result.Items[0].ID != "KB-0x80070005" {
|
|
t.Fatalf("unexpected ranking: %+v", result.Items)
|
|
}
|
|
if result.Items[0].Score <= result.Items[1].Score {
|
|
t.Fatalf("expected first score to be higher: %+v", result.Items)
|
|
}
|
|
if !strings.Contains(strings.ToLower(result.Items[0].Excerpt), "zugriff") {
|
|
t.Fatalf("unexpected excerpt: %q", result.Items[0].Excerpt)
|
|
}
|
|
}
|
|
|
|
func TestImportDocumentCreatesNewFileAndRejectsDuplicateID(t *testing.T) {
|
|
dir := t.TempDir()
|
|
s, err := New(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
doc := map[string]any{
|
|
"id": "KB-AI-STAGING-TEST-001", "title": "Reviewed", "answer": "Lösung",
|
|
"auto_reply": false, "categories": []any{"AI-Staging"},
|
|
}
|
|
created, err := s.ImportDocument(doc, "fallback")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if created.ID != "KB-AI-STAGING-TEST-001" || s.Count() != 1 {
|
|
t.Fatalf("unexpected created item: %+v count=%d", created, s.Count())
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, "KB-AI-STAGING-TEST-001.json")); err != nil {
|
|
t.Fatalf("production file missing: %v", err)
|
|
}
|
|
if _, err := s.ImportDocument(doc, "fallback"); err == nil {
|
|
t.Fatal("expected duplicate ID to be rejected")
|
|
}
|
|
}
|