返回 DeepSeek-Reasonix
tool_extra_test.go
根目录 / internal / tool / builtin / tool_extra_test.go
1 package builtin
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "os"
8 "path/filepath"
9 "strings"
10 "testing"
11 )
12
13 // --- read_file extended tests ---
14
15 func TestReadFileMissing(t *testing.T) {
16 _, err := readFile{}.Execute(context.Background(), argsJSON(t, map[string]any{"path": "/nonexistent/file.txt"}))
17 if err == nil {
18 t.Fatal("expected error for missing file")
19 }
20 if !strings.Contains(err.Error(), "read") {
21 t.Errorf("error should mention 'read': %v", err)
22 }
23 }
24
25 func TestReadFileMissingPath(t *testing.T) {
26 _, err := readFile{}.Execute(context.Background(), argsJSON(t, map[string]any{}))
27 if err == nil {
28 t.Fatal("expected error for missing path")
29 }
30 }
31
32 func TestReadFileLargeFile(t *testing.T) {
33 dir := t.TempDir()
34 f := filepath.Join(dir, "large.txt")
35 var b strings.Builder
36 for i := 1; i <= 100; i++ {
37 fmt.Fprintf(&b, "line %d\n", i)
38 }
39 os.WriteFile(f, []byte(b.String()), 0o644)
40
41 // Read with small limit.
42 out := runTool(t, readFile{}, map[string]any{"path": f, "offset": 0, "limit": 3})
43 if !strings.Contains(out, "1→line 1") {
44 t.Errorf("missing first line: %s", out)
45 }
46 if !strings.Contains(out, "3→line 3") {
47 t.Errorf("missing third line: %s", out)
48 }
49 if strings.Contains(out, "4→line 4") {
50 t.Errorf("should not contain fourth line: %s", out)
51 }
52 // Pagination hint points at the next page; it no longer reads the whole file
53 // to compute an exact remaining count.
54 if !strings.Contains(out, "more line") || !strings.Contains(out, "offset=3") {
55 t.Errorf("pagination hint missing: %s", out)
56 }
57 }
58
59 func TestReadFileOffsetPastEOF(t *testing.T) {
60 dir := t.TempDir()
61 f := filepath.Join(dir, "short.txt")
62 os.WriteFile(f, []byte("one\ntwo\n"), 0o644)
63
64 out := runTool(t, readFile{}, map[string]any{"path": f, "offset": 100, "limit": 10})
65 if !strings.Contains(out, "past EOF") {
66 t.Errorf("should report past EOF: %s", out)
67 }
68 }
69
70 func TestReadFileInvalidArgs(t *testing.T) {
71 _, err := readFile{}.Execute(context.Background(), json.RawMessage(`{invalid`))
72 if err == nil {
73 t.Fatal("expected error for invalid JSON")
74 }
75 }
76
77 // --- ls extended tests ---
78
79 func TestLsEmptyDir(t *testing.T) {
80 dir := t.TempDir()
81 out := runTool(t, listDir{}, map[string]any{"path": dir})
82 if !strings.Contains(out, "(empty directory)") {
83 t.Errorf("empty dir should report (empty directory): %s", out)
84 }
85 }
86
87 func TestLsMissingDir(t *testing.T) {
88 _, err := listDir{}.Execute(context.Background(), argsJSON(t, map[string]any{"path": "/nonexistent"}))
89 if err == nil {
90 t.Fatal("expected error for missing dir")
91 }
92 }
93
94 func TestLsDefaultPath(t *testing.T) {
95 // Default path "." should list the current directory without error.
96 out := runTool(t, listDir{}, map[string]any{})
97 if out == "" {
98 t.Error("ls with default path should return something")
99 }
100 }
101
102 func TestLsInvalidArgs(t *testing.T) {
103 _, err := listDir{}.Execute(context.Background(), json.RawMessage(`{invalid`))
104 if err == nil {
105 t.Fatal("expected error for invalid JSON")
106 }
107 }
108
109 // --- grep extended tests ---
110
111 func TestGrepSingleFile(t *testing.T) {
112 dir := t.TempDir()
113 f := filepath.Join(dir, "code.go")
114 os.WriteFile(f, []byte("func Foo() {}\nfunc Bar() {}\nvar x = 1\n"), 0o644)
115
116 out := runTool(t, grepTool{}, map[string]any{"pattern": "func ", "path": f})
117 if !strings.Contains(out, "Foo") || !strings.Contains(out, "Bar") {
118 t.Errorf("should find both functions: %s", out)
119 }
120 if strings.Contains(out, "var x") {
121 t.Errorf("should not include non-matching line: %s", out)
122 }
123 }
124
125 func TestGrepNoMatches(t *testing.T) {
126 dir := t.TempDir()
127 os.WriteFile(filepath.Join(dir, "a.txt"), []byte("hello world\n"), 0o644)
128
129 out := runTool(t, grepTool{}, map[string]any{"pattern": "xyzzy", "path": dir})
130 if !strings.Contains(out, "(no matches)") {
131 t.Errorf("expected (no matches): %s", out)
132 }
133 }
134
135 func TestGrepInvalidPattern(t *testing.T) {
136 dir := t.TempDir()
137 os.WriteFile(filepath.Join(dir, "a.txt"), []byte("test\n"), 0o644)
138
139 _, err := grepTool{}.Execute(context.Background(), argsJSON(t, map[string]any{"pattern": "[invalid", "path": dir}))
140 if err == nil {
141 t.Fatal("expected error for invalid regex")
142 }
143 }
144
145 func TestGrepInvalidArgs(t *testing.T) {
146 _, err := grepTool{}.Execute(context.Background(), json.RawMessage(`{invalid`))
147 if err == nil {
148 t.Fatal("expected error for invalid JSON")
149 }
150 }
151
152 func TestGrepMissingPattern(t *testing.T) {
153 _, err := grepTool{}.Execute(context.Background(), argsJSON(t, map[string]any{"path": "."}))
154 if err == nil {
155 t.Fatal("expected error for missing pattern")
156 }
157 }
158
159 func TestGrepSkipsGitDir(t *testing.T) {
160 dir := t.TempDir()
161 os.MkdirAll(filepath.Join(dir, ".git"), 0o755)
162 os.WriteFile(filepath.Join(dir, ".git", "config"), []byte("secret = true\n"), 0o644)
163 os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\n"), 0o644)
164
165 out := runTool(t, grepTool{}, map[string]any{"pattern": "secret", "path": dir})
166 if strings.Contains(out, ".git") {
167 t.Errorf("grep should skip .git directory: %s", out)
168 }
169 }
170
171 func TestGrepTruncation(t *testing.T) {
172 dir := t.TempDir()
173 // Create a file with many matching lines.
174 var b strings.Builder
175 for i := 0; i < 300; i++ {
176 fmt.Fprintf(&b, "match %d\n", i)
177 }
178 os.WriteFile(filepath.Join(dir, "many.txt"), []byte(b.String()), 0o644)
179
180 out := runTool(t, grepTool{}, map[string]any{"pattern": "match", "path": dir})
181 if !strings.Contains(out, "truncated") {
182 t.Errorf("should mention truncation: %s", out)
183 }
184 }
185
186 // --- glob extended tests ---
187
188 func TestGlobEmptyPattern(t *testing.T) {
189 _, err := globTool{}.Execute(context.Background(), argsJSON(t, map[string]any{"pattern": ""}))
190 if err == nil {
191 t.Fatal("expected error for empty pattern")
192 }
193 }
194
195 func TestGlobInvalidArgs(t *testing.T) {
196 _, err := globTool{}.Execute(context.Background(), json.RawMessage(`{invalid`))
197 if err == nil {
198 t.Fatal("expected error for invalid JSON")
199 }
200 }
201
202 func TestGlobCharClass(t *testing.T) {
203 dir := t.TempDir()
204 os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a"), 0o644)
205 os.WriteFile(filepath.Join(dir, "b.txt"), []byte("b"), 0o644)
206 os.WriteFile(filepath.Join(dir, "c.log"), []byte("c"), 0o644)
207
208 out := runTool(t, globTool{}, map[string]any{"pattern": filepath.Join(dir, "[ab].txt")})
209 if !strings.Contains(out, "a.txt") || !strings.Contains(out, "b.txt") {
210 t.Errorf("should match [ab].txt: %s", out)
211 }
212 if strings.Contains(out, "c.log") {
213 t.Errorf("should not match c.log: %s", out)
214 }
215 }
216
217 func TestGlobQuestionMark(t *testing.T) {
218 dir := t.TempDir()
219 os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a"), 0o644)
220 os.WriteFile(filepath.Join(dir, "ab.txt"), []byte("ab"), 0o644)
221
222 out := runTool(t, globTool{}, map[string]any{"pattern": filepath.Join(dir, "?.txt")})
223 if !strings.Contains(out, "a.txt") {
224 t.Errorf("should match a.txt: %s", out)
225 }
226 if strings.Contains(out, "ab.txt") {
227 t.Errorf("?.txt should not match ab.txt: %s", out)
228 }
229 }
230
230 lines GO