Skip to main content
Glama

MCP Server for Odoo

by ivnvxd
Mozilla Public License 2.0
88
  • Apple
  • Linux
test_resource_query_params.py•9.87 kB
"""Tests for resource URI query parameter handling. This test file specifically tests the fix for issue #4 where resource URIs with query parameters were failing with "Unknown operation" errors. """ import json from unittest.mock import Mock from urllib.parse import quote import pytest from mcp.server.fastmcp import FastMCP from mcp_server_odoo.access_control import AccessController from mcp_server_odoo.config import OdooConfig from mcp_server_odoo.odoo_connection import OdooConnection from mcp_server_odoo.resources import OdooResourceHandler @pytest.fixture def mock_config(): """Create a mock configuration.""" config = Mock(spec=OdooConfig) config.default_limit = 10 config.max_limit = 100 return config @pytest.fixture def mock_connection(): """Create a mock Odoo connection.""" conn = Mock(spec=OdooConnection) conn.is_authenticated = True return conn @pytest.fixture def mock_access_controller(): """Create a mock access controller.""" controller = Mock(spec=AccessController) controller.validate_model_access.return_value = None return controller @pytest.fixture def fastmcp_app(): """Create a real FastMCP app instance.""" return FastMCP(name="test-odoo-mcp") @pytest.fixture def resource_handler(fastmcp_app, mock_connection, mock_access_controller, mock_config): """Create a resource handler instance with real FastMCP app.""" return OdooResourceHandler(fastmcp_app, mock_connection, mock_access_controller, mock_config) class TestResourceQueryParameterHandling: """Test that resource URIs with various query parameter combinations work correctly.""" @pytest.mark.asyncio async def test_search_with_limit_only(self, resource_handler, mock_connection): """Test search resource with only limit parameter (issue #4 case).""" # Setup mocks mock_connection.search_count.return_value = 10 mock_connection.search.return_value = [1, 2] mock_connection.read.return_value = [ {"id": 1, "name": "Record 1"}, {"id": 2, "name": "Record 2"}, ] mock_connection.fields_get.return_value = {} # This should work now with the fix result = await resource_handler._handle_search("res.partner", None, None, 2, None, None) # Verify the search was called with correct limit mock_connection.search.assert_called_once_with( "res.partner", [], limit=2, offset=0, order=None ) # Verify result contains the records assert "Record 1" in result assert "Record 2" in result assert "Showing records 1-2 of 10" in result @pytest.mark.asyncio async def test_search_with_domain_only(self, resource_handler, mock_connection): """Test search resource with only domain parameter.""" # Setup domain domain = [["is_company", "=", True]] domain_encoded = quote(json.dumps(domain)) # Setup mocks mock_connection.search_count.return_value = 3 mock_connection.search.return_value = [1, 2, 3] mock_connection.read.return_value = [ {"id": 1, "name": "Company A"}, {"id": 2, "name": "Company B"}, {"id": 3, "name": "Company C"}, ] mock_connection.fields_get.return_value = {} # Test search with domain only result = await resource_handler._handle_search( "res.partner", domain_encoded, None, None, None, None ) # Verify domain was parsed and used mock_connection.search_count.assert_called_once_with("res.partner", domain) mock_connection.search.assert_called_once_with( "res.partner", domain, limit=10, offset=0, order=None ) assert "Company A" in result assert "is_company = True" in result @pytest.mark.asyncio async def test_search_with_fields_only(self, resource_handler, mock_connection): """Test search resource with only fields parameter.""" fields = "name,email" # Setup mocks mock_connection.search_count.return_value = 1 mock_connection.search.return_value = [1] mock_connection.read.return_value = [ {"id": 1, "name": "Test Partner", "email": "test@example.com"} ] mock_connection.fields_get.return_value = {} # Test search with fields only result = await resource_handler._handle_search( "res.partner", None, fields, None, None, None ) # Verify fields were parsed and used mock_connection.read.assert_called_once_with("res.partner", [1], ["name", "email"]) assert "Fields: name, email" in result assert "Test Partner" in result assert "test@example.com" in result @pytest.mark.asyncio async def test_search_with_pagination_only(self, resource_handler, mock_connection): """Test search resource with limit and offset parameters.""" # Setup mocks mock_connection.search_count.return_value = 100 mock_connection.search.return_value = [21, 22, 23, 24, 25] mock_connection.read.return_value = [ {"id": i, "name": f"Record {i}"} for i in range(21, 26) ] mock_connection.fields_get.return_value = {} # Test with pagination result = await resource_handler._handle_search("res.partner", None, None, 5, 20, None) # Verify pagination mock_connection.search.assert_called_once_with( "res.partner", [], limit=5, offset=20, order=None ) assert "Page 5 of 20" in result # offset 20, limit 5 = page 5 assert "Showing records 21-25 of 100" in result assert "Record 21" in result assert "Record 25" in result @pytest.mark.asyncio async def test_search_with_domain_and_limit(self, resource_handler, mock_connection): """Test search resource with domain and limit parameters.""" domain = [["active", "=", True]] domain_encoded = quote(json.dumps(domain)) # Setup mocks mock_connection.search_count.return_value = 50 mock_connection.search.return_value = [1, 2, 3] mock_connection.read.return_value = [ {"id": i, "name": f"Active Record {i}"} for i in range(1, 4) ] mock_connection.fields_get.return_value = {} # Test with domain and limit result = await resource_handler._handle_search( "res.partner", domain_encoded, None, 3, None, None ) # Verify both domain and limit were used mock_connection.search_count.assert_called_once_with("res.partner", domain) mock_connection.search.assert_called_once_with( "res.partner", domain, limit=3, offset=0, order=None ) assert "active = True" in result assert "Active Record 1" in result assert "Showing records 1-3 of 50" in result # Browse test removed - browse resource not supported due to FastMCP query parameter limitations # Use get_record multiple times or search_records tool instead @pytest.mark.asyncio async def test_count_without_domain(self, resource_handler, mock_connection): """Test count resource without domain parameter.""" # Setup mocks mock_connection.search_count.return_value = 150 # Test count without domain result = await resource_handler._handle_count("res.partner", None) # Verify count was called with empty domain mock_connection.search_count.assert_called_once_with("res.partner", []) assert "Total count: 150 record(s)" in result assert "Search criteria: All records" in result @pytest.mark.asyncio async def test_count_with_domain(self, resource_handler, mock_connection): """Test count resource with domain parameter.""" domain = [["customer_rank", ">", 0]] domain_encoded = quote(json.dumps(domain)) # Setup mocks mock_connection.search_count.return_value = 75 # Test count with domain result = await resource_handler._handle_count("res.partner", domain_encoded) # Verify count was called with parsed domain mock_connection.search_count.assert_called_once_with("res.partner", domain) assert "Total count: 75 record(s)" in result assert "customer_rank > 0" in result class TestResourceRegistration: """Test that resources are properly registered with FastMCP.""" def test_search_resources_registered(self, resource_handler, fastmcp_app): """Verify that multiple search resource patterns are registered.""" # Get registered handlers from the app # Note: FastMCP doesn't expose registered resources directly, # but we can verify they work by checking the handler exists # Due to FastMCP limitations, only these patterns are registered: # - odoo://{model}/search (no parameters) # - odoo://{model}/record/{record_id} # - odoo://{model}/count (no parameters) # - odoo://{model}/fields # Since we can't directly inspect FastMCP's internal registry, # we verify that the handler methods exist assert hasattr(resource_handler, "_handle_search") # Browse resource removed due to FastMCP limitations assert hasattr(resource_handler, "_handle_count") assert hasattr(resource_handler, "_handle_fields") assert hasattr(resource_handler, "_handle_record_retrieval") def test_count_resources_registered(self, resource_handler, fastmcp_app): """Verify that count resource patterns are registered.""" # Count only supports basic pattern due to FastMCP limitations: # - odoo://{model}/count (no parameters) assert hasattr(resource_handler, "_handle_count")

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/ivnvxd/mcp-server-odoo'

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