Why: preserve scoped work findings and truthful activity across reloads. Evidence: task JSON loss and validation/state regressions RED to GREEN; complete Core suite. Impact: optional card/document wire fields and bounded candidate validation; old Body remains supported. Refs: fast-slow U2/U3 Task1, R03 R05 R09 R17
100 lines
4.0 KiB
Go
100 lines
4.0 KiB
Go
package core
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Loss of the optional task object makes refresh discard stage findings.
|
|
func TestTaskSnapshotCardRoundTrip(t *testing.T) {
|
|
raw := []byte(`{"id":"card1","projectId":"p1","conversationId":"c1","kind":"progress","status":"active","task":{"schemaVersion":1,"taskId":"t1","attemptId":"a1","cardId":"card1","revision":4,"executionState":"running","focus":"核对材料","findings":[{"id":"f1","text":"日期不同","kind":"material_record","status":"active","revision":1}]}}`)
|
|
var card ActivityCard
|
|
if err := json.Unmarshal(raw, &card); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
encoded, err := json.Marshal(card)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var restored map[string]json.RawMessage
|
|
if err := json.Unmarshal(encoded, &restored); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := restored["task"]; !ok {
|
|
t.Fatal("task findings were lost in card persistence")
|
|
}
|
|
var task map[string]json.RawMessage
|
|
if err := json.Unmarshal(restored["task"], &task); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(task["revision"]) != "4" || string(task["focus"]) != `"核对材料"` {
|
|
t.Fatal("revision/focus did not survive")
|
|
}
|
|
}
|
|
|
|
func validTaskSnapshot() TaskSnapshot {
|
|
return TaskSnapshot{SchemaVersion: 1, TaskID: "t1", AttemptID: "a1", CardID: "card1", ProjectID: "p1", ConversationID: "c1", OwnerInstanceID: "i1", Revision: 1, ExecutionState: "queued", OutcomeCompleteness: "none", DeliveryState: "pending", StreamScope: "conversation:c1", Title: "整理材料"}
|
|
}
|
|
func TestTaskSnapshotRejectsUnsafeAndUnboundState(t *testing.T) {
|
|
for _, mutate := range []func(*TaskSnapshot){
|
|
func(s *TaskSnapshot) { s.TaskID = "" }, func(s *TaskSnapshot) { s.AttemptID = "" },
|
|
func(s *TaskSnapshot) { s.Revision = 9007199254740992 }, func(s *TaskSnapshot) { s.SourceSeq = -1 },
|
|
func(s *TaskSnapshot) { s.ExecutionState = "imagined" }, func(s *TaskSnapshot) { s.StreamScope = "conversation:other" },
|
|
func(s *TaskSnapshot) { s.ExecutionState = "running" },
|
|
} {
|
|
s := validTaskSnapshot()
|
|
mutate(&s)
|
|
if s.Validate() == nil {
|
|
t.Errorf("invalid snapshot accepted: %+v", s)
|
|
}
|
|
}
|
|
if err := validTaskSnapshot().Validate(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
func TestWorkCheckpointRejectsVerifiedAndEmptyEvidence(t *testing.T) {
|
|
c := WorkCheckpointCandidate{CheckpointID: "cp1", PhaseID: "crosscheck", Findings: []WorkFindingCandidate{{FindingKey: "date", Text: "日期不同", Kind: "verified", Sources: []TaskSourceQuote{{SourceID: "src1", Quote: "六月"}}}}}
|
|
if err := ValidateWorkCheckpoint(c); err == nil || !strings.Contains(err.Error(), "kind") {
|
|
t.Fatal("model granted verified status", err)
|
|
}
|
|
c.Findings[0].Kind = "material_record"
|
|
c.Findings[0].Sources = nil
|
|
if ValidateWorkCheckpoint(c) == nil {
|
|
t.Fatal("source-free fact accepted")
|
|
}
|
|
c.Findings[0].Sources = []TaskSourceQuote{{SourceID: "src1", Quote: "六月"}}
|
|
if err := ValidateWorkCheckpoint(c); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c.Findings[0].Text = strings.Repeat("x", 17000)
|
|
if ValidateWorkCheckpoint(c) == nil {
|
|
t.Fatal("unbounded candidate accepted")
|
|
}
|
|
}
|
|
func TestTaskSnapshotBodyPreservesLimitsAndHonestStates(t *testing.T) {
|
|
s := validTaskSnapshot()
|
|
s.Remaining = "图片内容未纳入"
|
|
s.ExecutionState = "failed"
|
|
s.Conclusion = "处理失败,已有发现可查看"
|
|
s.Findings = []TaskFinding{{ID: "f1", Text: "金额缺失", Kind: "material_record", Status: "active", Revision: 1}}
|
|
raw, _ := json.Marshal(s.SummaryBody())
|
|
for _, want := range []string{"执行失败", "图片内容未纳入", "金额缺失", "处理失败"} {
|
|
if !strings.Contains(string(raw), want) {
|
|
t.Errorf("lost critical summary %s", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTaskSnapshotUnstartedAndFinishedDoNotClaimReading(t *testing.T) {
|
|
for _, state := range []string{"queued", "completed", "interrupted"} {
|
|
s := validTaskSnapshot()
|
|
s.ExecutionState = state
|
|
s.Conclusion = "处理已结束"
|
|
b, _ := json.Marshal(s.SummaryBody())
|
|
if strings.Contains(string(b), "正在读取") {
|
|
t.Errorf("%s falsely claims active reading", state)
|
|
}
|
|
}
|
|
}
|