ForcePilot/backend/test/unit/channel/plugins/test_registry.py
Kris bab30f2715
Some checks failed
Deploy VitePress site to Pages / build (push) Has been cancelled
Ruff Format Check / Ruff Format & Lint (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
feat:0715
2026-07-15 12:30:58 +08:00

217 lines
8.3 KiB
Python

from __future__ import annotations
from unittest.mock import MagicMock, patch
import pytest
from yuxi.channel.plugins.protocol import ChannelMeta, DeliveryCapabilities, InboundMedia, OutboundMessage
from yuxi.channel.plugins.registry import ChannelRegistry, get_registry
from yuxi.channel.ports import MetaPort, OutboundPort
@pytest.fixture
def sample_plugin():
plugin = MagicMock()
plugin.get_meta.return_value = ChannelMeta(
channel_type="test",
display_name="Test",
aliases=["t", "tst"],
)
return plugin
@pytest.fixture
def aliased_plugin():
plugin = MagicMock()
plugin.get_meta.return_value = ChannelMeta(
channel_type="other",
display_name="Other",
aliases=["t"],
)
return plugin
class TestChannelRegistryRegister:
def test_register_stores_plugin_by_channel_type(self, sample_plugin):
registry = ChannelRegistry()
registry.register(sample_plugin)
assert registry.get_plugin("test") is sample_plugin
def test_register_stores_plugin_by_aliases(self, sample_plugin):
registry = ChannelRegistry()
registry.register(sample_plugin)
assert registry.get_plugin("t") is sample_plugin
assert registry.get_plugin("tst") is sample_plugin
def test_register_primary_stores_only_channel_type(self, sample_plugin):
registry = ChannelRegistry()
registry.register_primary(sample_plugin)
assert registry.get_plugin("test") is sample_plugin
assert registry.get_plugin("t") is None
def test_register_overwrites_same_plugin_without_warning(self, sample_plugin):
registry = ChannelRegistry()
with patch("yuxi.channel.plugins.registry.logger") as mock_logger:
registry.register(sample_plugin)
registry.register(sample_plugin)
mock_logger.warning.assert_not_called()
def test_register_overwrites_different_plugin_logs_warning(self, sample_plugin):
registry = ChannelRegistry()
other_plugin = MagicMock()
other_plugin.get_meta.return_value = ChannelMeta(channel_type="test", display_name="Other")
with patch("yuxi.channel.plugins.registry.logger") as mock_logger:
registry.register(sample_plugin)
registry.register(other_plugin)
mock_logger.warning.assert_called_once()
def test_register_alias_conflict_logs_warning_and_skips(self, sample_plugin, aliased_plugin):
registry = ChannelRegistry()
with patch("yuxi.channel.plugins.registry.logger") as mock_logger:
registry.register(sample_plugin)
registry.register(aliased_plugin)
assert registry.get_plugin("t") is sample_plugin
assert registry.get_plugin("other") is aliased_plugin
assert mock_logger.warning.call_count == 1
class TestChannelRegistryUnregister:
def test_unregister_removes_channel_type_and_aliases(self, sample_plugin):
registry = ChannelRegistry()
registry.register(sample_plugin)
registry.unregister("test")
assert registry.get_plugin("test") is None
assert registry.get_plugin("t") is None
assert registry.get_plugin("tst") is None
def test_unregister_unknown_channel_type_is_safe(self):
registry = ChannelRegistry()
registry.unregister("unknown")
assert registry.get_plugin("unknown") is None
def test_unregister_only_removes_aliases_for_primary(self, sample_plugin, aliased_plugin):
registry = ChannelRegistry()
registry.register(sample_plugin)
# Register alias from another plugin under a different alias
aliased_plugin.get_meta.return_value.aliases = ["other-alias"]
registry.register(aliased_plugin)
registry.unregister("other")
assert registry.get_plugin("test") is sample_plugin
assert registry.get_plugin("t") is sample_plugin
class TestChannelRegistryListPlugins:
def test_list_plugins_dedupes_by_identity(self, sample_plugin):
registry = ChannelRegistry()
registry.register(sample_plugin)
metas = registry.list_plugins()
assert len(metas) == 1
assert metas[0].channel_type == "test"
def test_list_plugins_includes_only_unique_plugins(self, sample_plugin):
registry = ChannelRegistry()
registry.register(sample_plugin)
metas = registry.list_plugins()
assert len(metas) == 1
class TestGlobalRegistry:
def test_get_registry_returns_singleton(self):
with patch("yuxi.channel.plugins.registry._GLOBAL_REGISTRY", None):
r1 = get_registry()
r2 = get_registry()
assert r1 is r2
def test_get_registry_creates_new_instance_when_none(self):
with patch("yuxi.channel.plugins.registry._GLOBAL_REGISTRY", None):
registry = get_registry()
assert isinstance(registry, ChannelRegistry)
class TestPortBasedRegistryQueries:
def _make_partial_plugin(self, channel_type: str = "partial"):
class PartialPlugin(MetaPort, OutboundPort):
def __init__(self) -> None:
self._meta = ChannelMeta(channel_type=channel_type, display_name="Partial")
def get_meta(self) -> ChannelMeta:
return self._meta
async def format_outbound(self, message: OutboundMessage, config: dict | None = None) -> dict:
return {}
async def send_message(
self, channel_session_id: str, payload: dict, config: dict | None = None
) -> str | None:
return None
def chunk_text(self, text: str, limit: int) -> list[str]:
return []
def get_delivery_capabilities(self) -> DeliveryCapabilities:
return DeliveryCapabilities()
async def enrich_outbound(
self, payload: dict, message: OutboundMessage, config: dict | None = None
) -> dict:
return payload
async def upload_media(self, media: InboundMedia | dict, config: dict | None = None) -> dict | None:
return None
def supports_batch_send(self, config: dict | None = None) -> bool:
return False
async def send_batch(
self, channel_session_id: str, payloads: list[dict], config: dict | None = None
) -> list[str | None]:
return []
async def send_typing_indicator(self, channel_session_id: str, config: dict | None = None) -> None:
return None
def supports_content_type(self, content_type: str, config: dict | None = None) -> bool:
return False
return PartialPlugin()
def test_register_partial_plugin_succeeds(self):
registry = ChannelRegistry()
plugin = self._make_partial_plugin()
registry.register(plugin)
assert registry.get_plugin("partial") is plugin
def test_get_plugins_by_port_filters_by_outbound(self):
registry = ChannelRegistry()
outbound_plugin = self._make_partial_plugin("outbound-only")
meta_only_plugin = MagicMock()
meta_only_plugin.get_meta.return_value = ChannelMeta(channel_type="meta-only", display_name="Meta Only")
registry.register(outbound_plugin)
registry.register(meta_only_plugin)
outbound_plugins = registry.get_plugins_by_port(OutboundPort)
assert outbound_plugins == [outbound_plugin]
def test_has_port_returns_true_for_implemented_port(self):
registry = ChannelRegistry()
plugin = self._make_partial_plugin("partial")
registry.register(plugin)
assert registry.has_port("partial", OutboundPort) is True
def test_has_port_returns_false_for_missing_port(self):
registry = ChannelRegistry()
meta_only_plugin = MagicMock()
meta_only_plugin.get_meta.return_value = ChannelMeta(channel_type="meta-only", display_name="Meta Only")
registry.register(meta_only_plugin)
assert registry.has_port("meta-only", OutboundPort) is False
def test_get_plugins_by_port_dedupes_aliases(self):
registry = ChannelRegistry()
plugin = self._make_partial_plugin("aliased")
plugin.get_meta().aliases = ["a1", "a2"]
registry.register(plugin)
outbound_plugins = registry.get_plugins_by_port(OutboundPort)
assert len(outbound_plugins) == 1
assert outbound_plugins[0] is plugin