504 lines
19 KiB
Python
504 lines
19 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from yuxi.channels.models import DeliveryResult
|
||
|
|
|
||
|
|
|
||
|
|
class TestMessageActionAdapterWithAdapter:
|
||
|
|
def test_get_handler_returns_none_without_adapter(self):
|
||
|
|
from yuxi.channels.adapters.wechat.message_actions import WeChatMessageActionAdapter
|
||
|
|
|
||
|
|
adapter = WeChatMessageActionAdapter()
|
||
|
|
assert adapter.get_handler("send") is None
|
||
|
|
assert adapter.get_handler("reply") is None
|
||
|
|
assert adapter.get_handler("react") is None
|
||
|
|
|
||
|
|
def test_get_handler_returns_callable_with_adapter(self):
|
||
|
|
from yuxi.channels.adapters.wechat.message_actions import WeChatMessageActionAdapter
|
||
|
|
|
||
|
|
mock_wechat = MagicMock()
|
||
|
|
mock_wechat.channel_id = "wechat"
|
||
|
|
mock_wechat.channel_type = "wechat"
|
||
|
|
|
||
|
|
action_adapter = WeChatMessageActionAdapter(mock_wechat)
|
||
|
|
|
||
|
|
handler = action_adapter.get_handler("send")
|
||
|
|
assert handler is not None
|
||
|
|
assert callable(handler)
|
||
|
|
|
||
|
|
handler = action_adapter.get_handler("reply")
|
||
|
|
assert handler is not None
|
||
|
|
assert callable(handler)
|
||
|
|
|
||
|
|
handler = action_adapter.get_handler("sendAttachment")
|
||
|
|
assert handler is not None
|
||
|
|
assert callable(handler)
|
||
|
|
|
||
|
|
def test_get_handler_returns_none_for_unsupported_actions(self):
|
||
|
|
from yuxi.channels.adapters.wechat.message_actions import WeChatMessageActionAdapter
|
||
|
|
|
||
|
|
mock_wechat = MagicMock()
|
||
|
|
mock_wechat.channel_id = "wechat"
|
||
|
|
mock_wechat.channel_type = "wechat"
|
||
|
|
|
||
|
|
action_adapter = WeChatMessageActionAdapter(mock_wechat)
|
||
|
|
|
||
|
|
assert action_adapter.get_handler("react") is None
|
||
|
|
assert action_adapter.get_handler("edit") is None
|
||
|
|
assert action_adapter.get_handler("unsend") is None
|
||
|
|
assert action_adapter.get_handler("delete") is None
|
||
|
|
|
||
|
|
def test_is_supported_works_without_adapter(self):
|
||
|
|
from yuxi.channels.adapters.wechat.message_actions import WeChatMessageActionAdapter
|
||
|
|
|
||
|
|
assert WeChatMessageActionAdapter.is_supported("send") is True
|
||
|
|
assert WeChatMessageActionAdapter.is_supported("reply") is True
|
||
|
|
assert WeChatMessageActionAdapter.is_supported("react") is False
|
||
|
|
assert WeChatMessageActionAdapter.is_supported("edit") is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_send_handler_calls_adapter_send(self):
|
||
|
|
from yuxi.channels.adapters.wechat.message_actions import WeChatMessageActionAdapter
|
||
|
|
|
||
|
|
mock_wechat = MagicMock()
|
||
|
|
mock_wechat.channel_id = "wechat"
|
||
|
|
mock_wechat.channel_type = "wechat"
|
||
|
|
mock_wechat.send = AsyncMock(return_value=DeliveryResult(success=True))
|
||
|
|
|
||
|
|
action_adapter = WeChatMessageActionAdapter(mock_wechat)
|
||
|
|
handler = action_adapter.get_handler("send")
|
||
|
|
|
||
|
|
result = await handler(chat_id="test_chat", content="Hello World", channel_user_id="user_123")
|
||
|
|
|
||
|
|
assert result["success"] is True
|
||
|
|
mock_wechat.send.assert_called_once()
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_send_handler_fails_without_chat_id(self):
|
||
|
|
from yuxi.channels.adapters.wechat.message_actions import WeChatMessageActionAdapter
|
||
|
|
|
||
|
|
mock_wechat = MagicMock()
|
||
|
|
mock_wechat.channel_id = "wechat"
|
||
|
|
mock_wechat.channel_type = "wechat"
|
||
|
|
|
||
|
|
action_adapter = WeChatMessageActionAdapter(mock_wechat)
|
||
|
|
handler = action_adapter.get_handler("send")
|
||
|
|
|
||
|
|
result = await handler(content="Hello World")
|
||
|
|
assert result["success"] is False
|
||
|
|
assert "missing chat_id" in result["error"]
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_reply_handler_calls_adapter_send_with_reply_metadata(self):
|
||
|
|
from yuxi.channels.adapters.wechat.message_actions import WeChatMessageActionAdapter
|
||
|
|
|
||
|
|
mock_wechat = MagicMock()
|
||
|
|
mock_wechat.channel_id = "wechat"
|
||
|
|
mock_wechat.channel_type = "wechat"
|
||
|
|
mock_wechat.send = AsyncMock(return_value=DeliveryResult(success=True))
|
||
|
|
|
||
|
|
action_adapter = WeChatMessageActionAdapter(mock_wechat)
|
||
|
|
handler = action_adapter.get_handler("reply")
|
||
|
|
|
||
|
|
result = await handler(
|
||
|
|
chat_id="test_chat",
|
||
|
|
content="Reply message",
|
||
|
|
channel_user_id="user_123",
|
||
|
|
reply_to_msg_id="msg_456",
|
||
|
|
reply_to_channel_user_id="sender_789",
|
||
|
|
chat_type="direct",
|
||
|
|
)
|
||
|
|
|
||
|
|
assert result["success"] is True
|
||
|
|
mock_wechat.send.assert_called_once()
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_download_file_handler_calls_download_media(self):
|
||
|
|
from yuxi.channels.adapters.wechat.message_actions import WeChatMessageActionAdapter
|
||
|
|
|
||
|
|
mock_data = b"fake_media_data"
|
||
|
|
mock_wechat = MagicMock()
|
||
|
|
mock_wechat.channel_id = "wechat"
|
||
|
|
mock_wechat.channel_type = "wechat"
|
||
|
|
mock_wechat.download_media = AsyncMock(return_value=mock_data)
|
||
|
|
|
||
|
|
action_adapter = WeChatMessageActionAdapter(mock_wechat)
|
||
|
|
handler = action_adapter.get_handler("download-file")
|
||
|
|
|
||
|
|
result = await handler(file_id="media_123")
|
||
|
|
assert result["success"] is True
|
||
|
|
assert result["data"] == mock_data
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_download_file_handler_fails_without_file_id(self):
|
||
|
|
from yuxi.channels.adapters.wechat.message_actions import WeChatMessageActionAdapter
|
||
|
|
|
||
|
|
mock_wechat = MagicMock()
|
||
|
|
action_adapter = WeChatMessageActionAdapter(mock_wechat)
|
||
|
|
handler = action_adapter.get_handler("download-file")
|
||
|
|
|
||
|
|
result = await handler()
|
||
|
|
assert result["success"] is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_read_handler_calls_read_message(self):
|
||
|
|
from yuxi.channels.adapters.wechat.message_actions import WeChatMessageActionAdapter
|
||
|
|
|
||
|
|
mock_wechat = MagicMock()
|
||
|
|
mock_wechat.channel_id = "wechat"
|
||
|
|
mock_wechat.channel_type = "wechat"
|
||
|
|
mock_wechat.read_message = AsyncMock(return_value={"msg_id": "123", "available": True})
|
||
|
|
|
||
|
|
action_adapter = WeChatMessageActionAdapter(mock_wechat)
|
||
|
|
handler = action_adapter.get_handler("read")
|
||
|
|
|
||
|
|
result = await handler(msg_id="123")
|
||
|
|
assert result["available"] is True
|
||
|
|
|
||
|
|
def test_list_supported_actions_static(self):
|
||
|
|
from yuxi.channels.adapters.wechat.message_actions import WeChatMessageActionAdapter
|
||
|
|
|
||
|
|
supported = WeChatMessageActionAdapter.list_supported_actions()
|
||
|
|
assert "send" in supported
|
||
|
|
assert "reply" in supported
|
||
|
|
assert "read" in supported
|
||
|
|
assert "react" not in supported
|
||
|
|
assert "edit" not in supported
|
||
|
|
|
||
|
|
def test_list_all_actions_static(self):
|
||
|
|
from yuxi.channels.adapters.wechat.message_actions import WeChatMessageActionAdapter
|
||
|
|
|
||
|
|
all_actions = WeChatMessageActionAdapter.list_all_actions()
|
||
|
|
assert len(all_actions) == 56
|
||
|
|
assert "send" in all_actions
|
||
|
|
assert "react" in all_actions
|
||
|
|
|
||
|
|
|
||
|
|
class TestWeChatHandleAction:
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_handle_action_delegates_to_get_handler(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
adapter = WeChatAdapter(config={})
|
||
|
|
adapter._mode = "wecom"
|
||
|
|
|
||
|
|
mock_client = MagicMock()
|
||
|
|
mock_client.get_access_token = AsyncMock(return_value="test_token")
|
||
|
|
adapter._wecom_client = mock_client
|
||
|
|
adapter._http_client = AsyncMock()
|
||
|
|
|
||
|
|
from yuxi.channels.adapters.wechat.wecom import WeComMonitor
|
||
|
|
|
||
|
|
class FakeMonitor:
|
||
|
|
async def start(self, cb):
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def stop(self):
|
||
|
|
pass
|
||
|
|
|
||
|
|
adapter._monitor = FakeMonitor()
|
||
|
|
|
||
|
|
adapter._message_actions = MagicMock()
|
||
|
|
mock_handler = AsyncMock(return_value={"success": True})
|
||
|
|
adapter._message_actions.get_handler.return_value = mock_handler
|
||
|
|
adapter._message_actions.is_supported.return_value = True
|
||
|
|
|
||
|
|
from yuxi.channels.protocols.actions import ActionContext
|
||
|
|
|
||
|
|
ctx = ActionContext(
|
||
|
|
action="send",
|
||
|
|
channel_id="wechat",
|
||
|
|
chat_id="test_chat",
|
||
|
|
msg_id="msg_001",
|
||
|
|
args={"content": "Hello", "channel_user_id": "user_1"},
|
||
|
|
)
|
||
|
|
result = await adapter.handle_action(ctx)
|
||
|
|
|
||
|
|
assert result.success is True
|
||
|
|
adapter._message_actions.get_handler.assert_called_once_with("send")
|
||
|
|
mock_handler.assert_called_once()
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_handle_action_returns_error_for_unsupported_action(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
adapter = WeChatAdapter(config={})
|
||
|
|
adapter._message_actions = MagicMock()
|
||
|
|
adapter._message_actions.get_handler.return_value = None
|
||
|
|
adapter._message_actions.is_supported.return_value = False
|
||
|
|
|
||
|
|
from yuxi.channels.protocols.actions import ActionContext
|
||
|
|
|
||
|
|
ctx = ActionContext(
|
||
|
|
action="react",
|
||
|
|
channel_id="wechat",
|
||
|
|
chat_id="test_chat",
|
||
|
|
msg_id="msg_001",
|
||
|
|
args={"emoji": "👍"},
|
||
|
|
)
|
||
|
|
result = await adapter.handle_action(ctx)
|
||
|
|
|
||
|
|
assert result.success is False
|
||
|
|
assert "not supported" in result.error
|
||
|
|
|
||
|
|
def test_supports_action(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
adapter = WeChatAdapter(config={})
|
||
|
|
adapter._message_actions = MagicMock()
|
||
|
|
adapter._message_actions.is_supported.side_effect = lambda action: action == "send"
|
||
|
|
|
||
|
|
assert adapter.supports_action("send") is True
|
||
|
|
assert adapter.supports_action("react") is False
|
||
|
|
|
||
|
|
def test_resolve_execution_mode(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
adapter = WeChatAdapter(config={})
|
||
|
|
assert adapter.resolve_execution_mode("send") == "direct"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_handle_action_handler_exception(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
adapter = WeChatAdapter(config={})
|
||
|
|
adapter._message_actions = MagicMock()
|
||
|
|
async def failing_handler(**kwargs):
|
||
|
|
raise RuntimeError("boom")
|
||
|
|
adapter._message_actions.get_handler.return_value = failing_handler
|
||
|
|
adapter._message_actions.is_supported.return_value = True
|
||
|
|
|
||
|
|
from yuxi.channels.protocols.actions import ActionContext
|
||
|
|
|
||
|
|
ctx = ActionContext(
|
||
|
|
action="send",
|
||
|
|
channel_id="wechat",
|
||
|
|
chat_id="test_chat",
|
||
|
|
msg_id="msg_001",
|
||
|
|
args={},
|
||
|
|
)
|
||
|
|
result = await adapter.handle_action(ctx)
|
||
|
|
|
||
|
|
assert result.success is False
|
||
|
|
assert "boom" in result.error
|
||
|
|
|
||
|
|
|
||
|
|
class TestWeChatAuthAdapter:
|
||
|
|
def test_get_dm_exposure(self):
|
||
|
|
from yuxi.channels.adapters.wechat.auth_adapter import WeChatAuthAdapter
|
||
|
|
|
||
|
|
auth = WeChatAuthAdapter()
|
||
|
|
assert auth.get_dm_exposure({}) == "pairing"
|
||
|
|
assert auth.get_dm_exposure({"dm_policy": "allowlist"}) == "allowlist"
|
||
|
|
assert auth.get_dm_exposure({"dm_policy": "open"}) == "open"
|
||
|
|
|
||
|
|
def test_get_authorization_url_with_config(self):
|
||
|
|
from yuxi.channels.adapters.wechat.auth_adapter import WeChatAuthAdapter
|
||
|
|
|
||
|
|
auth = WeChatAuthAdapter()
|
||
|
|
assert auth.get_authorization_url({}) is None
|
||
|
|
assert auth.get_authorization_url({"wecom_auth_url": "https://example.com/auth"}) == "https://example.com/auth"
|
||
|
|
|
||
|
|
def test_build_wecom_oauth_url(self):
|
||
|
|
from yuxi.channels.adapters.wechat.auth_adapter import WeChatAuthAdapter
|
||
|
|
|
||
|
|
auth = WeChatAuthAdapter()
|
||
|
|
url = auth.build_wecom_oauth_url("corp_123", "https://myapp.com/callback", state="state_456")
|
||
|
|
|
||
|
|
assert "open.weixin.qq.com" in url
|
||
|
|
assert "appid=corp_123" in url
|
||
|
|
assert "redirect_uri=https://myapp.com/callback" in url
|
||
|
|
assert "state=state_456" in url
|
||
|
|
assert url.endswith("#wechat_redirect")
|
||
|
|
|
||
|
|
def test_build_wecom_oauth_url_without_state(self):
|
||
|
|
from yuxi.channels.adapters.wechat.auth_adapter import WeChatAuthAdapter
|
||
|
|
|
||
|
|
auth = WeChatAuthAdapter()
|
||
|
|
url = auth.build_wecom_oauth_url("corp_123", "https://myapp.com/callback")
|
||
|
|
|
||
|
|
assert "state" not in url or url.endswith("#wechat_redirect")
|
||
|
|
|
||
|
|
def test_build_mp_oauth_url(self):
|
||
|
|
from yuxi.channels.adapters.wechat.auth_adapter import WeChatAuthAdapter
|
||
|
|
|
||
|
|
auth = WeChatAuthAdapter()
|
||
|
|
url = auth.build_mp_oauth_url("wx_app_123", "https://myapp.com/callback", state="state_789", scope="snsapi_userinfo")
|
||
|
|
|
||
|
|
assert "open.weixin.qq.com" in url
|
||
|
|
assert "appid=wx_app_123" in url
|
||
|
|
assert "scope=snsapi_userinfo" in url
|
||
|
|
assert "state=state_789" in url
|
||
|
|
|
||
|
|
def test_build_mp_oauth_url_default_scope(self):
|
||
|
|
from yuxi.channels.adapters.wechat.auth_adapter import WeChatAuthAdapter
|
||
|
|
|
||
|
|
auth = WeChatAuthAdapter()
|
||
|
|
url = auth.build_mp_oauth_url("wx_app_123", "https://myapp.com/callback")
|
||
|
|
|
||
|
|
assert "scope=snsapi_userinfo" in url
|
||
|
|
|
||
|
|
def test_get_oauth_enabled(self):
|
||
|
|
from yuxi.channels.adapters.wechat.auth_adapter import WeChatAuthAdapter
|
||
|
|
|
||
|
|
auth = WeChatAuthAdapter()
|
||
|
|
assert auth.get_oauth_enabled({}) is False
|
||
|
|
assert auth.get_oauth_enabled({"wecom_oauth_enabled": True}) is True
|
||
|
|
assert auth.get_oauth_enabled({"mp_oauth_enabled": True}) is True
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_exchange_wecom_code_success(self):
|
||
|
|
from yuxi.channels.adapters.wechat.auth_adapter import WeChatAuthAdapter
|
||
|
|
|
||
|
|
auth = WeChatAuthAdapter()
|
||
|
|
|
||
|
|
with patch("httpx.AsyncClient") as mock_client_cls:
|
||
|
|
mock_client = AsyncMock()
|
||
|
|
mock_client_cls.return_value.__aenter__.return_value = mock_client
|
||
|
|
|
||
|
|
token_resp = MagicMock()
|
||
|
|
token_resp.json.return_value = {"access_token": "token_abc", "expires_in": 7200}
|
||
|
|
user_resp = MagicMock()
|
||
|
|
user_resp.json.return_value = {"errcode": 0, "UserId": "user_123"}
|
||
|
|
|
||
|
|
mock_client.get.side_effect = [token_resp, user_resp]
|
||
|
|
|
||
|
|
result = await auth.exchange_wecom_code("auth_code", "corp_123", "corp_secret_456")
|
||
|
|
|
||
|
|
assert result["success"] is True
|
||
|
|
assert result["access_token"] == "token_abc"
|
||
|
|
assert result["user_info"]["UserId"] == "user_123"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_exchange_wecom_code_token_failure(self):
|
||
|
|
from yuxi.channels.adapters.wechat.auth_adapter import WeChatAuthAdapter
|
||
|
|
|
||
|
|
auth = WeChatAuthAdapter()
|
||
|
|
|
||
|
|
with patch("httpx.AsyncClient") as mock_client_cls:
|
||
|
|
mock_client = AsyncMock()
|
||
|
|
mock_client_cls.return_value.__aenter__.return_value = mock_client
|
||
|
|
|
||
|
|
token_resp = MagicMock()
|
||
|
|
token_resp.json.return_value = {"errcode": 40001, "errmsg": "invalid credential"}
|
||
|
|
|
||
|
|
mock_client.get.return_value = token_resp
|
||
|
|
|
||
|
|
result = await auth.exchange_wecom_code("auth_code", "corp_123", "corp_secret_456")
|
||
|
|
|
||
|
|
assert result["success"] is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_exchange_mp_code_success(self):
|
||
|
|
from yuxi.channels.adapters.wechat.auth_adapter import WeChatAuthAdapter
|
||
|
|
|
||
|
|
auth = WeChatAuthAdapter()
|
||
|
|
|
||
|
|
with patch("httpx.AsyncClient") as mock_client_cls:
|
||
|
|
mock_client = AsyncMock()
|
||
|
|
mock_client_cls.return_value.__aenter__.return_value = mock_client
|
||
|
|
|
||
|
|
token_resp = MagicMock()
|
||
|
|
token_resp.json.return_value = {
|
||
|
|
"access_token": "mp_token_abc",
|
||
|
|
"openid": "openid_123",
|
||
|
|
"refresh_token": "refresh_xyz",
|
||
|
|
}
|
||
|
|
userinfo_resp = MagicMock()
|
||
|
|
userinfo_resp.json.return_value = {"openid": "openid_123", "nickname": "TestUser"}
|
||
|
|
|
||
|
|
mock_client.get.side_effect = [token_resp, userinfo_resp]
|
||
|
|
|
||
|
|
result = await auth.exchange_mp_code("auth_code", "wx_app_123", "app_secret_456")
|
||
|
|
|
||
|
|
assert result["success"] is True
|
||
|
|
assert result["access_token"] == "mp_token_abc"
|
||
|
|
assert result["openid"] == "openid_123"
|
||
|
|
assert result["refresh_token"] == "refresh_xyz"
|
||
|
|
assert result["user_info"]["nickname"] == "TestUser"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_exchange_mp_code_failure(self):
|
||
|
|
from yuxi.channels.adapters.wechat.auth_adapter import WeChatAuthAdapter
|
||
|
|
|
||
|
|
auth = WeChatAuthAdapter()
|
||
|
|
|
||
|
|
with patch("httpx.AsyncClient") as mock_client_cls:
|
||
|
|
mock_client = AsyncMock()
|
||
|
|
mock_client_cls.return_value.__aenter__.return_value = mock_client
|
||
|
|
|
||
|
|
token_resp = MagicMock()
|
||
|
|
token_resp.json.return_value = {"errcode": 40029, "errmsg": "invalid code"}
|
||
|
|
|
||
|
|
mock_client.get.return_value = token_resp
|
||
|
|
|
||
|
|
result = await auth.exchange_mp_code("bad_code", "wx_app_123", "app_secret_456")
|
||
|
|
|
||
|
|
assert result["success"] is False
|
||
|
|
|
||
|
|
|
||
|
|
class TestWeChatSubAccount:
|
||
|
|
def test_sub_account_tracking_fields_in_init(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
adapter = WeChatAdapter(config={})
|
||
|
|
|
||
|
|
assert isinstance(adapter._sub_monitors, dict)
|
||
|
|
assert len(adapter._sub_monitors) == 0
|
||
|
|
assert isinstance(adapter._sub_clients, dict)
|
||
|
|
assert len(adapter._sub_clients) == 0
|
||
|
|
assert isinstance(adapter._sub_polling_tasks, dict)
|
||
|
|
assert len(adapter._sub_polling_tasks) == 0
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_connect_sub_accounts_skips_when_only_default(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
adapter = WeChatAdapter(config={})
|
||
|
|
adapter._account_mgr = MagicMock()
|
||
|
|
adapter._account_mgr.list_account_ids.return_value = ["default"]
|
||
|
|
|
||
|
|
await adapter._connect_sub_accounts()
|
||
|
|
|
||
|
|
assert len(adapter._sub_monitors) == 0
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_connect_sub_accounts_skips_disabled(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
adapter = WeChatAdapter(config={})
|
||
|
|
adapter._account_mgr = MagicMock()
|
||
|
|
adapter._account_mgr.list_account_ids.return_value = ["default", "sub1"]
|
||
|
|
|
||
|
|
mock_account = MagicMock()
|
||
|
|
mock_account.enabled = False
|
||
|
|
mock_account.mode = "wecom"
|
||
|
|
adapter._account_mgr.resolve_account.return_value = mock_account
|
||
|
|
|
||
|
|
await adapter._connect_sub_accounts()
|
||
|
|
|
||
|
|
assert len(adapter._sub_monitors) == 0
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_connect_sub_accounts_respects_limit(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
adapter = WeChatAdapter(config={"max_sub_accounts": 2})
|
||
|
|
adapter._account_mgr = MagicMock()
|
||
|
|
adapter._account_mgr.list_account_ids.return_value = [
|
||
|
|
"default", "sub1", "sub2", "sub3", "sub4"
|
||
|
|
]
|
||
|
|
|
||
|
|
mock_account = MagicMock()
|
||
|
|
mock_account.enabled = True
|
||
|
|
mock_account.mode = "mp"
|
||
|
|
adapter._account_mgr.resolve_account.return_value = mock_account
|
||
|
|
|
||
|
|
await adapter._connect_sub_accounts()
|
||
|
|
|
||
|
|
adapter._account_mgr.resolve_account.assert_called()
|
||
|
|
call_count = adapter._account_mgr.resolve_account.call_count
|
||
|
|
assert call_count <= 2
|