Skip to main content
Glama

R Econometrics MCP Server

MIT License
187
  • Linux
  • Apple
test_file_output_scenarios.py14.2 kB
""" File output scenario tests for the RMCP approval system. Tests real file creation and validation with the approval workflow. """ import asyncio import os import tempfile from pathlib import Path import pytest from rmcp.core.context import Context, LifespanState from rmcp.security.vfs import VFS from rmcp.tools.flexible_r import approve_operation, execute_r_analysis @pytest.fixture async def mock_context_with_vfs(): """Create a test context with VFS for file operations and approval state.""" lifespan = LifespanState() # Create a temporary directory for testing temp_dir = tempfile.mkdtemp() allowed_paths = [Path(temp_dir)] # Initialize VFS with write permissions vfs = VFS(allowed_roots=allowed_paths, read_only=False) lifespan.vfs = vfs context = Context.create("test", "test", lifespan) context.temp_dir = temp_dir # Initialize approval state for the context if not already present if not hasattr(context, "_approved_operations"): context._approved_operations = {} # Copy test iris data to temp directory to eliminate network dependency import shutil test_data_dir = Path(__file__).parent.parent / "fixtures" iris_source = test_data_dir / "iris_test_data.csv" iris_dest = Path(temp_dir) / "iris_test_data.csv" shutil.copy(iris_source, iris_dest) context.iris_data_path = str(iris_dest) yield context # Cleanup shutil.rmtree(temp_dir, ignore_errors=True) class TestFileOutputScenarios: """Test real file output scenarios with approval workflow.""" @pytest.mark.asyncio async def test_complete_iris_analysis_with_file_saving(self, mock_context_with_vfs): """Test complete iris analysis workflow with plot saving.""" context = mock_context_with_vfs # Step 1: Approve file operations (both ggsave and write.csv) approval_result = await approve_operation( context, { "operation_type": "file_operations", "specific_operation": "ggsave", "action": "approve", "scope": "session", "directory": context.temp_dir, }, ) assert approval_result["success"] is True assert approval_result["action"] == "approved" # Also approve write.csv csv_approval = await approve_operation( context, { "operation_type": "file_operations", "specific_operation": "write.csv", "action": "approve", "scope": "session", "directory": context.temp_dir, }, ) assert csv_approval["success"] is True # Step 2: Approve package installation (might be needed for readr) package_approval = await approve_operation( context, { "operation_type": "package_installation", "specific_operation": "install.packages", "action": "approve", "scope": "session", }, ) assert package_approval["success"] is True # Step 3: Execute iris analysis with file saving (using local test data) iris_analysis_code = f""" # Load iris data from local file (no network dependency) iris_data <- read.csv("{context.iris_data_path}") # Create analysis plot using base R (more reliable in CI) plot_file <- "{context.temp_dir}/iris_analysis.png" png(plot_file, width = 800, height = 600, res = 100) # Create scatter plot with base R plot(iris_data$petal_length, iris_data$petal_width, col = as.factor(iris_data$species), pch = 19, main = "Iris Species Classification", xlab = "Petal Length (cm)", ylab = "Petal Width (cm)") legend("topright", legend = unique(iris_data$species), col = 1:length(unique(iris_data$species)), pch = 19) dev.off() # Also save data to CSV data_file <- "{context.temp_dir}/iris_data.csv" write.csv(iris_data, data_file, row.names = FALSE) result <- list( analysis_complete = TRUE, plot_file = plot_file, data_file = data_file, plot_exists = file.exists(plot_file), data_exists = file.exists(data_file), rows_analyzed = nrow(iris_data), species_count = length(unique(iris_data$species)), debug_info = list( file_size_plot = if(file.exists(plot_file)) file.size(plot_file) else 0, file_size_data = if(file.exists(data_file)) file.size(data_file) else 0, working_dir = getwd(), temp_dir_exists = dir.exists("{context.temp_dir}") ) ) """ analysis_result = await execute_r_analysis( context, { "r_code": iris_analysis_code, "description": "Complete iris analysis with file saving (base R)", "packages": [], # Using base R only, no external packages needed "return_image": False, }, ) # Verify analysis succeeded with better error reporting if not analysis_result["success"]: error_info = analysis_result.get("error", "Unknown error") pytest.fail(f"R analysis failed: {error_info}") result_data = analysis_result["result"] assert result_data["analysis_complete"] is True assert result_data["plot_exists"] is True assert result_data["data_exists"] is True assert ( result_data["rows_analyzed"] == 30 ) # Our test data has 30 data rows (file has 30 lines total, no final newline) assert result_data["species_count"] == 3 # Verify files actually exist on filesystem plot_path = Path(context.temp_dir) / "iris_analysis.png" data_path = Path(context.temp_dir) / "iris_data.csv" assert plot_path.exists() assert data_path.exists() assert plot_path.stat().st_size > 0 # PNG file has content assert data_path.stat().st_size > 0 # CSV file has content @pytest.mark.asyncio async def test_file_approval_workflow_rejection(self, mock_context_with_vfs): """Test that file operations are blocked without approval.""" context = mock_context_with_vfs # Try to save file without approval save_code = f""" library(ggplot2) p <- ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point() ggsave("{context.temp_dir}/test.png", plot = p) result <- list(saved = TRUE) """ analysis_result = await execute_r_analysis( context, { "r_code": save_code, "description": "Test file saving without approval", "packages": ["ggplot2"], }, ) # Should fail due to lack of approval assert analysis_result["success"] is False assert "OPERATION_APPROVAL_NEEDED" in analysis_result["error"] assert "file_operations" in analysis_result["error"] @pytest.mark.asyncio async def test_package_installation_workflow(self, mock_context_with_vfs): """Test package installation with approval.""" context = mock_context_with_vfs # First try without approval - should fail install_code = """ install.packages("moments") result <- list(installed = TRUE) """ result_without_approval = await execute_r_analysis( context, { "r_code": install_code, "description": "Test package installation without approval", }, ) assert result_without_approval["success"] is False assert "OPERATION_APPROVAL_NEEDED" in result_without_approval["error"] assert "package_installation" in result_without_approval["error"] # Now approve package installation approval_result = await approve_operation( context, { "operation_type": "package_installation", "specific_operation": "install.packages", "action": "approve", "scope": "session", }, ) assert approval_result["success"] is True # Try again with approval - should work result_with_approval = await execute_r_analysis( context, { "r_code": install_code, "description": "Test package installation with approval", }, ) # Note: This might still fail in test environment if package can't be installed, # but it should pass the validation step if result_with_approval["success"]: assert result_with_approval["result"]["installed"] is True else: # If it fails, it should be due to R execution, not approval assert "OPERATION_APPROVAL_NEEDED" not in result_with_approval["error"] @pytest.mark.asyncio async def test_multiple_file_formats(self, mock_context_with_vfs): """Test saving multiple file formats with approval.""" context = mock_context_with_vfs # Approve file operations await approve_operation( context, { "operation_type": "file_operations", "specific_operation": "ggsave", "action": "approve", "directory": context.temp_dir, }, ) await approve_operation( context, { "operation_type": "file_operations", "specific_operation": "write.csv", "action": "approve", "directory": context.temp_dir, }, ) await approve_operation( context, { "operation_type": "file_operations", "specific_operation": "writeLines", "action": "approve", "directory": context.temp_dir, }, ) # Create and save multiple file formats multi_format_code = f""" # Create some data data <- data.frame( x = 1:10, y = (1:10)^2, category = rep(c("A", "B"), 5) ) # Save as CSV write.csv(data, "{context.temp_dir}/data.csv", row.names = FALSE) # Create and save plot as PNG library(ggplot2) p <- ggplot(data, aes(x = x, y = y, color = category)) + geom_point(size = 3) + theme_minimal() ggsave("{context.temp_dir}/plot.png", plot = p, width = 8, height = 6) # Save text summary summary_text <- c( "Analysis Summary", paste("Rows:", nrow(data)), paste("Mean Y:", mean(data$y)), paste("Categories:", paste(unique(data$category), collapse = ", ")) ) writeLines(summary_text, "{context.temp_dir}/summary.txt") result <- list( csv_exists = file.exists("{context.temp_dir}/data.csv"), png_exists = file.exists("{context.temp_dir}/plot.png"), txt_exists = file.exists("{context.temp_dir}/summary.txt"), files_created = 3 ) """ result = await execute_r_analysis( context, { "r_code": multi_format_code, "description": "Test multiple file format saving", "packages": ["ggplot2"], }, ) assert result["success"] is True assert result["result"]["csv_exists"] is True assert result["result"]["png_exists"] is True assert result["result"]["txt_exists"] is True assert result["result"]["files_created"] == 3 # Verify files exist on filesystem csv_path = Path(context.temp_dir) / "data.csv" png_path = Path(context.temp_dir) / "plot.png" txt_path = Path(context.temp_dir) / "summary.txt" assert csv_path.exists() and csv_path.stat().st_size > 0 assert png_path.exists() and png_path.stat().st_size > 0 assert txt_path.exists() and txt_path.stat().st_size > 0 @pytest.mark.asyncio async def test_vfs_security_boundaries(self, mock_context_with_vfs): """Test that VFS security boundaries are maintained.""" context = mock_context_with_vfs # Approve file operations await approve_operation( context, { "operation_type": "file_operations", "specific_operation": "write.csv", "action": "approve", "directory": context.temp_dir, }, ) # Try to write outside allowed directory - should fail outside_dir_code = """ data <- data.frame(x = 1:5, y = 1:5) write.csv(data, "/tmp/unauthorized.csv", row.names = FALSE) result <- list(written = TRUE) """ result = await execute_r_analysis( context, { "r_code": outside_dir_code, "description": "Test writing outside allowed directory", }, ) # The test should either: # 1. Fail during R execution due to approval system blocking, OR # 2. Succeed in R but VFS should prevent actual file creation unauthorized_file = Path("/tmp/unauthorized.csv") # Clean up any existing file first if unauthorized_file.exists(): unauthorized_file.unlink() # After R execution, the unauthorized file should not exist assert ( not unauthorized_file.exists() ), "VFS security was bypassed - unauthorized file was created" # The operation should have been blocked by approval system if not result["success"]: assert "OPERATION_APPROVAL_NEEDED" in result.get("error", "")

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/finite-sample/rmcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server