新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块 同时在测试配置中添加了测试用的OpenAI API密钥环境变量
559 lines
21 KiB
Python
559 lines
21 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from yuxi.channels.message_actions import ActionDeclaration, ActionRegistry, ActionStatus, MessageAction
|
|
from yuxi.channels.base import BaseChannelAdapter
|
|
from yuxi.channels.capabilities import (
|
|
CAPS_FULL_FEATURED,
|
|
CAPS_SIMPLE_TEXT,
|
|
ChannelCapabilities,
|
|
)
|
|
from yuxi.channels.models import (
|
|
ChannelMessage,
|
|
ChannelResponse,
|
|
ChannelType,
|
|
DeliveryResult,
|
|
HealthStatus,
|
|
)
|
|
|
|
|
|
class _EmptyAdapter(BaseChannelAdapter):
|
|
channel_id = "test_empty"
|
|
channel_type = ChannelType.WEBCHAT
|
|
|
|
async def connect(self) -> None:
|
|
pass
|
|
|
|
async def disconnect(self) -> None:
|
|
pass
|
|
|
|
async def send(self, response: ChannelResponse) -> DeliveryResult:
|
|
return DeliveryResult(success=True)
|
|
|
|
def normalize_inbound(self, raw) -> ChannelMessage:
|
|
raise NotImplementedError
|
|
|
|
def format_outbound(self, response: ChannelResponse):
|
|
return {"content": response.content}
|
|
|
|
async def health_check(self) -> HealthStatus:
|
|
return HealthStatus(status="healthy")
|
|
|
|
|
|
class _FullAdapter(_EmptyAdapter):
|
|
channel_id = "test_full"
|
|
supports_streaming = True
|
|
streaming_modes = ["off", "block"]
|
|
supports_markdown = True
|
|
|
|
async def send_media(self, chat_id, media_type, data) -> DeliveryResult:
|
|
return DeliveryResult(success=True)
|
|
|
|
async def edit_message(self, chat_id, msg_id, content) -> DeliveryResult:
|
|
return DeliveryResult(success=True)
|
|
|
|
async def delete_message(self, chat_id, msg_id) -> DeliveryResult:
|
|
return DeliveryResult(success=True)
|
|
|
|
async def send_reaction(self, chat_id, msg_id, emoji) -> DeliveryResult:
|
|
return DeliveryResult(success=True)
|
|
|
|
|
|
class _FeishuLikeAdapter(_EmptyAdapter):
|
|
channel_id = "test_feishu_like"
|
|
|
|
async def send_media(self, chat_id, media_type, data) -> DeliveryResult:
|
|
return DeliveryResult(success=True)
|
|
|
|
async def edit_message(self, chat_id, msg_id, content) -> DeliveryResult:
|
|
return DeliveryResult(success=True)
|
|
|
|
async def delete_message(self, chat_id, msg_id) -> DeliveryResult:
|
|
return DeliveryResult(success=True)
|
|
|
|
async def send_reaction(self, chat_id, msg_id, emoji) -> DeliveryResult:
|
|
return DeliveryResult(success=True)
|
|
|
|
async def pin_message(self, chat_id, msg_id) -> DeliveryResult:
|
|
return DeliveryResult(success=True)
|
|
|
|
async def unpin_message(self, chat_id, msg_id) -> DeliveryResult:
|
|
return DeliveryResult(success=True)
|
|
|
|
async def list_pins(self, chat_id) -> dict:
|
|
return {}
|
|
|
|
|
|
class TestMessageActionEnum:
|
|
def test_all_values_are_strings(self):
|
|
for action in MessageAction:
|
|
assert isinstance(action.value, str)
|
|
|
|
def test_send_is_str_enum(self):
|
|
assert MessageAction.SEND == "send"
|
|
assert str(MessageAction.SEND) == "send"
|
|
|
|
def test_contains_expected_actions(self):
|
|
assert MessageAction.EDIT == "edit"
|
|
assert MessageAction.DELETE == "delete"
|
|
assert MessageAction.REACT == "react"
|
|
assert MessageAction.STREAM == "stream"
|
|
assert MessageAction.PIN == "pin"
|
|
assert MessageAction.UNPIN == "unpin"
|
|
assert MessageAction.LIST_PINS == "list_pins"
|
|
|
|
def test_contains_p2_actions(self):
|
|
assert MessageAction.KICK == "kick"
|
|
assert MessageAction.BAN == "ban"
|
|
assert MessageAction.MUTE == "mute"
|
|
assert MessageAction.CREATE_POLL == "create_poll"
|
|
assert MessageAction.CREATE_THREAD == "create_thread"
|
|
|
|
|
|
class TestActionRegistryDeriveFromAdapter:
|
|
def test_empty_adapter_derives_no_capabilities(self):
|
|
ActionRegistry.clear()
|
|
caps = ActionRegistry.derive_from_adapter_cls(_EmptyAdapter)
|
|
assert caps.edit is False
|
|
assert caps.unsend is False
|
|
assert caps.reactions is False
|
|
assert caps.media is False
|
|
|
|
def test_full_adapter_derives_all_capabilities(self):
|
|
caps = ActionRegistry.derive_from_adapter_cls(_FullAdapter)
|
|
assert caps.media is True
|
|
assert caps.edit is True
|
|
assert caps.unsend is True
|
|
assert caps.reactions is True
|
|
assert caps.supports_streaming is True
|
|
|
|
def test_feishu_like_derives_pin_capabilities(self):
|
|
caps = ActionRegistry.derive_from_adapter_cls(_FeishuLikeAdapter)
|
|
assert caps.pin is True
|
|
assert caps.unpin is True
|
|
assert caps.list_pins is True
|
|
|
|
def test_feishu_like_has_no_send_ephemeral(self):
|
|
caps = ActionRegistry.derive_from_adapter_cls(_FeishuLikeAdapter)
|
|
assert caps.send_ephemeral is False
|
|
|
|
|
|
class TestActionRegistryRegisterAndQuery:
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_registry(self):
|
|
ActionRegistry.clear()
|
|
yield
|
|
ActionRegistry.clear()
|
|
|
|
def test_register_and_get(self):
|
|
caps = CAPS_FULL_FEATURED
|
|
ActionRegistry.register_adapter(_FullAdapter)
|
|
result = ActionRegistry.get("test_full")
|
|
assert result is not None
|
|
assert result.edit is True
|
|
|
|
def test_supports_edit(self):
|
|
ActionRegistry.register_adapter(_FullAdapter)
|
|
assert ActionRegistry.supports("test_full", "edit") is True
|
|
|
|
def test_supports_react(self):
|
|
ActionRegistry.register_adapter(_FullAdapter)
|
|
assert ActionRegistry.supports("test_full", "react") is True
|
|
|
|
def test_does_not_support_pin_for_full_adapter(self):
|
|
ActionRegistry.register_adapter(_FullAdapter)
|
|
assert ActionRegistry.supports("test_full", "pin") is False
|
|
|
|
def test_unknown_channel_returns_false(self):
|
|
assert ActionRegistry.supports("nonexistent", "edit") is False
|
|
|
|
def test_unknown_channel_get_returns_none(self):
|
|
assert ActionRegistry.get("nonexistent") is None
|
|
|
|
def test_find_channels_supporting_edit(self):
|
|
ActionRegistry.register_adapter(_FullAdapter)
|
|
ActionRegistry.register_adapter(_EmptyAdapter)
|
|
channels = ActionRegistry.find_channels_supporting("edit")
|
|
assert "test_full" in channels
|
|
assert "test_empty" not in channels
|
|
|
|
def test_find_channels_supporting_pin(self):
|
|
ActionRegistry.register_adapter(_FeishuLikeAdapter)
|
|
ActionRegistry.register_adapter(_FullAdapter)
|
|
channels = ActionRegistry.find_channels_supporting("pin")
|
|
assert channels == ["test_feishu_like"]
|
|
|
|
def test_list_all(self):
|
|
ActionRegistry.register_adapter(_FullAdapter)
|
|
all_caps = ActionRegistry.list_all()
|
|
assert "test_full" in all_caps
|
|
|
|
def test_clear(self):
|
|
ActionRegistry.register_adapter(_FullAdapter)
|
|
ActionRegistry.clear()
|
|
assert ActionRegistry.get("test_full") is None
|
|
|
|
|
|
class TestActionRegistryExplicitCapabilities:
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_registry(self):
|
|
ActionRegistry.clear()
|
|
yield
|
|
ActionRegistry.clear()
|
|
|
|
def test_explicit_caps_take_precedence(self):
|
|
caps = ChannelCapabilities(
|
|
edit=True,
|
|
reactions=True,
|
|
)
|
|
ActionRegistry.register("test_explicit", caps)
|
|
result = ActionRegistry.get("test_explicit")
|
|
assert result is not None
|
|
assert result.edit is True
|
|
|
|
def test_register_adapters_batch(self):
|
|
ActionRegistry.register_adapters([_FullAdapter, _FeishuLikeAdapter])
|
|
assert ActionRegistry.supports("test_full", "edit") is True
|
|
assert ActionRegistry.supports("test_feishu_like", "pin") is True
|
|
|
|
|
|
class TestChannelCapabilitiesSupportedActions:
|
|
def test_empty_caps_has_no_actions(self):
|
|
caps = ChannelCapabilities()
|
|
assert caps.supported_actions() == []
|
|
|
|
def test_simple_text_has_no_actions(self):
|
|
actions = CAPS_SIMPLE_TEXT.supported_actions()
|
|
assert actions == []
|
|
|
|
def test_full_featured_has_expected_actions(self):
|
|
actions = CAPS_FULL_FEATURED.supported_actions()
|
|
assert "send_media" in actions
|
|
assert "edit" in actions
|
|
assert "delete" in actions
|
|
assert "react" in actions
|
|
assert "create_poll" in actions
|
|
assert "create_thread" in actions
|
|
assert "pin" not in actions
|
|
|
|
def test_caps_with_pin(self):
|
|
caps = ChannelCapabilities(pin=True, unpin=True, list_pins=True)
|
|
actions = caps.supported_actions()
|
|
assert "pin" in actions
|
|
assert "unpin" in actions
|
|
assert "list_pins" in actions
|
|
|
|
def test_caps_with_group_management(self):
|
|
caps = ChannelCapabilities(group_management=True)
|
|
actions = caps.supported_actions()
|
|
assert "kick" in actions
|
|
assert "ban" in actions
|
|
assert "mute" in actions
|
|
|
|
def test_caps_with_send_ephemeral(self):
|
|
caps = ChannelCapabilities(send_ephemeral=True)
|
|
actions = caps.supported_actions()
|
|
assert "send_ephemeral" in actions
|
|
|
|
|
|
class TestMessageActionEnumExpanded:
|
|
def test_enum_has_58_plus_actions(self):
|
|
all_actions = list(MessageAction)
|
|
assert len(all_actions) >= 58
|
|
|
|
def test_new_standard_actions(self):
|
|
assert MessageAction.BROADCAST == "broadcast"
|
|
assert MessageAction.REPLY == "reply"
|
|
assert MessageAction.UNSEND == "unsend"
|
|
assert MessageAction.READ == "read"
|
|
assert MessageAction.LIST_REACTIONS == "list_reactions"
|
|
assert MessageAction.SEND_WITH_EFFECT == "send_with_effect"
|
|
assert MessageAction.RENAME_GROUP == "rename_group"
|
|
assert MessageAction.SET_GROUP_ICON == "set_group_icon"
|
|
assert MessageAction.ADD_PARTICIPANT == "add_participant"
|
|
assert MessageAction.REMOVE_PARTICIPANT == "remove_participant"
|
|
assert MessageAction.LEAVE_GROUP == "leave_group"
|
|
assert MessageAction.PERMISSIONS == "permissions"
|
|
|
|
def test_thread_actions(self):
|
|
assert MessageAction.THREAD_CREATE == "thread_create"
|
|
assert MessageAction.THREAD_LIST == "thread_list"
|
|
assert MessageAction.THREAD_REPLY == "thread_reply"
|
|
|
|
def test_sticker_emoji_actions(self):
|
|
assert MessageAction.STICKER == "sticker"
|
|
assert MessageAction.STICKER_SEARCH == "sticker_search"
|
|
assert MessageAction.STICKER_UPLOAD == "sticker_upload"
|
|
assert MessageAction.EMOJI_LIST == "emoji_list"
|
|
assert MessageAction.EMOJI_UPLOAD == "emoji_upload"
|
|
|
|
def test_member_role_actions(self):
|
|
assert MessageAction.MEMBER_INFO == "member_info"
|
|
assert MessageAction.ROLE_INFO == "role_info"
|
|
assert MessageAction.ROLE_ADD == "role_add"
|
|
assert MessageAction.ROLE_REMOVE == "role_remove"
|
|
|
|
def test_channel_category_actions(self):
|
|
assert MessageAction.CHANNEL_INFO == "channel_info"
|
|
assert MessageAction.CHANNEL_LIST == "channel_list"
|
|
assert MessageAction.CHANNEL_CREATE == "channel_create"
|
|
assert MessageAction.CHANNEL_EDIT == "channel_edit"
|
|
assert MessageAction.CHANNEL_DELETE == "channel_delete"
|
|
assert MessageAction.CHANNEL_MOVE == "channel_move"
|
|
assert MessageAction.CATEGORY_CREATE == "category_create"
|
|
assert MessageAction.CATEGORY_EDIT == "category_edit"
|
|
assert MessageAction.CATEGORY_DELETE == "category_delete"
|
|
|
|
def test_file_profile_actions(self):
|
|
assert MessageAction.DOWNLOAD_FILE == "download_file"
|
|
assert MessageAction.UPLOAD_FILE == "upload_file"
|
|
assert MessageAction.SEND_ATTACHMENT == "send_attachment"
|
|
assert MessageAction.SET_PROFILE == "set_profile"
|
|
assert MessageAction.SET_PRESENCE == "set_presence"
|
|
|
|
def test_event_voice_actions(self):
|
|
assert MessageAction.EVENT_LIST == "event_list"
|
|
assert MessageAction.EVENT_CREATE == "event_create"
|
|
assert MessageAction.VOICE_STATUS == "voice_status"
|
|
assert MessageAction.SEARCH == "search"
|
|
assert MessageAction.TOPIC_CREATE == "topic_create"
|
|
assert MessageAction.TOPIC_EDIT == "topic_edit"
|
|
|
|
def test_backward_compat_actions(self):
|
|
assert MessageAction.SEND_MEDIA == "send_media"
|
|
assert MessageAction.STREAM == "stream"
|
|
assert MessageAction.SEND_EPHEMERAL == "send_ephemeral"
|
|
assert MessageAction.CREATE_THREAD == "create_thread"
|
|
assert MessageAction.REPLY_THREAD == "reply_thread"
|
|
|
|
|
|
class TestActionStatus:
|
|
def test_supported(self):
|
|
assert ActionStatus.SUPPORTED == "supported"
|
|
|
|
def test_unsupported(self):
|
|
assert ActionStatus.UNSUPPORTED == "unsupported"
|
|
|
|
def test_partial(self):
|
|
assert ActionStatus.PARTIAL == "partial"
|
|
|
|
|
|
class TestActionDeclaration:
|
|
def test_create_declaration(self):
|
|
decl = ActionDeclaration(
|
|
action=MessageAction.SEND,
|
|
status=ActionStatus.SUPPORTED,
|
|
reason="",
|
|
impl="adapter.send()",
|
|
)
|
|
assert decl.action == MessageAction.SEND
|
|
assert decl.status == ActionStatus.SUPPORTED
|
|
assert decl.impl == "adapter.send()"
|
|
|
|
def test_create_unsupported_declaration(self):
|
|
decl = ActionDeclaration(
|
|
action=MessageAction.PIN,
|
|
status=ActionStatus.UNSUPPORTED,
|
|
reason="WeChat 不支持置顶",
|
|
)
|
|
assert decl.action == MessageAction.PIN
|
|
assert decl.status == ActionStatus.UNSUPPORTED
|
|
assert decl.reason == "WeChat 不支持置顶"
|
|
assert decl.impl == ""
|
|
|
|
|
|
class TestActionRegistryChannelActions:
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_registry(self):
|
|
ActionRegistry.clear()
|
|
yield
|
|
ActionRegistry.clear()
|
|
|
|
def _make_channel_actions(self):
|
|
declarations: dict[MessageAction, ActionDeclaration] = {}
|
|
declarations[MessageAction.SEND] = ActionDeclaration(
|
|
action=MessageAction.SEND,
|
|
status=ActionStatus.SUPPORTED,
|
|
impl="adapter.send()",
|
|
)
|
|
declarations[MessageAction.EDIT] = ActionDeclaration(
|
|
action=MessageAction.EDIT,
|
|
status=ActionStatus.SUPPORTED,
|
|
impl="adapter.edit_message()",
|
|
)
|
|
declarations[MessageAction.PIN] = ActionDeclaration(
|
|
action=MessageAction.PIN,
|
|
status=ActionStatus.UNSUPPORTED,
|
|
reason="Not supported",
|
|
)
|
|
declarations[MessageAction.BROADCAST] = ActionDeclaration(
|
|
action=MessageAction.BROADCAST,
|
|
status=ActionStatus.PARTIAL,
|
|
reason="Template only",
|
|
impl="sendMessageTemplate",
|
|
)
|
|
return declarations
|
|
|
|
def test_register_channel_actions(self):
|
|
declarations = self._make_channel_actions()
|
|
ActionRegistry.register_channel_actions("test_ch", declarations)
|
|
assert len(ActionRegistry.list_all_declarations("test_ch")) == 4
|
|
|
|
def test_is_supported_true(self):
|
|
declarations = self._make_channel_actions()
|
|
ActionRegistry.register_channel_actions("test_ch", declarations)
|
|
assert ActionRegistry.is_supported("test_ch", MessageAction.SEND) is True
|
|
assert ActionRegistry.is_supported("test_ch", MessageAction.EDIT) is True
|
|
|
|
def test_is_supported_false(self):
|
|
declarations = self._make_channel_actions()
|
|
ActionRegistry.register_channel_actions("test_ch", declarations)
|
|
assert ActionRegistry.is_supported("test_ch", MessageAction.PIN) is False
|
|
|
|
def test_is_supported_falls_back_to_caps(self):
|
|
ActionRegistry.register_adapter(_FullAdapter)
|
|
assert ActionRegistry.is_supported("test_full", MessageAction.EDIT) is True
|
|
assert ActionRegistry.is_supported("test_full", MessageAction.PIN) is False
|
|
|
|
def test_list_supported_actions(self):
|
|
declarations = self._make_channel_actions()
|
|
ActionRegistry.register_channel_actions("test_ch", declarations)
|
|
supported = ActionRegistry.list_supported_actions("test_ch")
|
|
assert MessageAction.SEND in supported
|
|
assert MessageAction.EDIT in supported
|
|
assert MessageAction.PIN not in supported
|
|
assert MessageAction.BROADCAST not in supported
|
|
|
|
def test_list_supported_actions_falls_back_to_caps(self):
|
|
ActionRegistry.register_adapter(_FullAdapter)
|
|
supported = ActionRegistry.list_supported_actions("test_full")
|
|
assert MessageAction.EDIT in supported
|
|
assert MessageAction.REACT in supported
|
|
|
|
def test_list_channels_for_action(self):
|
|
declarations = self._make_channel_actions()
|
|
ActionRegistry.register_channel_actions("channel_a", declarations)
|
|
ActionRegistry.register_channel_actions("channel_b", {
|
|
MessageAction.SEND: ActionDeclaration(
|
|
action=MessageAction.SEND,
|
|
status=ActionStatus.SUPPORTED,
|
|
impl="send",
|
|
),
|
|
MessageAction.PIN: ActionDeclaration(
|
|
action=MessageAction.PIN,
|
|
status=ActionStatus.SUPPORTED,
|
|
impl="pin",
|
|
),
|
|
})
|
|
channels = ActionRegistry.list_channels_for_action(MessageAction.PIN)
|
|
assert "channel_b" in channels
|
|
assert "channel_a" not in channels
|
|
assert ActionRegistry.list_channels_for_action(MessageAction.SEND) == ["channel_a", "channel_b"]
|
|
|
|
def test_list_channels_for_action_includes_caps_channels(self):
|
|
ActionRegistry.register_adapter(_FullAdapter)
|
|
declarations = self._make_channel_actions()
|
|
ActionRegistry.register_channel_actions("test_ch", declarations)
|
|
channels = ActionRegistry.list_channels_for_action(MessageAction.EDIT)
|
|
assert "test_ch" in channels
|
|
assert "test_full" in channels
|
|
|
|
def test_get_action_declaration(self):
|
|
declarations = self._make_channel_actions()
|
|
ActionRegistry.register_channel_actions("test_ch", declarations)
|
|
decl = ActionRegistry.get_action_declaration("test_ch", MessageAction.PIN)
|
|
assert decl is not None
|
|
assert decl.status == ActionStatus.UNSUPPORTED
|
|
assert decl.reason == "Not supported"
|
|
|
|
def test_get_action_declaration_nonexistent(self):
|
|
assert ActionRegistry.get_action_declaration("nonexistent", MessageAction.SEND) is None
|
|
|
|
def test_list_all_declarations_empty(self):
|
|
assert ActionRegistry.list_all_declarations("nonexistent") == {}
|
|
|
|
def test_clear_includes_channel_actions(self):
|
|
declarations = self._make_channel_actions()
|
|
ActionRegistry.register_channel_actions("test_ch", declarations)
|
|
ActionRegistry.clear()
|
|
assert ActionRegistry.list_all_declarations("test_ch") == {}
|
|
|
|
|
|
class TestChannelRegistrations:
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_registry(self):
|
|
ActionRegistry.clear()
|
|
yield
|
|
ActionRegistry.clear()
|
|
|
|
def test_wechat_registration(self):
|
|
from yuxi.channels.adapters.wechat.message_actions import register_wechat_actions
|
|
|
|
register_wechat_actions()
|
|
declarations = ActionRegistry.list_all_declarations("wechat")
|
|
assert len(declarations) > 0
|
|
send_decl = declarations.get(MessageAction.SEND)
|
|
assert send_decl is not None
|
|
assert send_decl.status == ActionStatus.SUPPORTED
|
|
|
|
def test_telegram_registration(self):
|
|
from yuxi.channels.adapters.telegram.message_actions import register_telegram_actions
|
|
|
|
register_telegram_actions()
|
|
declarations = ActionRegistry.list_all_declarations("telegram")
|
|
assert len(declarations) > 0
|
|
supported = ActionRegistry.list_supported_actions("telegram")
|
|
assert MessageAction.SEND in supported
|
|
assert MessageAction.EDIT in supported
|
|
assert MessageAction.REACT in supported
|
|
|
|
def test_discord_registration(self):
|
|
from yuxi.channels.adapters.discord.message_actions import register_discord_actions
|
|
|
|
register_discord_actions()
|
|
declarations = ActionRegistry.list_all_declarations("discord")
|
|
assert len(declarations) > 0
|
|
supported = ActionRegistry.list_supported_actions("discord")
|
|
assert MessageAction.SEND in supported
|
|
assert MessageAction.EDIT in supported
|
|
|
|
def test_slack_registration(self):
|
|
from yuxi.channels.adapters.slack.message_actions import register_slack_actions
|
|
|
|
register_slack_actions()
|
|
declarations = ActionRegistry.list_all_declarations("slack")
|
|
assert len(declarations) > 0
|
|
supported = ActionRegistry.list_supported_actions("slack")
|
|
assert MessageAction.SEND in supported
|
|
|
|
def test_whatsapp_registration(self):
|
|
from yuxi.channels.adapters.whatsapp.message_actions import register_whatsapp_actions
|
|
|
|
register_whatsapp_actions()
|
|
declarations = ActionRegistry.list_all_declarations("whatsapp")
|
|
assert len(declarations) > 0
|
|
supported = ActionRegistry.list_supported_actions("whatsapp")
|
|
assert MessageAction.SEND in supported
|
|
|
|
def test_feishu_registration(self):
|
|
from yuxi.channels.adapters.feishu.message_actions import register_feishu_actions
|
|
|
|
register_feishu_actions()
|
|
declarations = ActionRegistry.list_all_declarations("feishu")
|
|
assert len(declarations) > 0
|
|
supported = ActionRegistry.list_supported_actions("feishu")
|
|
assert MessageAction.SEND in supported
|
|
assert MessageAction.PIN in supported
|
|
|
|
def test_cross_channel_query_pin(self):
|
|
from yuxi.channels.adapters.discord.message_actions import register_discord_actions
|
|
from yuxi.channels.adapters.feishu.message_actions import register_feishu_actions
|
|
from yuxi.channels.adapters.telegram.message_actions import register_telegram_actions
|
|
|
|
register_telegram_actions()
|
|
register_discord_actions()
|
|
register_feishu_actions()
|
|
channels = ActionRegistry.list_channels_for_action(MessageAction.PIN)
|
|
assert "feishu" in channels
|
|
assert "telegram" in channels |