from __future__ import annotations from unittest.mock import AsyncMock import pytest from yuxi.channel.channels.web.adapter import WebAdapter from yuxi.channel.channels.web.config import WebConfig from yuxi.channel.channels.web.outbound import WEB_CAPABILITIES, WebOutbound from yuxi.channel.channels.web.translator import WebTranslator from yuxi.channel.domain.model.shared.channel_type import ChannelType class TestWebTranslator: def test_translate_message(self) -> None: raw = { "id": "msg-001", "sender_id": "user-1", "sender_name": "Alice", "content": "hello world", "metadata": {"key": "value"}, } msg = WebTranslator.translate_message(raw) assert msg.message_id == "msg-001" assert msg.channel_type == ChannelType.WEB assert msg.sender.id == "user-1" assert msg.sender.name == "Alice" assert msg.content == "hello world" assert msg.metadata == {"key": "value"} def test_translate_message_defaults(self) -> None: raw = {} msg = WebTranslator.translate_message(raw) assert msg.message_id == "" assert msg.content == "" assert msg.sender.id == "" assert msg.metadata == {} class TestWebConfig: def test_defaults(self) -> None: config = WebConfig() assert config.max_connections == 1000 def test_with_values(self) -> None: config = WebConfig(max_connections=500) assert config.max_connections == 500 class TestWebOutbound: @pytest.mark.asyncio async def test_send_text_without_sse_returns_false(self) -> None: outbound = WebOutbound() result = await outbound.send_text("sess-1", "hello") assert result is False @pytest.mark.asyncio async def test_send_text_with_sse_push(self) -> None: sse = AsyncMock() sse.push_event = AsyncMock(return_value=True) outbound = WebOutbound(sse_push=sse) result = await outbound.send_text("sess-1", "hello", metadata={"channel_type": "web"}) assert result is True sse.push_event.assert_called_once() event = sse.push_event.call_args[0][1] assert event["type"] == "assistant_message" assert event["content"] == "hello" @pytest.mark.asyncio async def test_send_text_sse_push_failure(self) -> None: sse = AsyncMock() sse.push_event = AsyncMock(return_value=False) outbound = WebOutbound(sse_push=sse) result = await outbound.send_text("sess-1", "hello") assert result is False @pytest.mark.asyncio async def test_send_typing_without_sse(self) -> None: outbound = WebOutbound() await outbound.send_typing("sess-1") @pytest.mark.asyncio async def test_send_typing_with_sse(self) -> None: sse = AsyncMock() sse.push_event = AsyncMock(return_value=True) outbound = WebOutbound(sse_push=sse) await outbound.send_typing("sess-1") sse.push_event.assert_called_once() @pytest.mark.asyncio async def test_send_media_without_sse_returns_false(self) -> None: outbound = WebOutbound() result = await outbound.send_media("sess-1", url="http://x", media_type="image") assert result is False @pytest.mark.asyncio async def test_send_media_with_sse(self) -> None: sse = AsyncMock() sse.push_event = AsyncMock(return_value=True) outbound = WebOutbound(sse_push=sse) result = await outbound.send_media("sess-1", url="http://x", media_type="image") assert result is True class TestWebAdapter: def test_channel_type(self) -> None: adapter = WebAdapter() assert adapter.channel_type == ChannelType.WEB.value def test_capabilities(self) -> None: assert WEB_CAPABILITIES.media is True assert WEB_CAPABILITIES.streaming is True assert WEB_CAPABILITIES.max_text_length == 32768 def test_get_default_config(self) -> None: config = WebAdapter.get_default_config() assert "max_connections" in config @pytest.mark.asyncio async def test_is_healthy_before_open(self) -> None: adapter = WebAdapter() assert await adapter.is_healthy() is False @pytest.mark.asyncio async def test_is_healthy_after_open(self) -> None: adapter = WebAdapter() await adapter.open() assert await adapter.is_healthy() is True @pytest.mark.asyncio async def test_close(self) -> None: adapter = WebAdapter() await adapter.open() await adapter.close() assert await adapter.is_healthy() is False @pytest.mark.asyncio async def test_receive_message(self) -> None: adapter = WebAdapter() raw = {"id": "m1", "content": "hi", "sender_id": "u1"} msg = await adapter.receive_message(raw) assert msg.message_id == "m1" @pytest.mark.asyncio async def test_send_message_without_sse_returns_failure(self) -> None: adapter = WebAdapter() result = await adapter.send_message("sess-1", "hello", channel_type="web", metadata={}) assert result.success is False @pytest.mark.asyncio async def test_send_message_with_sse(self) -> None: sse = AsyncMock() sse.push_event = AsyncMock(return_value=True) adapter = WebAdapter(sse_push=sse) await adapter.open() result = await adapter.send_message("sess-1", "hello", channel_type="web", metadata={}) assert result.success is True @pytest.mark.asyncio async def test_send_typing(self) -> None: sse = AsyncMock() sse.push_event = AsyncMock(return_value=True) adapter = WebAdapter(sse_push=sse) await adapter.send_typing("sess-1") def test_ws_connection_is_none(self) -> None: adapter = WebAdapter() assert adapter.ws_connection is None def test_route_contributor_has_router(self) -> None: adapter = WebAdapter() contributor = adapter.route_contributor assert contributor.router is not None