"""channels plugins/wecom 层单元测试共享 fixture。 提供 ``fake_wecom_client`` 桩,模拟 ``WecomClient`` (``yuxi.channels.plugins.wecom.wecom_client.WecomClient``)。wecom 适配器 测试通过此桩注入客户端依赖,不发起真实 HTTP 请求。 另提供 ``_make_cache`` / ``_make_logger`` / ``_make_config`` 辅助工厂, 构造 ``CachePort`` / ``LoggerPort`` / ``ConfigPort`` 的 AsyncMock 桩,供 需要端口依赖的测试按需调用。 桩使用 ``AsyncMock()``,预设主要方法返回值,调用方可按需覆盖。方法签名 以 ``WecomClient`` 实现为准。单文件独有的 helper 应保留在对应测试文件内 (见 testing-guidelines.md)。 """ from __future__ import annotations from unittest.mock import AsyncMock import pytest from yuxi.channels.contract.dtos.option import Nothing @pytest.fixture def fake_wecom_client() -> AsyncMock: """WecomClient 桩。 ``AsyncMock()`` 模拟 ``WecomClient``,预设主要方法返回值: - ``send_message`` / ``send_kf_message`` / ``recall_message`` / ``update_template_card`` 返回 ``{"msgid": "msg-1", "errcode": 0}``; - ``upload_media`` 返回 ``{"media_id": "media-1", "errcode": 0}``; - ``download_media`` 返回 ``b"fake-content"``; - ``get_access_token`` 返回 ``"fake-token"``; - 通讯录 / 客户联系 / 客服 / OAuth API 返回 ``{}``。 """ client = AsyncMock() # Token 管理 client.get_access_token.return_value = "fake-token" client.verify_token.return_value = True client.get_token_with_credentials.return_value = ("fake-token", 7200) # 应用消息 API client.send_message.return_value = {"msgid": "msg-1", "errcode": 0} client.recall_message.return_value = {"msgid": "msg-1", "errcode": 0} client.update_template_card.return_value = {"msgid": "msg-1", "errcode": 0} # 资源上传/下载 client.upload_media.return_value = {"media_id": "media-1", "errcode": 0} client.download_media.return_value = b"fake-content" # 通讯录 API client.get_user.return_value = {} client.list_user_ids.return_value = {} client.convert_to_openid.return_value = {} # 客户联系 API client.get_external_contact.return_value = {} client.list_external_contacts.return_value = {} client.list_group_chats.return_value = {} client.get_group_chat.return_value = {} client.unionid_to_external_userid.return_value = {} # 微信客服 API client.send_kf_message.return_value = {"msgid": "msg-1", "errcode": 0} client.list_kf_accounts.return_value = {} # OAuth API client.get_oauth_user_info.return_value = {} client.get_user_detail.return_value = {} client.get_oauth_user_info_with_credentials.return_value = {} client.get_user_detail_with_credentials.return_value = {} # 其它 client.get_api_domain_ip.return_value = {} return client def _make_cache(get_value=None) -> AsyncMock: """构造 CachePort 桩。 Args: get_value: ``get`` 方法的返回值,默认为 ``Nothing()``。传入 ``Some(...)`` 可模拟命中缓存。 Returns: AsyncMock 模拟 ``CachePort``,``get`` 默认返回 ``Nothing()``, ``set`` / ``delete`` 等方法为无返回值的 AsyncMock。 """ cache = AsyncMock() cache.get.return_value = Nothing() if get_value is None else get_value cache.set.return_value = None cache.delete.return_value = None return cache def _make_logger() -> AsyncMock: """构造 LoggerPort 桩。 Returns: AsyncMock 模拟 ``LoggerPort``,``debug`` / ``info`` / ``warning`` / ``error`` 等方法为无返回值的 AsyncMock。 """ logger = AsyncMock() logger.debug.return_value = None logger.info.return_value = None logger.warning.return_value = None logger.error.return_value = None return logger def _make_config(config_value=None) -> AsyncMock: """构造 ConfigPort 桩。 Args: config_value: ``get`` 方法返回的 ConfigValue mock。默认构造一个带 ``.value`` 属性的 mock,调用方可传入自定义 mock 覆盖。 Returns: AsyncMock 模拟 ``ConfigPort``,``get`` 默认返回带 ``.value`` 的 ConfigValue mock。 """ config = AsyncMock() if config_value is None: cv = AsyncMock() cv.value = None config.get.return_value = cv else: config.get.return_value = config_value return config