ForcePilot/backend/test/unit/channels/test_wechat_media.py
Kris 3264900bc9 test: 新增多渠道单元测试用例并配置测试环境变量
新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块
同时在测试配置中添加了测试用的OpenAI API密钥环境变量
2026-05-12 00:56:47 +08:00

273 lines
9.9 KiB
Python

from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock, patch
import httpx
import pytest
from yuxi.channels.models import DeliveryResult
class TestSendMediaAdapter:
@pytest.mark.asyncio
async def test_send_media_not_initialized_mode(self):
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
adapter._mode = "personal"
adapter._http_client = AsyncMock()
result = await adapter.send_media("chat1", "image", b"\x89PNG")
assert result.success is False
assert "not supported" in result.error
@pytest.mark.asyncio
async def test_send_media_wecom_image_bytes_required(self):
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
adapter._mode = "wecom"
adapter._wecom_client = MagicMock()
result = await adapter._send_wecom_media("image", "not_bytes", "user1")
assert result.success is False
assert "raw bytes" in result.error
@pytest.mark.asyncio
async def test_send_media_wecom_image_success(self):
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
adapter._mode = "wecom"
adapter._http_client = AsyncMock()
adapter.config = {"agent_id": "1000001", "dm_policy": "open"}
mock_client = MagicMock()
mock_client.upload_media = AsyncMock(return_value="media_123")
adapter._wecom_client = mock_client
with patch(
"yuxi.channels.adapters.wechat.wecom.send.send_wecom_message",
new=AsyncMock(return_value=DeliveryResult(success=True)),
) as mock_send:
result = await adapter._send_wecom_media("image", b"\x89PNG\x00", "user1")
assert result.success is True
mock_client.upload_media.assert_called_once()
mock_send.assert_called_once()
payload = mock_send.call_args[0][2]
assert payload["msgtype"] == "image"
assert payload["image"]["media_id"] == "media_123"
@pytest.mark.asyncio
async def test_send_media_wecom_file_success(self):
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
adapter._mode = "wecom"
adapter._http_client = AsyncMock()
adapter.config = {"agent_id": "1000001", "dm_policy": "open"}
mock_client = MagicMock()
mock_client.upload_media = AsyncMock(return_value="file_456")
adapter._wecom_client = mock_client
with patch(
"yuxi.channels.adapters.wechat.wecom.send.send_wecom_message",
new=AsyncMock(return_value=DeliveryResult(success=True)),
) as mock_send:
result = await adapter._send_wecom_media("file", b"PDF_CONTENT", "user1")
assert result.success is True
payload = mock_send.call_args[0][2]
assert payload["msgtype"] == "file"
assert payload["file"]["media_id"] == "file_456"
@pytest.mark.asyncio
async def test_send_media_wecom_unsupported_type(self):
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
adapter._mode = "wecom"
adapter.config = {"agent_id": "1000001", "dm_policy": "open"}
mock_client = MagicMock()
mock_client.upload_media = AsyncMock(return_value="voice_789")
adapter._wecom_client = mock_client
adapter._http_client = AsyncMock()
result = await adapter._send_wecom_media("voice", b"audio_data", "user1")
assert result.success is False
assert "Unsupported media_type" in result.error
@pytest.mark.asyncio
async def test_send_media_wecom_upload_failure(self):
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
adapter._mode = "wecom"
adapter.config = {"agent_id": "1000001", "dm_policy": "open"}
mock_client = MagicMock()
mock_client.upload_media = AsyncMock(side_effect=RuntimeError("Network error"))
adapter._wecom_client = mock_client
result = await adapter._send_wecom_media("image", b"img", "user1")
assert result.success is False
assert "upload failed" in result.error
@pytest.mark.asyncio
async def test_send_media_mp_not_bytes(self):
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
adapter._mode = "mp"
adapter._mp_client = MagicMock()
result = await adapter._send_mp_media("image", "not_bytes", "user1")
assert result.success is False
assert "raw bytes" in result.error
@pytest.mark.asyncio
async def test_send_media_mp_unsupported_type(self):
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
adapter._mode = "mp"
adapter._mp_client = MagicMock()
result = await adapter._send_mp_media("file", b"data", "user1")
assert result.success is False
assert "only supports image" in result.error
@pytest.mark.asyncio
async def test_send_media_mp_image_success(self):
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
adapter._mode = "mp"
adapter._http_client = AsyncMock()
with patch(
"yuxi.channels.adapters.wechat.mp.send.send_mp_image",
new=AsyncMock(return_value=DeliveryResult(success=True)),
) as mock_send:
result = await adapter._send_mp_media("image", b"\x89PNG", "openid123")
assert result.success is True
mock_send.assert_called_once()
class TestWeComMediaPayload:
def test_build_image_payload(self):
from yuxi.channels.adapters.wechat.wecom.send import build_wecom_image_payload
payload = build_wecom_image_payload("agent1", "user123", "media_abc")
assert payload["touser"] == "user123"
assert payload["msgtype"] == "image"
assert payload["agentid"] == "agent1"
assert payload["image"]["media_id"] == "media_abc"
def test_build_file_payload(self):
from yuxi.channels.adapters.wechat.wecom.send import build_wecom_file_payload
payload = build_wecom_file_payload("agent1", "user123", "media_xyz")
assert payload["msgtype"] == "file"
assert payload["file"]["media_id"] == "media_xyz"
class TestRetryHelper:
@pytest.mark.asyncio
async def test_retry_success_first_attempt(self):
from yuxi.channels.adapters.wechat.retry import retry_with_backoff
call_count = 0
async def succeed():
nonlocal call_count
call_count += 1
return "ok"
result = await retry_with_backoff(succeed, max_retries=3, base_delay=0.01)
assert result == "ok"
assert call_count == 1
@pytest.mark.asyncio
async def test_retry_timeout_retries(self):
from yuxi.channels.adapters.wechat.retry import retry_with_backoff
fail_count = 0
async def flaky():
nonlocal fail_count
fail_count += 1
if fail_count < 2:
raise httpx.TimeoutException("timeout")
return "recovered"
result = await retry_with_backoff(flaky, max_retries=3, base_delay=0.01)
assert result == "recovered"
assert fail_count == 2
@pytest.mark.asyncio
async def test_retry_timeout_exhausted(self):
from yuxi.channels.adapters.wechat.retry import retry_with_backoff
async def always_fail():
raise httpx.TimeoutException("always timeout")
with pytest.raises(httpx.TimeoutException):
await retry_with_backoff(always_fail, max_retries=2, base_delay=0.01)
@pytest.mark.asyncio
async def test_retry_no_retry_on_non_retryable(self):
from yuxi.channels.adapters.wechat.retry import retry_with_backoff
async def auth_fail():
raise ValueError("auth error")
with pytest.raises(ValueError):
await retry_with_backoff(auth_fail, max_retries=3, base_delay=0.01)
@pytest.mark.asyncio
async def test_retry_backoff_increasing_delay(self):
from yuxi.channels.adapters.wechat.retry import retry_with_backoff
delays = []
async def fail_then_ok():
delays.append(len(delays))
if len(delays) >= 3:
return "ok"
raise httpx.ConnectError("refused")
result = await retry_with_backoff(fail_then_ok, max_retries=4, base_delay=0.01)
assert result == "ok"
assert delays == [0, 1, 2]
class TestMediaExtensionHelper:
def test_image_extension(self):
from yuxi.channels.adapters.wechat.adapter import _media_extension
assert _media_extension("image") == "png"
def test_voice_extension(self):
from yuxi.channels.adapters.wechat.adapter import _media_extension
assert _media_extension("voice") == "mp3"
def test_video_extension(self):
from yuxi.channels.adapters.wechat.adapter import _media_extension
assert _media_extension("video") == "mp4"
def test_unknown_extension(self):
from yuxi.channels.adapters.wechat.adapter import _media_extension
assert _media_extension("unknown") == "bin"
def test_file_extension(self):
from yuxi.channels.adapters.wechat.adapter import _media_extension
assert _media_extension("file") == "bin"