Spaces:
Sleeping
Sleeping
| package tools | |
| import ( | |
| "context" | |
| "os" | |
| "path/filepath" | |
| "strings" | |
| "testing" | |
| "time" | |
| "github.com/sipeed/picoclaw/pkg/config" | |
| ) | |
| // TestShellTool_Success verifies successful command execution | |
| func TestShellTool_Success(t *testing.T) { | |
| tool := NewExecTool("", false, config.ResourceLimits{}) | |
| ctx := context.Background() | |
| args := map[string]interface{}{ | |
| "command": "echo 'hello world'", | |
| } | |
| result := tool.Execute(ctx, args) | |
| // Success should not be an error | |
| if result.IsError { | |
| t.Errorf("Expected success, got IsError=true: %s", result.ForLLM) | |
| } | |
| // ForUser should contain command output | |
| if !strings.Contains(result.ForUser, "hello world") { | |
| t.Errorf("Expected ForUser to contain 'hello world', got: %s", result.ForUser) | |
| } | |
| // ForLLM should contain full output | |
| if !strings.Contains(result.ForLLM, "hello world") { | |
| t.Errorf("Expected ForLLM to contain 'hello world', got: %s", result.ForLLM) | |
| } | |
| } | |
| // TestShellTool_Failure verifies failed command execution | |
| func TestShellTool_Failure(t *testing.T) { | |
| tool := NewExecTool("", false, config.ResourceLimits{}) | |
| ctx := context.Background() | |
| args := map[string]interface{}{ | |
| "command": "ls /nonexistent_directory_12345", | |
| } | |
| result := tool.Execute(ctx, args) | |
| // Failure should be marked as error | |
| if !result.IsError { | |
| t.Errorf("Expected error for failed command, got IsError=false") | |
| } | |
| // ForUser should contain error information | |
| if result.ForUser == "" { | |
| t.Errorf("Expected ForUser to contain error info, got empty string") | |
| } | |
| // ForLLM should contain exit code or error | |
| if !strings.Contains(result.ForLLM, "Exit code") && result.ForUser == "" { | |
| t.Errorf("Expected ForLLM to contain exit code or error, got: %s", result.ForLLM) | |
| } | |
| } | |
| // TestShellTool_Timeout verifies command timeout handling | |
| func TestShellTool_Timeout(t *testing.T) { | |
| tool := NewExecTool("", false, config.ResourceLimits{}) | |
| tool.SetTimeout(100 * time.Millisecond) | |
| ctx := context.Background() | |
| args := map[string]interface{}{ | |
| "command": "sleep 10", | |
| } | |
| result := tool.Execute(ctx, args) | |
| // Timeout should be marked as error | |
| if !result.IsError { | |
| t.Errorf("Expected error for timeout, got IsError=false") | |
| } | |
| // Should mention timeout | |
| if !strings.Contains(result.ForLLM, "timed out") && !strings.Contains(result.ForUser, "timed out") { | |
| t.Errorf("Expected timeout message, got ForLLM: %s, ForUser: %s", result.ForLLM, result.ForUser) | |
| } | |
| } | |
| // TestShellTool_WorkingDir verifies custom working directory | |
| func TestShellTool_WorkingDir(t *testing.T) { | |
| // Create temp directory | |
| tmpDir := t.TempDir() | |
| testFile := filepath.Join(tmpDir, "test.txt") | |
| os.WriteFile(testFile, []byte("test content"), 0644) | |
| tool := NewExecTool("", false, config.ResourceLimits{}) | |
| ctx := context.Background() | |
| args := map[string]interface{}{ | |
| "command": "cat test.txt", | |
| "working_dir": tmpDir, | |
| } | |
| result := tool.Execute(ctx, args) | |
| if result.IsError { | |
| t.Errorf("Expected success in custom working dir, got error: %s", result.ForLLM) | |
| } | |
| if !strings.Contains(result.ForUser, "test content") { | |
| t.Errorf("Expected output from custom dir, got: %s", result.ForUser) | |
| } | |
| } | |
| // TestShellTool_DangerousCommand verifies safety guard blocks dangerous commands | |
| func TestShellTool_DangerousCommand(t *testing.T) { | |
| tool := NewExecTool("", false, config.ResourceLimits{}) | |
| ctx := context.Background() | |
| args := map[string]interface{}{ | |
| "command": "rm -rf /", | |
| } | |
| result := tool.Execute(ctx, args) | |
| // Dangerous command should be blocked | |
| if !result.IsError { | |
| t.Errorf("Expected dangerous command to be blocked (IsError=true)") | |
| } | |
| if !strings.Contains(result.ForLLM, "blocked") && !strings.Contains(result.ForUser, "blocked") { | |
| t.Errorf("Expected 'blocked' message, got ForLLM: %s, ForUser: %s", result.ForLLM, result.ForUser) | |
| } | |
| } | |
| // TestShellTool_MissingCommand verifies error handling for missing command | |
| func TestShellTool_MissingCommand(t *testing.T) { | |
| tool := NewExecTool("", false, config.ResourceLimits{}) | |
| ctx := context.Background() | |
| args := map[string]interface{}{} | |
| result := tool.Execute(ctx, args) | |
| // Should return error result | |
| if !result.IsError { | |
| t.Errorf("Expected error when command is missing") | |
| } | |
| } | |
| // TestShellTool_StderrCapture verifies stderr is captured and included | |
| func TestShellTool_StderrCapture(t *testing.T) { | |
| tool := NewExecTool("", false, config.ResourceLimits{}) | |
| ctx := context.Background() | |
| args := map[string]interface{}{ | |
| "command": "sh -c 'echo stdout; echo stderr >&2'", | |
| } | |
| result := tool.Execute(ctx, args) | |
| // Both stdout and stderr should be in output | |
| if !strings.Contains(result.ForLLM, "stdout") { | |
| t.Errorf("Expected stdout in output, got: %s", result.ForLLM) | |
| } | |
| if !strings.Contains(result.ForLLM, "stderr") { | |
| t.Errorf("Expected stderr in output, got: %s", result.ForLLM) | |
| } | |
| } | |
| // TestShellTool_OutputTruncation verifies long output is truncated | |
| func TestShellTool_OutputTruncation(t *testing.T) { | |
| tool := NewExecTool("", false, config.ResourceLimits{}) | |
| ctx := context.Background() | |
| // Generate long output (>10000 chars) | |
| args := map[string]interface{}{ | |
| "command": "python3 -c \"print('x' * 20000)\" || echo " + strings.Repeat("x", 20000), | |
| } | |
| result := tool.Execute(ctx, args) | |
| // Should have truncation message or be truncated | |
| if len(result.ForLLM) > 15000 { | |
| t.Errorf("Expected output to be truncated, got length: %d", len(result.ForLLM)) | |
| } | |
| } | |
| // TestShellTool_RestrictToWorkspace verifies workspace restriction | |
| func TestShellTool_RestrictToWorkspace(t *testing.T) { | |
| tmpDir := t.TempDir() | |
| tool := NewExecTool(tmpDir, false, config.ResourceLimits{}) | |
| tool.SetRestrictToWorkspace(true) | |
| ctx := context.Background() | |
| args := map[string]interface{}{ | |
| "command": "cat ../../etc/passwd", | |
| } | |
| result := tool.Execute(ctx, args) | |
| // Path traversal should be blocked | |
| if !result.IsError { | |
| t.Errorf("Expected path traversal to be blocked with restrictToWorkspace=true") | |
| } | |
| if !strings.Contains(result.ForLLM, "blocked") && !strings.Contains(result.ForUser, "blocked") { | |
| t.Errorf("Expected 'blocked' message for path traversal, got ForLLM: %s, ForUser: %s", result.ForLLM, result.ForUser) | |
| } | |
| } | |