新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块 同时在测试配置中添加了测试用的OpenAI API密钥环境变量
133 lines
4.3 KiB
Python
133 lines
4.3 KiB
Python
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from yuxi.channels.base import BaseChannelAdapter
|
|
from yuxi.channels.registry import ChannelRegistry, _BUILTIN_ADAPTERS, register_builtin_adapter
|
|
|
|
|
|
def _make_adapter_cls(channel_id: str) -> type[BaseChannelAdapter]:
|
|
cid = channel_id
|
|
|
|
class _TestAdapter(BaseChannelAdapter):
|
|
channel_id = cid
|
|
channel_type = MagicMock(value="test")
|
|
|
|
async def connect(self) -> None: ...
|
|
async def disconnect(self) -> None: ...
|
|
async def send(self, response): ...
|
|
def normalize_inbound(self, raw): ...
|
|
def format_outbound(self, response): ...
|
|
async def health_check(self): ...
|
|
|
|
return _TestAdapter
|
|
|
|
|
|
class TestChannelRegistry:
|
|
def test_init_empty(self):
|
|
registry = ChannelRegistry()
|
|
assert registry.list_channels() == []
|
|
|
|
def test_register_and_get(self):
|
|
registry = ChannelRegistry()
|
|
cls = _make_adapter_cls("test_chan")
|
|
registry.register("test_chan", cls)
|
|
assert registry.get("test_chan") is cls
|
|
|
|
def test_get_nonexistent(self):
|
|
registry = ChannelRegistry()
|
|
assert registry.get("nonexistent") is None
|
|
|
|
def test_list_channels(self):
|
|
registry = ChannelRegistry()
|
|
registry.register("a", _make_adapter_cls("a"))
|
|
registry.register("b", _make_adapter_cls("b"))
|
|
channels = registry.list_channels()
|
|
assert set(channels) == {"a", "b"}
|
|
|
|
def test_unregister(self):
|
|
registry = ChannelRegistry()
|
|
cls = _make_adapter_cls("test_chan")
|
|
registry.register("test_chan", cls)
|
|
registry.unregister("test_chan")
|
|
assert registry.get("test_chan") is None
|
|
|
|
def test_unregister_nonexistent_does_nothing(self):
|
|
registry = ChannelRegistry()
|
|
registry.unregister("nonexistent")
|
|
|
|
def test_register_overwrites(self):
|
|
registry = ChannelRegistry()
|
|
cls1 = _make_adapter_cls("test_chan")
|
|
cls2 = _make_adapter_cls("test_chan")
|
|
registry.register("test_chan", cls1)
|
|
registry.register("test_chan", cls2)
|
|
assert registry.get("test_chan") is cls2
|
|
|
|
def test_list_channels_includes_builtin(self):
|
|
registry = ChannelRegistry()
|
|
registry.register("custom_chan", _make_adapter_cls("custom_chan"))
|
|
|
|
saved = dict(_BUILTIN_ADAPTERS)
|
|
_BUILTIN_ADAPTERS.clear()
|
|
_BUILTIN_ADAPTERS["builtin_chan"] = _make_adapter_cls("builtin_chan")
|
|
|
|
channels = set(registry.list_channels())
|
|
_BUILTIN_ADAPTERS.clear()
|
|
_BUILTIN_ADAPTERS.update(saved)
|
|
|
|
assert "custom_chan" in channels
|
|
assert "builtin_chan" in channels
|
|
|
|
def test_get_falls_back_to_builtin(self):
|
|
registry = ChannelRegistry()
|
|
cls = _make_adapter_cls("builtin_chan")
|
|
|
|
saved = dict(_BUILTIN_ADAPTERS)
|
|
_BUILTIN_ADAPTERS.clear()
|
|
_BUILTIN_ADAPTERS["builtin_chan"] = cls
|
|
|
|
result = registry.get("builtin_chan")
|
|
_BUILTIN_ADAPTERS.clear()
|
|
_BUILTIN_ADAPTERS.update(saved)
|
|
|
|
assert result is cls
|
|
|
|
def test_registered_takes_priority_over_builtin(self):
|
|
registry = ChannelRegistry()
|
|
cls1 = _make_adapter_cls("test_chan")
|
|
cls2 = _make_adapter_cls("test_chan")
|
|
registry.register("test_chan", cls1)
|
|
|
|
saved = dict(_BUILTIN_ADAPTERS)
|
|
_BUILTIN_ADAPTERS.clear()
|
|
_BUILTIN_ADAPTERS["test_chan"] = cls2
|
|
|
|
result = registry.get("test_chan")
|
|
_BUILTIN_ADAPTERS.clear()
|
|
_BUILTIN_ADAPTERS.update(saved)
|
|
|
|
assert result is cls1
|
|
|
|
|
|
class TestBuiltinAdapterRegistration:
|
|
def teardown_method(self):
|
|
_BUILTIN_ADAPTERS.pop("_test_reg_adapter", None)
|
|
|
|
def test_register_builtin_adapter_decorator(self):
|
|
@register_builtin_adapter
|
|
class _TestRegAdapter(BaseChannelAdapter):
|
|
channel_id = "_test_reg_adapter"
|
|
channel_type = MagicMock()
|
|
|
|
async def connect(self) -> None: ...
|
|
async def disconnect(self) -> None: ...
|
|
async def send(self, response): ...
|
|
def normalize_inbound(self, raw): ...
|
|
def format_outbound(self, response): ...
|
|
async def health_check(self): ...
|
|
|
|
assert "_test_reg_adapter" in _BUILTIN_ADAPTERS
|
|
assert issubclass(_BUILTIN_ADAPTERS["_test_reg_adapter"], BaseChannelAdapter) |