Skip to main content
Glama
seletz
by seletz
test_mcp_tools.py10.9 kB
""" Tests for MCP tool integration and global state management. """ import os from unittest.mock import Mock, patch import pytest import mcp_odoo_shell.server from mcp_odoo_shell import ( execute_odoo_code, get_shell_manager, list_odoo_models, odoo_model_info, reset_odoo_shell, ) class TestGetShellManager: """Test the global shell manager singleton.""" def test_creates_manager_from_env_vars(self, reset_global_state): """Test that get_shell_manager creates manager from environment variables.""" env_vars = { 'ODOO_BIN_PATH': '/test/odoo-bin', 'ODOO_ADDONS_PATH': '/test/addons', 'ODOO_DATABASE': 'test_db', 'ODOO_CONFIG_FILE': '/test/config.conf' } with patch.dict(os.environ, env_vars): manager = get_shell_manager() assert manager.odoo_bin_path == '/test/odoo-bin' assert manager.addons_path == '/test/addons' assert manager.db_name == 'test_db' assert manager.config_file == '/test/config.conf' def test_uses_defaults_when_env_vars_missing(self, reset_global_state): """Test that get_shell_manager uses defaults when env vars are missing.""" # Remove env vars by patching os.getenv directly with patch('os.getenv', side_effect=lambda key, default=None: { 'ODOO_BIN_PATH': '/usr/bin/odoo-bin', 'ODOO_ADDONS_PATH': '/odoo/addons', 'ODOO_DATABASE': 'odoo', 'ODOO_CONFIG_FILE': None }.get(key, default)): manager = get_shell_manager() assert manager.odoo_bin_path == '/usr/bin/odoo-bin' assert manager.addons_path == '/odoo/addons' assert manager.db_name == 'odoo' assert manager.config_file is None def test_returns_same_instance_on_subsequent_calls(self, reset_global_state): """Test that get_shell_manager returns the same instance (singleton).""" manager1 = get_shell_manager() manager2 = get_shell_manager() assert manager1 is manager2 def test_respects_existing_global_manager(self, reset_global_state): """Test that existing global manager is reused.""" # Set a custom manager custom_manager = Mock() mcp_odoo_shell.server.shell_manager = custom_manager result = get_shell_manager() assert result is custom_manager class TestExecuteOdooCode: """Test the execute_odoo_code MCP tool.""" def test_executes_code_successfully(self, reset_global_state): """Test successful code execution.""" mock_manager = Mock() mock_manager.execute_code.return_value = "Result: 42" with patch('mcp_odoo_shell.tools.get_shell_manager', return_value=mock_manager): result = execute_odoo_code("2 + 2") mock_manager.execute_code.assert_called_once_with("2 + 2") assert result == "Result: 42" def test_handles_execution_exception(self, reset_global_state): """Test handling of exceptions during code execution.""" mock_manager = Mock() mock_manager.execute_code.side_effect = RuntimeError("Shell crashed") with patch('mcp_odoo_shell.tools.get_shell_manager', return_value=mock_manager): result = execute_odoo_code("bad code") assert result == "Error executing Odoo code: Shell crashed" def test_handles_timeout_exception(self, reset_global_state): """Test handling of timeout exceptions.""" mock_manager = Mock() mock_manager.execute_code.side_effect = TimeoutError("Command execution timed out after 30 seconds") with patch('mcp_odoo_shell.tools.get_shell_manager', return_value=mock_manager): result = execute_odoo_code("slow_operation()") assert result == "Error executing Odoo code: Command execution timed out after 30 seconds" class TestResetOdooShell: """Test the reset_odoo_shell MCP tool.""" def test_resets_shell_successfully(self, reset_global_state): """Test successful shell reset.""" mock_manager = Mock() mcp_odoo_shell.server.shell_manager = mock_manager result = reset_odoo_shell() mock_manager.stop.assert_called_once() assert mcp_odoo_shell.server.shell_manager is None assert result == "Odoo shell session reset successfully" def test_handles_no_existing_shell(self, reset_global_state): """Test reset when no shell exists.""" # No shell manager set assert mcp_odoo_shell.server.shell_manager is None result = reset_odoo_shell() assert result == "Odoo shell session reset successfully" assert mcp_odoo_shell.server.shell_manager is None def test_handles_stop_exception(self, reset_global_state): """Test handling of exceptions during shell stop.""" mock_manager = Mock() mock_manager.stop.side_effect = RuntimeError("Failed to stop") mcp_odoo_shell.server.shell_manager = mock_manager result = reset_odoo_shell() assert result == "Error resetting shell: Failed to stop" # Looking at the actual code: shell_manager = None is in try block, # exception occurs during stop(), so shell_manager is NOT cleared assert mcp_odoo_shell.server.shell_manager is mock_manager class TestListOdooModels: """Test the list_odoo_models MCP tool.""" def test_lists_models_without_pattern(self, reset_global_state): """Test listing all models without pattern.""" expected_code = """ models = env.registry.keys() if '': models = [m for m in models if '' in m] for model in sorted(models)[:50]: # Limit to first 50 print(model) """ with patch('mcp_odoo_shell.tools.execute_odoo_code') as mock_execute: mock_execute.return_value = "res.partner\nres.users\nsale.order" result = list_odoo_models() mock_execute.assert_called_once_with(expected_code) assert result == "res.partner\nres.users\nsale.order" def test_lists_models_with_pattern(self, reset_global_state): """Test listing models with pattern filter.""" expected_code = """ models = env.registry.keys() if 'sale': models = [m for m in models if 'sale' in m] for model in sorted(models)[:50]: # Limit to first 50 print(model) """ with patch('mcp_odoo_shell.tools.execute_odoo_code') as mock_execute: mock_execute.return_value = "sale.order\nsale.order.line" result = list_odoo_models("sale") mock_execute.assert_called_once_with(expected_code) assert result == "sale.order\nsale.order.line" class TestOdooModelInfo: """Test the odoo_model_info MCP tool.""" def test_gets_model_info_successfully(self, reset_global_state): """Test successful model info retrieval.""" expected_code = """ try: model = env['res.partner'] print(f"Model: res.partner") print(f"Description: {model._description}") print(f"Table: {model._table}") print("\\nFields:") for field_name, field in model._fields.items(): print(f" {field_name}: {type(field).__name__}") except KeyError: print(f"Model 'res.partner' not found") except Exception as e: print(f"Error: {e}") """ expected_output = """Model: res.partner Description: Partner Table: res_partner Fields: name: Char email: Char""" with patch('mcp_odoo_shell.tools.execute_odoo_code') as mock_execute: mock_execute.return_value = expected_output result = odoo_model_info("res.partner") mock_execute.assert_called_once_with(expected_code) assert result == expected_output def test_handles_model_not_found(self, reset_global_state): """Test handling when model is not found.""" expected_code = """ try: model = env['nonexistent.model'] print(f"Model: nonexistent.model") print(f"Description: {model._description}") print(f"Table: {model._table}") print("\\nFields:") for field_name, field in model._fields.items(): print(f" {field_name}: {type(field).__name__}") except KeyError: print(f"Model 'nonexistent.model' not found") except Exception as e: print(f"Error: {e}") """ with patch('mcp_odoo_shell.tools.execute_odoo_code') as mock_execute: mock_execute.return_value = "Model 'nonexistent.model' not found" result = odoo_model_info("nonexistent.model") mock_execute.assert_called_once_with(expected_code) assert result == "Model 'nonexistent.model' not found" class TestGlobalStateManagement: """Test global state management across tools.""" def test_shell_manager_persists_across_tool_calls(self, reset_global_state): """Test that shell manager persists across multiple tool calls.""" # First tool call should create manager with patch('mcp_odoo_shell.server.get_shell_manager') as mock_get: mock_manager = Mock() mock_get.return_value = mock_manager execute_odoo_code("print('first')") first_call_count = mock_get.call_count execute_odoo_code("print('second')") second_call_count = mock_get.call_count # Should reuse the same manager assert second_call_count == first_call_count * 2 def test_reset_clears_state_for_subsequent_calls(self, reset_global_state): """Test that reset clears state for subsequent tool calls.""" # Create initial manager mock_manager = Mock() mcp_odoo_shell.server.shell_manager = mock_manager # Reset should clear it reset_odoo_shell() assert mcp_odoo_shell.server.shell_manager is None # Next call should create new manager with patch('mcp_odoo_shell.tools.get_shell_manager') as mock_get: new_manager = Mock() mock_get.return_value = new_manager execute_odoo_code("test") mock_get.assert_called_once() # The shell_manager is still None because execute_odoo_code calls get_shell_manager # but doesn't store the result back to the global variable # get_shell_manager() manages the global state internally

Latest Blog Posts

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/seletz/mcp-odoo-shell'

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