Files
knowledge-governance/internal/httpapi/qa_mirror_sync_test.go
T
姚章浩 3e7fcce0b1
build / build (push) Successful in 1m11s
feat(qa-ops): 持久本地问答副本与安全答案预览
新增迁移50与每5分钟/手动持久同步,API只读本地;同步失败保留、权限阻断、租约与质量失效原子激活。
保留全量稳定分页/固定列,已存质量批量读取,元数据不反复解压长答案;详情安全Markdown与原文。

已运行整仓 make verify、源与副本隔离PG -race、真实只读源及隔离浏览器。
实际同步2525条;固定窗口640条本地中位1.46秒,对照直接源33.10秒。
停worker、缺源凭据、重启API后仍可查询;未更新8088,未修改生产原问答。
2026-10-18 19:10:47 +08:00

87 lines
3.4 KiB
Go

package httpapi
import (
"context"
"encoding/json"
"knowledge-governance/internal/qaopsmirror"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
type mirrorSyncSpy struct {
qaOpsSpy
syncCalls int
key, actor string
}
func (s *mirrorSyncSpy) GetSyncStatus(context.Context) (qaopsmirror.Status, error) {
return qaopsmirror.Status{Mode: "local", IntervalSeconds: 300, WorkerReady: true}, nil
}
func (s *mirrorSyncSpy) EnqueueSync(_ context.Context, key, actor string) (qaopsmirror.SyncReceipt, bool, error) {
s.syncCalls++
s.key = key
s.actor = actor
return qaopsmirror.SyncReceipt{JobID: "fixture-mirror-job"}, false, nil
}
func TestQAMirrorSyncRequiresSessionIntentAndIdempotency(t *testing.T) {
spy := &mirrorSyncSpy{}
auth := newFakeAuthStore()
auth.sessions["mirror-admin"] = "usr-boss"
auth.sessions["mirror-viewer"] = "usr-eye"
h, e := NewHandler(Config{Store: &fakeJobStore{}, QAOps: spy, QAOpsEnabled: true, QAOpsTenantKey: "fixture-tenant", Auth: &AuthConfig{Store: auth}})
if e != nil {
t.Fatal(e)
}
for _, tc := range []struct {
method, token, key, intent, origin, body string
status int
}{
{"GET", "", "", "", "", "", 401}, {"GET", "mirror-viewer", "", "", "", "", 403}, {"GET", "mirror-admin", "", "", "", "", 200},
{"POST", "mirror-admin", "op-one", "", "", "{}", 403}, {"POST", "mirror-admin", "op-one", "qa-records-sync", "https://foreign.invalid", "{}", 403},
{"POST", "mirror-admin", "", "qa-records-sync", "", "{}", 400}, {"POST", "mirror-admin", "op-one", "qa-records-sync", "", "null", 400},
{"POST", "mirror-admin", "op-one", "qa-records-sync", "", `{"tenant":"forged"}`, 400}, {"POST", "mirror-admin", "op-one", "qa-records-sync", "", "{}", 202},
} {
req := httptest.NewRequest(tc.method, "/api/v1/qa-ops/sync", strings.NewReader(tc.body))
if tc.token != "" {
req.AddCookie(&http.Cookie{Name: sessionCookieName, Value: tc.token})
}
req.Header.Set("Idempotency-Key", tc.key)
req.Header.Set("X-Governance-Action", tc.intent)
req.Header.Set("Origin", tc.origin)
w := httptest.NewRecorder()
h.ServeHTTP(w, req)
if w.Code != tc.status {
t.Fatalf("%s %s got %d want %d: %s", tc.method, tc.body, w.Code, tc.status, w.Body.String())
}
}
if spy.syncCalls != 1 || spy.key != "op-one" || spy.actor == "" {
t.Fatal("invalid sync reached store or operator was not session-stamped")
}
req := httptest.NewRequest("GET", "/api/v1/qa-ops/status", nil)
req.AddCookie(&http.Cookie{Name: sessionCookieName, Value: "mirror-admin"})
w := httptest.NewRecorder()
h.ServeHTTP(w, req)
var body struct {
Data struct {
Cache *qaopsmirror.Status `json:"cache"`
}
}
if json.Unmarshal(w.Body.Bytes(), &body) != nil || body.Data.Cache == nil || body.Data.Cache.Mode != "local" {
t.Fatal("local serving mode missing from status")
}
}
func TestQAMirrorSyncNamespaceCannotBypassEntry(t *testing.T) {
auth := newFakeAuthStore()
auth.sessions["mirror-admin"] = "usr-boss"
h, _ := NewHandler(Config{Store: &fakeJobStore{}, Auth: &AuthConfig{Store: auth}})
req := httptest.NewRequest("POST", "/api/v1/jobs", strings.NewReader(`{"type":"qaops.records.sync.fixture","payload":{},"idempotency_key":"bypass"}`))
req.AddCookie(&http.Cookie{Name: sessionCookieName, Value: "mirror-admin"})
w := httptest.NewRecorder()
h.ServeHTTP(w, req)
if w.Code != 400 {
t.Fatal("generic jobs endpoint bypassed mirror sync admission", w.Code)
}
}