From cd0b49350cf3534cd76865ffd280ef258a4f36d9 Mon Sep 17 00:00:00 2001 From: latinogino <154024559+latinogino@users.noreply.github.com> Date: Thu, 10 Jul 2025 12:07:58 +0200 Subject: [PATCH] feat: Add comprehensive test suite with unit and integration tests --- tests/test_dolibarr_client.py | 185 ++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 tests/test_dolibarr_client.py diff --git a/tests/test_dolibarr_client.py b/tests/test_dolibarr_client.py new file mode 100644 index 0000000..441fd6f --- /dev/null +++ b/tests/test_dolibarr_client.py @@ -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__])