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.media is False assert caps.tts 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", "polls", "reactions", "edit", "unsend", "reply", "effects", "group_management", "threads", "media", "max_media_size_mb", "tts", "native_commands", "block_streaming", "pin", "unpin", "list_pins", "send_ephemeral", "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