Improve validation, correlation IDs, and client safeguards

This commit is contained in:
latinogino
2026-01-02 17:52:07 +01:00
parent 59e91efd30
commit 7356d6c409
9 changed files with 501 additions and 80 deletions

View File

@@ -16,6 +16,7 @@ import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from dolibarr_mcp import DolibarrClient, Config
from dolibarr_mcp.dolibarr_client import DolibarrValidationError
class TestCRUDOperations:
@@ -78,7 +79,9 @@ class TestCRUDOperations:
# Create
mock_request.return_value = {"id": 10}
product_id = await client.create_product({
"ref": "TEST-PROD",
"label": "Test Product",
"type": "service",
"price": 99.99,
"description": "Test product description"
})
@@ -330,7 +333,7 @@ class TestCRUDOperations:
# Test validation error
mock_request.side_effect = Exception("Validation Error: Missing required field")
with pytest.raises(Exception, match="Validation"):
with pytest.raises(DolibarrValidationError):
await client.create_product({})
# Test connection error

View File

@@ -4,7 +4,7 @@ import pytest
from unittest.mock import AsyncMock, patch
from dolibarr_mcp.config import Config
from dolibarr_mcp.dolibarr_client import DolibarrClient, DolibarrAPIError
from dolibarr_mcp.dolibarr_client import DolibarrClient, DolibarrAPIError, DolibarrValidationError
class TestDolibarrClient:
@@ -110,6 +110,66 @@ class TestDolibarrClient:
assert exc_info.value.status_code == 404
assert "Object not found" in str(exc_info.value)
@pytest.mark.asyncio
async def test_validation_error_on_missing_ref(self):
"""Ensure client-side validation catches missing product ref."""
config = Config(
dolibarr_url="https://test.dolibarr.com/api/index.php",
api_key="test_key",
allow_ref_autogen=False,
)
client = DolibarrClient(config)
client.request = AsyncMock(return_value={"id": 1}) # Should not be called
with pytest.raises(DolibarrValidationError) as exc_info:
await client.create_product({"label": "No Ref", "type": "service", "price": 12.5})
assert exc_info.value.response_data["missing_fields"] == ["ref"]
client.request.assert_not_called()
@pytest.mark.asyncio
async def test_autogen_ref_when_enabled(self):
"""Auto-generate refs when allowed by configuration."""
config = Config(
dolibarr_url="https://test.dolibarr.com/api/index.php",
api_key="test_key",
allow_ref_autogen=True,
ref_autogen_prefix="AUTOREF",
)
client = DolibarrClient(config)
client.request = AsyncMock(return_value={"id": 42, "ref": "AUTOREF_123"})
await client.create_product({"label": "Generated Ref Product", "type": "service", "price": 10})
assert client.request.await_count == 1
sent_payload = client.request.call_args.kwargs["data"]
assert "ref" in sent_payload
assert sent_payload["ref"].startswith("AUTOREF_")
@pytest.mark.asyncio
@patch('aiohttp.ClientSession.request')
async def test_internal_error_correlation_id(self, mock_request):
"""Include correlation IDs for unexpected server errors."""
mock_response = AsyncMock()
mock_response.status = 500
mock_response.reason = "Internal Server Error"
mock_response.text.return_value = '{"message": "Database unavailable"}'
mock_request.return_value.__aenter__.return_value = mock_response
config = Config(
dolibarr_url="https://test.dolibarr.com/api/index.php",
api_key="test_key"
)
async with DolibarrClient(config) as client:
with pytest.raises(DolibarrAPIError) as exc_info:
await client.get_project_by_id(1)
assert exc_info.value.status_code == 500
assert "correlation_id" in exc_info.value.response_data
def test_url_building(self):
"""Test URL building functionality."""

View File

@@ -42,6 +42,7 @@ class TestProjectOperations:
# Create
mock_request.return_value = {"id": 200}
project_id = await client.create_project({
"ref": "PRJ-NEW-WEBSITE",
"title": "New Website",
"description": "Website redesign project",
"socid": 1,