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.
103 lines
3.3 KiB
Go
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
|
|
}
|