返回 DeepSeek-Reasonix
history_replay_test.go
根目录 / internal / provider / history_replay_test.go
1 package provider
2
3 import (
4 "bytes"
5 "encoding/json"
6 "reflect"
7 "strings"
8 "testing"
9 )
10
11 func TestHistoryReplayRecoversMalformedBatchesWithoutMutatingEvidence(t *testing.T) {
12 for _, args := range []string{`{"body":"quote ["text"]"}`, "", "null", "[]", `"value"`} {
13 for _, state := range []ToolRunState{"", ToolRunUnknown, ToolRunCompleted, ToolRunNotStarted, ToolRunCancelled} {
14 t.Run(args+"/"+string(state), func(t *testing.T) {
15 messages := []Message{
16 {ID: "proposal", Role: RoleAssistant, ToolCalls: []ToolCall{{ID: "call", Name: "write_file", Arguments: args}}, ReasoningSignature: "private-signature"},
17 {ID: "result", Role: RoleTool, ToolCallID: "call", Name: "write_file", Content: "recorded result", ToolRunState: state, RawContent: "private-raw"},
18 {ID: "next", Role: RoleUser, Content: "continue"},
19 }
20 before, _ := json.Marshal(messages)
21 repaired := RepairHistoryForReplay(messages)
22 if err := ValidateModelTranscript(repaired); err != nil {
23 t.Fatal(err)
24 }
25 if state == ToolRunNotStarted || state == ToolRunCancelled {
26 if repaired[0].ToolCalls[0].Arguments != "{}" {
27 t.Fatal("refused call was not repaired")
28 }
29 } else if len(repaired[0].ToolCalls) != 0 || repaired[1].Role != RoleUser || !strings.Contains(repaired[1].Content, "recorded result") {
30 t.Fatal("unknown/executed call was not retained as historical observations")
31 }
32 wire, _ := json.Marshal(ModelMessages(repaired))
33 if strings.Contains(string(wire), "private-raw") {
34 t.Fatal("local metadata leaked")
35 }
36 if !reflect.DeepEqual(repaired, RepairHistoryForReplay(repaired)) {
37 t.Fatal("repair is not idempotent")
38 }
39 after, _ := json.Marshal(messages)
40 if !bytes.Equal(before, after) {
41 t.Fatal("canonical evidence changed")
42 }
43 })
44 }
45 }
46 }
47
48 func TestHistoryReplayIdentityAndImageRecovery(t *testing.T) {
49 for _, calls := range [][]ToolCall{
50 {{ID: "same", Name: "read", Arguments: "{}"}, {ID: "same", Name: "read", Arguments: "{}"}},
51 {{ID: "call", Name: "", Arguments: "{}"}},
52 {{ID: " ", Name: "read", Arguments: "{}"}},
53 } {
54 messages := []Message{
55 {Role: RoleAssistant, ToolCalls: calls, ResponsesItems: []json.RawMessage{json.RawMessage(`{"type":"reasoning","id":"old"}`)}},
56 {Role: RoleTool, ToolCallID: calls[0].ID, Name: "read", Content: "observation", Images: []string{"https://example.invalid/image.png"}},
57 }
58 got := RepairHistoryForReplay(messages)
59 if err := ValidateModelTranscript(got); err != nil {
60 t.Fatal(err)
61 }
62 if len(got[0].ResponsesItems) != 0 || got[1].Role != RoleUser || len(got[1].Images) != 1 {
63 t.Fatal("native replay or images were not safely converted")
64 }
65 }
66 orphan := Message{Role: RoleTool, ToolCallID: "missing", Name: "read", Content: "orphan observation"}
67 if got := RepairHistoryForReplay([]Message{orphan}); len(got) != 1 || !strings.Contains(got[0].Content, orphan.Content) {
68 t.Fatal("orphan facts were lost")
69 }
70 }
71
72 func TestHistoryReplayPreservesHealthyWireBytes(t *testing.T) {
73 messages := []Message{
74 {Role: RoleSystem, Content: "stable system"},
75 {Role: RoleAssistant, ToolCalls: []ToolCall{{ID: "call", Name: "read", Arguments: ` { "path": "one" } `}}, ReasoningContent: "thinking"},
76 {Role: RoleTool, ToolCallID: "call", Name: "read", Content: "result", ToolRunState: ToolRunCompleted},
77 {Role: RoleUser, Content: "continue"},
78 }
79 before, _ := json.Marshal(ModelMessages(messages))
80 after, _ := json.Marshal(ModelMessages(RepairHistoryForReplay(messages)))
81 if !bytes.Equal(before, after) {
82 t.Fatalf("healthy prefix changed:\n%s\n%s", before, after)
83 }
84 }
85
85 lines GO