Files
knowledge-governance/internal/qaopsmirror/master_feedback_integration_test.go
T
姚章浩 2c0bb32183
build / build (push) Successful in 1m11s
fix(qaops): 点踩问题统一判为不正确
通过持久来源关联读取最新本地反馈,强制总表有效正确性为否,保留原问答和人工证据。统一列表、详情、指标和两阶段读取的反馈快照,拒绝冲突选择并保留其他字段编辑。已通过完整本地门禁、隔离PG竞态和真实恢复库52条点踩逐条读回、桌面及手机验收;未调用模型或连接源生产库。
2026-10-20 08:18:03 +08:00

87 lines
4.8 KiB
Go

package qaopsmirror
import (
"context"
"encoding/json"
"knowledge-governance/internal/dobotaiops"
pg "knowledge-governance/internal/platform/postgres"
"testing"
"time"
)
func TestMasterDownvoteOverridesStoredAndInlineCorrectnessAcrossReaders(t *testing.T) {
p := mirrorFixture(t)
ctx := context.Background()
s := NewStore(p, "fixture-feedback-rule")
queue := pg.NewStore(p)
entry := fixtureEntry("feedback-rule", "已有资料")
var wire map[string]any
if err := json.Unmarshal(entry.Payload, &wire); err != nil {
t.Fatal(err)
}
wire["asked_at"] = "2026-10-06T08:00:00+08:00"
entry.Payload, _ = json.Marshal(wire)
job := claimMirrorJob(t, s, queue, "feedback-rule-seed")
if err := s.HandleSync(ctx, *job, func(context.Context) (Source, error) {
return &fixtureSource{rows: []dobotaiops.MirrorRecord{entry}}, nil
}); err != nil {
t.Fatal(err)
}
if _, err := p.Exec(ctx, `UPDATE qa_ops_master_records SET local_fields=local_fields||'{"coverage":"是","is_correct":"是","product_category":"MG400","problem_type":"参数"}'::jsonb WHERE tenant_key=$1 AND id=$2`, s.tenant, "feedback-rule"); err != nil {
t.Fatal(err)
}
rec, err := s.GetMasterRecord(ctx, s.tenant, "feedback-rule")
if err != nil {
t.Fatal(err)
}
keys := []dobotaiops.QualityKey{{ID: rec.ID, InputHash: rec.QualityInputHash}}
views, err := s.ReadMasterQualityViews(ctx, keys)
if err != nil {
t.Fatal(err)
}
v := views[rec.ID]
receipt, err := s.PatchMasterFields(ctx, rec.ID, MasterFieldPatch{InputHash: v.InputHash, InputEpoch: v.InputEpoch, BaseReviewRevision: v.Revision, Nonce: "fixture_feedback_rule_001", Fields: map[string]string{"is_correct": "是"}}, "fixture-admin")
if err != nil || receipt.SavedRevision != 1 {
t.Fatal("fixture inline positive edit failed", err)
}
var before string
if err = p.QueryRow(ctx, `SELECT md5(payload::text||original_fields::text||local_fields::text) FROM qa_ops_master_records WHERE tenant_key=$1 AND id=$2`, s.tenant, rec.ID).Scan(&before); err != nil {
t.Fatal(err)
}
// Only the replaceable local source mirror receives the live feedback update.
var revisionBefore, revisionAfter int64
if err = p.QueryRow(ctx, `SELECT revision FROM qa_ops_master_state WHERE tenant_key=$1`, s.tenant).Scan(&revisionBefore); err != nil {
t.Fatal(err)
}
if _, err = p.Exec(ctx, `UPDATE qa_ops_mirror_records SET payload=jsonb_set(payload,'{feedback}','{"asker_value":"down","up":0,"down":1}') WHERE tenant_key=$1 AND id=$2`, s.tenant, rec.ID); err != nil {
t.Fatal(err)
}
if err = p.QueryRow(ctx, `SELECT revision FROM qa_ops_master_state WHERE tenant_key=$1`, s.tenant).Scan(&revisionAfter); err != nil || revisionAfter <= revisionBefore {
t.Fatal("current master feedback changed without invalidating its snapshot revision", err)
}
fresh, err := s.GetMasterRecord(ctx, s.tenant, rec.ID)
if err != nil || fresh.Feedback.Down != 1 || fresh.MasterFields["is_correct"] != "否" {
t.Fatalf("live downvote did not force detail/master fields to incorrect: fields=%v feedback=%+v error=%v", fresh.MasterFields, fresh.Feedback, err)
}
page, err := s.ListMasterRecords(ctx, dobotaiops.RecordQuery{Window: dobotaiops.Window{TenantKey: s.tenant, Start: rec.AskedAt.Add(-time.Hour), End: rec.AskedAt.Add(time.Hour)}, Limit: 10})
if err != nil || len(page.Items) != 1 || page.Items[0].MasterFields["is_correct"] != "否" {
t.Fatal("list differs from detail", err)
}
views, err = s.ReadMasterQualityViews(ctx, keys)
if err != nil || views[rec.ID].Effective.Correctness != "incorrect" {
t.Fatal("effective quality retained the old positive inline edit", err)
}
if _, err = s.PatchMasterFields(ctx, rec.ID, MasterFieldPatch{InputHash: fresh.QualityInputHash, InputEpoch: views[rec.ID].InputEpoch, BaseRevision: 1, BaseReviewRevision: views[rec.ID].Revision, Nonce: "fixture_feedback_rule_002", Fields: map[string]string{"is_correct": "是"}}, "fixture-admin"); err == nil {
t.Fatal("an active downvote accepted a positive correctness write")
}
start, _ := time.Parse(time.RFC3339, "2026-10-05T00:00:00+08:00")
dashboard, err := s.ReadMasterDashboard(ctx, dobotaiops.RecordQuery{Window: dobotaiops.Window{TenantKey: s.tenant, Start: start, End: start.AddDate(0, 0, 7)}})
if err != nil || dashboard.Quality.Rates.Correctness.Numerator != 0 || dashboard.Quality.Rates.Correctness.Denominator != 1 {
t.Fatal("dashboard did not consume the same downvote rule", err)
}
var after, originalInline string
if err = p.QueryRow(ctx, `SELECT md5(m.payload::text||m.original_fields::text||m.local_fields::text),e.fields->>'is_correct' FROM qa_ops_master_records m JOIN qa_ops_master_field_overrides e ON e.tenant_key=m.tenant_key AND e.record_id=m.id WHERE m.tenant_key=$1 AND m.id=$2`, s.tenant, rec.ID).Scan(&after, &originalInline); err != nil || after != before || originalInline != "是" {
t.Fatal("feedback projection rewrote original questions, labels or human evidence", err)
}
}