diff --git a/src/langbot/pkg/platform/sources/wecomcs.py b/src/langbot/pkg/platform/sources/wecomcs.py index 9af809f7d..ea7d8ef5c 100644 --- a/src/langbot/pkg/platform/sources/wecomcs.py +++ b/src/langbot/pkg/platform/sources/wecomcs.py @@ -2,6 +2,7 @@ from __future__ import annotations import typing import asyncio import traceback +import uuid import datetime import pydantic @@ -182,7 +183,28 @@ class WecomCSAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): ) async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain): - pass + if target_type != 'person': + raise ValueError('WeCom customer service only supports sending messages to person targets') + + open_kfid = self.bot_account_id + external_userid = target_id + if '|' in target_id: + open_kfid, external_userid = target_id.split('|', 1) + if external_userid.startswith('u'): + external_userid = external_userid[1:] + if not open_kfid: + raise ValueError('WeCom customer service open_kfid is required before sending messages') + + content_list = await WecomMessageConverter.yiri2target(message, self.bot) + for content in content_list: + msgid = f'langbot_{uuid.uuid4().hex}' + if content['type'] == 'text': + await self.bot.send_text_msg( + open_kfid=open_kfid, + external_userid=external_userid, + msgid=msgid, + content=content['content'], + ) def set_bot_uuid(self, bot_uuid: str): """设置 bot UUID(用于生成 webhook URL)""" diff --git a/tests/unit_tests/platform/test_wecomcs_adapter.py b/tests/unit_tests/platform/test_wecomcs_adapter.py new file mode 100644 index 000000000..ee3425b61 --- /dev/null +++ b/tests/unit_tests/platform/test_wecomcs_adapter.py @@ -0,0 +1,91 @@ +from types import SimpleNamespace +from unittest.mock import AsyncMock + +import pytest + +import langbot_plugin.api.definition.abstract.platform.event_logger as abstract_platform_logger +import langbot_plugin.api.entities.builtin.platform.message as platform_message +from langbot.pkg.platform.sources.wecomcs import WecomCSAdapter + + +class DummyLogger(abstract_platform_logger.AbstractEventLogger): + async def info(self, *args, **kwargs): + pass + + async def debug(self, *args, **kwargs): + pass + + async def warning(self, *args, **kwargs): + pass + + async def error(self, *args, **kwargs): + pass + + +def make_adapter(): + return WecomCSAdapter( + config={ + 'corpid': 'corp-id', + 'secret': 'secret', + 'token': 'token', + 'EncodingAESKey': 'encoding-key', + }, + logger=DummyLogger(), + ) + + +@pytest.mark.asyncio +async def test_send_message_sends_text_to_customer_service_user(): + adapter = make_adapter() + adapter.bot_account_id = 'kf-test' + adapter.bot = SimpleNamespace(send_text_msg=AsyncMock()) + + message = platform_message.MessageChain([platform_message.Plain(text='hello')]) + + await adapter.send_message('person', 'uexternal-user', message) + + adapter.bot.send_text_msg.assert_awaited_once() + kwargs = adapter.bot.send_text_msg.await_args.kwargs + assert kwargs['open_kfid'] == 'kf-test' + assert kwargs['external_userid'] == 'external-user' + assert kwargs['content'] == 'hello' + assert kwargs['msgid'].startswith('langbot_') + + +@pytest.mark.asyncio +async def test_send_message_allows_explicit_open_kfid_in_target_id(): + adapter = make_adapter() + adapter.bot = SimpleNamespace(send_text_msg=AsyncMock()) + + message = platform_message.MessageChain([platform_message.Plain(text='hello')]) + + await adapter.send_message('person', 'kf-explicit|uexternal-user', message) + + kwargs = adapter.bot.send_text_msg.await_args.kwargs + assert kwargs['open_kfid'] == 'kf-explicit' + assert kwargs['external_userid'] == 'external-user' + + +@pytest.mark.asyncio +async def test_send_message_requires_open_kfid(): + adapter = make_adapter() + adapter.bot = SimpleNamespace(send_text_msg=AsyncMock()) + message = platform_message.MessageChain([platform_message.Plain(text='hello')]) + + with pytest.raises(ValueError, match='open_kfid is required'): + await adapter.send_message('person', 'uexternal-user', message) + + adapter.bot.send_text_msg.assert_not_called() + + +@pytest.mark.asyncio +async def test_send_message_rejects_group_targets(): + adapter = make_adapter() + adapter.bot_account_id = 'kf-test' + adapter.bot = SimpleNamespace(send_text_msg=AsyncMock()) + message = platform_message.MessageChain([platform_message.Plain(text='hello')]) + + with pytest.raises(ValueError, match='only supports sending messages to person'): + await adapter.send_message('group', 'group-id', message) + + adapter.bot.send_text_msg.assert_not_called()