from __future__ import annotations from unittest.mock import AsyncMock, MagicMock import pytest from yuxi.channel.application.service.delivery_service import DeliveryService from yuxi.channel.domain.exception.agent_crash_error import AgentCrashError from yuxi.channel.domain.model.message.dispatch_result import DispatchResult, SendResult from yuxi.channel.domain.model.session.channel_session import ChannelSession class TestDeliveryService: @pytest.fixture def mock_adapters(self): adapter = AsyncMock() adapter.send_message.return_value = SendResult(success=True) adapter.capabilities.media = True return {"web": adapter} @pytest.fixture def mock_message_repo(self): return AsyncMock() @pytest.fixture def mock_outbox_repo(self): return AsyncMock() @pytest.fixture def mock_event_publisher(self): return AsyncMock() @pytest.fixture def mock_agent_port(self): agent = AsyncMock() agent.stream_chat.return_value = "Hello, user!" return agent @pytest.fixture def delivery_service(self, mock_adapters, mock_message_repo, mock_outbox_repo, mock_event_publisher, mock_agent_port): return DeliveryService( adapters=mock_adapters, message_repo=mock_message_repo, outbox_repo=mock_outbox_repo, event_publisher=mock_event_publisher, agent_port=mock_agent_port, ) @pytest.fixture def sample_session(self): return ChannelSession( id=1, thread_id="thread123", user_id="user1", agent_id="1", channel_type="web", channel_session_key="key1", status="active", title=None, ) @pytest.fixture def sample_payload(self): return { "message_id": "msg123", "channel_type": "web", "content": "Hello", "sender_id": "user1", "session_id": "session123", "trace_id": "trace123", "metadata": {"deliver": True}, "attachments": [{"url": "http://example.com/image.png", "media_type": "image"}], } @pytest.mark.asyncio async def test_deliver_success(self, delivery_service, sample_payload, sample_session): result = await delivery_service.deliver(sample_payload, sample_session) assert result.success is True @pytest.mark.asyncio async def test_deliver_no_deliver(self, delivery_service, mock_message_repo, sample_payload, sample_session): sample_payload["metadata"]["deliver"] = False result = await delivery_service.deliver(sample_payload, sample_session) assert result.success is True mock_message_repo.save_assistant_message.assert_awaited_once() @pytest.mark.asyncio async def test_deliver_agent_crash(self, delivery_service, mock_agent_port, sample_payload, sample_session): mock_agent_port.stream_chat.side_effect = AgentCrashError("agent crashed") result = await delivery_service.deliver(sample_payload, sample_session) assert result.success is False assert result.error == "agent_crash" @pytest.mark.asyncio async def test_deliver_send_failure(self, delivery_service, mock_adapters, mock_outbox_repo, sample_payload, sample_session): mock_adapters["web"].send_message.return_value = SendResult(success=False, error="send failed") result = await delivery_service.deliver(sample_payload, sample_session) mock_outbox_repo.enqueue.assert_awaited_once() @pytest.mark.asyncio async def test_deliver_no_adapter(self, delivery_service, sample_payload, sample_session): sample_payload["channel_type"] = "unknown" result = await delivery_service.deliver(sample_payload, sample_session) assert result.success is False assert "no adapter" in result.error @pytest.mark.asyncio async def test_deliver_publishes_event(self, delivery_service, mock_event_publisher, sample_payload, sample_session): await delivery_service.deliver(sample_payload, sample_session) mock_event_publisher.publish.assert_awaited_once() @pytest.mark.asyncio async def test_deliver_saves_message(self, delivery_service, mock_message_repo, sample_payload, sample_session): await delivery_service.deliver(sample_payload, sample_session) mock_message_repo.save_assistant_message.assert_awaited_once() @pytest.mark.asyncio async def test_deliver_with_media(self, delivery_service, mock_adapters, sample_payload, sample_session): await delivery_service.deliver(sample_payload, sample_session) mock_adapters["web"].send_media.assert_awaited_once() @pytest.mark.asyncio async def test_deliver_typing_indicator(self, delivery_service, mock_adapters, sample_payload, sample_session): mock_adapters["web"].capabilities.typing = True await delivery_service.deliver(sample_payload, sample_session) @pytest.mark.asyncio async def test_call_agent_timeout(self, delivery_service, mock_agent_port, sample_payload, sample_session): mock_agent_port.stream_chat.side_effect = TimeoutError("timeout") with pytest.raises(AgentCrashError): await delivery_service._call_agent(sample_payload, sample_session, "Hello", "web", "msg123", "trace123") @pytest.mark.asyncio async def test_try_send_success(self, delivery_service, mock_adapters, sample_session): result = await delivery_service._try_send( mock_adapters["web"], "thread123", "Hello", "web", "trace123", "msg123" ) assert result.success is True @pytest.mark.asyncio async def test_try_send_failure(self, delivery_service, mock_adapters, sample_session): mock_adapters["web"].send_message.side_effect = Exception("send error") result = await delivery_service._try_send( mock_adapters["web"], "thread123", "Hello", "web", "trace123", "msg123" ) assert result.success is False