from __future__ import annotations from unittest.mock import AsyncMock, MagicMock import pytest from yuxi.channel.application.service.dispatch_service import DispatchService from yuxi.channel.domain.exception.agent_crash_error import AgentCrashError from yuxi.channel.domain.exception.recoverable_error import RecoverableError from yuxi.channel.domain.model.message.dispatch_result import DispatchResult class TestDispatchService: @pytest.fixture def mock_content_filter(self): filter_mock = AsyncMock() filter_mock.check.return_value = MagicMock(passed=True, violations=[], masked_fields=[]) return filter_mock @pytest.fixture def mock_bot_loop_guard(self): guard = AsyncMock() guard.check.return_value = True return guard @pytest.fixture def mock_circuit_breaker(self): cb = AsyncMock() cb.is_available.return_value = True return cb @pytest.fixture def mock_session_resolver(self): resolver = AsyncMock() session = MagicMock() session.thread_id = "thread123" session.agent_id = "1" resolver.resolve.return_value = session return resolver @pytest.fixture def mock_delivery_service(self): delivery = AsyncMock() delivery.deliver.return_value = DispatchResult(success=True, message_id="msg123") return delivery @pytest.fixture def dispatch_service(self, mock_content_filter, mock_bot_loop_guard, mock_circuit_breaker, mock_session_resolver, mock_delivery_service): return DispatchService( content_filter=mock_content_filter, bot_loop_guard=mock_bot_loop_guard, circuit_breaker=mock_circuit_breaker, session_resolver=mock_session_resolver, delivery_service=mock_delivery_service, ) @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": {"is_group": False}, } @pytest.mark.asyncio async def test_dispatch_success(self, dispatch_service, sample_payload): result = await dispatch_service.dispatch(sample_payload) assert isinstance(result, DispatchResult) @pytest.mark.asyncio async def test_dispatch_content_blocked(self, dispatch_service, mock_content_filter, sample_payload): mock_content_filter.check.return_value = MagicMock(passed=False, violations=["badword"], masked_fields=[]) result = await dispatch_service.dispatch(sample_payload) assert result.success is True @pytest.mark.asyncio async def test_dispatch_bot_loop_detected(self, dispatch_service, mock_bot_loop_guard, sample_payload): mock_bot_loop_guard.check.return_value = False result = await dispatch_service.dispatch(sample_payload) assert result.success is True @pytest.mark.asyncio async def test_dispatch_session_none(self, dispatch_service, mock_session_resolver, sample_payload): mock_session_resolver.resolve.return_value = None result = await dispatch_service.dispatch(sample_payload) assert result.success is False assert result.error == "session_error" @pytest.mark.asyncio async def test_dispatch_circuit_open(self, dispatch_service, mock_circuit_breaker, sample_payload): mock_circuit_breaker.is_available.return_value = False result = await dispatch_service.dispatch(sample_payload) assert result.success is False assert result.error == "circuit_open" @pytest.mark.asyncio async def test_dispatch_delivery_success(self, dispatch_service, mock_delivery_service, sample_payload): result = await dispatch_service.dispatch(sample_payload) mock_circuit_breaker.record_success.assert_awaited_once() @pytest.mark.asyncio async def test_dispatch_agent_crash(self, dispatch_service, mock_delivery_service, sample_payload): mock_delivery_service.deliver.side_effect = AgentCrashError("agent crashed") with pytest.raises(AgentCrashError): await dispatch_service.dispatch(sample_payload) mock_circuit_breaker.record_failure.assert_awaited_once() @pytest.mark.asyncio async def test_dispatch_recoverable_error(self, dispatch_service, mock_delivery_service, sample_payload): mock_delivery_service.deliver.side_effect = RecoverableError("recoverable") with pytest.raises(RecoverableError): await dispatch_service.dispatch(sample_payload) @pytest.mark.asyncio async def test_dispatch_generic_error(self, dispatch_service, mock_delivery_service, sample_payload): mock_delivery_service.deliver.side_effect = Exception("generic error") result = await dispatch_service.dispatch(sample_payload) assert result.success is False mock_circuit_breaker.record_failure.assert_awaited_once() @pytest.mark.asyncio async def test_dispatch_with_metrics(self, dispatch_service, sample_payload): metrics = AsyncMock() dispatch_service._metrics = metrics await dispatch_service.dispatch(sample_payload) metrics.record_worker_dispatch_total.assert_awaited()