File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,163 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user