ForcePilot/backend/package/yuxi/channel/sdk/testing/mocks.py
Kris b438af3ba8 feat(channel-sdk): 新增完整的渠道SDK工具链
本提交新增了全渠道SDK核心模块:
1.  异步锁、目标解析、动作调度等基础工具
2.  消息动作注册与统一调度系统
3.  测试套件与契约测试框架
4.  完整的目标解析流水线与工具函数
5.  资源依赖注入与生命周期管理
2026-05-21 10:29:12 +08:00

131 lines
3.9 KiB
Python

from dataclasses import dataclass, field
from typing import Any
from unittest.mock import AsyncMock, MagicMock
from yuxi.channel.capabilities import ChannelCapabilities
@dataclass
class SentMessage:
text: str
target_id: str = ""
msg_id: str = ""
kwargs: dict = field(default_factory=dict)
def create_mock_channel_plugin(
channel_id: str = "mock_channel",
capabilities: ChannelCapabilities | None = None,
*,
with_gateway: bool = False,
with_status: bool = False,
account_ids: list[str] | None = None,
) -> Any:
class _MockPlugin:
id = channel_id
name = f"Mock {channel_id}"
order = 10
def __init__(self):
self._sent: list[SentMessage] = []
plugin = _MockPlugin()
_caps = capabilities or ChannelCapabilities(chat_types=["direct", "group"], message_types=["text"])
setattr(plugin.__class__, "capabilities", property(lambda self: _caps))
if account_ids is None:
account_ids = ["mock_account"]
def list_account_ids(config: dict | None = None) -> list[str]:
if config is None:
config = {}
return list(account_ids)
async def resolve_account(account_id: str) -> dict:
return {"id": account_id, "enabled": True}
def is_configured(account: dict) -> bool:
return True
plugin.list_account_ids = list_account_ids
plugin.resolve_account = resolve_account
plugin.is_configured = is_configured
async def send_text(target_id: str, content: str, *, reply_to_id=None, thread_id=None, account_id=None) -> str:
msg_id = f"mock_msg_{len(plugin._sent) + 1}"
plugin._sent.append(SentMessage(
text=content,
target_id=target_id,
msg_id=msg_id,
kwargs={"reply_to_id": reply_to_id, "thread_id": thread_id, "account_id": account_id},
))
return msg_id
async def send_media(
target_id: str, media_url: str, media_type: str, *, reply_to_id=None, thread_id=None, account_id=None
) -> str:
msg_id = f"mock_media_{len(plugin._sent) + 1}"
plugin._sent.append(SentMessage(
text=f"[media: {media_url}]",
target_id=target_id,
msg_id=msg_id,
kwargs={"media_url": media_url, "media_type": media_type, "reply_to_id": reply_to_id},
))
return msg_id
async def edit_message(target_id: str, message_id: str, content: str, *, thread_id=None, account_id=None) -> str:
return message_id
plugin.send_text = send_text
plugin.send_media = send_media
plugin.edit_message = edit_message
async def check_allowlist(peer_id: str, channel_type: str) -> bool:
return True
plugin.check_allowlist = check_allowlist
if with_gateway:
async def start(ctx): return {"ok": True}
async def stop(ctx): pass
plugin.start = start
plugin.stop = stop
if with_status:
async def probe(account: dict | None = None) -> bool:
return True
def build_summary(snapshot: object) -> dict:
return {"ok": True}
plugin.probe = probe
plugin.build_summary = build_summary
return plugin
def create_mock_gateway_client():
client = MagicMock()
client.call = AsyncMock(return_value={"success": True})
client.connect = AsyncMock()
client.disconnect = AsyncMock()
return client
def create_mock_binding(
agent_config_id: int = 1,
channel: str = "mock_channel",
account_id: str = "mock_account",
dm_scope: str = "per-channel-peer",
priority: int = 0,
):
from yuxi.channel.routing.models import BindingMatch, PeerConstraint, PeerKind, RouteBinding
peer = PeerConstraint(kind=PeerKind.DIRECT, peer_id=None)
match = BindingMatch(channel=channel, account_id=account_id, peer=peer)
return RouteBinding(
agent_config_id=agent_config_id,
match=match,
dm_scope=dm_scope,
priority=priority,
)