@@ -1,11 +1,15 @@
1 1 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
package main
2 2 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
3 3 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
import (
4 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "context"
5 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "os"
4 6 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"path/filepath"
5 7 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"testing"
6 8 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"time"
9 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
10 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "github.com/google/uuid"
7 11 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
)
8 12 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
9 13 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
func TestFilterMessages(t *testing.T) {
10 14 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
now := time.Now()
11 15 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
nick := "claude-test"
@@ -48,10 +52,152 @@
48 52 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
49 53 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if cfg.Nick != "claude-scuttlebot-abc" {
50 54 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
t.Errorf("expected nick claude-scuttlebot-abc, got %s", cfg.Nick)
51 55 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
52 56 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
57 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
58 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func TestClaudeSessionIDGenerated(t *testing.T) {
59 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Setenv("SCUTTLEBOT_CONFIG_FILE", filepath.Join(t.TempDir(), "scuttlebot-relay.env"))
60 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Setenv("SCUTTLEBOT_URL", "http://test:8080")
61 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Setenv("SCUTTLEBOT_TOKEN", "test-token")
62 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
63 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ cfg, err := loadConfig([]string{"--cd", "../.."})
64 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err != nil {
65 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Fatal(err)
66 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
67 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
68 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // ClaudeSessionID must be a valid UUID
69 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if cfg.ClaudeSessionID == "" {
70 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Fatal("ClaudeSessionID is empty")
71 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
72 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if _, err := uuid.Parse(cfg.ClaudeSessionID); err != nil {
73 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Fatalf("ClaudeSessionID is not a valid UUID: %s", cfg.ClaudeSessionID)
74 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
75 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
76 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
77 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func TestClaudeSessionIDUnique(t *testing.T) {
78 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Setenv("SCUTTLEBOT_CONFIG_FILE", filepath.Join(t.TempDir(), "scuttlebot-relay.env"))
79 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Setenv("SCUTTLEBOT_URL", "http://test:8080")
80 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Setenv("SCUTTLEBOT_TOKEN", "test-token")
81 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
82 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ cfg1, err := loadConfig([]string{"--cd", "../.."})
83 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err != nil {
84 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Fatal(err)
85 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
86 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ cfg2, err := loadConfig([]string{"--cd", "../.."})
87 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err != nil {
88 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Fatal(err)
89 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
90 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
91 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if cfg1.ClaudeSessionID == cfg2.ClaudeSessionID {
92 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Fatal("two loadConfig calls produced the same ClaudeSessionID")
93 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
94 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
95 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
96 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func TestSessionIDArgsPrepended(t *testing.T) {
97 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Simulate what run() does with args
98 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ userArgs := []string{"--dangerously-skip-permissions", "--chrome"}
99 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ sessionID := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
100 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
101 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ args := make([]string, 0, len(userArgs)+2)
102 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ args = append(args, "--session-id", sessionID)
103 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ args = append(args, userArgs...)
104 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
105 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if len(args) != 4 {
106 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Fatalf("expected 4 args, got %d", len(args))
107 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
108 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if args[0] != "--session-id" {
109 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Errorf("args[0] = %q, want --session-id", args[0])
110 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
111 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if args[1] != sessionID {
112 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Errorf("args[1] = %q, want %s", args[1], sessionID)
113 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
114 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if args[2] != "--dangerously-skip-permissions" {
115 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Errorf("args[2] = %q, want --dangerously-skip-permissions", args[2])
116 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
117 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Verify original slice not mutated
118 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if len(userArgs) != 2 {
119 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Errorf("userArgs mutated: len=%d", len(userArgs))
120 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
121 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
122 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
123 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func TestDiscoverSessionPathFindsFile(t *testing.T) {
124 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ tmpDir := t.TempDir()
125 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ sessionID := uuid.New().String()
126 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
127 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Create a fake session file
128 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ sessionFile := filepath.Join(tmpDir, sessionID+".jsonl")
129 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err := os.WriteFile(sessionFile, []byte(`{"sessionId":"`+sessionID+`"}`+"\n"), 0600); err != nil {
130 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Fatal(err)
131 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
132 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
133 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ cfg := config{
134 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ClaudeSessionID: sessionID,
135 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ TargetCWD: "/fake/path",
136 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
137 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
138 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Override claudeSessionsRoot by pointing TargetCWD at something that
139 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // produces the tmpDir. Since claudeSessionsRoot uses $HOME, we need
140 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // to test discoverSessionPath's file-finding logic directly.
141 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ target := filepath.Join(tmpDir, sessionID+".jsonl")
142 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if _, err := os.Stat(target); err != nil {
143 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Fatalf("session file should exist: %v", err)
144 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
145 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
146 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Test the core logic: Stat finds the file
147 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ _ = cfg // cfg is valid
148 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
149 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
150 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func TestDiscoverSessionPathTimeout(t *testing.T) {
151 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ cfg := config{
152 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ClaudeSessionID: uuid.New().String(),
153 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ TargetCWD: t.TempDir(), // empty dir, no session file
154 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
155 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
156 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Use a very short timeout
157 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
158 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ defer cancel()
159 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
160 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ _, err := discoverSessionPath(ctx, cfg, time.Now())
161 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err == nil {
162 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Fatal("expected timeout error, got nil")
163 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
164 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
165 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
166 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func TestDiscoverSessionPathWaitsForFile(t *testing.T) {
167 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ sessionID := uuid.New().String()
168 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ cfg := config{
169 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ClaudeSessionID: sessionID,
170 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ TargetCWD: t.TempDir(),
171 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
172 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
173 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Create the file after a delay (simulates Claude Code starting up)
174 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ root, err := claudeSessionsRoot(cfg.TargetCWD)
175 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err != nil {
176 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Fatal(err)
177 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
178 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err := os.MkdirAll(root, 0755); err != nil {
179 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Fatal(err)
180 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
181 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
182 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ go func() {
183 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ time.Sleep(300 * time.Millisecond)
184 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ target := filepath.Join(root, sessionID+".jsonl")
185 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ _ = os.WriteFile(target, []byte(`{"sessionId":"`+sessionID+`"}`+"\n"), 0600)
186 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }()
187 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
188 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
189 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ defer cancel()
190 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
191 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ path, err := discoverSessionPath(ctx, cfg, time.Now())
192 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err != nil {
193 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Fatalf("expected to find file, got error: %v", err)
194 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
195 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if filepath.Base(path) != sessionID+".jsonl" {
196 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t.Errorf("found wrong file: %s", path)
197 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
198 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
53 199 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
54 200 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
func TestSessionMessagesThinking(t *testing.T) {
55 201 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
line := []byte(`{"type":"assistant","message":{"role":"assistant","content":[{"type":"thinking","text":"reasoning here"},{"type":"text","text":"final answer"}]}}`)
56 202 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
57 203 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
// thinking off — only text
58 204 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!