|
50baf1a…
|
lmata
|
1 |
package main |
|
50baf1a…
|
lmata
|
2 |
|
|
50baf1a…
|
lmata
|
3 |
import ( |
|
50baf1a…
|
lmata
|
4 |
"bytes" |
|
f3c383e…
|
noreply
|
5 |
"fmt" |
|
f3c383e…
|
noreply
|
6 |
"os" |
|
50baf1a…
|
lmata
|
7 |
"path/filepath" |
|
50baf1a…
|
lmata
|
8 |
"strings" |
|
50baf1a…
|
lmata
|
9 |
"testing" |
|
50baf1a…
|
lmata
|
10 |
"time" |
|
50baf1a…
|
lmata
|
11 |
) |
|
50baf1a…
|
lmata
|
12 |
|
|
50baf1a…
|
lmata
|
13 |
func TestFilterMessages(t *testing.T) { |
|
50baf1a…
|
lmata
|
14 |
t.Helper() |
|
50baf1a…
|
lmata
|
15 |
|
|
50baf1a…
|
lmata
|
16 |
base := time.Date(2026, 3, 31, 21, 0, 0, 0, time.FixedZone("CST", -6*60*60)) |
|
50baf1a…
|
lmata
|
17 |
since := base.Add(-time.Second) |
|
50baf1a…
|
lmata
|
18 |
nick := "codex-scuttlebot-1234" |
|
50baf1a…
|
lmata
|
19 |
|
|
50baf1a…
|
lmata
|
20 |
messages := []message{ |
|
24a217e…
|
lmata
|
21 |
{Nick: "bridge", Text: "[glengoolie] hello", At: base}, |
|
24a217e…
|
lmata
|
22 |
{Nick: "glengoolie", Text: "ambient chat", At: base.Add(time.Second)}, |
|
24a217e…
|
lmata
|
23 |
{Nick: "codex-otherrepo-9999", Text: "status post", At: base.Add(2 * time.Second)}, |
|
24a217e…
|
lmata
|
24 |
{Nick: "glengoolie", Text: nick + ": check README.md", At: base.Add(3 * time.Second)}, |
|
24a217e…
|
lmata
|
25 |
{Nick: "glengoolie", Text: nick + ": and inspect bridge.go", At: base.Add(4 * time.Second)}, |
|
50baf1a…
|
lmata
|
26 |
} |
|
50baf1a…
|
lmata
|
27 |
|
|
cefe27d…
|
lmata
|
28 |
got, newest := filterMessages(messages, since, nick, "worker") |
|
50baf1a…
|
lmata
|
29 |
if len(got) != 2 { |
|
50baf1a…
|
lmata
|
30 |
t.Fatalf("len(filterMessages) = %d, want 2", len(got)) |
|
50baf1a…
|
lmata
|
31 |
} |
|
50baf1a…
|
lmata
|
32 |
if got[0].Text != nick+": check README.md" { |
|
50baf1a…
|
lmata
|
33 |
t.Fatalf("first injected message = %q", got[0].Text) |
|
50baf1a…
|
lmata
|
34 |
} |
|
50baf1a…
|
lmata
|
35 |
if got[1].Text != nick+": and inspect bridge.go" { |
|
50baf1a…
|
lmata
|
36 |
t.Fatalf("second injected message = %q", got[1].Text) |
|
50baf1a…
|
lmata
|
37 |
} |
|
50baf1a…
|
lmata
|
38 |
if !newest.Equal(base.Add(4 * time.Second)) { |
|
50baf1a…
|
lmata
|
39 |
t.Fatalf("newest = %s", newest) |
|
50baf1a…
|
lmata
|
40 |
} |
|
50baf1a…
|
lmata
|
41 |
} |
|
50baf1a…
|
lmata
|
42 |
|
|
50baf1a…
|
lmata
|
43 |
func TestTargetCWD(t *testing.T) { |
|
50baf1a…
|
lmata
|
44 |
t.Helper() |
|
50baf1a…
|
lmata
|
45 |
|
|
50baf1a…
|
lmata
|
46 |
cwd, err := filepath.Abs(".") |
|
50baf1a…
|
lmata
|
47 |
if err != nil { |
|
50baf1a…
|
lmata
|
48 |
t.Fatal(err) |
|
50baf1a…
|
lmata
|
49 |
} |
|
50baf1a…
|
lmata
|
50 |
|
|
50baf1a…
|
lmata
|
51 |
got, err := targetCWD([]string{"--cd", "../.."}) |
|
50baf1a…
|
lmata
|
52 |
if err != nil { |
|
50baf1a…
|
lmata
|
53 |
t.Fatal(err) |
|
50baf1a…
|
lmata
|
54 |
} |
|
50baf1a…
|
lmata
|
55 |
want := filepath.Clean(filepath.Join(cwd, "../..")) |
|
50baf1a…
|
lmata
|
56 |
if got != want { |
|
50baf1a…
|
lmata
|
57 |
t.Fatalf("targetCWD = %q, want %q", got, want) |
|
50baf1a…
|
lmata
|
58 |
} |
|
50baf1a…
|
lmata
|
59 |
} |
|
50baf1a…
|
lmata
|
60 |
|
|
50baf1a…
|
lmata
|
61 |
func TestRelayStateShouldInterruptOnlyWhenRecentlyBusy(t *testing.T) { |
|
50baf1a…
|
lmata
|
62 |
t.Helper() |
|
50baf1a…
|
lmata
|
63 |
|
|
50baf1a…
|
lmata
|
64 |
var state relayState |
|
50baf1a…
|
lmata
|
65 |
now := time.Date(2026, 3, 31, 21, 47, 0, 0, time.UTC) |
|
50baf1a…
|
lmata
|
66 |
state.observeOutput([]byte("Working (1s • esc to interrupt)"), now) |
|
50baf1a…
|
lmata
|
67 |
|
|
50baf1a…
|
lmata
|
68 |
if !state.shouldInterrupt(now.Add(defaultBusyWindow / 2)) { |
|
50baf1a…
|
lmata
|
69 |
t.Fatal("shouldInterrupt = false, want true for recent busy session") |
|
50baf1a…
|
lmata
|
70 |
} |
|
50baf1a…
|
lmata
|
71 |
if state.shouldInterrupt(now.Add(defaultBusyWindow + time.Millisecond)) { |
|
50baf1a…
|
lmata
|
72 |
t.Fatal("shouldInterrupt = true, want false after busy window expires") |
|
50baf1a…
|
lmata
|
73 |
} |
|
50baf1a…
|
lmata
|
74 |
} |
|
50baf1a…
|
lmata
|
75 |
|
|
50baf1a…
|
lmata
|
76 |
func TestInjectMessagesIdleSkipsCtrlCAndSubmits(t *testing.T) { |
|
50baf1a…
|
lmata
|
77 |
t.Helper() |
|
50baf1a…
|
lmata
|
78 |
|
|
50baf1a…
|
lmata
|
79 |
var writer bytes.Buffer |
|
50baf1a…
|
lmata
|
80 |
cfg := config{ |
|
50baf1a…
|
lmata
|
81 |
Nick: "codex-scuttlebot-1234", |
|
50baf1a…
|
lmata
|
82 |
InterruptOnMessage: true, |
|
50baf1a…
|
lmata
|
83 |
} |
|
50baf1a…
|
lmata
|
84 |
state := &relayState{} |
|
50baf1a…
|
lmata
|
85 |
batch := []message{{ |
|
50baf1a…
|
lmata
|
86 |
Nick: "glengoolie", |
|
50baf1a…
|
lmata
|
87 |
Text: "codex-scuttlebot-1234: check README.md", |
|
50baf1a…
|
lmata
|
88 |
}} |
|
50baf1a…
|
lmata
|
89 |
|
|
1d3caa2…
|
lmata
|
90 |
if err := injectMessages(&writer, cfg, state, "#general", batch); err != nil { |
|
50baf1a…
|
lmata
|
91 |
t.Fatal(err) |
|
50baf1a…
|
lmata
|
92 |
} |
|
50baf1a…
|
lmata
|
93 |
|
|
1d3caa2…
|
lmata
|
94 |
want := "[IRC operator messages]\n[general] glengoolie: check README.md\n\r" |
|
50baf1a…
|
lmata
|
95 |
if writer.String() != want { |
|
50baf1a…
|
lmata
|
96 |
t.Fatalf("injectMessages idle = %q, want %q", writer.String(), want) |
|
50baf1a…
|
lmata
|
97 |
} |
|
50baf1a…
|
lmata
|
98 |
} |
|
50baf1a…
|
lmata
|
99 |
|
|
50baf1a…
|
lmata
|
100 |
func TestInjectMessagesBusySendsCtrlCBeforeSubmit(t *testing.T) { |
|
50baf1a…
|
lmata
|
101 |
t.Helper() |
|
50baf1a…
|
lmata
|
102 |
|
|
50baf1a…
|
lmata
|
103 |
var writer bytes.Buffer |
|
50baf1a…
|
lmata
|
104 |
cfg := config{ |
|
50baf1a…
|
lmata
|
105 |
Nick: "codex-scuttlebot-1234", |
|
50baf1a…
|
lmata
|
106 |
InterruptOnMessage: true, |
|
50baf1a…
|
lmata
|
107 |
} |
|
50baf1a…
|
lmata
|
108 |
state := &relayState{} |
|
50baf1a…
|
lmata
|
109 |
state.observeOutput([]byte("Working (2s • esc to interrupt)"), time.Now()) |
|
50baf1a…
|
lmata
|
110 |
batch := []message{{ |
|
50baf1a…
|
lmata
|
111 |
Nick: "glengoolie", |
|
50baf1a…
|
lmata
|
112 |
Text: "codex-scuttlebot-1234: stop and re-read bridge.go", |
|
50baf1a…
|
lmata
|
113 |
}} |
|
50baf1a…
|
lmata
|
114 |
|
|
1d3caa2…
|
lmata
|
115 |
if err := injectMessages(&writer, cfg, state, "#general", batch); err != nil { |
|
50baf1a…
|
lmata
|
116 |
t.Fatal(err) |
|
50baf1a…
|
lmata
|
117 |
} |
|
50baf1a…
|
lmata
|
118 |
|
|
1d3caa2…
|
lmata
|
119 |
want := string([]byte{3}) + "[IRC operator messages]\n[general] glengoolie: stop and re-read bridge.go\n\r" |
|
50baf1a…
|
lmata
|
120 |
if writer.String() != want { |
|
50baf1a…
|
lmata
|
121 |
t.Fatalf("injectMessages busy = %q, want %q", writer.String(), want) |
|
50baf1a…
|
lmata
|
122 |
} |
|
50baf1a…
|
lmata
|
123 |
} |
|
50baf1a…
|
lmata
|
124 |
|
|
50baf1a…
|
lmata
|
125 |
func TestSummarizeFunctionCallExecCommandRedactsSecrets(t *testing.T) { |
|
50baf1a…
|
lmata
|
126 |
t.Helper() |
|
50baf1a…
|
lmata
|
127 |
|
|
50baf1a…
|
lmata
|
128 |
msg := summarizeFunctionCall("exec_command", `{"cmd":"cd /repo && curl -H \"Authorization: Bearer d2f5565f5f34fe6ea81d3cba6c20117f032180e3cf4aa401\" http://localhost:8080/v1/status"}`) |
|
50baf1a…
|
lmata
|
129 |
if !strings.HasPrefix(msg, "› curl") { |
|
50baf1a…
|
lmata
|
130 |
t.Fatalf("summarizeFunctionCall prefix = %q", msg) |
|
50baf1a…
|
lmata
|
131 |
} |
|
50baf1a…
|
lmata
|
132 |
if strings.Contains(msg, "d2f5565f5f34fe6ea81d3cba6c20117f032180e3cf4aa401") { |
|
50baf1a…
|
lmata
|
133 |
t.Fatalf("summarizeFunctionCall leaked token: %q", msg) |
|
50baf1a…
|
lmata
|
134 |
} |
|
50baf1a…
|
lmata
|
135 |
if !strings.Contains(msg, "[redacted]") { |
|
50baf1a…
|
lmata
|
136 |
t.Fatalf("summarizeFunctionCall did not redact secret: %q", msg) |
|
50baf1a…
|
lmata
|
137 |
} |
|
50baf1a…
|
lmata
|
138 |
} |
|
50baf1a…
|
lmata
|
139 |
|
|
50baf1a…
|
lmata
|
140 |
func TestSummarizeCustomToolCallApplyPatch(t *testing.T) { |
|
50baf1a…
|
lmata
|
141 |
t.Helper() |
|
50baf1a…
|
lmata
|
142 |
|
|
50baf1a…
|
lmata
|
143 |
patch := strings.Join([]string{ |
|
50baf1a…
|
lmata
|
144 |
"*** Begin Patch", |
|
50baf1a…
|
lmata
|
145 |
"*** Update File: cmd/codex-relay/main.go", |
|
50baf1a…
|
lmata
|
146 |
"*** Add File: glengoolie.tmp", |
|
50baf1a…
|
lmata
|
147 |
"*** End Patch", |
|
50baf1a…
|
lmata
|
148 |
}, "\n") |
|
50baf1a…
|
lmata
|
149 |
|
|
50baf1a…
|
lmata
|
150 |
got := summarizeCustomToolCall("apply_patch", patch) |
|
50baf1a…
|
lmata
|
151 |
want := "patch 2 files: cmd/codex-relay/main.go, glengoolie.tmp" |
|
50baf1a…
|
lmata
|
152 |
if got != want { |
|
50baf1a…
|
lmata
|
153 |
t.Fatalf("summarizeCustomToolCall = %q, want %q", got, want) |
|
50baf1a…
|
lmata
|
154 |
} |
|
50baf1a…
|
lmata
|
155 |
} |
|
50baf1a…
|
lmata
|
156 |
|
|
50baf1a…
|
lmata
|
157 |
func TestSessionMessagesFunctionCallAndAssistant(t *testing.T) { |
|
50baf1a…
|
lmata
|
158 |
t.Helper() |
|
50baf1a…
|
lmata
|
159 |
|
|
50baf1a…
|
lmata
|
160 |
fnLine := []byte(`{"type":"response_item","payload":{"type":"function_call","name":"exec_command","arguments":"{\"cmd\":\"pwd\"}"}}`) |
|
67e0178…
|
lmata
|
161 |
got := sessionMessages(fnLine, false) |
|
f3c383e…
|
noreply
|
162 |
if len(got) != 1 || got[0].Text != "› pwd" { |
|
50baf1a…
|
lmata
|
163 |
t.Fatalf("sessionMessages function_call = %#v", got) |
|
50baf1a…
|
lmata
|
164 |
} |
|
50baf1a…
|
lmata
|
165 |
|
|
50baf1a…
|
lmata
|
166 |
msgLine := []byte(`{"type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"one line\nsecond line"}]}}`) |
|
67e0178…
|
lmata
|
167 |
got = sessionMessages(msgLine, false) |
|
f3c383e…
|
noreply
|
168 |
if len(got) != 2 || got[0].Text != "one line" || got[1].Text != "second line" { |
|
50baf1a…
|
lmata
|
169 |
t.Fatalf("sessionMessages assistant = %#v", got) |
|
67e0178…
|
lmata
|
170 |
} |
|
67e0178…
|
lmata
|
171 |
} |
|
67e0178…
|
lmata
|
172 |
|
|
67e0178…
|
lmata
|
173 |
func TestSessionMessagesReasoning(t *testing.T) { |
|
67e0178…
|
lmata
|
174 |
line := []byte(`{"type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"reasoning","text":"thinking hard"},{"type":"output_text","text":"done"}]}}`) |
|
67e0178…
|
lmata
|
175 |
|
|
67e0178…
|
lmata
|
176 |
// reasoning off — only output_text |
|
67e0178…
|
lmata
|
177 |
got := sessionMessages(line, false) |
|
f3c383e…
|
noreply
|
178 |
if len(got) != 1 || got[0].Text != "done" { |
|
67e0178…
|
lmata
|
179 |
t.Fatalf("mirrorReasoning=false: got %#v", got) |
|
67e0178…
|
lmata
|
180 |
} |
|
67e0178…
|
lmata
|
181 |
|
|
67e0178…
|
lmata
|
182 |
// reasoning on — both, reasoning prefixed |
|
67e0178…
|
lmata
|
183 |
got = sessionMessages(line, true) |
|
f3c383e…
|
noreply
|
184 |
if len(got) != 2 || got[0].Text != "💭 thinking hard" || got[1].Text != "done" { |
|
67e0178…
|
lmata
|
185 |
t.Fatalf("mirrorReasoning=true: got %#v", got) |
|
50baf1a…
|
lmata
|
186 |
} |
|
50baf1a…
|
lmata
|
187 |
} |
|
50baf1a…
|
lmata
|
188 |
|
|
50baf1a…
|
lmata
|
189 |
func TestExplicitThreadID(t *testing.T) { |
|
50baf1a…
|
lmata
|
190 |
t.Helper() |
|
50baf1a…
|
lmata
|
191 |
|
|
50baf1a…
|
lmata
|
192 |
got := explicitThreadID([]string{"resume", "019d45e1-8328-7261-9a02-5c4304e07724"}) |
|
50baf1a…
|
lmata
|
193 |
want := "019d45e1-8328-7261-9a02-5c4304e07724" |
|
50baf1a…
|
lmata
|
194 |
if got != want { |
|
50baf1a…
|
lmata
|
195 |
t.Fatalf("explicitThreadID = %q, want %q", got, want) |
|
f3c383e…
|
noreply
|
196 |
} |
|
f3c383e…
|
noreply
|
197 |
} |
|
f3c383e…
|
noreply
|
198 |
|
|
f3c383e…
|
noreply
|
199 |
func writeSessionFile(t *testing.T, dir, uuid, cwd, timestamp string) string { |
|
f3c383e…
|
noreply
|
200 |
t.Helper() |
|
f3c383e…
|
noreply
|
201 |
content := fmt.Sprintf(`{"type":"session_meta","payload":{"id":"%s","timestamp":"%s","cwd":"%s"}}`, uuid, timestamp, cwd) |
|
f3c383e…
|
noreply
|
202 |
name := fmt.Sprintf("rollout-%s-%s.jsonl", strings.ReplaceAll(timestamp[:19], ":", "-"), uuid) |
|
f3c383e…
|
noreply
|
203 |
path := filepath.Join(dir, name) |
|
f3c383e…
|
noreply
|
204 |
if err := os.WriteFile(path, []byte(content+"\n"), 0644); err != nil { |
|
f3c383e…
|
noreply
|
205 |
t.Fatal(err) |
|
f3c383e…
|
noreply
|
206 |
} |
|
f3c383e…
|
noreply
|
207 |
return path |
|
f3c383e…
|
noreply
|
208 |
} |
|
f3c383e…
|
noreply
|
209 |
|
|
f3c383e…
|
noreply
|
210 |
func TestFindLatestSessionPathSkipsPreExisting(t *testing.T) { |
|
f3c383e…
|
noreply
|
211 |
t.Helper() |
|
f3c383e…
|
noreply
|
212 |
|
|
f3c383e…
|
noreply
|
213 |
root := t.TempDir() |
|
f3c383e…
|
noreply
|
214 |
dateDir := filepath.Join(root, "2026", "04", "04") |
|
f3c383e…
|
noreply
|
215 |
if err := os.MkdirAll(dateDir, 0755); err != nil { |
|
f3c383e…
|
noreply
|
216 |
t.Fatal(err) |
|
f3c383e…
|
noreply
|
217 |
} |
|
f3c383e…
|
noreply
|
218 |
|
|
f3c383e…
|
noreply
|
219 |
cwd := "/home/user/project" |
|
f3c383e…
|
noreply
|
220 |
|
|
f3c383e…
|
noreply
|
221 |
// Create a pre-existing session file. |
|
f3c383e…
|
noreply
|
222 |
oldPath := writeSessionFile(t, dateDir, |
|
f3c383e…
|
noreply
|
223 |
"aaaa-aaaa-aaaa-aaaa", cwd, "2026-04-04T10:00:00Z") |
|
f3c383e…
|
noreply
|
224 |
|
|
f3c383e…
|
noreply
|
225 |
// Snapshot includes the old file. |
|
f3c383e…
|
noreply
|
226 |
preExisting := map[string]struct{}{oldPath: {}} |
|
f3c383e…
|
noreply
|
227 |
|
|
f3c383e…
|
noreply
|
228 |
// Create a new session file (not in snapshot). |
|
f3c383e…
|
noreply
|
229 |
newPath := writeSessionFile(t, dateDir, |
|
f3c383e…
|
noreply
|
230 |
"bbbb-bbbb-bbbb-bbbb", cwd, "2026-04-04T10:00:01Z") |
|
f3c383e…
|
noreply
|
231 |
|
|
f3c383e…
|
noreply
|
232 |
notBefore, _ := time.Parse(time.RFC3339, "2026-04-04T09:59:58Z") |
|
f3c383e…
|
noreply
|
233 |
got, err := findLatestSessionPath(root, cwd, notBefore, preExisting) |
|
f3c383e…
|
noreply
|
234 |
if err != nil { |
|
f3c383e…
|
noreply
|
235 |
t.Fatalf("findLatestSessionPath error: %v", err) |
|
f3c383e…
|
noreply
|
236 |
} |
|
f3c383e…
|
noreply
|
237 |
if got != newPath { |
|
f3c383e…
|
noreply
|
238 |
t.Fatalf("findLatestSessionPath = %q, want %q", got, newPath) |
|
f3c383e…
|
noreply
|
239 |
} |
|
f3c383e…
|
noreply
|
240 |
} |
|
f3c383e…
|
noreply
|
241 |
|
|
f3c383e…
|
noreply
|
242 |
func TestFindLatestSessionPathPicksOldestNew(t *testing.T) { |
|
f3c383e…
|
noreply
|
243 |
t.Helper() |
|
f3c383e…
|
noreply
|
244 |
|
|
f3c383e…
|
noreply
|
245 |
root := t.TempDir() |
|
f3c383e…
|
noreply
|
246 |
dateDir := filepath.Join(root, "2026", "04", "04") |
|
f3c383e…
|
noreply
|
247 |
if err := os.MkdirAll(dateDir, 0755); err != nil { |
|
f3c383e…
|
noreply
|
248 |
t.Fatal(err) |
|
f3c383e…
|
noreply
|
249 |
} |
|
f3c383e…
|
noreply
|
250 |
|
|
f3c383e…
|
noreply
|
251 |
cwd := "/home/user/project" |
|
f3c383e…
|
noreply
|
252 |
|
|
f3c383e…
|
noreply
|
253 |
// Two new sessions in the same CWD, no pre-existing. |
|
f3c383e…
|
noreply
|
254 |
earlyPath := writeSessionFile(t, dateDir, |
|
f3c383e…
|
noreply
|
255 |
"cccc-cccc-cccc-cccc", cwd, "2026-04-04T10:00:01Z") |
|
f3c383e…
|
noreply
|
256 |
_ = writeSessionFile(t, dateDir, |
|
f3c383e…
|
noreply
|
257 |
"dddd-dddd-dddd-dddd", cwd, "2026-04-04T10:00:02Z") |
|
f3c383e…
|
noreply
|
258 |
|
|
f3c383e…
|
noreply
|
259 |
notBefore, _ := time.Parse(time.RFC3339, "2026-04-04T10:00:00Z") |
|
f3c383e…
|
noreply
|
260 |
got, err := findLatestSessionPath(root, cwd, notBefore, map[string]struct{}{}) |
|
f3c383e…
|
noreply
|
261 |
if err != nil { |
|
f3c383e…
|
noreply
|
262 |
t.Fatalf("findLatestSessionPath error: %v", err) |
|
f3c383e…
|
noreply
|
263 |
} |
|
f3c383e…
|
noreply
|
264 |
if got != earlyPath { |
|
f3c383e…
|
noreply
|
265 |
t.Fatalf("findLatestSessionPath = %q, want oldest %q", got, earlyPath) |
|
f3c383e…
|
noreply
|
266 |
} |
|
f3c383e…
|
noreply
|
267 |
} |
|
f3c383e…
|
noreply
|
268 |
|
|
f3c383e…
|
noreply
|
269 |
func TestFindLatestSessionPathNilPreExistingAllowsAll(t *testing.T) { |
|
f3c383e…
|
noreply
|
270 |
t.Helper() |
|
f3c383e…
|
noreply
|
271 |
|
|
f3c383e…
|
noreply
|
272 |
root := t.TempDir() |
|
f3c383e…
|
noreply
|
273 |
dateDir := filepath.Join(root, "2026", "04", "04") |
|
f3c383e…
|
noreply
|
274 |
if err := os.MkdirAll(dateDir, 0755); err != nil { |
|
f3c383e…
|
noreply
|
275 |
t.Fatal(err) |
|
f3c383e…
|
noreply
|
276 |
} |
|
f3c383e…
|
noreply
|
277 |
|
|
f3c383e…
|
noreply
|
278 |
cwd := "/home/user/project" |
|
f3c383e…
|
noreply
|
279 |
|
|
f3c383e…
|
noreply
|
280 |
// Single file — nil preExisting (reconnect path) should find it. |
|
f3c383e…
|
noreply
|
281 |
path := writeSessionFile(t, dateDir, |
|
f3c383e…
|
noreply
|
282 |
"eeee-eeee-eeee-eeee", cwd, "2026-04-04T10:00:00Z") |
|
f3c383e…
|
noreply
|
283 |
|
|
f3c383e…
|
noreply
|
284 |
got, err := findLatestSessionPath(root, cwd, time.Time{}, nil) |
|
f3c383e…
|
noreply
|
285 |
if err != nil { |
|
f3c383e…
|
noreply
|
286 |
t.Fatalf("findLatestSessionPath error: %v", err) |
|
f3c383e…
|
noreply
|
287 |
} |
|
f3c383e…
|
noreply
|
288 |
if got != path { |
|
f3c383e…
|
noreply
|
289 |
t.Fatalf("findLatestSessionPath = %q, want %q", got, path) |
|
f3c383e…
|
noreply
|
290 |
} |
|
f3c383e…
|
noreply
|
291 |
} |
|
f3c383e…
|
noreply
|
292 |
|
|
f3c383e…
|
noreply
|
293 |
func TestSnapshotSessionFiles(t *testing.T) { |
|
f3c383e…
|
noreply
|
294 |
t.Helper() |
|
f3c383e…
|
noreply
|
295 |
|
|
f3c383e…
|
noreply
|
296 |
root := t.TempDir() |
|
f3c383e…
|
noreply
|
297 |
dateDir := filepath.Join(root, "2026", "04", "04") |
|
f3c383e…
|
noreply
|
298 |
if err := os.MkdirAll(dateDir, 0755); err != nil { |
|
f3c383e…
|
noreply
|
299 |
t.Fatal(err) |
|
f3c383e…
|
noreply
|
300 |
} |
|
f3c383e…
|
noreply
|
301 |
|
|
f3c383e…
|
noreply
|
302 |
path := writeSessionFile(t, dateDir, |
|
f3c383e…
|
noreply
|
303 |
"ffff-ffff-ffff-ffff", "/tmp", "2026-04-04T10:00:00Z") |
|
f3c383e…
|
noreply
|
304 |
|
|
f3c383e…
|
noreply
|
305 |
snap := snapshotSessionFiles(root) |
|
f3c383e…
|
noreply
|
306 |
if _, ok := snap[path]; !ok { |
|
f3c383e…
|
noreply
|
307 |
t.Fatalf("snapshotSessionFiles missing %q", path) |
|
f3c383e…
|
noreply
|
308 |
} |
|
f3c383e…
|
noreply
|
309 |
if len(snap) != 1 { |
|
f3c383e…
|
noreply
|
310 |
t.Fatalf("snapshotSessionFiles len = %d, want 1", len(snap)) |
|
50baf1a…
|
lmata
|
311 |
} |
|
50baf1a…
|
lmata
|
312 |
} |