Files
Agent-System-Backend/internal/server/ext9_upload_switch_test.go
T
weichen adcd8fefd2 Fence U3 account writes and stabilize directory cursors
Why: Account disable could be followed by late Backend business writes, API key creation could implicitly create users, account bootstrap locks could invert, and directory cursors were process-local or leaked out-of-scope changes.

Evidence: Local go test ./... and isolated PostgreSQL package suites passed on the staged candidate; exact committed-head SELF replay and stage 6 human review remain pending.

Impact: Adds final account transactions and external-effect checks across HTTP, Host, Bus and run manager; tightens API key issuance and persists versioned cursor signing. Delegated RDM rename still needs the separately proposed real-operator contract, so A1 and stage 6 are not complete.

Refs: Documents U3 r1, approved A1-A4 amendment fb3a821, pending delegated-projection amendment 5783940, Dev feature/iam-u3-account-directory-recovery.
2026-10-14 07:33:54 +00:00

103 lines
3.3 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 := createLegacyAPIKeyFixture(t, st, "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
}