This commit is contained in:
+132
-1
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -165,7 +166,7 @@ func TestAIFallbackOnlyRunsForZeroResultsAndReturnsStagingArticle(t *testing.T)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h := newApp(s, web, appConfig{Mode: "google", Title: "Helpdesk", Writable: false, AIFallbackEnabled: true}).withAI(ai).routes()
|
||||
h := newApp(s, web, appConfig{Mode: "google", Title: "Helpdesk", Writable: false, AIFallbackEnabled: true}).withStaging(st).withAI(ai).routes()
|
||||
|
||||
// Existing results must block the AI path before Ollama is called.
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/ai/fallback", bytes.NewBufferString(`{"query":"Bekannter Fehler"}`))
|
||||
@@ -202,3 +203,133 @@ func TestAIFallbackOnlyRunsForZeroResultsAndReturnsStagingArticle(t *testing.T)
|
||||
t.Fatalf("staging get status=%d body=%s", getRR.Code, getRR.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditorCanReviewPromoteAndDeleteStaging(t *testing.T) {
|
||||
knowledge := t.TempDir()
|
||||
stagingDir := t.TempDir()
|
||||
t.Setenv("BACKUP_DIR", filepath.Join(t.TempDir(), "backups"))
|
||||
s, err := store.New(knowledge)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st, err := staging.New(stagingDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
first, err := st.Save("unbekannt 0xAABBCCDD", "test-model", staging.Draft{
|
||||
Title: "Zu prüfender Entwurf", Text: "Symptom", Answer: "Lösung", Keywords: []string{"0xAABBCCDD"},
|
||||
}, false, 0.78)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
second, err := st.Save("anderer Entwurf", "test-model", staging.Draft{
|
||||
Title: "Zu löschender Entwurf", Text: "Symptom", Answer: "Lösung",
|
||||
}, false, 0.78)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
web, err := fs.Sub(webFS, "web")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h := newApp(s, web, appConfig{Mode: "editor", Title: "Editor", Writable: true}).withStaging(st).routes()
|
||||
|
||||
listReq := httptest.NewRequest(http.MethodGet, "/api/staging?q=AABBCCDD&page=1&page_size=20", nil)
|
||||
listRR := httptest.NewRecorder()
|
||||
h.ServeHTTP(listRR, listReq)
|
||||
if listRR.Code != http.StatusOK {
|
||||
t.Fatalf("list status=%d body=%s", listRR.Code, listRR.Body.String())
|
||||
}
|
||||
var list staging.ListResult
|
||||
if err := json.Unmarshal(listRR.Body.Bytes(), &list); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if list.Total != 1 || list.Items[0].Key != first.Key {
|
||||
t.Fatalf("unexpected staging list: %+v", list)
|
||||
}
|
||||
|
||||
updated := first.Document
|
||||
updated["title"] = "Geprüfter Entwurf"
|
||||
updated["auto_reply"] = true
|
||||
body, _ := json.Marshal(updated)
|
||||
putReq := httptest.NewRequest(http.MethodPut, "/api/staging/"+first.Key, bytes.NewReader(body))
|
||||
putReq.Header.Set("Content-Type", "application/json")
|
||||
putRR := httptest.NewRecorder()
|
||||
h.ServeHTTP(putRR, putReq)
|
||||
if putRR.Code != http.StatusOK {
|
||||
t.Fatalf("put status=%d body=%s", putRR.Code, putRR.Body.String())
|
||||
}
|
||||
|
||||
promoteReq := httptest.NewRequest(http.MethodPost, "/api/staging/"+first.Key+"/promote", bytes.NewBufferString(`{}`))
|
||||
promoteReq.Header.Set("Content-Type", "application/json")
|
||||
promoteRR := httptest.NewRecorder()
|
||||
h.ServeHTTP(promoteRR, promoteReq)
|
||||
if promoteRR.Code != http.StatusCreated {
|
||||
t.Fatalf("promote status=%d body=%s", promoteRR.Code, promoteRR.Body.String())
|
||||
}
|
||||
if s.Count() != 1 || st.Count() != 1 {
|
||||
t.Fatalf("counts after promote: production=%d staging=%d", s.Count(), st.Count())
|
||||
}
|
||||
prod := s.List(store.Query{Page: 1, PageSize: 10})
|
||||
if prod.Items[0].Title != "Geprüfter Entwurf" || prod.Items[0].AutoReply == nil || !*prod.Items[0].AutoReply {
|
||||
t.Fatalf("promoted item not preserved: %+v", prod.Items[0])
|
||||
}
|
||||
if _, err := st.Get(first.Key); !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatalf("promoted staging file should be gone, err=%v", err)
|
||||
}
|
||||
|
||||
deleteReq := httptest.NewRequest(http.MethodDelete, "/api/staging/"+second.Key, nil)
|
||||
deleteRR := httptest.NewRecorder()
|
||||
h.ServeHTTP(deleteRR, deleteReq)
|
||||
if deleteRR.Code != http.StatusOK {
|
||||
t.Fatalf("delete status=%d body=%s", deleteRR.Code, deleteRR.Body.String())
|
||||
}
|
||||
if st.Count() != 0 {
|
||||
t.Fatalf("staging should be empty, count=%d", st.Count())
|
||||
}
|
||||
approved, err := filepath.Glob(filepath.Join(stagingDir, ".approved", "*.json"))
|
||||
if err != nil || len(approved) != 1 {
|
||||
t.Fatalf("expected one promoted draft in .approved, files=%v err=%v", approved, err)
|
||||
}
|
||||
trash, err := filepath.Glob(filepath.Join(stagingDir, ".trash", "*.json"))
|
||||
if err != nil || len(trash) != 1 {
|
||||
t.Fatalf("expected one deleted draft in .trash, files=%v err=%v", trash, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditorBulkStagingPromote(t *testing.T) {
|
||||
knowledge := t.TempDir()
|
||||
s, err := store.New(knowledge)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st, err := staging.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a, err := st.Save("a", "model", staging.Draft{Title: "A", Answer: "Lösung A"}, false, .78)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, err := st.Save("b", "model", staging.Draft{Title: "B", Answer: "Lösung B"}, false, .78)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
web, _ := fs.Sub(webFS, "web")
|
||||
h := newApp(s, web, appConfig{Mode: "editor", Writable: true}).withStaging(st).routes()
|
||||
payload, _ := json.Marshal(map[string]any{"keys": []string{a.Key, b.Key}, "action": "promote"})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/staging/bulk", bytes.NewReader(payload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rr := httptest.NewRecorder()
|
||||
h.ServeHTTP(rr, req)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("status=%d body=%s", rr.Code, rr.Body.String())
|
||||
}
|
||||
var result struct{ Succeeded, Failed int }
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &result); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Succeeded != 2 || result.Failed != 0 || s.Count() != 2 || st.Count() != 0 {
|
||||
t.Fatalf("unexpected bulk result=%+v prod=%d staging=%d", result, s.Count(), st.Count())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user