ForcePilot/backend/test/unit/channels/plugins/wechat_mp/conftest.py

122 lines
4.4 KiB
Python
Raw Normal View History

"""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()