ForcePilot/backend/test/unit/channels/test_capabilities.py
Kris 69fe97a90d test: 批量修复并新增单元测试用例
1. 移除Telegram格式化测试中未使用的导入项
2. 修复Teams测试用例,添加monkeypatch参数并配置通配符开关
3. 更新钉钉适配器测试,替换弃用的流属性检查
4. 修正Twitch规范化测试,更新ROOMSTATE测试逻辑
5. 重构会话映射测试,完善数据库执行结果模拟
6. 格式化Slack块构建测试的长参数调用
7. 修复LINE适配器测试,更新能力断言和异步锁使用
8. 修正Slack会话解析测试,修复聊天类型判断错误
9. 更新能力测试,补充缺失的字段检查
10. 修复Matrix适配器测试,修正位置参数和配置校验逻辑
11. 为飞书分析模块测试添加跳过标记
12. 新增微信能力、限流、链接格式、会话路由等模块的单元测试
13. 修复Twitch适配器导入路径和测试断言
14. 新增Discord Webhook、Nextcloud Talk、Signal多账户等模块的单元测试
15. 修复Manager阶段测试的导入路径
16. 新增iMessage异常和命令处理的单元测试
17. 新增Nostr健康检查和相关模块的单元测试
18. 新增Signal守护进程和SSE重连相关测试
2026-05-13 16:43:01 +08:00

267 lines
8.8 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.thread_caps.supports_native_threads is False
assert caps.media 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", "delivery_mode", "polls", "reactions",
"edit", "unsend", "reply", "effects", "group_management",
"threads", "thread_caps", "media", "max_media_size_mb",
"tts", "native_commands", "block_streaming",
"lane_streaming", "reasoning_streaming", "pin", "unpin",
"list_pins", "send_ephemeral", "vision", "approval",
"typing", "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