Files
2026-07-13 13:00:08 +08:00

230 lines
6.7 KiB
Go

package builtin
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)
// --- read_file extended tests ---
func TestReadFileMissing(t *testing.T) {
_, err := readFile{}.Execute(context.Background(), argsJSON(t, map[string]any{"path": "/nonexistent/file.txt"}))
if err == nil {
t.Fatal("expected error for missing file")
}
if !strings.Contains(err.Error(), "read") {
t.Errorf("error should mention 'read': %v", err)
}
}
func TestReadFileMissingPath(t *testing.T) {
_, err := readFile{}.Execute(context.Background(), argsJSON(t, map[string]any{}))
if err == nil {
t.Fatal("expected error for missing path")
}
}
func TestReadFileLargeFile(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "large.txt")
var b strings.Builder
for i := 1; i <= 100; i++ {
fmt.Fprintf(&b, "line %d\n", i)
}
os.WriteFile(f, []byte(b.String()), 0o644)
// Read with small limit.
out := runTool(t, readFile{}, map[string]any{"path": f, "offset": 0, "limit": 3})
if !strings.Contains(out, "1→line 1") {
t.Errorf("missing first line: %s", out)
}
if !strings.Contains(out, "3→line 3") {
t.Errorf("missing third line: %s", out)
}
if strings.Contains(out, "4→line 4") {
t.Errorf("should not contain fourth line: %s", out)
}
// Pagination hint points at the next page; it no longer reads the whole file
// to compute an exact remaining count.
if !strings.Contains(out, "more line") || !strings.Contains(out, "offset=3") {
t.Errorf("pagination hint missing: %s", out)
}
}
func TestReadFileOffsetPastEOF(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "short.txt")
os.WriteFile(f, []byte("one\ntwo\n"), 0o644)
out := runTool(t, readFile{}, map[string]any{"path": f, "offset": 100, "limit": 10})
if !strings.Contains(out, "past EOF") {
t.Errorf("should report past EOF: %s", out)
}
}
func TestReadFileInvalidArgs(t *testing.T) {
_, err := readFile{}.Execute(context.Background(), json.RawMessage(`{invalid`))
if err == nil {
t.Fatal("expected error for invalid JSON")
}
}
// --- ls extended tests ---
func TestLsEmptyDir(t *testing.T) {
dir := t.TempDir()
out := runTool(t, listDir{}, map[string]any{"path": dir})
if !strings.Contains(out, "(empty directory)") {
t.Errorf("empty dir should report (empty directory): %s", out)
}
}
func TestLsMissingDir(t *testing.T) {
_, err := listDir{}.Execute(context.Background(), argsJSON(t, map[string]any{"path": "/nonexistent"}))
if err == nil {
t.Fatal("expected error for missing dir")
}
}
func TestLsDefaultPath(t *testing.T) {
// Default path "." should list the current directory without error.
out := runTool(t, listDir{}, map[string]any{})
if out == "" {
t.Error("ls with default path should return something")
}
}
func TestLsInvalidArgs(t *testing.T) {
_, err := listDir{}.Execute(context.Background(), json.RawMessage(`{invalid`))
if err == nil {
t.Fatal("expected error for invalid JSON")
}
}
// --- grep extended tests ---
func TestGrepSingleFile(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "code.go")
os.WriteFile(f, []byte("func Foo() {}\nfunc Bar() {}\nvar x = 1\n"), 0o644)
out := runTool(t, grepTool{}, map[string]any{"pattern": "func ", "path": f})
if !strings.Contains(out, "Foo") || !strings.Contains(out, "Bar") {
t.Errorf("should find both functions: %s", out)
}
if strings.Contains(out, "var x") {
t.Errorf("should not include non-matching line: %s", out)
}
}
func TestGrepNoMatches(t *testing.T) {
dir := t.TempDir()
os.WriteFile(filepath.Join(dir, "a.txt"), []byte("hello world\n"), 0o644)
out := runTool(t, grepTool{}, map[string]any{"pattern": "xyzzy", "path": dir})
if !strings.Contains(out, "(no matches)") {
t.Errorf("expected (no matches): %s", out)
}
}
func TestGrepInvalidPattern(t *testing.T) {
dir := t.TempDir()
os.WriteFile(filepath.Join(dir, "a.txt"), []byte("test\n"), 0o644)
_, err := grepTool{}.Execute(context.Background(), argsJSON(t, map[string]any{"pattern": "[invalid", "path": dir}))
if err == nil {
t.Fatal("expected error for invalid regex")
}
}
func TestGrepInvalidArgs(t *testing.T) {
_, err := grepTool{}.Execute(context.Background(), json.RawMessage(`{invalid`))
if err == nil {
t.Fatal("expected error for invalid JSON")
}
}
func TestGrepMissingPattern(t *testing.T) {
_, err := grepTool{}.Execute(context.Background(), argsJSON(t, map[string]any{"path": "."}))
if err == nil {
t.Fatal("expected error for missing pattern")
}
}
func TestGrepSkipsGitDir(t *testing.T) {
dir := t.TempDir()
os.MkdirAll(filepath.Join(dir, ".git"), 0o755)
os.WriteFile(filepath.Join(dir, ".git", "config"), []byte("secret = true\n"), 0o644)
os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\n"), 0o644)
out := runTool(t, grepTool{}, map[string]any{"pattern": "secret", "path": dir})
if strings.Contains(out, ".git") {
t.Errorf("grep should skip .git directory: %s", out)
}
}
func TestGrepTruncation(t *testing.T) {
dir := t.TempDir()
// Create a file with many matching lines.
var b strings.Builder
for i := 0; i < 300; i++ {
fmt.Fprintf(&b, "match %d\n", i)
}
os.WriteFile(filepath.Join(dir, "many.txt"), []byte(b.String()), 0o644)
out := runTool(t, grepTool{}, map[string]any{"pattern": "match", "path": dir})
if !strings.Contains(out, "truncated") {
t.Errorf("should mention truncation: %s", out)
}
}
// --- glob extended tests ---
func TestGlobEmptyPattern(t *testing.T) {
_, err := globTool{}.Execute(context.Background(), argsJSON(t, map[string]any{"pattern": ""}))
if err == nil {
t.Fatal("expected error for empty pattern")
}
}
func TestGlobInvalidArgs(t *testing.T) {
_, err := globTool{}.Execute(context.Background(), json.RawMessage(`{invalid`))
if err == nil {
t.Fatal("expected error for invalid JSON")
}
}
func TestGlobCharClass(t *testing.T) {
dir := t.TempDir()
os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a"), 0o644)
os.WriteFile(filepath.Join(dir, "b.txt"), []byte("b"), 0o644)
os.WriteFile(filepath.Join(dir, "c.log"), []byte("c"), 0o644)
out := runTool(t, globTool{}, map[string]any{"pattern": filepath.Join(dir, "[ab].txt")})
if !strings.Contains(out, "a.txt") || !strings.Contains(out, "b.txt") {
t.Errorf("should match [ab].txt: %s", out)
}
if strings.Contains(out, "c.log") {
t.Errorf("should not match c.log: %s", out)
}
}
func TestGlobQuestionMark(t *testing.T) {
dir := t.TempDir()
os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a"), 0o644)
os.WriteFile(filepath.Join(dir, "ab.txt"), []byte("ab"), 0o644)
out := runTool(t, globTool{}, map[string]any{"pattern": filepath.Join(dir, "?.txt")})
if !strings.Contains(out, "a.txt") {
t.Errorf("should match a.txt: %s", out)
}
if strings.Contains(out, "ab.txt") {
t.Errorf("?.txt should not match ab.txt: %s", out)
}
}