Files
Agent-System-Backend/internal/server/ext9_upload_switch_test.go
T
weichen 1ee32822c3 Add the EXT-9 publish/revoke surface and red acceptance tests
Why: EXT-9 is the other step-4 hard prerequisite; the surface (Options.DisableUploadIndexing passthrough, Host.KnowledgePublish idempotent replace, Host.KnowledgeDelete prefix-enforced revoke, SurfaceVersion 2.2.0-experimental) must land with the acceptance reds pinned before implementation.
Evidence: app red = publish not implemented; server red = switch-on still indexes (hits=1); rows recorded on the task card.
Impact: pkg/extension additive surface (surface.txt updated); internal Options passthrough; Server.DisableUploadIndexing field; host method stubs. Behavior unchanged.
Refs: EXT-9 proposal; task card.
2026-10-08 15:53:39 +00:00

103 lines
3.2 KiB
Go

package server
import (
"bytes"
"encoding/json"
"fmt"
"mime/multipart"
"net/http"
"net/http/httptest"
"testing"
)
// EXT-9 验收:DisableUploadIndexing=true 时上传不再自动进检索(原件与摄取卡
// 行为不变);=false 恢复现状(可解析文本入索引)。
func TestUploadPreIndexSwitch(t *testing.T) {
srv, st := newAuthTestServer(t)
h := srv.Handler()
alice, err := st.CreateAPIKey("alice", "e2e")
if err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodPost, "/api/projects", bytes.NewReader(mustJSON(t, map[string]string{"name": "EXT9"})))
req.Header.Set("Authorization", "Bearer "+alice)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("create project: %d %s", rec.Code, rec.Body.String())
}
var pj map[string]any
json.Unmarshal(rec.Body.Bytes(), &pj)
pid := fmt.Sprint(pj["id"])
upload := func(name, text string) string {
var buf bytes.Buffer
mw := multipart.NewWriter(&buf)
fw, _ := mw.CreateFormFile("file", name)
fw.Write([]byte(text))
mw.Close()
req := httptest.NewRequest(http.MethodPost, "/api/projects/"+pid+"/uploads", &buf)
req.Header.Set("Authorization", "Bearer "+alice)
req.Header.Set("Content-Type", mw.FormDataContentType())
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("upload %s: %d %s", name, rec.Code, rec.Body.String())
}
var up map[string]any
json.Unmarshal(rec.Body.Bytes(), &up)
return fmt.Sprint(up["artifactId"])
}
search := func(q string) int {
req := httptest.NewRequest(http.MethodGet, "/api/knowledge/search?q="+q, nil)
req.Header.Set("Authorization", "Bearer "+alice)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("search: %d %s", rec.Code, rec.Body.String())
}
var arr []any
if err := json.Unmarshal(rec.Body.Bytes(), &arr); err == nil {
return len(arr) // 直接数组形态
}
var out struct {
Hits []any `json:"hits"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
t.Fatalf("search decode: %v (%s)", err, rec.Body.String())
}
return len(out.Hits)
}
// 开关开:上传不入检索,但原件完整可取回。
srv.DisableUploadIndexing = true
aid := upload("ext9-off.txt", "EXT9OFF 索引开关独占词甲")
if n := search("索引开关独占词甲"); n != 0 {
t.Fatalf("switch on: search hits=%d, want 0(存疑确认前不入正式检索)", n)
}
reqC := httptest.NewRequest(http.MethodGet, "/api/artifacts/"+aid+"/content", nil)
reqC.Header.Set("Authorization", "Bearer "+alice)
recC := httptest.NewRecorder()
h.ServeHTTP(recC, reqC)
if recC.Code != http.StatusOK || !bytes.Contains(recC.Body.Bytes(), []byte("索引开关独占词甲")) {
t.Fatalf("original must remain retrievable: %d %q", recC.Code, recC.Body.String())
}
// 开关关(现状):同一上传路径恢复自动索引。
srv.DisableUploadIndexing = false
upload("ext9-on.txt", "EXT9ON 索引开关独占词乙")
if n := search("索引开关独占词乙"); n != 1 {
t.Fatalf("switch off: search hits=%d, want 1(现状自动索引)", n)
}
}
func mustJSON(t *testing.T, v any) []byte {
t.Helper()
b, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
return b
}