mirror of
https://github.com/latinogino/dolibarr-mcp.git
synced 2026-04-30 21:35:36 +02:00
Improve validation, correlation IDs, and client safeguards
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user