WeCom Bot MCP Server

by loonghao
Verified
MIT License
18
  • Linux
  • Apple
"""Tests for file module.""" # Import built-in modules from pathlib import Path from unittest.mock import AsyncMock from unittest.mock import MagicMock from unittest.mock import patch # Import third-party modules import pytest # Import local modules from wecom_bot_mcp_server.errors import WeComError from wecom_bot_mcp_server.file import _get_webhook_url from wecom_bot_mcp_server.file import _process_file_response from wecom_bot_mcp_server.file import _send_file_to_wecom from wecom_bot_mcp_server.file import _validate_file from wecom_bot_mcp_server.file import send_wecom_file @pytest.mark.asyncio async def test_send_wecom_file(mock_file_send, fs): """Test send_wecom_file function.""" # Create a test file fs.create_file("test_file.txt", contents="Test file content") # Call function result = await send_wecom_file("test_file.txt") # Assertions assert result["status"] == "success" assert "file_name" in result assert result["file_name"] == "test_file.txt" @pytest.mark.asyncio @patch("wecom_bot_mcp_server.file.Path.exists") async def test_send_wecom_file_not_found(mock_exists): """Test send_wecom_file with non-existent file.""" # Setup mock mock_exists.return_value = False # Call function and check exception with pytest.raises(WeComError) as excinfo: await send_wecom_file("non_existent_file.txt") # Assertions assert "File not found" in str(excinfo.value) @pytest.mark.asyncio async def test_send_wecom_file_api_error(mock_file_operations, mock_file_api_error): """Test send_wecom_file with API error.""" # Call function and check exception with pytest.raises(WeComError) as excinfo: await send_wecom_file("test_file.txt") # Assertions # Check if the error message contains "invalid credential", regardless of how it's raised assert "invalid credential" in str(excinfo.value) @pytest.mark.asyncio async def test_send_wecom_file_response_failure(mock_file_operations, mock_file_response_failure): """Test send_wecom_file with response failure.""" # Call function and check exception with pytest.raises(WeComError) as excinfo: await send_wecom_file("test_file.txt") # Assertions assert "Failed to send file" in str(excinfo.value) @pytest.mark.asyncio @patch("wecom_bot_mcp_server.file.Path.exists") @patch("wecom_bot_mcp_server.file.Path.is_file") @patch("wecom_bot_mcp_server.file.get_webhook_url") @patch("wecom_bot_mcp_server.file.NotifyBridge") async def test_send_wecom_file_exception(mock_notify_bridge, mock_get_webhook_url, mock_is_file, mock_exists): """Test send_wecom_file with exception.""" # Setup mocks mock_exists.return_value = True mock_is_file.return_value = True mock_get_webhook_url.return_value = "https://example.com/webhook" mock_nb_instance = AsyncMock() mock_nb_instance.send_async.side_effect = Exception("Test exception") mock_notify_bridge.return_value.__aenter__.return_value = mock_nb_instance # Call function and check exception with pytest.raises(WeComError) as excinfo: await send_wecom_file("test_file.txt") # Assertions assert "Error sending file: Test exception" in str(excinfo.value) @pytest.mark.asyncio async def test_send_wecom_file_exception(mock_file_network_error): """Test send_wecom_file with exception.""" # Create mock context mock_ctx = AsyncMock() # Call function and check exception with pytest.raises(WeComError) as excinfo: await send_wecom_file("test_file.txt", mock_ctx) # Assertions assert "Error sending file" in str(excinfo.value) @pytest.mark.asyncio async def test_validate_file_with_string_path(mock_file_exists): """Test _validate_file with string path.""" # Call function result = await _validate_file("test_file.txt") # Assertions assert isinstance(result, Path) assert result.name == "test_file.txt" @pytest.mark.asyncio async def test_validate_file_with_path_object(mock_file_exists): """Test _validate_file with Path object.""" # Call function file_path = Path("test_file.txt") result = await _validate_file(file_path) # Assertions assert result == file_path @pytest.mark.asyncio async def test_validate_file_not_exists(mock_file_not_found): """Test _validate_file with non-existent file.""" # Call function with expected exception with pytest.raises(WeComError) as excinfo: await _validate_file("non_existent_file.txt") # Assertions assert "File not found" in str(excinfo.value) @pytest.mark.asyncio async def test_validate_file_not_a_file(mock_file_not_a_file): """Test _validate_file with a directory instead of a file.""" # Call function with expected exception with pytest.raises(WeComError) as excinfo: await _validate_file("directory") # Assertions assert "Not a file: directory" in str(excinfo.value) @pytest.mark.asyncio async def test_validate_file_not_a_file_with_context(mock_file_not_a_file): """Test _validate_file with a directory instead of a file and context.""" # Create mock context mock_ctx = AsyncMock() # Call function with expected exception with pytest.raises(WeComError) as excinfo: await _validate_file("directory", mock_ctx) # Assertions assert "Not a file: directory" in str(excinfo.value) mock_ctx.error.assert_called_once() @pytest.mark.asyncio async def test_get_webhook_url_function(mock_get_webhook_url): """Test _get_webhook_url function.""" # Call function result = await _get_webhook_url() # Assertions assert result == "https://example.com/webhook" @pytest.mark.asyncio async def test_get_webhook_url_with_error(mock_get_webhook_url_error): """Test _get_webhook_url with error.""" # Call function and expect error with pytest.raises(WeComError) as excinfo: await _get_webhook_url() # Assertions assert "Webhook URL not found" in str(excinfo.value) @pytest.mark.asyncio async def test_get_webhook_url_with_error_and_context(mock_get_webhook_url_error): """Test _get_webhook_url with error and context.""" # Create mock context mock_ctx = AsyncMock() # Call function and expect error with pytest.raises(WeComError) as excinfo: await _get_webhook_url(mock_ctx) # Assertions assert "Webhook URL not found" in str(excinfo.value) mock_ctx.error.assert_called_once() @pytest.mark.asyncio async def test_send_file_to_wecom(mock_file_send): """Test _send_file_to_wecom function.""" # Unpack fixtures _, mock_nb_instance, mock_response = mock_file_send # Setup test params file_path = Path("test_file.txt") base_url = "https://example.com/webhook" # Call function result = await _send_file_to_wecom(file_path, base_url) # Assertions assert result == mock_response mock_nb_instance.send_async.assert_called_once_with( "wecom", { "base_url": base_url, "msg_type": "file", "file_path": str(file_path.absolute()), }, ) @pytest.mark.asyncio async def test_process_file_response_success(mock_file_stat): """Test _process_file_response with success response.""" # Unpack fixtures _, _ = mock_file_stat mock_response = MagicMock() mock_response.success = True mock_response.data = {"errcode": 0, "media_id": "test_media_id"} file_path = Path("test_file.txt") # Call function result = await _process_file_response(mock_response, file_path) # Assertions assert result["status"] == "success" assert result["message"] == "File sent successfully" assert result["file_name"] == "test_file.txt" assert result["file_size"] == 2048 assert result["media_id"] == "test_media_id" @pytest.mark.asyncio async def test_process_file_response_request_failure(): """Test _process_file_response with request failure.""" # Setup mock response mock_response = MagicMock() mock_response.success = False file_path = Path("test_file.txt") # Call function with expected exception with pytest.raises(WeComError) as excinfo: await _process_file_response(mock_response, file_path) # Assertions assert "Failed to send file" in str(excinfo.value) @pytest.mark.asyncio async def test_process_file_response_api_error_with_context(): """Test _process_file_response with API error and context.""" # Setup mock response mock_response = MagicMock() mock_response.success = True mock_response.data = {"errcode": 40001, "errmsg": "invalid credential"} file_path = Path("test_file.txt") # Create mock context mock_ctx = AsyncMock() # Call function with expected exception with pytest.raises(WeComError) as excinfo: await _process_file_response(mock_response, file_path, mock_ctx) # Assertions assert "WeChat API error" in str(excinfo.value) assert "invalid credential" in str(excinfo.value) mock_ctx.error.assert_called_once() @pytest.mark.asyncio async def test_send_wecom_file_with_context(mock_file_with_context): """Test send_wecom_file function with context.""" # Unpack fixtures _, _, _, _, _, _, _, mock_ctx = mock_file_with_context # Call function result = await send_wecom_file("test_file.txt", mock_ctx) # Assertions assert result["status"] == "success" # Verify ctx methods were called mock_ctx.report_progress.assert_called() mock_ctx.info.assert_called() @pytest.mark.asyncio async def test_validate_file_with_context(mock_file_exists): """Test _validate_file function with context.""" # Create mock context mock_ctx = AsyncMock() # Call function result = await _validate_file("test_file.txt", mock_ctx) # Assertions assert isinstance(result, Path) assert result.name == "test_file.txt" mock_ctx.report_progress.assert_called_once() @pytest.mark.asyncio async def test_validate_file_not_exists_with_context(mock_file_not_found): """Test _validate_file with non-existent file and context.""" # Create mock context mock_ctx = AsyncMock() # Call function with expected exception with pytest.raises(WeComError) as excinfo: await _validate_file("nonexistent_file.txt", mock_ctx) # Assertions assert "File not found: nonexistent_file.txt" in str(excinfo.value) mock_ctx.error.assert_called_once_with("File not found: nonexistent_file.txt") @pytest.mark.asyncio async def test_get_webhook_url_with_context(mock_get_webhook_url): """Test _get_webhook_url function with context.""" # Create mock context mock_ctx = AsyncMock() # Call function result = await _get_webhook_url(mock_ctx) # Assertions assert result == "https://example.com/webhook" mock_ctx.report_progress.assert_called_once() @pytest.mark.asyncio async def test_get_webhook_url_with_error_and_context(mock_get_webhook_url_error): """Test _get_webhook_url function with error and context.""" # Create mock context mock_ctx = AsyncMock() # Call function with expected exception with pytest.raises(WeComError) as excinfo: await _get_webhook_url(mock_ctx) # Assertions assert "Webhook URL not found" in str(excinfo.value) mock_ctx.error.assert_called_once() @pytest.mark.asyncio @patch("wecom_bot_mcp_server.file.NotifyBridge") async def test_send_file_to_wecom_with_context(mock_notify_bridge): """Test _send_file_to_wecom function with context.""" # Setup mocks mock_response = MagicMock() mock_response.success = True mock_response.data = {"errcode": 0, "errmsg": "ok"} 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() # Call function file_path = Path("test_file.txt") result = await _send_file_to_wecom(file_path, "https://example.com/webhook", mock_ctx) # Assertions assert result == mock_response # Check NotifyBridge was called with correct parameters mock_nb_instance.send_async.assert_called_once() args = mock_nb_instance.send_async.call_args[0] assert args[0] == "wecom" assert "base_url" in args[1] assert "file_path" in args[1] assert args[1]["file_path"] == str(file_path.absolute()) # Context methods should not be called in _send_file_to_wecom mock_ctx.report_progress.assert_called_once_with(0.7) mock_ctx.info.assert_called_once_with(f"Sending file: {file_path}") @pytest.mark.asyncio async def test_process_file_response_success_with_context(mock_file_stat): """Test _process_file_response with success response and context.""" # Setup mock response mock_response = MagicMock() mock_response.success = True mock_response.data = {"errcode": 0, "errmsg": "ok"} file_path = Path("test_file.txt") # Create mock context mock_ctx = AsyncMock() # Call function result = await _process_file_response(mock_response, file_path, mock_ctx) # Assertions assert result["status"] == "success" assert result["file_size"] == 2048 assert "file_name" in result # Verify context methods were called mock_ctx.report_progress.assert_called_once_with(1.0) mock_ctx.info.assert_called_once() @pytest.mark.asyncio async def test_process_file_response_request_failure_with_context(): """Test _process_file_response with request failure and context.""" # Setup mock response mock_response = MagicMock() mock_response.success = False file_path = Path("test_file.txt") # Create mock context mock_ctx = AsyncMock() # Call function with expected exception with pytest.raises(WeComError) as excinfo: await _process_file_response(mock_response, file_path, mock_ctx) # Assertions assert "Failed to send file" in str(excinfo.value) mock_ctx.error.assert_called_once() @pytest.mark.asyncio async def test_process_file_response_api_error_with_context(): """Test _process_file_response with API error and context.""" # Setup mocks mock_response = MagicMock() mock_response.success = True mock_response.data = {"errcode": 40001, "errmsg": "invalid credential"} file_path = Path("test_file.txt") # Create mock context mock_ctx = AsyncMock() # Call function with expected exception with pytest.raises(WeComError) as excinfo: await _process_file_response(mock_response, file_path, mock_ctx) # Assertions assert "WeChat API error: invalid credential" in str(excinfo.value) mock_ctx.error.assert_called_once() @pytest.mark.asyncio async def test_send_wecom_file_network_error(mock_file_network_error): """Test send_wecom_file with network error.""" # Create mock context mock_ctx = AsyncMock() # Call function with expected exception with pytest.raises(WeComError) as excinfo: await send_wecom_file("test_file.txt", mock_ctx) # Assertions assert "Error sending file: Network connection failed" in str(excinfo.value) mock_ctx.error.assert_called_once() @pytest.mark.asyncio async def test_send_wecom_file_with_context(mock_file_with_context): """Test send_wecom_file function with context.""" # Unpack fixtures _, _, _, _, _, _, _, mock_ctx = mock_file_with_context # Call function result = await send_wecom_file("test_file.txt", mock_ctx) # Assertions assert result["status"] == "success" # Verify ctx methods were called mock_ctx.report_progress.assert_called() mock_ctx.info.assert_called()