1. 新增QQBot、Wecom、Dingtalk、Instagram、WechatMp等渠道的单元测试夹具与测试用例 2. 修复WechatILink的缓存键顺序与测试用例 3. 补全Feishu适配器的日志断言与测试逻辑 4. 清理WechatILink冗余的clear_context_token测试代码
122 lines
4.4 KiB
Python
122 lines
4.4 KiB
Python
"""channels plugins/wechat_mp 层单元测试共享 fixture。
|
||
|
||
提供 ``fake_wechat_mp_client`` 桩,模拟 ``WeChatMpClient``
|
||
(``yuxi.channels.plugins.wechat_mp.wechat_mp_client.WeChatMpClient``)。
|
||
微信公众号适配器测试通过此桩注入客户端依赖,不发起真实 HTTP 请求。
|
||
|
||
桩使用 ``AsyncMock()``,预设主要方法返回值(errcode=0 响应 / media_id /
|
||
token / 用户信息等),调用方可按需覆盖。方法签名以 ``WeChatMpClient``
|
||
实现为准。单文件独有的 helper 应保留在对应测试文件内
|
||
(见 testing-guidelines.md)。
|
||
|
||
同时提供 ``fake_config_port`` / ``fake_cache_port`` / ``fake_logger_port`` /
|
||
``fake_persistence_port`` 端口桩,供适配器测试注入 DI 依赖。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from unittest.mock import AsyncMock, MagicMock
|
||
|
||
import pytest
|
||
|
||
|
||
@pytest.fixture
|
||
def fake_wechat_mp_client() -> AsyncMock:
|
||
"""WeChatMpClient 桩。
|
||
|
||
``AsyncMock()`` 模拟 ``WeChatMpClient``,预设主要方法返回值:
|
||
- ``send_custom_message`` 返回 ``{"errcode": 0, "msgid": "msg_123"}``;
|
||
- ``upload_media_cached`` / ``upload_media`` 返回 ``"media_id_abc"``;
|
||
- ``download_media`` 返回 ``b"fake_image_bytes"``;
|
||
- ``get_user_info`` 返回含 openid / nickname / headimgurl 的字典;
|
||
- ``get_user_list`` 返回含 total / count / data / next_openid 的字典;
|
||
- ``get_callback_ip`` 返回 ``{"ip_list": ["127.0.0.1"]}``;
|
||
- ``refresh_access_token`` / ``get_access_token`` 返回 token 字符串;
|
||
- ``check_customer_service_window`` / ``consume_customer_service_quota``
|
||
返回 ``True``;
|
||
- ``mark_user_message`` / ``mark_other_event`` / ``set_active`` /
|
||
``attach_http_client`` / ``detach_http_client`` 返回 ``None``;
|
||
- ``drain`` 返回 ``True``。
|
||
"""
|
||
client = AsyncMock()
|
||
# 客服消息发送
|
||
client.send_custom_message.return_value = {"errcode": 0, "msgid": "msg_123"}
|
||
# 媒体上传/下载
|
||
client.upload_media_cached.return_value = "media_id_abc"
|
||
client.upload_media.return_value = "media_id_abc"
|
||
client.download_media.return_value = b"fake_image_bytes"
|
||
# 用户信息查询
|
||
client.get_user_info.return_value = {
|
||
"openid": "o123",
|
||
"nickname": "TestUser",
|
||
"headimgurl": "http://example.com/avatar.jpg",
|
||
}
|
||
client.get_user_list.return_value = {
|
||
"total": 1,
|
||
"count": 1,
|
||
"data": {"openid": ["o123", "o456"]},
|
||
"next_openid": "o456",
|
||
}
|
||
# 通用 API
|
||
client.get_callback_ip.return_value = {"ip_list": ["127.0.0.1"]}
|
||
# access_token 中控
|
||
client.refresh_access_token.return_value = "fake_access_token"
|
||
client.get_access_token.return_value = "fake_access_token"
|
||
# 客服消息窗口与额度
|
||
client.check_customer_service_window.return_value = True
|
||
client.consume_customer_service_quota.return_value = True
|
||
client.mark_user_message.return_value = None
|
||
client.mark_other_event.return_value = None
|
||
# 生命周期管理
|
||
client.set_active.return_value = None
|
||
client.drain.return_value = True
|
||
client.attach_http_client.return_value = None
|
||
client.detach_http_client.return_value = None
|
||
return client
|
||
|
||
|
||
@pytest.fixture
|
||
def fake_config_port() -> AsyncMock:
|
||
"""ConfigPort 桩。
|
||
|
||
``ConfigPort`` 的 ``get`` 方法为异步,使用 ``AsyncMock()``。
|
||
默认返回 ``None``,调用方可按需覆盖。
|
||
"""
|
||
return AsyncMock()
|
||
|
||
|
||
@pytest.fixture
|
||
def fake_cache_port() -> AsyncMock:
|
||
"""CachePort 桩。
|
||
|
||
``CachePort`` 的 ``get`` / ``set`` / ``delete`` / ``acquireAdvisoryLock``
|
||
等方法为异步,使用 ``AsyncMock()``。默认返回 ``None``,调用方可按需覆盖。
|
||
"""
|
||
return AsyncMock()
|
||
|
||
|
||
@pytest.fixture
|
||
def fake_logger_port() -> MagicMock:
|
||
"""LoggerPort 桩。
|
||
|
||
``LoggerPort`` 的 ``info`` / ``warn`` / ``error`` / ``exception`` /
|
||
``debug`` 方法为异步,使用 ``MagicMock()`` + 按方法挂 ``AsyncMock()``
|
||
以精确模拟同步/异步混合的端口协议。
|
||
"""
|
||
logger = MagicMock()
|
||
logger.debug = AsyncMock()
|
||
logger.info = AsyncMock()
|
||
logger.warn = AsyncMock()
|
||
logger.error = AsyncMock()
|
||
logger.exception = AsyncMock()
|
||
return logger
|
||
|
||
|
||
@pytest.fixture
|
||
def fake_persistence_port() -> AsyncMock:
|
||
"""PersistencePort 桩。
|
||
|
||
``PersistencePort`` 的方法为异步,使用 ``AsyncMock()``。
|
||
"""
|
||
return AsyncMock()
|