package tools import ( "context" "os" "path/filepath" "strings" "testing" ) // TestFilesystemTool_ReadFile_Success verifies successful file reading func TestFilesystemTool_ReadFile_Success(t *testing.T) { tmpDir := t.TempDir() testFile := filepath.Join(tmpDir, "test.txt") os.WriteFile(testFile, []byte("test content"), 0644) tool := &ReadFileTool{} ctx := context.Background() args := map[string]interface{}{ "path": testFile, } result := tool.Execute(ctx, args) // Success should not be an error if result.IsError { t.Errorf("Expected success, got IsError=true: %s", result.ForLLM) } // ForLLM should contain file content if !strings.Contains(result.ForLLM, "test content") { t.Errorf("Expected ForLLM to contain 'test content', got: %s", result.ForLLM) } // ReadFile returns NewToolResult which only sets ForLLM, not ForUser // This is the expected behavior - file content goes to LLM, not directly to user if result.ForUser != "" { t.Errorf("Expected ForUser to be empty for NewToolResult, got: %s", result.ForUser) } } // TestFilesystemTool_ReadFile_NotFound verifies error handling for missing file func TestFilesystemTool_ReadFile_NotFound(t *testing.T) { tool := &ReadFileTool{} ctx := context.Background() args := map[string]interface{}{ "path": "/nonexistent_file_12345.txt", } result := tool.Execute(ctx, args) // Failure should be marked as error if !result.IsError { t.Errorf("Expected error for missing file, got IsError=false") } // Should contain error message if !strings.Contains(result.ForLLM, "failed to read") && !strings.Contains(result.ForUser, "failed to read") { t.Errorf("Expected error message, got ForLLM: %s, ForUser: %s", result.ForLLM, result.ForUser) } } // TestFilesystemTool_ReadFile_MissingPath verifies error handling for missing path func TestFilesystemTool_ReadFile_MissingPath(t *testing.T) { tool := &ReadFileTool{} ctx := context.Background() args := map[string]interface{}{} result := tool.Execute(ctx, args) // Should return error result if !result.IsError { t.Errorf("Expected error when path is missing") } // Should mention required parameter if !strings.Contains(result.ForLLM, "path is required") && !strings.Contains(result.ForUser, "path is required") { t.Errorf("Expected 'path is required' message, got ForLLM: %s", result.ForLLM) } } // TestFilesystemTool_WriteFile_Success verifies successful file writing func TestFilesystemTool_WriteFile_Success(t *testing.T) { tmpDir := t.TempDir() testFile := filepath.Join(tmpDir, "newfile.txt") tool := &WriteFileTool{} ctx := context.Background() args := map[string]interface{}{ "path": testFile, "content": "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) } // WriteFile returns SilentResult if !result.Silent { t.Errorf("Expected Silent=true for WriteFile, got false") } // ForUser should be empty (silent result) if result.ForUser != "" { t.Errorf("Expected ForUser to be empty for SilentResult, got: %s", result.ForUser) } // Verify file was actually written content, err := os.ReadFile(testFile) if err != nil { t.Fatalf("Failed to read written file: %v", err) } if string(content) != "hello world" { t.Errorf("Expected file content 'hello world', got: %s", string(content)) } } // TestFilesystemTool_WriteFile_CreateDir verifies directory creation func TestFilesystemTool_WriteFile_CreateDir(t *testing.T) { tmpDir := t.TempDir() testFile := filepath.Join(tmpDir, "subdir", "newfile.txt") tool := &WriteFileTool{} ctx := context.Background() args := map[string]interface{}{ "path": testFile, "content": "test", } result := tool.Execute(ctx, args) // Success should not be an error if result.IsError { t.Errorf("Expected success with directory creation, got IsError=true: %s", result.ForLLM) } // Verify directory was created and file written content, err := os.ReadFile(testFile) if err != nil { t.Fatalf("Failed to read written file: %v", err) } if string(content) != "test" { t.Errorf("Expected file content 'test', got: %s", string(content)) } } // TestFilesystemTool_WriteFile_MissingPath verifies error handling for missing path func TestFilesystemTool_WriteFile_MissingPath(t *testing.T) { tool := &WriteFileTool{} ctx := context.Background() args := map[string]interface{}{ "content": "test", } result := tool.Execute(ctx, args) // Should return error result if !result.IsError { t.Errorf("Expected error when path is missing") } } // TestFilesystemTool_WriteFile_MissingContent verifies error handling for missing content func TestFilesystemTool_WriteFile_MissingContent(t *testing.T) { tool := &WriteFileTool{} ctx := context.Background() args := map[string]interface{}{ "path": "/tmp/test.txt", } result := tool.Execute(ctx, args) // Should return error result if !result.IsError { t.Errorf("Expected error when content is missing") } // Should mention required parameter if !strings.Contains(result.ForLLM, "content is required") && !strings.Contains(result.ForUser, "content is required") { t.Errorf("Expected 'content is required' message, got ForLLM: %s", result.ForLLM) } } // TestFilesystemTool_ListDir_Success verifies successful directory listing func TestFilesystemTool_ListDir_Success(t *testing.T) { tmpDir := t.TempDir() os.WriteFile(filepath.Join(tmpDir, "file1.txt"), []byte("content"), 0644) os.WriteFile(filepath.Join(tmpDir, "file2.txt"), []byte("content"), 0644) os.Mkdir(filepath.Join(tmpDir, "subdir"), 0755) tool := &ListDirTool{} ctx := context.Background() args := map[string]interface{}{ "path": tmpDir, } result := tool.Execute(ctx, args) // Success should not be an error if result.IsError { t.Errorf("Expected success, got IsError=true: %s", result.ForLLM) } // Should list files and directories if !strings.Contains(result.ForLLM, "file1.txt") || !strings.Contains(result.ForLLM, "file2.txt") { t.Errorf("Expected files in listing, got: %s", result.ForLLM) } if !strings.Contains(result.ForLLM, "subdir") { t.Errorf("Expected subdir in listing, got: %s", result.ForLLM) } } // TestFilesystemTool_ListDir_NotFound verifies error handling for non-existent directory func TestFilesystemTool_ListDir_NotFound(t *testing.T) { tool := &ListDirTool{} ctx := context.Background() args := map[string]interface{}{ "path": "/nonexistent_directory_12345", } result := tool.Execute(ctx, args) // Failure should be marked as error if !result.IsError { t.Errorf("Expected error for non-existent directory, got IsError=false") } // Should contain error message if !strings.Contains(result.ForLLM, "failed to read") && !strings.Contains(result.ForUser, "failed to read") { t.Errorf("Expected error message, got ForLLM: %s, ForUser: %s", result.ForLLM, result.ForUser) } } // TestFilesystemTool_ListDir_DefaultPath verifies default to current directory func TestFilesystemTool_ListDir_DefaultPath(t *testing.T) { tool := &ListDirTool{} ctx := context.Background() args := map[string]interface{}{} result := tool.Execute(ctx, args) // Should use "." as default path if result.IsError { t.Errorf("Expected success with default path '.', got IsError=true: %s", result.ForLLM) } } // Block paths that look inside workspace but point outside via symlink. func TestFilesystemTool_ReadFile_RejectsSymlinkEscape(t *testing.T) { root := t.TempDir() workspace := filepath.Join(root, "workspace") if err := os.MkdirAll(workspace, 0755); err != nil { t.Fatalf("failed to create workspace: %v", err) } secret := filepath.Join(root, "secret.txt") if err := os.WriteFile(secret, []byte("top secret"), 0644); err != nil { t.Fatalf("failed to write secret file: %v", err) } link := filepath.Join(workspace, "leak.txt") if err := os.Symlink(secret, link); err != nil { t.Skipf("symlink not supported in this environment: %v", err) } tool := NewReadFileTool(workspace, true) result := tool.Execute(context.Background(), map[string]interface{}{ "path": link, }) if !result.IsError { t.Fatalf("expected symlink escape to be blocked") } if !strings.Contains(result.ForLLM, "symlink resolves outside workspace") { t.Fatalf("expected symlink escape error, got: %s", result.ForLLM) } }