f5e0f8d20f
build / build (push) Successful in 1m7s
将可维护总表与可替换2.0镜像分离,稳定来源关联和原始事件键保留; 源端更新/删除不覆盖主表或人工复核。首次收录保留原评估审计, 主表输入/复核独立持久化;历史原标注不冒充新AI或冻结证据。 历史导入绑定源哈希、逐行批准清单与真实恢复备份;三字段精确去重, 批次及原字段留档,重复执行不覆盖后续修改。仅操作本地治理库。 验证:完整make verify退出0;镜像与区域两组隔离PG race退出0; 真实恢复数据隔离导入12522、跳过792、暂缓1,总表15050; 重放保留模拟修改,结构down拒绝丢历史;真实浏览器第41周551条完整, 历史原标注和独立主表复核读回通过,移动布局及JS异常0。
79 lines
3.1 KiB
Go
79 lines
3.1 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"context"
|
|
"knowledge-governance/internal/dobotaiops"
|
|
"knowledge-governance/internal/qaopsquality"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type masterFixtureReader struct {
|
|
calls int
|
|
tenant string
|
|
}
|
|
|
|
func (f *masterFixtureReader) ListRecords(context.Context, dobotaiops.RecordQuery) (dobotaiops.RecordPage, error) {
|
|
panic("master must never fall back to replaceable mirror/source reads")
|
|
}
|
|
func (f *masterFixtureReader) GetRecord(context.Context, string, string) (dobotaiops.Record, error) {
|
|
panic("master must never fetch the production source")
|
|
}
|
|
func (f *masterFixtureReader) Available(context.Context) error { return nil }
|
|
func (f *masterFixtureReader) Safety() dobotaiops.SourceSafety { return dobotaiops.SourceSafety{} }
|
|
func (f *masterFixtureReader) MasterQualityStore() *qaopsquality.Store { return nil }
|
|
func (f *masterFixtureReader) ListMasterRecords(_ context.Context, q dobotaiops.RecordQuery) (dobotaiops.RecordPage, error) {
|
|
f.calls++
|
|
f.tenant = q.Window.TenantKey
|
|
return dobotaiops.RecordPage{Items: []dobotaiops.Record{}, Columns: []string{"question"}, AsOf: time.Date(2026, 10, 19, 0, 0, 0, 0, time.UTC), LocalRevision: 3}, nil
|
|
}
|
|
func (f *masterFixtureReader) GetMasterRecord(_ context.Context, tenant, id string) (dobotaiops.Record, error) {
|
|
f.calls++
|
|
f.tenant = tenant
|
|
return dobotaiops.Record{ID: id, Question: "fixture master"}, nil
|
|
}
|
|
func (f *masterFixtureReader) ReadMasterQualityScope(context.Context, dobotaiops.RecordQuery) (dobotaiops.QualityScope, error) {
|
|
return dobotaiops.QualityScope{}, nil
|
|
}
|
|
func TestQAMasterUsesFixedTenantAndNeverSourceFallback(t *testing.T) {
|
|
auth := newFakeAuthStore()
|
|
auth.sessions["fixture-master-admin"] = "usr-boss"
|
|
reader := &masterFixtureReader{}
|
|
h, e := NewHandler(Config{Store: &fakeJobStore{}, Auth: &AuthConfig{Store: auth}, QAOps: reader, QAOpsTenantKey: "fixed-master-tenant", Now: func() time.Time { return time.Date(2026, 10, 19, 0, 0, 0, 0, time.UTC) }})
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
base := "/api/v1/qa-ops/master/records"
|
|
for _, c := range []struct {
|
|
path string
|
|
cookie bool
|
|
status int
|
|
}{{base + "?start=2026-10-05T00:00:00Z&end=2026-10-12T00:00:00Z", true, 200}, {base + "/fixture-id", true, 200}, {base + "?tenant=other", true, 400}, {base, false, 401}} {
|
|
r := httptest.NewRequest(http.MethodGet, c.path, nil)
|
|
if c.cookie {
|
|
r.AddCookie(&http.Cookie{Name: sessionCookieName, Value: "fixture-master-admin"})
|
|
}
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
if w.Code != c.status {
|
|
t.Fatalf("master route %s: got %d, wanted %d", c.path, w.Code, c.status)
|
|
}
|
|
if w.Header().Get("Cache-Control") != "no-store" {
|
|
t.Fatal("master employee data was cacheable")
|
|
}
|
|
}
|
|
if reader.calls != 2 || reader.tenant != "fixed-master-tenant" {
|
|
t.Fatal("master fixed-tenant read path missing")
|
|
}
|
|
r := httptest.NewRequest(http.MethodPost, base+"/fixture-id/reviews", strings.NewReader(`{}`))
|
|
r.AddCookie(&http.Cookie{Name: sessionCookieName, Value: "fixture-master-admin"})
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
if w.Code != 403 {
|
|
t.Fatal("master review lacked explicit same-origin intent gate", w.Code)
|
|
}
|
|
}
|