1package core
2
3import (
4 "encoding/json"
5 "testing"
6 "time"
7
8 "github.com/bnema/zut/packages/provider"
9)
10
11func TestReadSessionHistoryRetainsPreCompactionSegments(t *testing.T) {
12 root := t.TempDir()
13 cwd := "/workspace/history"
14 session, err := NewSession(root, cwd, "test", "model", "test")
15 if err != nil {
16 t.Fatal(err)
17 }
18 oldPrompt := textMessage(provider.RoleUser, "old prompt")
19 oldAnswer := textMessage(provider.RoleAssistant, "old answer")
20 toolCall := provider.Message{
21 Role: provider.RoleAssistant,
22 Content: []provider.Content{provider.ToolCallBlock{ID: "call-1", Name: "read"}},
23 }
24 toolResult := provider.Message{
25 Role: provider.RoleTool,
26 Content: []provider.Content{provider.ToolResultBlock{
27 CallID: "call-1",
28 Content: []provider.Content{provider.TextBlock{Text: "old file"}},
29 }},
30 }
31 for _, message := range []provider.Message{oldPrompt, oldAnswer, toolCall, toolResult} {
32 if err := session.AppendMessage(message); err != nil {
33 t.Fatal(err)
34 }
35 }
36 if err := session.AppendCompaction([]provider.Message{
37 {
38 Role: provider.RoleUser,
39 Meta: map[string]string{"compaction": "true"},
40 Content: []provider.Content{
41 provider.TextBlock{Text: "summary"},
42 },
43 },
44 }); err != nil {
45 t.Fatal(err)
46 }
47 if err := session.AppendMessage(textMessage(provider.RoleUser, "new prompt")); err != nil {
48 t.Fatal(err)
49 }
50 path := session.Path
51 if err := session.Close(); err != nil {
52 t.Fatal(err)
53 }
54
55 history, err := ReadSessionHistory(path)
56 if err != nil {
57 t.Fatalf("ReadSessionHistory: %v", err)
58 }
59 if len(history.Segments) != 2 {
60 t.Fatalf("history segments = %d, want 2", len(history.Segments))
61 }
62 if history.Segments[0].Compacted {
63 t.Fatal("pre-compaction segment marked compacted")
64 }
65 if !history.Segments[1].Compacted {
66 t.Fatal("replacement segment not marked compacted")
67 }
68 assertMessageTexts(t, history.Segments[0].Messages, []string{"old prompt", "old answer", "", ""})
69 assertMessageTexts(t, history.Segments[1].Messages, []string{"summary", "new prompt"})
70
71 branchPath, err := BranchSessionHiddenFromHistory(path, root, cwd, "test", history.Segments[0], 1)
72 if err != nil {
73 t.Fatalf("BranchSessionHiddenFromHistory: %v", err)
74 }
75 branch, messages, err := OpenSession(branchPath)
76 if err != nil {
77 t.Fatalf("OpenSession branch: %v", err)
78 }
79 if err := branch.Close(); err != nil {
80 t.Fatal(err)
81 }
82 assertMessageTexts(t, messages, []string{"old prompt"})
83 if branch.Meta.Parent != history.Meta.ID {
84 t.Fatalf("branch parent = %q, want %q", branch.Meta.Parent, history.Meta.ID)
85 }
86}
87
88func TestReadSessionHistoryIgnoresExtensionStateRows(t *testing.T) {
89 root := t.TempDir()
90 session, err := NewSession(root, "/workspace/history-extension-state", "test", "model", "test")
91 if err != nil {
92 t.Fatal(err)
93 }
94 if err := session.AppendMessage(textMessage(provider.RoleUser, "prompt")); err != nil {
95 t.Fatal(err)
96 }
97 if err := session.AppendExtensionState("tasked-phases", json.RawMessage(`{"version":1}`)); err != nil {
98 t.Fatal(err)
99 }
100 if err := session.AppendMessage(textMessage(provider.RoleAssistant, "answer")); err != nil {
101 t.Fatal(err)
102 }
103 path := session.Path
104 if err := session.Close(); err != nil {
105 t.Fatal(err)
106 }
107
108 history, err := ReadSessionHistory(path)
109 if err != nil {
110 t.Fatalf("ReadSessionHistory: %v", err)
111 }
112 if len(history.Segments) != 1 {
113 t.Fatalf("history segments = %d, want 1", len(history.Segments))
114 }
115 assertMessageTexts(t, history.Segments[0].Messages, []string{"prompt", "answer"})
116}
117
118func TestReadSessionHistoryRepairsEachSegmentIndependently(t *testing.T) {
119 root := t.TempDir()
120 session, err := NewSession(root, "/workspace/history-repair", "test", "model", "test")
121 if err != nil {
122 t.Fatal(err)
123 }
124 call := provider.Message{
125 Role: provider.RoleAssistant,
126 Content: []provider.Content{provider.ToolCallBlock{ID: "call-1", Name: "bash"}},
127 Time: time.Unix(1, 0),
128 }
129 if err := session.AppendMessage(call); err != nil {
130 t.Fatal(err)
131 }
132 if err := session.AppendCompaction([]provider.Message{textMessage(provider.RoleUser, "summary")}); err != nil {
133 t.Fatal(err)
134 }
135 path := session.Path
136 if err := session.Close(); err != nil {
137 t.Fatal(err)
138 }
139
140 history, err := ReadSessionHistory(path)
141 if err != nil {
142 t.Fatal(err)
143 }
144 if len(history.Segments) != 2 {
145 t.Fatalf("history segments = %d, want 2", len(history.Segments))
146 }
147 if len(history.Segments[0].Messages) != 2 {
148 t.Fatalf("repaired old segment messages = %d, want assistant plus stub result", len(history.Segments[0].Messages))
149 }
150 if history.Segments[0].Messages[1].Role != provider.RoleTool {
151 t.Fatalf("repaired old segment tail role = %q, want tool", history.Segments[0].Messages[1].Role)
152 }
153}