Files
groot dc369dab65
All checks were successful
release-tag / release-image (push) Successful in 1m34s
Freigabe-Anpassung und Soft-Trash
2026-07-29 11:08:03 +02:00

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