blob: ac36cc27471ee9a4946e447bc6a3fab6a283b7d9 [file] [log] [blame]
Josh Bleecher Snyder112b9232025-05-23 11:26:33 -07001package claudetool
2
3import (
4 "context"
5 "encoding/json"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10)
11
12func TestTodoReadEmpty(t *testing.T) {
13 ctx := WithSessionID(context.Background(), "test-session-1")
14
15 // Ensure todo file doesn't exist
16 todoPath := todoFilePathForContext(ctx)
17 os.Remove(todoPath)
18
19 result, err := todoReadRun(ctx, []byte("{}"))
20 if err != nil {
21 t.Fatalf("expected no error, got %v", err)
22 }
23
24 if len(result) != 1 {
25 t.Fatalf("expected 1 content item, got %d", len(result))
26 }
27
28 expected := "No todo list found. Use todo_write to create one."
29 if result[0].Text != expected {
30 t.Errorf("expected %q, got %q", expected, result[0].Text)
31 }
32}
33
34func TestTodoWriteAndRead(t *testing.T) {
35 ctx := WithSessionID(context.Background(), "test-session-2")
36
37 // Clean up
38 todoPath := todoFilePathForContext(ctx)
39 defer os.Remove(todoPath)
40 os.Remove(todoPath)
41
42 // Write some todos
43 todos := []TodoItem{
44 {ID: "1", Task: "Implement todo tools", Status: "completed"},
45 {ID: "2", Task: "Update system prompt", Status: "in-progress"},
46 {ID: "3", Task: "Write tests", Status: "queued"},
47 }
48
49 writeInput := TodoWriteInput{Tasks: todos}
50 writeInputJSON, _ := json.Marshal(writeInput)
51
52 result, err := todoWriteRun(ctx, writeInputJSON)
53 if err != nil {
54 t.Fatalf("expected no error, got %v", err)
55 }
56
57 if len(result) != 1 {
58 t.Fatalf("expected 1 content item, got %d", len(result))
59 }
60
61 expected := "Updated todo list with 3 items."
62 if result[0].Text != expected {
63 t.Errorf("expected %q, got %q", expected, result[0].Text)
64 }
65
66 // Read the todos back
67 result, err = todoReadRun(ctx, []byte("{}"))
68 if err != nil {
69 t.Fatalf("expected no error, got %v", err)
70 }
71
72 if len(result) != 1 {
73 t.Fatalf("expected 1 content item, got %d", len(result))
74 }
75
76 resultText := result[0].Text
77 if !strings.Contains(resultText, "<todo_list count=\"3\">") {
78 t.Errorf("expected result to contain XML todo list header, got %q", resultText)
79 }
80
81 // Check that all todos are present with proper XML structure
82 if !strings.Contains(resultText, `<task id="1" status="completed">Implement todo tools</task>`) {
83 t.Errorf("expected result to contain first todo in XML format, got %q", resultText)
84 }
85 if !strings.Contains(resultText, `<task id="2" status="in-progress">Update system prompt</task>`) {
86 t.Errorf("expected result to contain second todo in XML format, got %q", resultText)
87 }
88 if !strings.Contains(resultText, `<task id="3" status="queued">Write tests</task>`) {
89 t.Errorf("expected result to contain third todo in XML format, got %q", resultText)
90 }
91
92 // Check XML structure
93 if !strings.Contains(resultText, "</todo_list>") {
94 t.Errorf("expected result to contain closing XML tag, got %q", resultText)
95 }
96}
97
98func TestTodoWriteMultipleInProgress(t *testing.T) {
99 ctx := WithSessionID(context.Background(), "test-session-3")
100
101 // Try to write todos with multiple in-progress items
102 todos := []TodoItem{
103 {ID: "1", Task: "Task 1", Status: "in-progress"},
104 {ID: "2", Task: "Task 2", Status: "in-progress"},
105 }
106
107 writeInput := TodoWriteInput{Tasks: todos}
108 writeInputJSON, _ := json.Marshal(writeInput)
109
110 _, err := todoWriteRun(ctx, writeInputJSON)
111 if err == nil {
112 t.Fatal("expected error for multiple in_progress tasks, got none")
113 }
114
115 expected := "only one task can be 'in-progress' at a time, found 2"
116 if err.Error() != expected {
117 t.Errorf("expected error %q, got %q", expected, err.Error())
118 }
119}
120
121func TestTodoSessionIsolation(t *testing.T) {
122 // Test that different sessions have different todo files
123 ctx1 := WithSessionID(context.Background(), "session-1")
124 ctx2 := WithSessionID(context.Background(), "session-2")
125
126 path1 := todoFilePathForContext(ctx1)
127 path2 := todoFilePathForContext(ctx2)
128
129 if path1 == path2 {
130 t.Errorf("expected different paths for different sessions, both got %q", path1)
131 }
132
133 expected1 := filepath.Join("/tmp", "session-1", "todos.json")
134 expected2 := filepath.Join("/tmp", "session-2", "todos.json")
135
136 if path1 != expected1 {
137 t.Errorf("expected path1 %q, got %q", expected1, path1)
138 }
139
140 if path2 != expected2 {
141 t.Errorf("expected path2 %q, got %q", expected2, path2)
142 }
143}
144
145func TestTodoFallbackPath(t *testing.T) {
146 // Test fallback when no session ID in context
147 ctx := context.Background() // No session ID
148
149 path := todoFilePathForContext(ctx)
150 expected := "/tmp/sketch_todos.json"
151
152 if path != expected {
153 t.Errorf("expected fallback path %q, got %q", expected, path)
154 }
155}