Files
knowledge-governance/internal/qaopsquality/quality_test.go
T
姚章浩 65653e5c01
build / build (push) Successful in 1m14s
fix(qa): 已覆盖正确性改为问题答案二元评估
已覆盖只允许正确或错误,未覆盖可待确认,缺引用不再阻止正确结论。
复用统一事务写入与不可变审计重评旧待确认,保留原问答和人工结论。
验证:全仓 make verify、真实隔离 PostgreSQL 全包与 race 回归通过。
2026-10-24 16:47:09 +08:00

86 lines
3.7 KiB
Go

package qaopsquality
import (
"encoding/json"
"knowledge-governance/internal/dobotaiops"
"strings"
"testing"
)
func TestEvidenceBindsQualityInputsNotPeopleFeedbackOrJSONKeyOrder(t *testing.T) {
record := dobotaiops.Record{ID: "fixture-turn", Status: "succeeded", Question: "示例问题", Answer: "有暂无已发布但存在引用", References: json.RawMessage(`[{"b":2,"a":1}]`), Media: json.RawMessage(`[]`)}
a, err := EvidenceFor(record)
if err != nil || len(a.Hash) != 64 || !strings.Contains(string(a.Payload), record.Answer) {
t.Fatal("quality inputs not snapshotted")
}
record.Asker = "another-person"
record.Department = "changed"
record.Feedback.Up = 10
record.References = json.RawMessage(`[{"a":1,"b":2}]`)
b, _ := EvidenceFor(record)
if b.Hash != a.Hash {
t.Fatal("irrelevant identity/feedback or JSON object ordering invalidated review")
}
record.Answer = "原答案变更"
b, _ = EvidenceFor(record)
if b.Hash == a.Hash {
t.Fatal("answer change undetected")
}
}
func TestHumanProjectionKeepsRuleAndStalesObservedInputChanges(t *testing.T) {
automatic := Check{Result: dobotaiops.CheckAnswerCoverage("暂无已发布"), InputEpoch: 2, InputHash: strings.Repeat("a", 64)}
review := &Review{InputEpoch: 1, InputHash: automatic.InputHash, Fields: Fields{Coverage: "covered", Correctness: "correct", ProductCategory: "MG400"}}
view := Project(automatic.InputHash, 2, 1, automatic, review)
if view.State != "stale" || view.Effective.Coverage != "uncovered" || view.Effective.Correctness != "" {
t.Fatal("old conclusion remained effective after input epoch changed")
}
review.InputEpoch = 2
view = Project(automatic.InputHash, 2, 1, automatic, review)
if view.State != "reviewed" || view.Effective.Coverage != "covered" || view.Automatic.Result.Verdict != "uncovered" {
t.Fatal("human and immutable rule not separated")
}
summary := Summarize(map[string]View{"a": view, "b": Project(automatic.InputHash, 3, 1, automatic, review), "c": Project(automatic.InputHash, 1, 0, automatic, nil)})
if summary.Total != 3 || summary.Reviewed != 1 || summary.Stale != 1 || summary.Unreviewed != 1 || summary.PendingConfirmation != 0 {
t.Fatal("quality counts invent rates or unknown judgments")
}
}
func TestReviewRequiresExplicitOutcomesAndEvidence(t *testing.T) {
valid := SaveRequest{InputHash: strings.Repeat("a", 64), InputEpoch: 1, Nonce: strings.Repeat("n", 32), Fields: Fields{Coverage: "uncovered", Correctness: "pending"}, Reason: "人工核对引用与答案,证据不足"}
if Validate(valid) != nil {
t.Fatal("valid review rejected")
}
for _, field := range []string{"reason", "hash", "coverage", "correctness", "root", "exclusion"} {
bad := valid
switch field {
case "reason":
bad.Reason = " "
case "hash":
bad.InputHash = "malformed"
case "coverage":
bad.Fields.Coverage = ""
case "correctness":
bad.Fields.Correctness = ""
case "root":
bad.Fields.Correctness = "correct"
bad.Fields.RootCause = "知识缺失"
case "exclusion":
bad.Fields.Coverage = "excluded"
}
if Validate(bad) == nil {
t.Fatal("invalid review accepted", field)
}
}
}
func TestOnlyUncoveredReviewMayBePending(t *testing.T) {
for _, coverage := range []string{"covered", "uncovered", "partial", "pending", "excluded"} {
request := SaveRequest{InputHash: strings.Repeat("a", 64), InputEpoch: 1, Nonce: strings.Repeat("b", 16), Reason: "原问题及实际回答复核", Fields: Fields{Coverage: coverage, Correctness: "pending"}}
if coverage == "excluded" {
request.Fields.ExclusionReason = "不计入"
}
if err := Validate(request); (err == nil) != (coverage == "uncovered") {
t.Fatalf("pending review coverage=%q validation=%v", coverage, err)
}
}
}