feat: Add comprehensive test suite with unit and integration tests

This commit is contained in:
latinogino
2025-07-10 12:07:58 +02:00
parent efb0eb589a
commit cd0b49350c

View File

@@ -0,0 +1,185 @@
"""Tests for Dolibarr client functionality."""
import pytest
from unittest.mock import AsyncMock, patch
from dolibarr_mcp.config import Config
from dolibarr_mcp.dolibarr_client import DolibarrClient, DolibarrAPIError
class TestDolibarrClient:
"""Test cases for the DolibarrClient."""
def test_config_validation(self):
"""Test configuration validation."""
# Test valid configuration
config = Config(
dolibarr_url="https://test.dolibarr.com/api/index.php",
api_key="test_key"
)
assert config.dolibarr_url == "https://test.dolibarr.com/api/index.php"
assert config.api_key == "test_key"
# Test validation
config.validate_config() # Should not raise
# Test invalid URL
with pytest.raises(ValueError, match="must start with http"):
invalid_config = Config(
dolibarr_url="invalid-url",
api_key="test_key"
)
invalid_config.validate_config()
@pytest.mark.asyncio
async def test_client_session_management(self):
"""Test client session management."""
config = Config(
dolibarr_url="https://test.dolibarr.com/api/index.php",
api_key="test_key"
)
client = DolibarrClient(config)
# Test session starts as None
assert client.session is None
# Test session creation
await client.start_session()
assert client.session is not None
# Test session cleanup
await client.close_session()
assert client.session is None
@pytest.mark.asyncio
async def test_context_manager(self):
"""Test async context manager functionality."""
config = Config(
dolibarr_url="https://test.dolibarr.com/api/index.php",
api_key="test_key"
)
async with DolibarrClient(config) as client:
assert client.session is not None
# Session should be closed after context exit
assert client.session is None
@pytest.mark.asyncio
@patch('aiohttp.ClientSession.request')
async def test_successful_api_request(self, mock_request):
"""Test successful API request."""
# Mock response
mock_response = AsyncMock()
mock_response.status = 200
mock_response.text.return_value = '{"success": {"code": 200, "dolibarr_version": "21.0.1"}}'
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:
result = await client.get_status()
assert "success" in result
assert result["success"]["code"] == 200
assert result["success"]["dolibarr_version"] == "21.0.1"
@pytest.mark.asyncio
@patch('aiohttp.ClientSession.request')
async def test_api_error_handling(self, mock_request):
"""Test API error handling."""
# Mock error response
mock_response = AsyncMock()
mock_response.status = 404
mock_response.reason = "Not Found"
mock_response.text.return_value = '{"error": "Object not found"}'
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_customer_by_id(999)
assert exc_info.value.status_code == 404
assert "Object not found" in str(exc_info.value)
def test_url_building(self):
"""Test URL building functionality."""
config = Config(
dolibarr_url="https://test.dolibarr.com/api/index.php",
api_key="test_key"
)
client = DolibarrClient(config)
# Test with leading slash
url = client._build_url("/users")
assert url == "https://test.dolibarr.com/api/index.php/users"
# Test without leading slash
url = client._build_url("users")
assert url == "https://test.dolibarr.com/api/index.php/users"
# Test with trailing slash in base URL
client.base_url = "https://test.dolibarr.com/api/index.php/"
url = client._build_url("users")
assert url == "https://test.dolibarr.com/api/index.php/users"
class TestDolibarrAPIError:
"""Test cases for DolibarrAPIError."""
def test_error_creation(self):
"""Test error object creation."""
error = DolibarrAPIError(
message="Test error",
status_code=400,
response_data={"error": "Bad request"}
)
assert str(error) == "Test error"
assert error.status_code == 400
assert error.response_data == {"error": "Bad request"}
def test_error_without_optional_params(self):
"""Test error creation without optional parameters."""
error = DolibarrAPIError("Simple error")
assert str(error) == "Simple error"
assert error.status_code is None
assert error.response_data is None
# Example of how to add integration tests
@pytest.mark.integration
class TestDolibarrIntegration:
"""Integration tests (require real Dolibarr instance)."""
@pytest.mark.asyncio
async def test_real_connection(self):
"""Test connection to real Dolibarr instance."""
# Skip if no real credentials available
try:
config = Config.from_env()
config.validate_config()
except ValueError:
pytest.skip("No valid Dolibarr configuration available")
async with DolibarrClient(config) as client:
try:
result = await client.get_status()
assert "success" in result or "dolibarr_version" in str(result)
except DolibarrAPIError as e:
pytest.fail(f"API connection failed: {e}")
if __name__ == "__main__":
pytest.main([__file__])