From 6154b8345558efe892bfaca5a123f1e86b357b2e Mon Sep 17 00:00:00 2001 From: aserrador Date: Mon, 9 Mar 2026 20:36:55 -0500 Subject: [PATCH] fix: validate date format in create_schedule with clear error message --- src/tryton_mcp/server.py | 64 ++++++++++++++++++++++++++++++++++++---- tests/test_customer.py | 48 ++++++++++++++++++++++++++++++ tests/test_server.py | 14 +++++++++ 3 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 tests/test_customer.py diff --git a/src/tryton_mcp/server.py b/src/tryton_mcp/server.py index 1eaf710..d1bd0d3 100644 --- a/src/tryton_mcp/server.py +++ b/src/tryton_mcp/server.py @@ -4,10 +4,17 @@ MCP Server for Naliia Module from contextlib import asynccontextmanager from fastmcp import FastMCP -from typing import List, AsyncIterator, Any, Dict -import datetime +from typing import List, Dict, AsyncIterator, Any +from datetime import datetime, timezone from tryton_mcp.config import settings +import logging + +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) + +logger = logging.getLogger(__name__) @asynccontextmanager @@ -29,18 +36,63 @@ def tryton_call(name: str, args: list) -> Dict[str, Any]: return {"success": False, "error": str(e)} +@mcp.tool() +def find_customer_by_identifier(identifier: str) -> List[Dict]: + result = tryton_call( + "model.party.identifier.search_read", + [ + [["code", "=", identifier]], + 0, + None, + None, + ["party", "party.name"], + {"company": 1}, + ], + ) + + if not result["success"]: + raise Exception(f"Customer with identifier {identifier} not found.") + + return result["data"] + + +@mcp.tool() +def create_customer( + name: str, identifiers: List[tuple[str, List[Dict[str, Any]]]] +) -> List[int]: + + result = tryton_call( + "model.party.party.create", + [{"name": name, "identifiers": identifiers}, {}], + ) + + if not result["success"]: + raise Exception(f"Failed to create {name} as customer") + + return result["data"] + + @mcp.tool() def create_schedule( professional: int, description: str, customer: int, date: str, service_center: int ) -> List[int]: + try: + dt = datetime.fromisoformat(date) + except ValueError: + raise ValueError( + f"Invalid date format: '{date}'. Expected ISO format (e.g., '2026-03-15T10:00:00' or '2026-03-15T10:00:00+00:00')" + ) + + utc_dt = dt.astimezone(timezone.utc) + example_schedule = [ { - "professional": 6, + "professional": professional, "description": description, - "customer": 4, - "date": datetime.datetime.fromisoformat(date), - "service_center": 11, + "customer": customer, + "date": utc_dt, + "service_center": service_center, } ] diff --git a/tests/test_customer.py b/tests/test_customer.py new file mode 100644 index 0000000..cafa629 --- /dev/null +++ b/tests/test_customer.py @@ -0,0 +1,48 @@ +import pytest +import sys +from pathlib import Path +import json + +from tryton_mcp.server import create_customer + + +sys.path.insert(0, str(Path(__file__).parent / "src")) + + +@pytest.mark.asyncio +class TestParty: + + async def test_create_new_customer(self, mcp_client, mock_settings): + mock_client = mock_settings.get_client.return_value + mock_client.call.return_value = [1] + + result = await mcp_client.call_tool( + "create_customer", + { + "name": "Alejandro Zapata", + "identifiers": [["create", [{"type": "mobile", "code": "310659595"}]]], + }, + ) + + assert result.content is not None + assert len(result.content) == 1 + assert result.content[0].text == "[1]" + + async def test_find_customer(self, mcp_client, mock_settings): + + expected_return = [ + {'id': 19, 'party': 44, 'party.': {'name': 'Alejandro Zapata', 'id': 44}} + ] + + mock_client = mock_settings.get_client.return_value + mock_client.call.return_value = expected_return + + result = await mcp_client.call_tool( + "find_customer_by_identifier", + { + "identifier": "310659595" + }) + + assert result.content is not None + assert len(result.content) == 1 + assert json.loads(result.content[0].text) == expected_return diff --git a/tests/test_server.py b/tests/test_server.py index 6db3d4d..f477f52 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -2,6 +2,7 @@ import pytest from unittest.mock import MagicMock, patch import datetime import json +from fastmcp.exceptions import ToolError import sys from pathlib import Path @@ -78,6 +79,19 @@ class TestCreateSchedule: }, ) + async def test_create_schedule_invalid_date_format(self, mcp_client, mock_settings): + with pytest.raises(ToolError, match="Invalid date format"): + await mcp_client.call_tool( + "create_schedule", + { + "professional": 6, + "description": "Test appointment", + "customer": 4, + "date": "invalid-date", + "service_center": 11, + }, + ) + @pytest.mark.asyncio class TestFindServiceCenters: