本提交新增了全渠道SDK核心模块: 1. 异步锁、目标解析、动作调度等基础工具 2. 消息动作注册与统一调度系统 3. 测试套件与契约测试框架 4. 完整的目标解析流水线与工具函数 5. 资源依赖注入与生命周期管理
131 lines
3.9 KiB
Python
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,
|
|
) |