ForcePilot/backend/test/unit/channels/test_capabilities.py
Kris 3264900bc9 test: 新增多渠道单元测试用例并配置测试环境变量
新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块
同时在测试配置中添加了测试用的OpenAI API密钥环境变量
2026-05-12 00:56:47 +08:00

265 lines
8.7 KiB
Python

from __future__ import annotations
import pytest
from yuxi.channels.base import BaseChannelAdapter
from yuxi.channels.capabilities import (
CAPS_FULL_FEATURED,
CAPS_GROUP_CHAT_ONLY,
CAPS_SIMPLE_TEXT,
ChannelCapabilities,
)
from yuxi.channels.meta import ChannelMeta
from yuxi.channels.models import (
ChannelMessage,
ChannelResponse,
ChannelType,
DeliveryResult,
HealthStatus,
)
class _MinimalAdapter(BaseChannelAdapter):
channel_id = "minimal"
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 TestChannelCapabilitiesDefaults:
def test_default_chat_types(self):
caps = ChannelCapabilities()
assert caps.chat_types == ["direct"]
def test_all_flags_false_by_default(self):
caps = ChannelCapabilities()
assert caps.polls is False
assert caps.reactions is False
assert caps.edit is False
assert caps.unsend is False
assert caps.reply is False
assert caps.effects is False
assert caps.group_management is False
assert caps.threads is False
assert caps.media is False
assert caps.tts is False
assert caps.native_commands is False
assert caps.block_streaming is False
assert caps.pin is False
assert caps.unpin is False
assert caps.list_pins is False
assert caps.send_ephemeral is False
def test_default_text_fields(self):
caps = ChannelCapabilities()
assert caps.text_chunk_limit == 4096
assert caps.supports_markdown is False
assert caps.supports_streaming is False
assert caps.streaming_modes == ["off"]
def test_default_max_media_size(self):
caps = ChannelCapabilities()
assert caps.max_media_size_mb == 100
class TestChannelCapabilitiesPresets:
def test_caps_simple_text(self):
caps = CAPS_SIMPLE_TEXT
assert caps.chat_types == ["direct"]
assert caps.supports_markdown is False
assert caps.supports_streaming is False
assert caps.streaming_modes == ["off"]
def test_caps_full_featured(self):
caps = CAPS_FULL_FEATURED
assert caps.chat_types == ["direct", "group", "thread"]
assert caps.polls is True
assert caps.reactions is True
assert caps.edit is True
assert caps.unsend is True
assert caps.reply is True
assert caps.threads is True
assert caps.media is True
assert caps.supports_markdown is True
assert caps.supports_streaming is True
assert caps.streaming_modes == ["chunked", "live"]
def test_caps_group_chat_only(self):
caps = CAPS_GROUP_CHAT_ONLY
assert caps.chat_types == ["direct", "group"]
assert caps.reply is True
assert caps.media is True
assert caps.group_management is True
assert caps.supports_markdown is True
assert caps.supports_streaming is True
assert caps.streaming_modes == ["block"]
def test_presets_are_independent(self):
simple = CAPS_SIMPLE_TEXT.model_dump()
full = CAPS_FULL_FEATURED.model_dump()
assert simple != full
class TestChannelCapabilitiesModelDump:
def test_model_dump_includes_all_fields(self):
caps = ChannelCapabilities()
dumped = caps.model_dump()
expected_fields = {
"chat_types", "polls", "reactions", "edit", "unsend",
"reply", "effects", "group_management", "threads",
"media", "max_media_size_mb", "tts", "native_commands",
"block_streaming", "pin", "unpin", "list_pins",
"send_ephemeral", "text_chunk_limit", "supports_markdown",
"supports_streaming", "streaming_modes",
}
assert set(dumped.keys()) == expected_fields
def test_model_dump_serializable(self):
import json
caps = CAPS_FULL_FEATURED
dumped = caps.model_dump()
json.dumps(dumped)
def test_model_validate_roundtrip(self):
original = CAPS_FULL_FEATURED
data = original.model_dump()
restored = ChannelCapabilities(**data)
assert restored == original
class TestChannelMeta:
def test_default_values(self):
meta = ChannelMeta(id="test", label="Test")
assert meta.id == "test"
assert meta.label == "Test"
assert meta.selection_label == ""
assert meta.docs_path == ""
assert meta.blurb == ""
assert meta.order == 100
assert meta.aliases == []
assert meta.system_image == ""
assert meta.markdown_capable is False
assert meta.exposure == "public"
assert meta.show_configured is True
assert meta.show_in_setup is True
assert meta.quickstart_allow_from == []
assert meta.force_account_binding is False
def test_full_meta_example(self):
meta = ChannelMeta(
id="telegram",
label="Telegram",
selection_label="Telegram Bot",
docs_path="docs/channels/telegram",
docs_label="Telegram 文档",
blurb="Telegram 渠道适配器",
order=5,
aliases=["tg"],
system_image="telegram",
markdown_capable=True,
exposure="public",
)
assert meta.id == "telegram"
assert meta.aliases == ["tg"]
assert meta.order == 5
assert meta.markdown_capable is True
def test_model_dump_serializable(self):
import json
meta = ChannelMeta(id="test", label="Test")
json.dumps(meta.model_dump())
class TestBaseChannelAdapterIntegration:
def test_default_capabilities(self):
assert _MinimalAdapter.capabilities == CAPS_SIMPLE_TEXT
def test_default_meta(self):
assert _MinimalAdapter.meta == ChannelMeta(id="", label="")
def test_override_capabilities(self):
class CustomAdapter(_MinimalAdapter):
channel_id = "custom"
capabilities = CAPS_FULL_FEATURED
assert CustomAdapter.capabilities == CAPS_FULL_FEATURED
assert CustomAdapter.capabilities.polls is True
assert CustomAdapter.capabilities.streaming_modes == ["chunked", "live"]
def test_override_meta(self):
class CustomAdapter(_MinimalAdapter):
channel_id = "custom"
meta = ChannelMeta(id="custom", label="Custom", order=3)
assert CustomAdapter.meta.id == "custom"
assert CustomAdapter.meta.label == "Custom"
assert CustomAdapter.meta.order == 3
def test_custom_capabilities_example(self):
class WeChatLikeAdapter(_MinimalAdapter):
channel_id = "wechat-like"
capabilities = ChannelCapabilities(
chat_types=["direct", "group"],
reply=True,
group_management=True,
media=True,
block_streaming=True,
text_chunk_limit=2048,
max_media_size_mb=20,
supports_markdown=False,
supports_streaming=False,
)
caps = WeChatLikeAdapter.capabilities
assert caps.chat_types == ["direct", "group"]
assert caps.text_chunk_limit == 2048
assert caps.max_media_size_mb == 20
assert caps.block_streaming is True
assert caps.supports_markdown is False
def test_capabilities_model_dump_integration(self):
class CustomAdapter(_MinimalAdapter):
channel_id = "custom"
capabilities = CAPS_FULL_FEATURED
dumped = CustomAdapter.capabilities.model_dump()
assert dumped["streaming_modes"] == ["chunked", "live"]
assert dumped["chat_types"] == ["direct", "group", "thread"]
def test_hasattr_compatibility(self):
assert hasattr(_MinimalAdapter, "capabilities")
assert hasattr(_MinimalAdapter, "meta")
class TestManagerCompatibility:
def test_old_adapter_has_classvar_fallback(self):
import types
OldStyle = types.new_class(
"OldStyle",
bases=(BaseChannelAdapter,),
exec_body=lambda ns: ns.update({
"channel_id": "old",
"channel_type": ChannelType.WEBCHAT,
}),
)
assert hasattr(OldStyle, "capabilities")
assert OldStyle.text_chunk_limit == 4096