blob: cb859fe840e8d8bf7f44ca71ba6ef62dcc12da8e [file] [log] [blame]
Earl Lee2e463fb2025-04-17 11:22:22 -07001package claudetool
2
3import (
4 "context"
5 "encoding/json"
6 "strings"
7 "testing"
8)
9
10// TestEmptyContentHandling tests handling of empty content in str_replace and related operations
11// This test specifically reproduces conditions that might lead to "index out of range [0]" panic
12func TestEmptyContentHandling(t *testing.T) {
13 // Create a file with empty content
14 emptyFile := setupTestFile(t, "")
15
16 // Test running EditRun directly with empty content
17 // This more closely simulates the actual call flow that led to the panic
18 input := map[string]any{
19 "command": "str_replace",
20 "path": emptyFile,
21 "old_str": "nonexistent text",
22 "new_str": "new content",
23 }
24
25 inputJSON, err := json.Marshal(input)
26 if err != nil {
27 t.Fatalf("Failed to marshal input: %v", err)
28 }
29
30 // This should not panic but return an error
31 _, err = EditRun(context.Background(), inputJSON)
32 if err == nil {
33 t.Fatalf("Expected error for empty file with str_replace but got none")
34 }
35
36 // Make sure the error message is as expected
37 if !strings.Contains(err.Error(), "did not appear verbatim") {
38 t.Errorf("Expected error message to indicate missing string, got: %s", err.Error())
39 }
40}
41
42// TestNilParameterHandling tests error cases with nil parameters
43// This test validates proper error handling when nil or invalid parameters are provided
44func TestNilParameterHandling(t *testing.T) {
45 // Create a test file
46 testFile := setupTestFile(t, "test content")
47
48 // Test case 1: nil old_str in str_replace
49 input1 := map[string]any{
50 "command": "str_replace",
51 "path": testFile,
52 // old_str is deliberately missing
53 "new_str": "replacement",
54 }
55
56 inputJSON1, err := json.Marshal(input1)
57 if err != nil {
58 t.Fatalf("Failed to marshal input: %v", err)
59 }
60
61 _, err = EditRun(context.Background(), inputJSON1)
62 if err == nil {
63 t.Fatalf("Expected error for missing old_str but got none")
64 }
65 if !strings.Contains(err.Error(), "parameter old_str is required") {
66 t.Errorf("Expected error message to indicate missing old_str, got: %s", err.Error())
67 }
68
69 // Test case 2: nil new_str in insert
70 input2 := map[string]any{
71 "command": "insert",
72 "path": testFile,
73 "insert_line": 1,
74 // new_str is deliberately missing
75 }
76
77 inputJSON2, err := json.Marshal(input2)
78 if err != nil {
79 t.Fatalf("Failed to marshal input: %v", err)
80 }
81
82 _, err = EditRun(context.Background(), inputJSON2)
83 if err == nil {
84 t.Fatalf("Expected error for missing new_str but got none")
85 }
86 if !strings.Contains(err.Error(), "parameter new_str is required") {
87 t.Errorf("Expected error message to indicate missing new_str, got: %s", err.Error())
88 }
89
90 // Test case 3: nil view_range in view
91 // This doesn't cause an error, but tests the code path
92 input3 := map[string]any{
93 "command": "view",
94 "path": testFile,
95 // No view_range
96 }
97
98 inputJSON3, err := json.Marshal(input3)
99 if err != nil {
100 t.Fatalf("Failed to marshal input: %v", err)
101 }
102
103 // This should not result in an error
104 _, err = EditRun(context.Background(), inputJSON3)
105 if err != nil {
106 t.Fatalf("Unexpected error for nil view_range: %v", err)
107 }
108}
109
110// TestEmptySplitResult tests the specific scenario where strings.Split might return empty results
111// This directly reproduces conditions that might have led to the "index out of range [0]" panic
112func TestEmptySplitResult(t *testing.T) {
113 // Direct test of strings.Split behavior and our handling of it
114 emptyCases := []struct {
115 content string
116 oldStr string
117 }{
118 {"", "any string"},
119 {"content", "not in string"},
120 {"\n\n", "also not here"},
121 }
122
123 for _, tc := range emptyCases {
124 parts := strings.Split(tc.content, tc.oldStr)
125
126 // Verify that strings.Split with non-matching separator returns a slice with original content
127 if len(parts) != 1 {
128 t.Errorf("Expected strings.Split to return a slice with 1 element when separator isn't found, got %d elements", len(parts))
129 }
130
131 // Double check the content
132 if len(parts) > 0 && parts[0] != tc.content {
133 t.Errorf("Expected parts[0] to be original content %q, got %q", tc.content, parts[0])
134 }
135 }
136
137 // Test the actual unsafe scenario with empty content
138 emptyFile := setupTestFile(t, "")
139
140 // Get the content and simulate the internal string splitting
141 content, _ := readFile(emptyFile)
142 oldStr := "nonexistent"
143 parts := strings.Split(content, oldStr)
144
145 // Validate that the defensive code would work
146 if len(parts) == 0 {
147 parts = []string{""} // This is the fix
148 }
149
150 // This would have panicked without the fix
151 _ = strings.Count(parts[0], "\n")
152}