Skip to main content
Glama
test_external_api.py13.5 kB
"""Integration tests for external API interactions.""" import pytest from unittest.mock import AsyncMock, patch import httpx from src.clients.kyc_api_client import KYCAPIClient, ValidationError, ServiceUnavailableError from src.tools.pan_verification import PANVerificationTool from src.tools.pan_aadhaar_link import PANAadhaarLinkTool class TestExternalAPIIntegration: """Integration tests for external KYC API.""" @pytest.fixture def api_client(self): """Create API client for testing.""" return KYCAPIClient( base_url="https://api.sandbox.co.in", api_key="test_api_key", jwt_token="Bearer test_token", timeout=30 ) @pytest.mark.asyncio @pytest.mark.integration async def test_pan_verification_end_to_end(self, api_client, mock_responses): """Test complete PAN verification flow.""" # Mock the HTTP client mock_response = mock_responses["pan_verification"]["success_all_match"] api_client.client.post = AsyncMock(return_value=type('Response', (), { 'status_code': 200, 'json': lambda: mock_response, 'raise_for_status': lambda: None })()) # Create tool and execute tool = PANVerificationTool(api_client=api_client) result = await tool.execute({ "pan": "ABCDE1234F", "name_as_per_pan": "John Doe", "date_of_birth": "01/01/1990", "consent": "Y", "reason": "KYC verification" }) assert result["pan"] == "ABCDE1234F" assert result["status"] == "valid" assert result["name_match"] is True assert result["dob_match"] is True @pytest.mark.asyncio @pytest.mark.integration async def test_pan_aadhaar_link_end_to_end(self, api_client, mock_responses): """Test complete PAN-Aadhaar link check flow.""" # Mock the HTTP client mock_response = mock_responses["pan_aadhaar_link"]["linked"] api_client.client.post = AsyncMock(return_value=type('Response', (), { 'status_code': 200, 'json': lambda: mock_response, 'raise_for_status': lambda: None })()) # Create tool and execute tool = PANAadhaarLinkTool(api_client=api_client) result = await tool.execute({ "pan": "ABCPE1234F", "aadhaar_number": "123456789012", "consent": "Y", "reason": "Link status check" }) assert result["linked"] is True assert result["status"] == "y" @pytest.mark.asyncio @pytest.mark.integration async def test_api_authentication_headers(self, api_client): """Test that authentication headers are properly set.""" # Verify headers are set in client assert "Authorization" in api_client.client.headers assert "x-api-key" in api_client.client.headers assert api_client.client.headers["Authorization"] == "Bearer test_token" assert api_client.client.headers["x-api-key"] == "test_api_key" @pytest.mark.asyncio @pytest.mark.integration async def test_api_timeout_handling(self, api_client): """Test handling of API timeouts.""" # Mock timeout api_client.client.post = AsyncMock( side_effect=httpx.TimeoutException("Request timed out") ) tool = PANVerificationTool(api_client=api_client) with pytest.raises(Exception): await tool.execute({ "pan": "ABCDE1234F", "name_as_per_pan": "John Doe", "date_of_birth": "01/01/1990", "consent": "Y", "reason": "Test" }) @pytest.mark.asyncio @pytest.mark.integration async def test_api_validation_error_422(self, api_client, mock_responses): """Test handling of 422 validation errors from API.""" # Mock validation error response mock_response = type('Response', (), { 'status_code': 422, 'json': lambda: mock_responses["pan_verification"]["invalid_pan"], 'request': type('Request', (), {'url': 'test', 'method': 'POST'})() })() def raise_error(): raise httpx.HTTPStatusError( message="422", request=mock_response.request, response=mock_response ) mock_response.raise_for_status = raise_error api_client.client.post = AsyncMock(return_value=mock_response) tool = PANVerificationTool(api_client=api_client) with pytest.raises(ValidationError): await tool.execute({ "pan": "INVALID", "name_as_per_pan": "John Doe", "date_of_birth": "01/01/1990", "consent": "Y", "reason": "Test" }) @pytest.mark.asyncio @pytest.mark.integration async def test_api_service_unavailable_503(self, api_client, mock_responses): """Test handling of 503 service unavailable errors.""" # Mock service unavailable response mock_response = type('Response', (), { 'status_code': 503, 'json': lambda: mock_responses["pan_verification"]["service_unavailable"], 'request': type('Request', (), {'url': 'test', 'method': 'POST'})() })() def raise_error(): raise httpx.HTTPStatusError( message="503", request=mock_response.request, response=mock_response ) mock_response.raise_for_status = raise_error api_client.client.post = AsyncMock(return_value=mock_response) tool = PANVerificationTool(api_client=api_client) with pytest.raises(ServiceUnavailableError): await tool.execute({ "pan": "ABCDE1234F", "name_as_per_pan": "John Doe", "date_of_birth": "01/01/1990", "consent": "Y", "reason": "Test" }) @pytest.mark.asyncio @pytest.mark.integration async def test_api_retry_on_failure(self, api_client, mock_responses): """Test that API requests are retried on failure.""" call_count = 0 async def mock_post(*args, **kwargs): nonlocal call_count call_count += 1 if call_count < 2: # Fail first attempt raise httpx.RequestError("Temporary failure") # Succeed on second attempt return type('Response', (), { 'status_code': 200, 'json': lambda: mock_responses["pan_verification"]["success_all_match"], 'raise_for_status': lambda: None })() api_client.client.post = mock_post tool = PANVerificationTool(api_client=api_client) result = await tool.execute({ "pan": "ABCDE1234F", "name_as_per_pan": "John Doe", "date_of_birth": "01/01/1990", "consent": "Y", "reason": "Test" }) assert result["status"] == "valid" assert call_count == 2 # Should have retried once @pytest.mark.asyncio @pytest.mark.integration async def test_concurrent_api_requests(self, api_client, mock_responses): """Test handling of concurrent API requests.""" import asyncio # Mock successful responses mock_response = mock_responses["pan_verification"]["success_all_match"] api_client.client.post = AsyncMock(return_value=type('Response', (), { 'status_code': 200, 'json': lambda: mock_response, 'raise_for_status': lambda: None })()) tool = PANVerificationTool(api_client=api_client) # Execute multiple requests concurrently requests = [ { "pan": f"ABCD{i}1234F", "name_as_per_pan": "John Doe", "date_of_birth": "01/01/1990", "consent": "Y", "reason": "Test" } for i in range(5) ] results = await asyncio.gather(*[ tool.execute(req) for req in requests ]) assert len(results) == 5 assert all(r["status"] == "valid" for r in results) @pytest.mark.asyncio @pytest.mark.integration async def test_api_response_parsing(self, api_client, mock_responses): """Test correct parsing of API responses.""" # Test with deceased holder response mock_response = mock_responses["pan_verification"]["success_deceased"] api_client.client.post = AsyncMock(return_value=type('Response', (), { 'status_code': 200, 'json': lambda: mock_response, 'raise_for_status': lambda: None })()) tool = PANVerificationTool(api_client=api_client) result = await tool.execute({ "pan": "DEFGH3456K", "name_as_per_pan": "John Doe", "date_of_birth": "01/01/1990", "consent": "Y", "reason": "Test" }) assert result["remarks"] == "Holder is Deceased" assert result["aadhaar_seeding_status"] == "na" @pytest.mark.asyncio @pytest.mark.integration async def test_api_connection_pooling(self, api_client): """Test that connection pooling is properly configured.""" # Verify limits are set assert api_client.client.limits.max_connections == 100 assert api_client.client.limits.max_keepalive_connections == 20 @pytest.mark.asyncio @pytest.mark.integration async def test_api_base_url_configuration(self, api_client): """Test that base URL is properly configured.""" assert str(api_client.client.base_url) == "https://api.sandbox.co.in" @pytest.mark.asyncio @pytest.mark.integration async def test_api_client_cleanup(self, api_client): """Test proper cleanup of API client resources.""" api_client.client.aclose = AsyncMock() await api_client.close() api_client.client.aclose.assert_called_once() @pytest.mark.asyncio @pytest.mark.integration async def test_multiple_tools_same_client(self, api_client, mock_responses): """Test using same client for multiple tools.""" # Mock responses for both tools pan_response = mock_responses["pan_verification"]["success_all_match"] aadhaar_response = mock_responses["pan_aadhaar_link"]["linked"] call_count = 0 async def mock_post(*args, **kwargs): nonlocal call_count call_count += 1 if call_count == 1: response_data = pan_response else: response_data = aadhaar_response return type('Response', (), { 'status_code': 200, 'json': lambda: response_data, 'raise_for_status': lambda: None })() api_client.client.post = mock_post # Execute both tools pan_tool = PANVerificationTool(api_client=api_client) aadhaar_tool = PANAadhaarLinkTool(api_client=api_client) pan_result = await pan_tool.execute({ "pan": "ABCDE1234F", "name_as_per_pan": "John Doe", "date_of_birth": "01/01/1990", "consent": "Y", "reason": "Test" }) aadhaar_result = await aadhaar_tool.execute({ "pan": "ABCPE1234F", "aadhaar_number": "123456789012", "consent": "Y", "reason": "Test" }) assert pan_result["status"] == "valid" assert aadhaar_result["linked"] is True assert call_count == 2 @pytest.mark.asyncio @pytest.mark.integration async def test_api_error_message_extraction(self, api_client): """Test extraction of error messages from API responses.""" error_response = { "code": "422", "message": "Invalid PAN format", "errors": [{"field": "pan", "message": "Must be 10 characters"}] } mock_response = type('Response', (), { 'status_code': 422, 'json': lambda: error_response, 'request': type('Request', (), {'url': 'test', 'method': 'POST'})() })() def raise_error(): raise httpx.HTTPStatusError( message="422", request=mock_response.request, response=mock_response ) mock_response.raise_for_status = raise_error api_client.client.post = AsyncMock(return_value=mock_response) tool = PANVerificationTool(api_client=api_client) try: await tool.execute({ "pan": "INVALID", "name_as_per_pan": "John Doe", "date_of_birth": "01/01/1990", "consent": "Y", "reason": "Test" }) except ValidationError as e: assert "Invalid PAN format" in str(e)

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/CTD-Techs/CTD-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server