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)