diff options
Diffstat (limited to 'internal/llm/tools/bash_test.go')
| -rw-r--r-- | internal/llm/tools/bash_test.go | 340 |
1 files changed, 0 insertions, 340 deletions
diff --git a/internal/llm/tools/bash_test.go b/internal/llm/tools/bash_test.go deleted file mode 100644 index dafb0ccc5..000000000 --- a/internal/llm/tools/bash_test.go +++ /dev/null @@ -1,340 +0,0 @@ -package tools - -import ( - "context" - "encoding/json" - "os" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestBashTool_Info(t *testing.T) { - tool := NewBashTool(newMockPermissionService(true)) - info := tool.Info() - - assert.Equal(t, BashToolName, info.Name) - assert.NotEmpty(t, info.Description) - assert.Contains(t, info.Parameters, "command") - assert.Contains(t, info.Parameters, "timeout") - assert.Contains(t, info.Required, "command") -} - -func TestBashTool_Run(t *testing.T) { - // Save original working directory - origWd, err := os.Getwd() - require.NoError(t, err) - defer func() { - os.Chdir(origWd) - }() - - t.Run("executes command successfully", func(t *testing.T) { - tool := NewBashTool(newMockPermissionService(true)) - params := BashParams{ - Command: "echo 'Hello World'", - } - - paramsJSON, err := json.Marshal(params) - require.NoError(t, err) - - call := ToolCall{ - Name: BashToolName, - Input: string(paramsJSON), - } - - response, err := tool.Run(context.Background(), call) - require.NoError(t, err) - assert.Equal(t, "Hello World\n", response.Content) - }) - - t.Run("handles invalid parameters", func(t *testing.T) { - tool := NewBashTool(newMockPermissionService(true)) - call := ToolCall{ - Name: BashToolName, - Input: "invalid json", - } - - response, err := tool.Run(context.Background(), call) - require.NoError(t, err) - assert.Contains(t, response.Content, "invalid parameters") - }) - - t.Run("handles missing command", func(t *testing.T) { - tool := NewBashTool(newMockPermissionService(true)) - params := BashParams{ - Command: "", - } - - paramsJSON, err := json.Marshal(params) - require.NoError(t, err) - - call := ToolCall{ - Name: BashToolName, - Input: string(paramsJSON), - } - - response, err := tool.Run(context.Background(), call) - require.NoError(t, err) - assert.Contains(t, response.Content, "missing command") - }) - - t.Run("handles banned commands", func(t *testing.T) { - tool := NewBashTool(newMockPermissionService(true)) - - for _, bannedCmd := range bannedCommands { - params := BashParams{ - Command: bannedCmd + " arg1 arg2", - } - - paramsJSON, err := json.Marshal(params) - require.NoError(t, err) - - call := ToolCall{ - Name: BashToolName, - Input: string(paramsJSON), - } - - response, err := tool.Run(context.Background(), call) - require.NoError(t, err) - assert.Contains(t, response.Content, "not allowed", "Command %s should be blocked", bannedCmd) - } - }) - - t.Run("handles multi-word safe commands without permission check", func(t *testing.T) { - tool := NewBashTool(newMockPermissionService(false)) - - // Test with multi-word safe commands - multiWordCommands := []string{ - "go env", - } - - for _, cmd := range multiWordCommands { - params := BashParams{ - Command: cmd, - } - - paramsJSON, err := json.Marshal(params) - require.NoError(t, err) - - call := ToolCall{ - Name: BashToolName, - Input: string(paramsJSON), - } - - response, err := tool.Run(context.Background(), call) - require.NoError(t, err) - assert.NotContains(t, response.Content, "permission denied", - "Command %s should be allowed without permission", cmd) - } - }) - - t.Run("handles permission denied", func(t *testing.T) { - tool := NewBashTool(newMockPermissionService(false)) - - // Test with a command that requires permission - params := BashParams{ - Command: "mkdir test_dir", - } - - paramsJSON, err := json.Marshal(params) - require.NoError(t, err) - - call := ToolCall{ - Name: BashToolName, - Input: string(paramsJSON), - } - - response, err := tool.Run(context.Background(), call) - require.NoError(t, err) - assert.Contains(t, response.Content, "permission denied") - }) - - t.Run("handles command timeout", func(t *testing.T) { - tool := NewBashTool(newMockPermissionService(true)) - params := BashParams{ - Command: "sleep 2", - Timeout: 100, // 100ms timeout - } - - paramsJSON, err := json.Marshal(params) - require.NoError(t, err) - - call := ToolCall{ - Name: BashToolName, - Input: string(paramsJSON), - } - - response, err := tool.Run(context.Background(), call) - require.NoError(t, err) - assert.Contains(t, response.Content, "aborted") - }) - - t.Run("handles command with stderr output", func(t *testing.T) { - tool := NewBashTool(newMockPermissionService(true)) - params := BashParams{ - Command: "echo 'error message' >&2", - } - - paramsJSON, err := json.Marshal(params) - require.NoError(t, err) - - call := ToolCall{ - Name: BashToolName, - Input: string(paramsJSON), - } - - response, err := tool.Run(context.Background(), call) - require.NoError(t, err) - assert.Contains(t, response.Content, "error message") - }) - - t.Run("handles command with both stdout and stderr", func(t *testing.T) { - tool := NewBashTool(newMockPermissionService(true)) - params := BashParams{ - Command: "echo 'stdout message' && echo 'stderr message' >&2", - } - - paramsJSON, err := json.Marshal(params) - require.NoError(t, err) - - call := ToolCall{ - Name: BashToolName, - Input: string(paramsJSON), - } - - response, err := tool.Run(context.Background(), call) - require.NoError(t, err) - assert.Contains(t, response.Content, "stdout message") - assert.Contains(t, response.Content, "stderr message") - }) - - t.Run("handles context cancellation", func(t *testing.T) { - tool := NewBashTool(newMockPermissionService(true)) - params := BashParams{ - Command: "sleep 5", - } - - paramsJSON, err := json.Marshal(params) - require.NoError(t, err) - - call := ToolCall{ - Name: BashToolName, - Input: string(paramsJSON), - } - - ctx, cancel := context.WithCancel(context.Background()) - - // Cancel the context after a short delay - go func() { - time.Sleep(100 * time.Millisecond) - cancel() - }() - - response, err := tool.Run(ctx, call) - require.NoError(t, err) - assert.Contains(t, response.Content, "aborted") - }) - - t.Run("respects max timeout", func(t *testing.T) { - tool := NewBashTool(newMockPermissionService(true)) - params := BashParams{ - Command: "echo 'test'", - Timeout: MaxTimeout + 1000, // Exceeds max timeout - } - - paramsJSON, err := json.Marshal(params) - require.NoError(t, err) - - call := ToolCall{ - Name: BashToolName, - Input: string(paramsJSON), - } - - response, err := tool.Run(context.Background(), call) - require.NoError(t, err) - assert.Equal(t, "test\n", response.Content) - }) - - t.Run("uses default timeout for zero or negative timeout", func(t *testing.T) { - tool := NewBashTool(newMockPermissionService(true)) - params := BashParams{ - Command: "echo 'test'", - Timeout: -100, // Negative timeout - } - - paramsJSON, err := json.Marshal(params) - require.NoError(t, err) - - call := ToolCall{ - Name: BashToolName, - Input: string(paramsJSON), - } - - response, err := tool.Run(context.Background(), call) - require.NoError(t, err) - assert.Equal(t, "test\n", response.Content) - }) -} - -func TestTruncateOutput(t *testing.T) { - t.Run("does not truncate short output", func(t *testing.T) { - output := "short output" - result := truncateOutput(output) - assert.Equal(t, output, result) - }) - - t.Run("truncates long output", func(t *testing.T) { - // Create a string longer than MaxOutputLength - longOutput := strings.Repeat("a\n", MaxOutputLength) - result := truncateOutput(longOutput) - - // Check that the result is shorter than the original - assert.Less(t, len(result), len(longOutput)) - - // Check that the truncation message is included - assert.Contains(t, result, "lines truncated") - - // Check that we have the beginning and end of the original string - assert.True(t, strings.HasPrefix(result, "a\n")) - assert.True(t, strings.HasSuffix(result, "a\n")) - }) -} - -func TestCountLines(t *testing.T) { - testCases := []struct { - name string - input string - expected int - }{ - { - name: "empty string", - input: "", - expected: 0, - }, - { - name: "single line", - input: "line1", - expected: 1, - }, - { - name: "multiple lines", - input: "line1\nline2\nline3", - expected: 3, - }, - { - name: "trailing newline", - input: "line1\nline2\n", - expected: 3, // Empty string after last newline counts as a line - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - result := countLines(tc.input) - assert.Equal(t, tc.expected, result) - }) - } -} |
