WeCom Bot MCP Server
by loonghao
Verified
- wecom-bot-mcp-server
- tests
"""Tests for image module."""
# Import built-in modules
from pathlib import Path
from unittest.mock import AsyncMock
from unittest.mock import MagicMock
from unittest.mock import mock_open
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.image import _get_webhook_url
from wecom_bot_mcp_server.image import _process_image_path
from wecom_bot_mcp_server.image import _process_image_response
from wecom_bot_mcp_server.image import _send_image_to_wecom
from wecom_bot_mcp_server.image import download_image
from wecom_bot_mcp_server.image import send_wecom_image
@pytest.mark.asyncio
async def test_send_wecom_image_local(mock_image_send, fs):
"""Test send_wecom_image function with local file."""
# Unpack fixtures
mock_exists, mock_pil_open, mock_notify_bridge, mock_get_webhook_url, mock_nb_instance = mock_image_send
# Create test image file
fs.create_file("test_image.jpg", contents=b"\x89PNG\r\n\x1a\n")
# Setup mock response
mock_response = MagicMock()
mock_response.success = True
mock_response.data = {"errcode": 0, "errmsg": "ok"}
mock_nb_instance.send_async.return_value = mock_response
# Call function
result = await send_wecom_image("test_image.jpg")
# Assertions
assert result["status"] == "success"
assert result["image_path"] == "test_image.jpg"
# Verify NotifyBridge was called correctly
mock_nb_instance.send_async.assert_called_once()
@pytest.mark.asyncio
async def test_send_wecom_image_with_context(mock_notify_bridge, mock_webhook_url, mock_image_processing):
"""Test send_wecom_image function with context."""
# Create mock context
mock_ctx = AsyncMock()
# Setup mock response
mock_response = MagicMock()
mock_response.success = True
mock_response.data = {"errcode": 0, "errmsg": "ok"}
mock_notify_bridge.return_value.__aenter__.return_value.send_async.return_value = mock_response
# Call function
result = await send_wecom_image("test_image.jpg", mock_ctx)
# Assertions
assert result["status"] == "success"
assert result["message"] == "Image sent successfully"
assert "test_image.jpg" in result["image_path"]
# Verify context methods were called
mock_ctx.report_progress.assert_called()
mock_ctx.info.assert_called()
@pytest.mark.asyncio
async def test_send_wecom_image_with_fixtures(mock_webhook_url, mock_notify_bridge, mock_image_processing):
"""Test send_wecom_image function using fixtures from conftest."""
# Call function
result = await send_wecom_image("test_image.jpg")
# Assertions
assert result["status"] == "success"
assert "message" in result
assert "image_path" in result
@pytest.mark.asyncio
async def test_send_wecom_image_not_found(mock_image_not_found):
"""Test send_wecom_image function with non-existent file."""
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await send_wecom_image("non_existent_image.jpg")
# Assertions
assert "Image file not found" in str(excinfo.value)
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.aiohttp.ClientSession")
@patch("builtins.open", new_callable=mock_open)
@patch("wecom_bot_mcp_server.image.os.makedirs")
async def test_download_image(mock_makedirs, mock_file_open, mock_client_session):
"""Test download_image function."""
# Setup mocks
mock_response = AsyncMock()
mock_response.status = 200
mock_response.headers = {"Content-Type": "image/jpeg"}
mock_response.read = AsyncMock(return_value=b"image_data")
# Create a mock session.get result
mock_get_cm = AsyncMock()
mock_get_cm.__aenter__.return_value = mock_response
# Create a mock session object
mock_session = AsyncMock()
mock_session.get = MagicMock(return_value=mock_get_cm)
# Create a mock ClientSession context manager
mock_session_cm = AsyncMock()
mock_session_cm.__aenter__.return_value = mock_session
# Set ClientSession to return the mock context manager
mock_client_session.return_value = mock_session_cm
# Call function
result = await download_image("https://example.com/image.jpg")
# Assertions
assert isinstance(result, Path)
mock_session.get.assert_called_once_with("https://example.com/image.jpg")
mock_file_open.assert_called_once()
mock_file_open().write.assert_called_once_with(b"image_data")
@pytest.mark.asyncio
async def test_send_wecom_image_url(mock_image_download, fs):
"""Test send_wecom_image function with URL."""
# Unpack fixtures
mock_exists, mock_pil_open, mock_download = mock_image_download
# Setup additional mocks for NotifyBridge and webhook
with (
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_get_webhook_url.return_value = "https://example.com/webhook"
# Fix Windows path issue
downloaded_path = Path("tmp/downloaded_image.jpg")
mock_download.return_value = downloaded_path
# Create the downloaded image file
fs.create_file(downloaded_path, contents=b"\x89PNG\r\n\x1a\n")
# 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
# Call function
result = await send_wecom_image("https://example.com/image.jpg")
# Assertions
assert result["status"] == "success"
# Convert Path object to string to match the actual return value
assert result["image_path"] == str(downloaded_path)
# Verify NotifyBridge was called correctly
mock_nb_instance.send_async.assert_called_once_with(
"wecom",
{
"base_url": "https://example.com/webhook",
"msg_type": "image",
"image": str(downloaded_path.absolute()),
},
)
@pytest.mark.asyncio
async def test_process_image_path_local_file(fs):
"""Test _process_image_path with a local file."""
# Create a test image file
fs.create_file("test_image.jpg", contents=b"\x89PNG\r\n\x1a\n")
# Setup additional mock for Image.open
with patch("wecom_bot_mcp_server.image.Image.open") as mock_pil_open:
# Mock PIL open success
mock_pil_open.return_value = MagicMock()
# Call function
result = await _process_image_path("test_image.jpg")
# Assertions
assert isinstance(result, Path)
assert result.name == "test_image.jpg"
mock_pil_open.assert_called_once()
@pytest.mark.asyncio
async def test_process_image_path_file_not_found(mock_image_not_a_file):
"""Test _process_image_path with non-existent file."""
# Setup mock
mock_exists = mock_image_not_a_file[0]
mock_exists.return_value = False
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await _process_image_path("non_existent_image.jpg")
# Assertions
assert "Image file not found" in str(excinfo.value)
@pytest.mark.asyncio
async def test_process_image_path_not_a_file(mock_image_not_a_file):
"""Test _process_image_path with a directory."""
# Setup additional mock for Image.open
with patch("wecom_bot_mcp_server.image.Image.open") as mock_pil_open:
# Mock PIL open failure, which is the actual check used in the source code
mock_pil_open.side_effect = Exception("Cannot identify image file")
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await _process_image_path("directory")
# Assertions
assert "Invalid image format" in str(excinfo.value)
@pytest.mark.asyncio
async def test_process_image_path_invalid_image(mock_image_not_a_file):
"""Test _process_image_path with an invalid image file."""
# Setup additional mock for Image.open
with patch("wecom_bot_mcp_server.image.Image.open") as mock_pil_open:
# Mock PIL open failure for invalid image
mock_pil_open.side_effect = Exception("Invalid image file")
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await _process_image_path("invalid_image.txt")
# Assertions
assert "Invalid image format" in str(excinfo.value)
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.download_image")
@patch("wecom_bot_mcp_server.image.Path.exists")
@patch("wecom_bot_mcp_server.image.Image.open")
async def test_process_image_path_url(mock_pil_open, mock_exists, mock_download):
"""Test _process_image_path with a URL."""
# Setup mocks
downloaded_path = Path("tmp/downloaded_image.jpg")
mock_download.return_value = downloaded_path
mock_exists.return_value = True
# Mock image opening function
mock_image = MagicMock()
mock_pil_open.return_value = mock_image
# Call function
result = await _process_image_path("https://example.com/image.jpg")
# Assertions
assert result == downloaded_path
mock_download.assert_called_once_with("https://example.com/image.jpg", None)
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.get_webhook_url")
async def test_get_webhook_url_function(mock_get_webhook_url):
"""Test _get_webhook_url function."""
# Setup mock
expected_url = "https://example.com/webhook"
mock_get_webhook_url.return_value = expected_url
# Call function
result = await _get_webhook_url()
# Assertions
assert result == expected_url
mock_get_webhook_url.assert_called_once()
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.get_webhook_url")
async def test_get_webhook_url_with_context(mock_get_webhook_url):
"""Test _get_webhook_url function with context."""
# Setup mock
expected_url = "https://example.com/webhook"
mock_get_webhook_url.return_value = expected_url
# Create mock context
mock_ctx = MagicMock()
# Call function
result = await _get_webhook_url(mock_ctx)
# Assertions
assert result == expected_url
mock_get_webhook_url.assert_called_once()
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.get_webhook_url")
async def test_get_webhook_url_with_error(mock_get_webhook_url):
"""Test _get_webhook_url function with error."""
# Setup mock
mock_get_webhook_url.side_effect = WeComError("Webhook URL not found")
# Call function with expected exception
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_send_image_to_wecom(fs):
"""Test _send_image_to_wecom function."""
# Create a test image file
image_path = Path("test_image.jpg")
fs.create_file(image_path, contents=b"\x89PNG\r\n\x1a\n")
base_url = "https://example.com/webhook"
# Setup NotifyBridge mock
with patch("wecom_bot_mcp_server.image.NotifyBridge") as mock_notify_bridge:
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
# Call function
result = await _send_image_to_wecom(image_path, base_url)
# Assertions
assert result == mock_response
mock_nb_instance.send_async.assert_called_once_with(
"wecom",
{
"base_url": base_url,
"msg_type": "image",
"image": str(image_path.absolute()),
},
)
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.NotifyBridge")
async def test_send_image_to_wecom_exception(mock_notify_bridge):
"""Test _send_image_to_wecom function with exception."""
# Setup mock
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
# Setup test params
image_path = Path("test_image.jpg")
base_url = "https://example.com/webhook"
# Call function with expected exception
with pytest.raises(Exception) as excinfo:
await _send_image_to_wecom(image_path, base_url)
# Assertions
assert "Test exception" in str(excinfo.value)
@pytest.mark.asyncio
async def test_process_image_response_success():
"""Test _process_image_response with success response."""
# Setup mocks
mock_response = MagicMock()
mock_response.success = True
mock_response.data = {"errcode": 0, "errmsg": "ok"}
image_path = Path("test_image.jpg")
# Call function
result = await _process_image_response(mock_response, image_path)
# Assertions
assert result["status"] == "success"
assert result["message"] == "Image sent successfully"
assert result["image_path"] == str(image_path)
@pytest.mark.asyncio
async def test_process_image_response_request_failure():
"""Test _process_image_response with request failure."""
# Setup mock response
mock_response = MagicMock()
mock_response.success = False
mock_response.data = {}
image_path = Path("test_image.jpg")
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await _process_image_response(mock_response, image_path)
# Assertions
assert "Failed to send image" in str(excinfo.value)
@pytest.mark.asyncio
async def test_process_image_response_api_error():
"""Test _process_image_response with API error."""
# Setup mock response
mock_response = MagicMock()
mock_response.success = True
mock_response.data = {"errcode": 40001, "errmsg": "Invalid token"}
image_path = Path("test_image.jpg")
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await _process_image_response(mock_response, image_path)
# Assertions
assert "WeChat API error" in str(excinfo.value)
assert "Invalid token" in str(excinfo.value)
@pytest.mark.asyncio
async def test_process_image_response_with_context():
"""Test _process_image_response with context."""
# Setup mocks
mock_response = MagicMock()
mock_response.success = True
mock_response.data = {"errcode": 0, "errmsg": "ok"}
image_path = Path("test_image.jpg")
# Create mock context
mock_ctx = AsyncMock()
# Call function
result = await _process_image_response(mock_response, image_path, mock_ctx)
# Assertions
assert result["status"] == "success"
# Verify context methods were called
mock_ctx.report_progress.assert_called()
mock_ctx.info.assert_called_with("Image sent successfully")
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.aiohttp.ClientSession")
async def test_download_image_with_context(mock_client_session):
"""Test download_image function with context."""
# Setup mocks
mock_response = AsyncMock()
mock_response.status = 200
mock_response.headers = {"Content-Type": "image/jpeg"}
mock_response.read = AsyncMock(return_value=b"image_data")
# Create a mock session.get result
mock_get_cm = AsyncMock()
mock_get_cm.__aenter__.return_value = mock_response
# Create a mock session object
mock_session = AsyncMock()
mock_session.get = MagicMock(return_value=mock_get_cm)
# Setup the context manager correctly
mock_session_cm = MagicMock()
mock_session_cm.__aenter__ = AsyncMock(return_value=mock_session)
mock_session_cm.__aexit__ = AsyncMock(return_value=None)
mock_client_session.return_value = mock_session_cm
# Create mock context
mock_ctx = AsyncMock()
# Mock open to avoid actual file writing
with patch("builtins.open", new_callable=mock_open):
with patch("wecom_bot_mcp_server.image.os.makedirs"):
# Call function
result = await download_image("https://example.com/image.jpg", mock_ctx)
# Assert context methods were called
mock_ctx.report_progress.assert_called()
mock_ctx.info.assert_called_with("Downloading image from https://example.com/image.jpg")
# Other assertions
assert isinstance(result, Path)
mock_session.get.assert_called_once_with("https://example.com/image.jpg")
@pytest.mark.asyncio
async def test_download_image_network_error(mock_network_error_with_context):
"""Test download_image with network error."""
# Unpack the fixture
_, mock_ctx = mock_network_error_with_context
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await download_image("https://example.com/image.jpg", mock_ctx)
# Assertions
assert "Failed to download image: Network error" in str(excinfo.value)
mock_ctx.error.assert_called_once()
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.Path.exists")
@patch("wecom_bot_mcp_server.image.Image.open")
async def test_process_image_path_with_context(mock_pil_open, mock_exists):
"""Test _process_image_path with context."""
# Setup mocks
mock_exists.return_value = True
mock_pil_open.return_value = MagicMock() # Valid image
# Setup context
mock_ctx = AsyncMock()
# Call function
result = await _process_image_path("test.jpg", mock_ctx)
# Assertions
assert isinstance(result, Path)
mock_pil_open.assert_called_once()
# No download should be attempted for local file
mock_exists.assert_called_once()
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.Path.exists")
@patch("wecom_bot_mcp_server.image.download_image")
async def test_process_image_path_url_with_download_error(mock_download, mock_exists):
"""Test _process_image_path with URL that has download error."""
# Setup mock to raise WeComError during download
mock_download.side_effect = WeComError("Download failed", "FILE_ERROR")
# Create mock context
mock_ctx = AsyncMock()
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await _process_image_path("https://example.com/image.jpg", mock_ctx)
# Assertions
assert "Download failed" in str(excinfo.value)
mock_ctx.error.assert_called_once()
mock_download.assert_called_once()
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.Path.exists")
@patch("wecom_bot_mcp_server.image.get_webhook_url")
async def test_get_webhook_url_with_error_and_context(mock_get_webhook_url, mock_exists):
"""Test _get_webhook_url with error and context."""
# Setup mock to raise WeComError
mock_get_webhook_url.side_effect = WeComError("Webhook URL not found", "VALIDATION_ERROR")
# 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_with("Webhook URL not found")
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.Path.exists")
@patch("wecom_bot_mcp_server.image.Image.open")
@patch("wecom_bot_mcp_server.image.NotifyBridge")
@patch("wecom_bot_mcp_server.image.get_webhook_url")
async def test_send_wecom_image_with_context(mock_get_webhook_url, mock_notify_bridge, mock_pil_open, mock_exists):
"""Test send_wecom_image function with context."""
# Setup mocks
mock_get_webhook_url.return_value = "https://example.com/webhook"
mock_exists.return_value = True
mock_pil_open.return_value = MagicMock() # Valid image
# Setup NotifyBridge to raise exception
mock_nb_instance = AsyncMock()
mock_nb_instance.send_async.side_effect = Exception("Connection error")
mock_notify_bridge.return_value.__aenter__.return_value = mock_nb_instance
# Setup context
mock_ctx = AsyncMock()
# Call function
with pytest.raises(WeComError) as exc_info:
await send_wecom_image("test.jpg", ctx=mock_ctx)
# Check error message
assert "Error sending image: Connection error" in str(exc_info.value)
# Verify context methods were called
mock_ctx.error.assert_called_with("Error sending image: Connection error")
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.NotifyBridge")
@patch("wecom_bot_mcp_server.image._get_webhook_url")
@patch("wecom_bot_mcp_server.image.Path.exists")
@patch("wecom_bot_mcp_server.image.Image.open")
async def test_send_wecom_image_with_api_error_and_context(
mock_pil_open, mock_exists, mock_get_webhook_url, mock_notify_bridge
):
"""Test send_wecom_image function with API error and context."""
# Setup mocks
mock_get_webhook_url.return_value = "https://example.com/webhook"
mock_exists.return_value = True
mock_pil_open.return_value = MagicMock() # Valid image
# Setup NotifyBridge mock response with API error
mock_response = MagicMock()
mock_response.success = True
mock_response.data = {"errcode": 40001, "errmsg": "invalid credential"}
mock_nb_instance = AsyncMock()
mock_nb_instance.send_async.return_value = mock_response
mock_notify_bridge.return_value.__aenter__.return_value = mock_nb_instance
# Setup context
mock_ctx = AsyncMock()
# Call function
with pytest.raises(WeComError) as exc_info:
await send_wecom_image("test.jpg", mock_ctx)
# Check error message
assert "WeChat API error: invalid credential" in str(exc_info.value)
# Verify context methods were called
assert mock_ctx.error.called
@pytest.mark.asyncio
async def test_send_wecom_image_with_context(mock_notify_bridge, mock_webhook_url, mock_image_processing):
"""Test send_wecom_image function with context."""
# Create mock context
mock_ctx = AsyncMock()
# Call function
result = await send_wecom_image("test_image.jpg", mock_ctx)
# Assertions
assert result["status"] == "success"
# Verify context methods were called
mock_ctx.report_progress.assert_called()
mock_ctx.info.assert_called()
@pytest.mark.asyncio
async def test_send_wecom_image_with_notify_bridge_error(
mock_notify_bridge_error, mock_webhook_url, mock_image_processing
):
"""Test send_wecom_image function with NotifyBridge error."""
# Call function
with pytest.raises(WeComError) as exc_info:
await send_wecom_image("test.jpg")
# Check error message
assert "Error sending image: Connection error" in str(exc_info.value)
assert "Connection error" in str(exc_info.value)
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.aiohttp.ClientSession")
async def test_download_image_http_error(mock_client_session):
"""Test download_image with HTTP error."""
# Setup mocks
mock_response = AsyncMock()
mock_response.status = 404
mock_response.headers = {"Content-Type": "text/html"}
mock_response.read = AsyncMock(return_value=b"error_data")
# Create a mock session.get result
mock_get_cm = AsyncMock()
mock_get_cm.__aenter__.return_value = mock_response
# Create a mock session object
mock_session = AsyncMock()
mock_session.get = MagicMock(return_value=mock_get_cm)
# Create a mock ClientSession context manager
mock_session_cm = AsyncMock()
mock_session_cm.__aenter__.return_value = mock_session
# Set ClientSession to return the mock context manager
mock_client_session.return_value = mock_session_cm
# Call function with a URL that will result in a 404
with pytest.raises(WeComError) as excinfo:
await download_image("https://example.com/not-found.jpg")
# Assertions
assert "Failed to download image: HTTP 404" in str(excinfo.value)
@pytest.mark.asyncio
async def test_download_image_invalid_content_type_no_context():
"""Test download_image with invalid content type without context."""
# Setup mock response with invalid content type
with patch("wecom_bot_mcp_server.image.aiohttp.ClientSession") as mock_session_class:
# Create a mock session
mock_session = MagicMock()
# Setup the response
mock_response = MagicMock()
mock_response.status = 200
mock_response.headers = {"Content-Type": "text/html"}
mock_response.read = AsyncMock(return_value=b"invalid data")
# Setup the get method to return a context manager that returns the mock response
mock_get_cm = MagicMock()
mock_get_cm.__aenter__ = AsyncMock(return_value=mock_response)
mock_get_cm.__aexit__ = AsyncMock(return_value=None)
mock_session.get = MagicMock(return_value=mock_get_cm)
# Setup session context manager
mock_session_cm = MagicMock()
mock_session_cm.__aenter__ = AsyncMock(return_value=mock_session)
mock_session_cm.__aexit__ = AsyncMock(return_value=None)
mock_session_class.return_value = mock_session_cm
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await download_image("https://example.com/image.jpg")
# Assertions
assert "Invalid content type: text/html" in str(excinfo.value)
@pytest.mark.asyncio
async def test_send_wecom_image_with_api_error(mock_image_send, fs):
"""Test send_wecom_image function with API error."""
# Create test image file
fs.create_file("/test/image.jpg", contents=b"test image content")
# Unpack fixtures
_, _, mock_notify_bridge, _, mock_nb_instance = mock_image_send
# Setup mock response with API error
mock_response = MagicMock()
mock_response.success = True
mock_response.data = {"errcode": 40001, "errmsg": "invalid credential"}
mock_nb_instance.send_async.return_value = mock_response
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await send_wecom_image("/test/image.jpg")
# Assertions
assert "WeChat API error: invalid credential" in str(excinfo.value)
@pytest.mark.asyncio
async def test_send_wecom_image_with_context(mock_image_with_context):
"""Test send_wecom_image function with context."""
# Unpack fixtures
_, _, _, _, _, mock_ctx = mock_image_with_context
# Call function
result = await send_wecom_image("test_image.jpg", mock_ctx)
# Assertions
assert result["status"] == "success"
# Verify context methods were called
mock_ctx.report_progress.assert_called()
mock_ctx.info.assert_called()
@pytest.mark.asyncio
async def test_send_wecom_image_with_notify_bridge_error_and_context(
mock_notify_bridge_error_with_context, mock_webhook_url, mock_image_processing
):
"""Test send_wecom_image function with NotifyBridge error and context."""
# Unpack the fixture
_, mock_ctx = mock_notify_bridge_error_with_context
# Call function
with pytest.raises(WeComError) as exc_info:
await send_wecom_image("test.jpg", ctx=mock_ctx)
# Check error message
assert "Error sending image: Connection error" in str(exc_info.value)
# Verify context methods were called
mock_ctx.error.assert_called_with("Error sending image: Connection error")
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.aiohttp.ClientSession")
async def test_download_image_http_error_with_context(mock_client_session):
"""Test download_image with HTTP error and context."""
# Create mock context
mock_ctx = AsyncMock()
# Create a proper async context manager for ClientSession
mock_session = AsyncMock()
mock_session_cm = AsyncMock()
mock_session_cm.__aenter__.return_value = mock_session
mock_client_session.return_value = mock_session_cm
# Setup the response
mock_response = AsyncMock()
mock_response.status = 404
mock_response.read = AsyncMock(return_value=b"not found")
# Create a proper async context manager for the response
mock_response_cm = AsyncMock()
mock_response_cm.__aenter__.return_value = mock_response
# Setup the get method to return the mock response context manager
mock_session.get.return_value = mock_response_cm
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await download_image("https://example.com/not-found.jpg", mock_ctx)
# Assertions
assert "Failed to download image: HTTP 404" in str(excinfo.value)
mock_ctx.error.assert_called_once()
mock_session.get.assert_called_once_with("https://example.com/not-found.jpg")
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.aiohttp.ClientSession")
async def test_download_image_invalid_content_type_with_context(mock_client_session):
"""Test download_image with invalid content type and context."""
# Setup mock context
mock_ctx = AsyncMock()
# Create a proper async context manager for ClientSession
mock_session = AsyncMock()
mock_session_cm = AsyncMock()
mock_session_cm.__aenter__.return_value = mock_session
mock_client_session.return_value = mock_session_cm
# Setup the response
mock_response = AsyncMock()
mock_response.status = 200
mock_response.headers = {"Content-Type": "text/html"}
mock_response.read = AsyncMock(return_value=b"invalid data")
# Create a proper async context manager for the response
mock_response_cm = AsyncMock()
mock_response_cm.__aenter__.return_value = mock_response
# Setup the get method to return the mock response context manager
mock_session.get.return_value = mock_response_cm
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await download_image("https://example.com/image.jpg", mock_ctx)
# Assertions
assert "Invalid content type: text/html" in str(excinfo.value)
mock_ctx.error.assert_called_once()
@pytest.mark.asyncio
async def test_send_wecom_image_with_api_error_and_context():
"""Test send_wecom_image function with API error and context."""
# Create mock context
mock_ctx = AsyncMock()
# Setup mock for image processing
with patch("wecom_bot_mcp_server.image._process_image_path") as mock_process_image:
# Return a valid path
mock_process_image.return_value = Path("test.jpg")
# Setup mock for webhook URL
with patch("wecom_bot_mcp_server.image._get_webhook_url") as mock_get_webhook_url:
mock_get_webhook_url.return_value = "https://example.com/webhook"
# Setup mock for sending image
with patch("wecom_bot_mcp_server.image._send_image_to_wecom") as mock_send_image:
# Create a mock response with API error
mock_response = MagicMock()
mock_response.success = True
mock_response.data = {"errcode": 40001, "errmsg": "invalid credential"}
mock_send_image.return_value = mock_response
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await send_wecom_image("test.jpg", mock_ctx)
# Assertions
assert "WeChat API error: invalid credential" in str(excinfo.value)
@pytest.mark.asyncio
async def test_send_wecom_image_with_webhook_error(mock_image_processing):
"""Test send_wecom_image function with webhook error."""
# Patch get_webhook_url to raise WeComError
with patch("wecom_bot_mcp_server.image.get_webhook_url") as mock_get_webhook_url:
mock_get_webhook_url.side_effect = WeComError("WECOM_WEBHOOK_URL environment variable not set")
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await send_wecom_image("test.jpg")
# Assertions
assert "WECOM_WEBHOOK_URL environment variable not set" in str(excinfo.value)
@pytest.mark.asyncio
async def test_send_wecom_image_with_webhook_error_and_context(mock_image_processing):
"""Test send_wecom_image function with webhook error and context."""
# Create mock context
mock_ctx = AsyncMock()
# Patch get_webhook_url to raise WeComError
with patch("wecom_bot_mcp_server.image.get_webhook_url") as mock_get_webhook_url:
mock_get_webhook_url.side_effect = WeComError("WECOM_WEBHOOK_URL environment variable not set")
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await send_wecom_image("test.jpg", mock_ctx)
# Assertions
assert "WECOM_WEBHOOK_URL environment variable not set" in str(excinfo.value)
mock_ctx.error.assert_called_once()
@pytest.mark.asyncio
@patch("wecom_bot_mcp_server.image.aiohttp.ClientSession")
async def test_download_image_invalid_content_type_with_context(mock_client_session):
"""Test download_image with invalid content type and context."""
# Setup mock context
mock_ctx = AsyncMock()
# Create a proper async context manager for ClientSession
mock_session = AsyncMock()
mock_session_cm = AsyncMock()
mock_session_cm.__aenter__.return_value = mock_session
mock_client_session.return_value = mock_session_cm
# Setup the response
mock_response = AsyncMock()
mock_response.status = 200
mock_response.headers = {"Content-Type": "text/html"}
mock_response.read = AsyncMock(return_value=b"invalid data")
# Create a proper async context manager for the response
mock_response_cm = AsyncMock()
mock_response_cm.__aenter__.return_value = mock_response
# Setup the get method to return the mock response context manager
mock_session.get.return_value = mock_response_cm
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await download_image("https://example.com/image.jpg", mock_ctx)
# Assertions
assert "Invalid content type: text/html" in str(excinfo.value)
mock_ctx.error.assert_called_once()
@pytest.mark.asyncio
async def test_send_wecom_image_with_webhook_error_and_context(mock_image_processing):
"""Test send_wecom_image function with webhook error and context."""
# Create mock context
mock_ctx = AsyncMock()
# Patch get_webhook_url to raise WeComError
with patch("wecom_bot_mcp_server.image.get_webhook_url") as mock_get_webhook_url:
mock_get_webhook_url.side_effect = WeComError("WECOM_WEBHOOK_URL environment variable not set")
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await send_wecom_image("test.jpg", mock_ctx)
# Assertions
assert "WECOM_WEBHOOK_URL environment variable not set" in str(excinfo.value)
assert mock_ctx.error.called
@pytest.mark.asyncio
async def test_download_image_invalid_content_type_with_context():
"""Test download_image with invalid content type and context."""
# Setup mock context
mock_ctx = AsyncMock()
# Setup mock response with invalid content type
with patch("wecom_bot_mcp_server.image.aiohttp.ClientSession") as mock_session_class:
# Create a mock session
mock_session = MagicMock()
# Setup the response
mock_response = MagicMock()
mock_response.status = 200
mock_response.headers = {"Content-Type": "text/html"}
mock_response.read = AsyncMock(return_value=b"invalid data")
# Setup the get method to return a context manager that returns the mock response
mock_get_cm = MagicMock()
mock_get_cm.__aenter__ = AsyncMock(return_value=mock_response)
mock_get_cm.__aexit__ = AsyncMock(return_value=None)
mock_session.get = MagicMock(return_value=mock_get_cm)
# Setup session context manager
mock_session_cm = MagicMock()
mock_session_cm.__aenter__ = AsyncMock(return_value=mock_session)
mock_session_cm.__aexit__ = AsyncMock(return_value=None)
mock_session_class.return_value = mock_session_cm
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await download_image("https://example.com/image.jpg", mock_ctx)
# Assertions
assert "Invalid content type: text/html" in str(excinfo.value)
mock_ctx.error.assert_called_once()
@pytest.mark.asyncio
async def test_download_image_http_error_with_context():
"""Test download_image with HTTP error and context."""
# Setup mock context
mock_ctx = AsyncMock()
# Setup mock response with HTTP error
with patch("wecom_bot_mcp_server.image.aiohttp.ClientSession") as mock_session_class:
# Create a mock session
mock_session = MagicMock()
# Setup the response
mock_response = MagicMock()
mock_response.status = 404
mock_response.read = AsyncMock(return_value=b"not found")
# Setup the get method to return a context manager that returns the mock response
mock_get_cm = MagicMock()
mock_get_cm.__aenter__ = AsyncMock(return_value=mock_response)
mock_get_cm.__aexit__ = AsyncMock(return_value=None)
mock_session.get = MagicMock(return_value=mock_get_cm)
# Setup session context manager
mock_session_cm = MagicMock()
mock_session_cm.__aenter__ = AsyncMock(return_value=mock_session)
mock_session_cm.__aexit__ = AsyncMock(return_value=None)
mock_session_class.return_value = mock_session_cm
# Call function with expected exception
with pytest.raises(WeComError) as excinfo:
await download_image("https://example.com/not-found.jpg", mock_ctx)
# Assertions
assert "Failed to download image: HTTP 404" in str(excinfo.value)
mock_ctx.error.assert_called_once()
mock_session.get.assert_called_once_with("https://example.com/not-found.jpg")