WeCom Bot MCP Server

by loonghao
Verified
MIT License
18
  • Linux
  • Apple
"""Pytest configuration file.""" # Import built-in modules import os from pathlib import Path import sys from unittest.mock import AsyncMock from unittest.mock import MagicMock from unittest.mock import patch # Import third-party modules import aiohttp from pyfakefs.fake_filesystem_unittest import Patcher import pytest # Import local modules from wecom_bot_mcp_server.errors import WeComError # Add the src directory to the Python path sys.path.insert(0, str(Path(__file__).parent.parent)) class AsyncContextManagerMock: """Helper class for mocking asynchronous context managers.""" def __init__(self, return_value=None): self.return_value = return_value or AsyncMock() self.enter_called = False self.exit_called = False async def __aenter__(self): self.enter_called = True return self.return_value async def __aexit__(self, exc_type, exc_val, exc_tb): self.exit_called = True return False class MockClientResponse(AsyncMock): """Mock for aiohttp.ClientResponse with async context manager support.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.status = kwargs.get("status", 200) self.reason = kwargs.get("reason", "") self.headers = kwargs.get("headers", {}) self.content = AsyncMock() self.content.read = AsyncMock(return_value=b"") async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): return False class MockClientSession(AsyncMock): """Mock for aiohttp.ClientSession with async context manager support.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.response = kwargs.get("response", None) def get(self, *args, **kwargs): return self.response or MockClientResponse() async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): return False def create_async_mock_session(response_mock=None): """Create a mock aiohttp ClientSession with proper async context manager support. Args: response_mock: Optional mock response to return from session.get() Returns: MockClientSession: A properly configured mock session """ # If no response mock is provided, create one if response_mock is None: response_mock = MockClientResponse() # Create a session that returns our response session_mock = MockClientSession(response=response_mock) return session_mock @pytest.fixture def fs(): """Fixture for pyfakefs.""" with Patcher() as patcher: yield patcher.fs @pytest.fixture def mock_notify_bridge(): """Fixture for mocking NotifyBridge.""" with ( patch("wecom_bot_mcp_server.message.NotifyBridge") as message_mock, patch("wecom_bot_mcp_server.image.NotifyBridge") as image_mock, ): # Setup mock response mock_response = MagicMock() mock_response.success = True mock_response.data = {"errcode": 0, "errmsg": "ok"} # Setup NotifyBridge instance mock_nb_instance = AsyncMock() mock_nb_instance.send_async.return_value = mock_response # Setup both mocks to return the same instance message_mock.return_value.__aenter__.return_value = mock_nb_instance image_mock.return_value.__aenter__.return_value = mock_nb_instance yield mock_nb_instance @pytest.fixture def mock_notify_bridge_error(): """Fixture for mocking NotifyBridge with error.""" with ( patch("wecom_bot_mcp_server.message.NotifyBridge") as message_mock, patch("wecom_bot_mcp_server.image.NotifyBridge") as image_mock, ): # Setup NotifyBridge instance with error mock_nb_instance = AsyncMock() mock_nb_instance.send_async.side_effect = Exception("Connection error") # Setup both mocks to return the same instance message_mock.return_value.__aenter__.return_value = mock_nb_instance image_mock.return_value.__aenter__.return_value = mock_nb_instance yield mock_nb_instance @pytest.fixture def mock_notify_bridge_error_with_context(): """Fixture for mocking NotifyBridge with error and context.""" # Setup context mock_ctx = AsyncMock() with ( patch("wecom_bot_mcp_server.message.NotifyBridge") as message_mock, patch("wecom_bot_mcp_server.image.NotifyBridge") as image_mock, ): # Setup NotifyBridge instance with error mock_nb_instance = AsyncMock() mock_nb_instance.send_async.side_effect = Exception("Connection error") # Setup both mocks to return the same instance message_mock.return_value.__aenter__.return_value = mock_nb_instance image_mock.return_value.__aenter__.return_value = mock_nb_instance yield (mock_nb_instance, mock_ctx) @pytest.fixture def mock_notify_bridge_api_error(): """Fixture for mocking NotifyBridge with API error.""" with ( patch("wecom_bot_mcp_server.message.NotifyBridge") as message_mock, patch("wecom_bot_mcp_server.image.NotifyBridge") as image_mock, ): # Setup mock response with API error mock_response = MagicMock() mock_response.success = True mock_response.data = {"errcode": 40001, "errmsg": "invalid credential"} # Setup NotifyBridge instance mock_nb_instance = AsyncMock() mock_nb_instance.send_async.return_value = mock_response # Setup both mocks to return the same instance message_mock.return_value.__aenter__.return_value = mock_nb_instance image_mock.return_value.__aenter__.return_value = mock_nb_instance yield mock_nb_instance @pytest.fixture def mock_notify_bridge_api_error_with_context(): """Fixture for mocking NotifyBridge with API error and context.""" # Setup context mock_ctx = AsyncMock() with ( patch("wecom_bot_mcp_server.message.NotifyBridge") as message_mock, patch("wecom_bot_mcp_server.image.NotifyBridge") as image_mock, ): # Setup mock response with API error mock_response = MagicMock() mock_response.success = True mock_response.data = {"errcode": 40001, "errmsg": "invalid credential"} # Setup NotifyBridge instance mock_nb_instance = AsyncMock() mock_nb_instance.send_async.return_value = mock_response # Setup both mocks to return the same instance message_mock.return_value.__aenter__.return_value = mock_nb_instance image_mock.return_value.__aenter__.return_value = mock_nb_instance yield (mock_nb_instance, mock_ctx) @pytest.fixture def mock_notify_bridge_network_error(): """Fixture for mocking NotifyBridge with network error.""" with ( patch("wecom_bot_mcp_server.message.NotifyBridge") as message_mock, patch("wecom_bot_mcp_server.image.NotifyBridge") as image_mock, ): # Setup NotifyBridge instance with network error mock_nb_instance = AsyncMock() mock_nb_instance.send_async.side_effect = Exception("Network connection failed") # Setup both mocks to return the same instance message_mock.return_value.__aenter__.return_value = mock_nb_instance image_mock.return_value.__aenter__.return_value = mock_nb_instance yield mock_nb_instance @pytest.fixture def mock_notify_bridge_network_error_with_context(): """Fixture for mocking NotifyBridge with network error and context.""" with ( patch("wecom_bot_mcp_server.message.NotifyBridge") as message_mock, patch("wecom_bot_mcp_server.image.NotifyBridge") as image_mock, ): # Setup NotifyBridge instance with network error mock_nb_instance = AsyncMock() mock_nb_instance.send_async.side_effect = Exception("Network connection failed") # Setup both mocks to return the same instance message_mock.return_value.__aenter__.return_value = mock_nb_instance image_mock.return_value.__aenter__.return_value = mock_nb_instance # Create mock context mock_ctx = AsyncMock() yield mock_nb_instance, mock_ctx @pytest.fixture def mock_webhook_url(): """Fixture for mocking get_webhook_url function.""" with ( patch("wecom_bot_mcp_server.message.get_webhook_url") as message_mock, patch("wecom_bot_mcp_server.image.get_webhook_url") as image_mock, ): # Setup both mocks to return the same URL message_mock.return_value = "https://example.com/webhook" image_mock.return_value = "https://example.com/webhook" yield "https://example.com/webhook" @pytest.fixture def mock_image_processing(): """Fixture for mocking image processing functions.""" with ( patch("wecom_bot_mcp_server.image.Path.exists") as exists_mock, patch("wecom_bot_mcp_server.image.Image.open") as image_open_mock, ): # Setup mocks exists_mock.return_value = True image_open_mock.return_value = MagicMock() yield (exists_mock, image_open_mock) @pytest.fixture def mock_http_error_response(): """Fixture for mocking HTTP error response.""" # Setup mock response with 404 status mock_response = MockClientResponse(status=404, reason="Not Found") # Create mock session mock_session = create_async_mock_session(mock_response) # Patch ClientSession with patch("wecom_bot_mcp_server.image.aiohttp.ClientSession", return_value=mock_session): yield mock_response @pytest.fixture def mock_http_error_response_with_context(): """Fixture for mocking HTTP error response with context.""" # Setup mock response with 404 status mock_response = MockClientResponse(status=404, reason="Not Found") # Create mock session mock_session = create_async_mock_session(mock_response) # Setup context mock_ctx = AsyncMock() # Patch ClientSession with patch("wecom_bot_mcp_server.image.aiohttp.ClientSession", return_value=mock_session): yield (mock_response, mock_ctx) @pytest.fixture def mock_invalid_content_type_response(): """Fixture for mocking invalid content type response.""" # Setup mock response with invalid content type mock_response = MockClientResponse(status=200, headers={"Content-Type": "text/html"}) # Create mock session mock_session = create_async_mock_session(mock_response) # Patch ClientSession with patch("wecom_bot_mcp_server.image.aiohttp.ClientSession", return_value=mock_session): yield mock_response @pytest.fixture def mock_invalid_content_type_with_context(): """Fixture for mocking invalid content type response with context.""" # Setup mock response with invalid content type mock_response = MockClientResponse(status=200, headers={"Content-Type": "text/html"}) # Create mock session mock_session = create_async_mock_session(mock_response) # Setup context mock_ctx = AsyncMock() # Patch ClientSession with patch("wecom_bot_mcp_server.image.aiohttp.ClientSession", return_value=mock_session): yield (mock_response, mock_ctx) @pytest.fixture def mock_network_error(): """Fixture for mocking network error.""" # Patch ClientSession to raise a network error with patch("wecom_bot_mcp_server.image.aiohttp.ClientSession") as mock_client_session: mock_client_session.side_effect = aiohttp.ClientError("Network error") yield mock_client_session @pytest.fixture def mock_network_error_with_context(): """Fixture for mocking network error with context.""" # Setup context mock_ctx = AsyncMock() # Patch ClientSession to raise a network error with patch("wecom_bot_mcp_server.image.aiohttp.ClientSession") as mock_client_session: mock_client_session.side_effect = aiohttp.ClientError("Network error") yield (mock_client_session, mock_ctx) @pytest.fixture def mock_file_operations(): """Fixture for mocking file operations.""" with ( patch("wecom_bot_mcp_server.file.Path.exists") as mock_exists, patch("wecom_bot_mcp_server.file.Path.is_file") as mock_is_file, patch("wecom_bot_mcp_server.file.Path.stat") as mock_stat, ): # Setup mocks mock_exists.return_value = True mock_is_file.return_value = True # Setup stat mock mock_stat_result = MagicMock() mock_stat_result.st_size = 1024 mock_stat.return_value = mock_stat_result yield mock_exists, mock_is_file, mock_stat @pytest.fixture def mock_file_api(): """Fixture for mocking file API operations.""" with ( patch("wecom_bot_mcp_server.file.NotifyBridge") as mock_notify_bridge, patch("wecom_bot_mcp_server.file.get_webhook_url") as mock_get_webhook_url, ): # Setup webhook URL mock_get_webhook_url.return_value = "https://example.com/webhook" # Setup NotifyBridge response mock_response = MagicMock() mock_response.success = True mock_response.data = {"errcode": 0, "media_id": "test_media_id"} # Setup NotifyBridge instance mock_nb_instance = AsyncMock() mock_nb_instance.send_async.return_value = mock_response mock_notify_bridge.return_value.__aenter__.return_value = mock_nb_instance yield mock_notify_bridge, mock_get_webhook_url, mock_nb_instance @pytest.fixture def mock_file_api_error(): """Fixture for mocking file API error.""" with ( patch("wecom_bot_mcp_server.file.NotifyBridge") as mock_notify_bridge, patch("wecom_bot_mcp_server.file.get_webhook_url") as mock_get_webhook_url, ): # Setup webhook URL mock_get_webhook_url.return_value = "https://example.com/webhook" # Setup NotifyBridge response with API error mock_response = MagicMock() mock_response.success = True mock_response.data = {"errcode": 40001, "errmsg": "invalid credential"} # Setup NotifyBridge instance mock_nb_instance = AsyncMock() mock_nb_instance.send_async.return_value = mock_response mock_notify_bridge.return_value.__aenter__.return_value = mock_nb_instance yield mock_notify_bridge, mock_get_webhook_url, mock_nb_instance @pytest.fixture def mock_file_response_failure(): """Fixture for mocking file response failure.""" with ( patch("wecom_bot_mcp_server.file.NotifyBridge") as mock_notify_bridge, patch("wecom_bot_mcp_server.file.get_webhook_url") as mock_get_webhook_url, ): # Setup webhook URL mock_get_webhook_url.return_value = "https://example.com/webhook" # Setup NotifyBridge response with failure mock_response = MagicMock() mock_response.success = False mock_response.data = {} # Setup NotifyBridge instance mock_nb_instance = AsyncMock() mock_nb_instance.send_async.return_value = mock_response mock_notify_bridge.return_value.__aenter__.return_value = mock_nb_instance yield mock_notify_bridge, mock_get_webhook_url, mock_nb_instance @pytest.fixture def mock_file_exists(): """Fixture for mocking file exists.""" with ( patch("wecom_bot_mcp_server.file.Path.exists") as mock_exists, patch("wecom_bot_mcp_server.file.Path.is_file") as mock_is_file, ): # Setup mocks mock_exists.return_value = True mock_is_file.return_value = True yield mock_exists, mock_is_file @pytest.fixture def mock_file_not_a_file(): """Fixture for mocking file that is not a file but a directory.""" with ( patch("wecom_bot_mcp_server.file.Path.exists") as mock_exists, patch("wecom_bot_mcp_server.file.Path.is_file") as mock_is_file, ): # Setup mocks mock_exists.return_value = True mock_is_file.return_value = False yield mock_exists, mock_is_file @pytest.fixture def mock_file_not_found(): """Fixture for mocking file not found.""" with ( patch("wecom_bot_mcp_server.file.Path.exists") as mock_exists, patch("wecom_bot_mcp_server.file.Path.is_file") as mock_is_file, ): # Setup mocks mock_exists.return_value = False mock_is_file.return_value = False yield mock_exists, mock_is_file @pytest.fixture def mock_file_send(): """Fixture for mocking file send operations.""" with patch("wecom_bot_mcp_server.file.NotifyBridge") as mock_notify_bridge: # Setup NotifyBridge response mock_response = MagicMock() mock_response.success = True mock_response.data = {"errcode": 0, "media_id": "test_media_id"} # Setup NotifyBridge instance mock_nb_instance = AsyncMock() mock_nb_instance.send_async.return_value = mock_response mock_notify_bridge.return_value.__aenter__.return_value = mock_nb_instance yield mock_notify_bridge, mock_nb_instance, mock_response @pytest.fixture def mock_file_stat(): """Fixture for mocking file stat operations.""" with patch("wecom_bot_mcp_server.file.Path.stat") as mock_stat: # Setup stat mock mock_stat_result = MagicMock() mock_stat_result.st_size = 2048 mock_stat.return_value = mock_stat_result yield mock_stat, mock_stat_result @pytest.fixture def mock_file_with_context(): """Fixture for mocking file operations with context.""" with ( patch("wecom_bot_mcp_server.file.Path.exists") as mock_exists, patch("wecom_bot_mcp_server.file.Path.is_file") as mock_is_file, patch("wecom_bot_mcp_server.file.Path.stat") as mock_stat, patch("wecom_bot_mcp_server.file.NotifyBridge") as mock_notify_bridge, patch("wecom_bot_mcp_server.file.get_webhook_url") as mock_get_webhook_url, ): # Setup mocks mock_exists.return_value = True mock_is_file.return_value = True mock_get_webhook_url.return_value = "https://example.com/webhook" # Setup stat mock mock_stat_result = MagicMock() mock_stat_result.st_size = 1024 mock_stat.return_value = mock_stat_result # Setup NotifyBridge response mock_response = MagicMock() mock_response.success = True mock_response.data = {"errcode": 0, "media_id": "test_media_id"} # Setup NotifyBridge instance mock_nb_instance = AsyncMock() mock_nb_instance.send_async.return_value = mock_response mock_notify_bridge.return_value.__aenter__.return_value = mock_nb_instance # Create mock context mock_ctx = AsyncMock() yield ( mock_exists, mock_is_file, mock_stat, mock_notify_bridge, mock_get_webhook_url, mock_nb_instance, mock_response, mock_ctx, ) @pytest.fixture def mock_get_webhook_url(): """Fixture for mocking get_webhook_url function.""" with patch("wecom_bot_mcp_server.file.get_webhook_url") as mock_get_webhook_url: # Setup mock mock_get_webhook_url.return_value = "https://example.com/webhook" yield mock_get_webhook_url @pytest.fixture def mock_get_webhook_url_error(): """Fixture for mocking get_webhook_url function with error.""" with patch("wecom_bot_mcp_server.file.get_webhook_url") as mock_get_webhook_url: # Setup mock mock_get_webhook_url.side_effect = WeComError("Webhook URL not found") yield mock_get_webhook_url @pytest.fixture def mock_file_network_error(): """Fixture for mocking file network error.""" with ( patch("wecom_bot_mcp_server.file.Path.exists") as mock_exists, patch("wecom_bot_mcp_server.file.Path.is_file") as mock_is_file, patch("wecom_bot_mcp_server.file.get_webhook_url") as mock_get_webhook_url, patch("wecom_bot_mcp_server.file.NotifyBridge") as mock_notify_bridge, ): # Setup mocks mock_exists.return_value = True mock_is_file.return_value = True mock_get_webhook_url.return_value = "https://example.com/webhook" # Setup NotifyBridge to raise an exception mock_nb_instance = AsyncMock() mock_nb_instance.send_async.side_effect = Exception("Network connection failed") mock_notify_bridge.return_value.__aenter__.return_value = mock_nb_instance yield mock_exists, mock_is_file, mock_get_webhook_url, mock_notify_bridge, mock_nb_instance @pytest.fixture def mock_image_send(): """Fixture for mocking image send operations.""" with ( patch("wecom_bot_mcp_server.image.Path.exists") as mock_exists, patch("wecom_bot_mcp_server.image.Image.open") as mock_image_open, patch("wecom_bot_mcp_server.image.NotifyBridge") as mock_notify_bridge, patch("wecom_bot_mcp_server.image.get_webhook_url") as mock_get_webhook_url, ): # Setup mocks mock_exists.return_value = True mock_image_open.return_value = MagicMock() mock_get_webhook_url.return_value = "https://example.com/webhook" # Setup NotifyBridge mock mock_nb_instance = AsyncMock() mock_nb_instance.send_async.return_value = {"errcode": 0, "errmsg": "ok"} mock_notify_bridge.return_value.__aenter__.return_value = mock_nb_instance yield mock_exists, mock_image_open, mock_notify_bridge, mock_get_webhook_url, mock_nb_instance @pytest.fixture def mock_image_download(): """Fixture for mocking image download operations.""" with ( patch("wecom_bot_mcp_server.image.Path.exists") as mock_exists, patch("wecom_bot_mcp_server.image.Image.open") as mock_image_open, patch("wecom_bot_mcp_server.image.download_image") as mock_download_image, ): # Setup mocks mock_exists.return_value = True mock_image_open.return_value = MagicMock() mock_download_image.return_value = "downloaded_image.jpg" yield mock_exists, mock_image_open, mock_download_image @pytest.fixture def mock_image_not_found(): """Fixture for mocking image not found.""" with ( patch("wecom_bot_mcp_server.image.Path.exists") as mock_exists, patch("wecom_bot_mcp_server.image.Path.is_file") as mock_is_file, ): # Setup mocks mock_exists.return_value = False mock_is_file.return_value = False yield mock_exists, mock_is_file @pytest.fixture def mock_image_not_a_file(): """Fixture for mocking image that is not a file but a directory.""" with ( patch("wecom_bot_mcp_server.image.Path.exists") as mock_exists, patch("wecom_bot_mcp_server.image.Path.is_file") as mock_is_file, ): # Setup mocks mock_exists.return_value = True mock_is_file.return_value = False yield mock_exists, mock_is_file @pytest.fixture def mock_image_network_error(): """Fixture for mocking image network error.""" with ( patch("wecom_bot_mcp_server.image.Path.exists") as mock_exists, patch("wecom_bot_mcp_server.image.Image.open") as mock_image_open, patch("wecom_bot_mcp_server.image.get_webhook_url") as mock_get_webhook_url, patch("wecom_bot_mcp_server.image.NotifyBridge") as mock_notify_bridge, ): # Setup mocks mock_exists.return_value = True mock_image_open.return_value = MagicMock() mock_get_webhook_url.return_value = "https://example.com/webhook" # Setup NotifyBridge to raise an exception mock_nb_instance = AsyncMock() mock_nb_instance.send_async.side_effect = Exception("Network connection failed") mock_notify_bridge.return_value.__aenter__.return_value = mock_nb_instance yield mock_exists, mock_image_open, mock_get_webhook_url, mock_notify_bridge, mock_nb_instance @pytest.fixture def mock_image_with_context(): """Fixture for mocking image operations with context.""" with ( patch("wecom_bot_mcp_server.image.Path.exists") as mock_exists, patch("wecom_bot_mcp_server.image.Image.open") as mock_image_open, patch("wecom_bot_mcp_server.image.NotifyBridge") as mock_notify_bridge, patch("wecom_bot_mcp_server.image.get_webhook_url") as mock_get_webhook_url, ): # Setup mocks mock_exists.return_value = True mock_image_open.return_value = MagicMock() mock_get_webhook_url.return_value = "https://example.com/webhook" # Setup NotifyBridge mock mock_nb_instance = AsyncMock() mock_response = MagicMock() mock_response.success = True mock_response.data = {"errcode": 0, "errmsg": "ok"} mock_nb_instance.send_async.return_value = mock_response mock_notify_bridge.return_value.__aenter__.return_value = mock_nb_instance # Create mock context mock_ctx = AsyncMock() yield mock_exists, mock_image_open, mock_notify_bridge, mock_get_webhook_url, mock_nb_instance, mock_ctx @pytest.fixture(autouse=True) def setup_env(): """Set up environment variables for tests.""" # Set up test environment variables os.environ["WECOM_WEBHOOK_URL"] = "https://example.com/webhook/test" yield # Clean up if "WECOM_WEBHOOK_URL" in os.environ: del os.environ["WECOM_WEBHOOK_URL"]