"""Tests for error handling utilities."""
import pytest
from unittest.mock import Mock
from src.utils.errors import (
ErrorCode,
MCPError,
APIError,
handle_external_api_error,
create_validation_error,
create_missing_api_key_error,
)
class TestErrorCode:
"""Test cases for ErrorCode enum."""
def test_error_code_values(self):
"""Test that error codes have expected values."""
assert ErrorCode.PARSE_ERROR.value == -32700
assert ErrorCode.INVALID_REQUEST.value == -32600
assert ErrorCode.METHOD_NOT_FOUND.value == -32601
assert ErrorCode.INVALID_PARAMS.value == -32602
assert ErrorCode.INTERNAL_ERROR.value == -32603
assert ErrorCode.API_KEY_MISSING.value == -32001
assert ErrorCode.API_KEY_INVALID.value == -32002
assert ErrorCode.EXTERNAL_API_ERROR.value == -32003
assert ErrorCode.RATE_LIMIT_EXCEEDED.value == -32004
assert ErrorCode.TOOL_NOT_AVAILABLE.value == -32005
class TestMCPError:
"""Test cases for MCPError model."""
def test_mcp_error_creation(self):
"""Test creating an MCPError instance."""
error = MCPError(
code=-32602,
message="Invalid parameters",
data={"field": "city", "value": None}
)
assert error.code == -32602
assert error.message == "Invalid parameters"
assert error.data == {"field": "city", "value": None}
def test_mcp_error_without_data(self):
"""Test creating an MCPError without data."""
error = MCPError(code=-32603, message="Internal error")
assert error.code == -32603
assert error.message == "Internal error"
assert error.data is None
def test_mcp_error_serialization(self):
"""Test that MCPError can be serialized to dict."""
error = MCPError(
code=-32602,
message="Invalid parameters",
data={"field": "city"}
)
error_dict = error.dict()
assert error_dict["code"] == -32602
assert error_dict["message"] == "Invalid parameters"
assert error_dict["data"] == {"field": "city"}
class TestAPIError:
"""Test cases for APIError exception."""
def test_api_error_creation(self):
"""Test creating an APIError instance."""
error = APIError(
message="Test error",
code=ErrorCode.INVALID_PARAMS,
data={"test": "data"}
)
assert str(error) == "Test error"
assert error.message == "Test error"
assert error.code == ErrorCode.INVALID_PARAMS
assert error.data == {"test": "data"}
assert error.original_error is None
def test_api_error_with_original_error(self):
"""Test APIError with original exception."""
original = ValueError("Original error")
error = APIError(
message="Wrapped error",
code=ErrorCode.INTERNAL_ERROR,
original_error=original
)
assert error.original_error == original
def test_api_error_default_code(self):
"""Test APIError with default error code."""
error = APIError("Test error")
assert error.code == ErrorCode.INTERNAL_ERROR
def test_api_error_to_mcp_error(self):
"""Test converting APIError to MCPError."""
error = APIError(
message="Test error",
code=ErrorCode.INVALID_PARAMS,
data={"field": "test"}
)
mcp_error = error.to_mcp_error()
assert isinstance(mcp_error, MCPError)
assert mcp_error.code == ErrorCode.INVALID_PARAMS.value
assert mcp_error.message == "Test error"
assert mcp_error.data == {"field": "test"}
def test_api_error_to_mcp_error_with_original(self):
"""Test converting APIError with original error to MCPError."""
original = ValueError("Original error")
error = APIError(
message="Test error",
code=ErrorCode.INTERNAL_ERROR,
data={"test": "data"},
original_error=original
)
mcp_error = error.to_mcp_error()
assert mcp_error.data["test"] == "data"
assert mcp_error.data["original_error"] == "Original error"
def test_api_error_to_mcp_error_no_data(self):
"""Test converting APIError without data to MCPError."""
error = APIError("Test error", ErrorCode.INTERNAL_ERROR)
mcp_error = error.to_mcp_error()
assert mcp_error.data is None
class TestErrorUtilities:
"""Test cases for error utility functions."""
def test_handle_external_api_error_with_401(self):
"""Test handling external API error with 401 status."""
# Mock an HTTP error with 401 status
mock_error = Mock()
mock_response = Mock()
mock_response.status_code = 401
mock_error.response = mock_response
result = handle_external_api_error(mock_error, "TestAPI", "test_operation")
assert isinstance(result, APIError)
assert result.code == ErrorCode.API_KEY_INVALID
assert "Invalid API key for TestAPI" in result.message
assert result.data["api"] == "TestAPI"
assert result.data["operation"] == "test_operation"
assert result.original_error == mock_error
def test_handle_external_api_error_with_429(self):
"""Test handling external API error with 429 status."""
mock_error = Mock()
mock_response = Mock()
mock_response.status_code = 429
mock_error.response = mock_response
result = handle_external_api_error(mock_error, "TestAPI", "test_operation")
assert isinstance(result, APIError)
assert result.code == ErrorCode.RATE_LIMIT_EXCEEDED
assert "Rate limit exceeded for TestAPI" in result.message
def test_handle_external_api_error_generic(self):
"""Test handling generic external API error."""
mock_error = Exception("Generic error")
result = handle_external_api_error(mock_error, "TestAPI", "test_operation")
assert isinstance(result, APIError)
assert result.code == ErrorCode.EXTERNAL_API_ERROR
assert "External API error: TestAPI test_operation failed" in result.message
assert result.data["api"] == "TestAPI"
assert result.data["operation"] == "test_operation"
def test_handle_external_api_error_without_response(self):
"""Test handling external API error without response attribute."""
mock_error = Exception("Error without response")
result = handle_external_api_error(mock_error, "TestAPI", "test_operation")
assert isinstance(result, APIError)
assert result.code == ErrorCode.EXTERNAL_API_ERROR
def test_create_validation_error(self):
"""Test creating a validation error."""
error = create_validation_error("city", None, "City cannot be None")
assert isinstance(error, APIError)
assert error.code == ErrorCode.INVALID_PARAMS
assert "Invalid parameter 'city'" in error.message
assert "City cannot be None" in error.message
assert error.data["field"] == "city"
assert error.data["value"] is None
def test_create_missing_api_key_error(self):
"""Test creating a missing API key error."""
error = create_missing_api_key_error("OpenWeatherMap")
assert isinstance(error, APIError)
assert error.code == ErrorCode.API_KEY_MISSING
assert "API key missing for OpenWeatherMap" in error.message
assert error.data["api"] == "OpenWeatherMap"
def test_create_validation_error_with_complex_value(self):
"""Test creating validation error with complex value."""
complex_value = {"nested": {"data": [1, 2, 3]}}
error = create_validation_error("config", complex_value, "Invalid configuration")
assert error.data["field"] == "config"
assert error.data["value"] == complex_value
def test_create_missing_api_key_error_different_apis(self):
"""Test creating missing API key errors for different APIs."""
apis = ["OpenWeatherMap", "NewsAPI", "AlphaVantage"]
for api in apis:
error = create_missing_api_key_error(api)
assert f"API key missing for {api}" in error.message
assert error.data["api"] == api