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重连相关测试
267 lines
8.8 KiB
Python
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 |