65653e5c01
build / build (push) Successful in 1m14s
已覆盖只允许正确或错误,未覆盖可待确认,缺引用不再阻止正确结论。 复用统一事务写入与不可变审计重评旧待确认,保留原问答和人工结论。 验证:全仓 make verify、真实隔离 PostgreSQL 全包与 race 回归通过。
86 lines
3.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|