mirror of
https://github.com/sst/opencode.git
synced 2025-08-06 22:38:04 +00:00
457 lines
11 KiB
Go
457 lines
11 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestLsTool_Info(t *testing.T) {
|
|
tool := NewLsTool()
|
|
info := tool.Info()
|
|
|
|
assert.Equal(t, LSToolName, info.Name)
|
|
assert.NotEmpty(t, info.Description)
|
|
assert.Contains(t, info.Parameters, "path")
|
|
assert.Contains(t, info.Parameters, "ignore")
|
|
assert.Contains(t, info.Required, "path")
|
|
}
|
|
|
|
func TestLsTool_Run(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tempDir, err := os.MkdirTemp("", "ls_tool_test")
|
|
require.NoError(t, err)
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
// Create a test directory structure
|
|
testDirs := []string{
|
|
"dir1",
|
|
"dir2",
|
|
"dir2/subdir1",
|
|
"dir2/subdir2",
|
|
"dir3",
|
|
"dir3/.hidden_dir",
|
|
"__pycache__",
|
|
}
|
|
|
|
testFiles := []string{
|
|
"file1.txt",
|
|
"file2.txt",
|
|
"dir1/file3.txt",
|
|
"dir2/file4.txt",
|
|
"dir2/subdir1/file5.txt",
|
|
"dir2/subdir2/file6.txt",
|
|
"dir3/file7.txt",
|
|
"dir3/.hidden_file.txt",
|
|
"__pycache__/cache.pyc",
|
|
".hidden_root_file.txt",
|
|
}
|
|
|
|
// Create directories
|
|
for _, dir := range testDirs {
|
|
dirPath := filepath.Join(tempDir, dir)
|
|
err := os.MkdirAll(dirPath, 0755)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Create files
|
|
for _, file := range testFiles {
|
|
filePath := filepath.Join(tempDir, file)
|
|
err := os.WriteFile(filePath, []byte("test content"), 0644)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
t.Run("lists directory successfully", func(t *testing.T) {
|
|
tool := NewLsTool()
|
|
params := LSParams{
|
|
Path: tempDir,
|
|
}
|
|
|
|
paramsJSON, err := json.Marshal(params)
|
|
require.NoError(t, err)
|
|
|
|
call := ToolCall{
|
|
Name: LSToolName,
|
|
Input: string(paramsJSON),
|
|
}
|
|
|
|
response, err := tool.Run(context.Background(), call)
|
|
require.NoError(t, err)
|
|
|
|
// Check that visible directories and files are included
|
|
assert.Contains(t, response.Content, "dir1")
|
|
assert.Contains(t, response.Content, "dir2")
|
|
assert.Contains(t, response.Content, "dir3")
|
|
assert.Contains(t, response.Content, "file1.txt")
|
|
assert.Contains(t, response.Content, "file2.txt")
|
|
|
|
// Check that hidden files and directories are not included
|
|
assert.NotContains(t, response.Content, ".hidden_dir")
|
|
assert.NotContains(t, response.Content, ".hidden_file.txt")
|
|
assert.NotContains(t, response.Content, ".hidden_root_file.txt")
|
|
|
|
// Check that __pycache__ is not included
|
|
assert.NotContains(t, response.Content, "__pycache__")
|
|
})
|
|
|
|
t.Run("handles non-existent path", func(t *testing.T) {
|
|
tool := NewLsTool()
|
|
params := LSParams{
|
|
Path: filepath.Join(tempDir, "non_existent_dir"),
|
|
}
|
|
|
|
paramsJSON, err := json.Marshal(params)
|
|
require.NoError(t, err)
|
|
|
|
call := ToolCall{
|
|
Name: LSToolName,
|
|
Input: string(paramsJSON),
|
|
}
|
|
|
|
response, err := tool.Run(context.Background(), call)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, response.Content, "path does not exist")
|
|
})
|
|
|
|
t.Run("handles empty path parameter", func(t *testing.T) {
|
|
// For this test, we need to mock the config.WorkingDirectory function
|
|
// Since we can't easily do that, we'll just check that the response doesn't contain an error message
|
|
|
|
tool := NewLsTool()
|
|
params := LSParams{
|
|
Path: "",
|
|
}
|
|
|
|
paramsJSON, err := json.Marshal(params)
|
|
require.NoError(t, err)
|
|
|
|
call := ToolCall{
|
|
Name: LSToolName,
|
|
Input: string(paramsJSON),
|
|
}
|
|
|
|
response, err := tool.Run(context.Background(), call)
|
|
require.NoError(t, err)
|
|
|
|
// The response should either contain a valid directory listing or an error
|
|
// We'll just check that it's not empty
|
|
assert.NotEmpty(t, response.Content)
|
|
})
|
|
|
|
t.Run("handles invalid parameters", func(t *testing.T) {
|
|
tool := NewLsTool()
|
|
call := ToolCall{
|
|
Name: LSToolName,
|
|
Input: "invalid json",
|
|
}
|
|
|
|
response, err := tool.Run(context.Background(), call)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, response.Content, "error parsing parameters")
|
|
})
|
|
|
|
t.Run("respects ignore patterns", func(t *testing.T) {
|
|
tool := NewLsTool()
|
|
params := LSParams{
|
|
Path: tempDir,
|
|
Ignore: []string{"file1.txt", "dir1"},
|
|
}
|
|
|
|
paramsJSON, err := json.Marshal(params)
|
|
require.NoError(t, err)
|
|
|
|
call := ToolCall{
|
|
Name: LSToolName,
|
|
Input: string(paramsJSON),
|
|
}
|
|
|
|
response, err := tool.Run(context.Background(), call)
|
|
require.NoError(t, err)
|
|
|
|
// The output format is a tree, so we need to check for specific patterns
|
|
// Check that file1.txt is not directly mentioned
|
|
assert.NotContains(t, response.Content, "- file1.txt")
|
|
|
|
// Check that dir1/ is not directly mentioned
|
|
assert.NotContains(t, response.Content, "- dir1/")
|
|
})
|
|
|
|
t.Run("handles relative path", func(t *testing.T) {
|
|
// Save original working directory
|
|
origWd, err := os.Getwd()
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
os.Chdir(origWd)
|
|
}()
|
|
|
|
// Change to a directory above the temp directory
|
|
parentDir := filepath.Dir(tempDir)
|
|
err = os.Chdir(parentDir)
|
|
require.NoError(t, err)
|
|
|
|
tool := NewLsTool()
|
|
params := LSParams{
|
|
Path: filepath.Base(tempDir),
|
|
}
|
|
|
|
paramsJSON, err := json.Marshal(params)
|
|
require.NoError(t, err)
|
|
|
|
call := ToolCall{
|
|
Name: LSToolName,
|
|
Input: string(paramsJSON),
|
|
}
|
|
|
|
response, err := tool.Run(context.Background(), call)
|
|
require.NoError(t, err)
|
|
|
|
// Should list the temp directory contents
|
|
assert.Contains(t, response.Content, "dir1")
|
|
assert.Contains(t, response.Content, "file1.txt")
|
|
})
|
|
}
|
|
|
|
func TestShouldSkip(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
path string
|
|
ignorePatterns []string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "hidden file",
|
|
path: "/path/to/.hidden_file",
|
|
ignorePatterns: []string{},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "hidden directory",
|
|
path: "/path/to/.hidden_dir",
|
|
ignorePatterns: []string{},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "pycache directory",
|
|
path: "/path/to/__pycache__/file.pyc",
|
|
ignorePatterns: []string{},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "node_modules directory",
|
|
path: "/path/to/node_modules/package",
|
|
ignorePatterns: []string{},
|
|
expected: false, // The shouldSkip function doesn't directly check for node_modules in the path
|
|
},
|
|
{
|
|
name: "normal file",
|
|
path: "/path/to/normal_file.txt",
|
|
ignorePatterns: []string{},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "normal directory",
|
|
path: "/path/to/normal_dir",
|
|
ignorePatterns: []string{},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "ignored by pattern",
|
|
path: "/path/to/ignore_me.txt",
|
|
ignorePatterns: []string{"ignore_*.txt"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "not ignored by pattern",
|
|
path: "/path/to/keep_me.txt",
|
|
ignorePatterns: []string{"ignore_*.txt"},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result := shouldSkip(tc.path, tc.ignorePatterns)
|
|
assert.Equal(t, tc.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateFileTree(t *testing.T) {
|
|
paths := []string{
|
|
"/path/to/file1.txt",
|
|
"/path/to/dir1/file2.txt",
|
|
"/path/to/dir1/subdir/file3.txt",
|
|
"/path/to/dir2/file4.txt",
|
|
}
|
|
|
|
tree := createFileTree(paths)
|
|
|
|
// Check the structure of the tree
|
|
assert.Len(t, tree, 1) // Should have one root node
|
|
|
|
// Check the root node
|
|
rootNode := tree[0]
|
|
assert.Equal(t, "path", rootNode.Name)
|
|
assert.Equal(t, "directory", rootNode.Type)
|
|
assert.Len(t, rootNode.Children, 1)
|
|
|
|
// Check the "to" node
|
|
toNode := rootNode.Children[0]
|
|
assert.Equal(t, "to", toNode.Name)
|
|
assert.Equal(t, "directory", toNode.Type)
|
|
assert.Len(t, toNode.Children, 3) // file1.txt, dir1, dir2
|
|
|
|
// Find the dir1 node
|
|
var dir1Node *TreeNode
|
|
for _, child := range toNode.Children {
|
|
if child.Name == "dir1" {
|
|
dir1Node = child
|
|
break
|
|
}
|
|
}
|
|
|
|
require.NotNil(t, dir1Node)
|
|
assert.Equal(t, "directory", dir1Node.Type)
|
|
assert.Len(t, dir1Node.Children, 2) // file2.txt and subdir
|
|
}
|
|
|
|
func TestPrintTree(t *testing.T) {
|
|
// Create a simple tree
|
|
tree := []*TreeNode{
|
|
{
|
|
Name: "dir1",
|
|
Path: "dir1",
|
|
Type: "directory",
|
|
Children: []*TreeNode{
|
|
{
|
|
Name: "file1.txt",
|
|
Path: "dir1/file1.txt",
|
|
Type: "file",
|
|
},
|
|
{
|
|
Name: "subdir",
|
|
Path: "dir1/subdir",
|
|
Type: "directory",
|
|
Children: []*TreeNode{
|
|
{
|
|
Name: "file2.txt",
|
|
Path: "dir1/subdir/file2.txt",
|
|
Type: "file",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "file3.txt",
|
|
Path: "file3.txt",
|
|
Type: "file",
|
|
},
|
|
}
|
|
|
|
result := printTree(tree, "/root")
|
|
|
|
// Check the output format
|
|
assert.Contains(t, result, "- /root/")
|
|
assert.Contains(t, result, " - dir1/")
|
|
assert.Contains(t, result, " - file1.txt")
|
|
assert.Contains(t, result, " - subdir/")
|
|
assert.Contains(t, result, " - file2.txt")
|
|
assert.Contains(t, result, " - file3.txt")
|
|
}
|
|
|
|
func TestListDirectory(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tempDir, err := os.MkdirTemp("", "list_directory_test")
|
|
require.NoError(t, err)
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
// Create a test directory structure
|
|
testDirs := []string{
|
|
"dir1",
|
|
"dir1/subdir1",
|
|
".hidden_dir",
|
|
}
|
|
|
|
testFiles := []string{
|
|
"file1.txt",
|
|
"file2.txt",
|
|
"dir1/file3.txt",
|
|
"dir1/subdir1/file4.txt",
|
|
".hidden_file.txt",
|
|
}
|
|
|
|
// Create directories
|
|
for _, dir := range testDirs {
|
|
dirPath := filepath.Join(tempDir, dir)
|
|
err := os.MkdirAll(dirPath, 0755)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Create files
|
|
for _, file := range testFiles {
|
|
filePath := filepath.Join(tempDir, file)
|
|
err := os.WriteFile(filePath, []byte("test content"), 0644)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
t.Run("lists files with no limit", func(t *testing.T) {
|
|
files, truncated, err := listDirectory(tempDir, []string{}, 1000)
|
|
require.NoError(t, err)
|
|
assert.False(t, truncated)
|
|
|
|
// Check that visible files and directories are included
|
|
containsPath := func(paths []string, target string) bool {
|
|
targetPath := filepath.Join(tempDir, target)
|
|
for _, path := range paths {
|
|
if strings.HasPrefix(path, targetPath) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
assert.True(t, containsPath(files, "dir1"))
|
|
assert.True(t, containsPath(files, "file1.txt"))
|
|
assert.True(t, containsPath(files, "file2.txt"))
|
|
assert.True(t, containsPath(files, "dir1/file3.txt"))
|
|
|
|
// Check that hidden files and directories are not included
|
|
assert.False(t, containsPath(files, ".hidden_dir"))
|
|
assert.False(t, containsPath(files, ".hidden_file.txt"))
|
|
})
|
|
|
|
t.Run("respects limit and returns truncated flag", func(t *testing.T) {
|
|
files, truncated, err := listDirectory(tempDir, []string{}, 2)
|
|
require.NoError(t, err)
|
|
assert.True(t, truncated)
|
|
assert.Len(t, files, 2)
|
|
})
|
|
|
|
t.Run("respects ignore patterns", func(t *testing.T) {
|
|
files, truncated, err := listDirectory(tempDir, []string{"*.txt"}, 1000)
|
|
require.NoError(t, err)
|
|
assert.False(t, truncated)
|
|
|
|
// Check that no .txt files are included
|
|
for _, file := range files {
|
|
assert.False(t, strings.HasSuffix(file, ".txt"), "Found .txt file: %s", file)
|
|
}
|
|
|
|
// But directories should still be included
|
|
containsDir := false
|
|
for _, file := range files {
|
|
if strings.Contains(file, "dir1") {
|
|
containsDir = true
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, containsDir)
|
|
})
|
|
}
|