Files
knowledge-governance/internal/qaopsmirror/master_integration_test.go
T
姚章浩 f5e0f8d20f
build / build (push) Successful in 1m7s
feat(qa-ops): 建立独立正式主表并保真导入历史问答
将可维护总表与可替换2.0镜像分离,稳定来源关联和原始事件键保留;
源端更新/删除不覆盖主表或人工复核。首次收录保留原评估审计,
主表输入/复核独立持久化;历史原标注不冒充新AI或冻结证据。

历史导入绑定源哈希、逐行批准清单与真实恢复备份;三字段精确去重,
批次及原字段留档,重复执行不覆盖后续修改。仅操作本地治理库。

验证:完整make verify退出0;镜像与区域两组隔离PG race退出0;
真实恢复数据隔离导入12522、跳过792、暂缓1,总表15050;
重放保留模拟修改,结构down拒绝丢历史;真实浏览器第41周551条完整,
历史原标注和独立主表复核读回通过,移动布局及JS异常0。
2026-10-19 08:49:18 +08:00

88 lines
3.4 KiB
Go

package qaopsmirror
import (
"context"
"encoding/json"
"knowledge-governance/internal/dobotaiops"
pg "knowledge-governance/internal/platform/postgres"
"testing"
"time"
)
func TestMasterTableKeepsLocalEditsAcrossSourceRefreshAndDeletion(t *testing.T) {
p := mirrorFixture(t)
ctx := context.Background()
s := NewStore(p, "fixture-master")
queue := pg.NewStore(p)
entry := fixtureEntry("master-source", "source original answer")
var wire map[string]any
if e := json.Unmarshal(entry.Payload, &wire); e != nil {
t.Fatal(e)
}
wire["asked_at"] = "2026-10-06T08:00:00+08:00"
wire["asker"] = "fixture asker"
entry.Payload, _ = json.Marshal(wire)
keeper := fixtureEntry("other-outside-window", "unrelated retained row")
source := &fixtureSource{rows: []dobotaiops.MirrorRecord{entry, keeper}}
job := claimMirrorJob(t, s, queue, "master-first")
if e := s.HandleSync(ctx, *job, func(context.Context) (Source, error) { return source, nil }); e != nil {
t.Fatal(e)
}
start, _ := time.Parse(time.RFC3339, "2026-10-05T00:00:00+08:00")
q := dobotaiops.RecordQuery{Window: dobotaiops.Window{TenantKey: s.tenant, Start: start, End: start.AddDate(0, 0, 7)}, Limit: 100}
if err := queue.Complete(ctx, job.ID, job.LeaseOwner, time.Now()); err != nil {
t.Fatal(err)
}
page, e := s.ListMasterRecords(ctx, q)
if e != nil || page.Total != 1 || len(page.Items) != 1 {
t.Fatal("new source record was not admitted into the durable master", e)
}
if _, e = p.Exec(ctx, `UPDATE qa_ops_master_records SET payload=jsonb_set(payload,'{answer}','"local edited answer"') WHERE tenant_key=$1 AND id='master-source'`, s.tenant); e != nil {
t.Fatal(e)
}
wire["answer"] = "changed at source"
entry.Payload, _ = json.Marshal(wire)
entry.Key.Fingerprint = stringsForMasterFixture("b")
source.rows = []dobotaiops.MirrorRecord{entry, keeper}
job = claimMirrorJob(t, s, queue, "master-source-update")
if e = s.HandleSync(ctx, *job, func(context.Context) (Source, error) { return source, nil }); e != nil {
t.Fatal(e)
}
if err := queue.Complete(ctx, job.ID, job.LeaseOwner, time.Now()); err != nil {
t.Fatal(err)
}
page, e = s.ListMasterRecords(ctx, q)
if e != nil || page.Items[0].Answer != "local edited answer" {
t.Fatal("source refresh overwrote master edits", e)
}
source.rows = []dobotaiops.MirrorRecord{keeper}
job = claimMirrorJob(t, s, queue, "master-source-delete")
if e = s.HandleSync(ctx, *job, func(context.Context) (Source, error) { return source, nil }); e != nil {
t.Fatal(e)
}
if err := queue.Complete(ctx, job.ID, job.LeaseOwner, time.Now()); err != nil {
t.Fatal(err)
}
page, e = s.ListMasterRecords(ctx, q)
if e != nil || page.Total != 1 || page.Items[0].Answer != "local edited answer" {
t.Fatal("source deletion removed local master content", e)
}
if _, e = p.Exec(ctx, `UPDATE qa_ops_mirror_state SET ready=false,blocked=true WHERE tenant_key=$1`, s.tenant); e != nil {
t.Fatal(e)
}
page, e = s.ListMasterRecords(ctx, q)
if e != nil || page.Total != 1 {
t.Fatal("source availability blocked independent master data", e)
}
if _, e = p.Exec(ctx, `UPDATE qa_ops_master_records SET payload=jsonb_set(payload,'{id}','"relabelled-id"') WHERE tenant_key=$1 AND id='master-source'`, s.tenant); e == nil {
t.Fatal("master payload ID was allowed to diverge from the stable master ID")
}
}
func stringsForMasterFixture(s string) string {
out := ""
for i := 0; i < 64; i++ {
out += s
}
return out
}