package tools import ( "context" "os" "path/filepath" "strings" "testing" "time" ) // TestShellTool_Success verifies successful command execution func TestShellTool_Success(t *testing.T) { tool := NewExecTool("") 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("") 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("") 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("") 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("") 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("") 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("") 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("") 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) 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) } }