from __future__ import annotations from unittest.mock import AsyncMock, MagicMock import pytest from yuxi.channel.worker.outbox_retry import OutboxRetryWorker class TestOutboxRetryWorker: @pytest.fixture def mock_outbox_repo(self): return AsyncMock() @pytest.fixture def mock_adapters(self): adapter = AsyncMock() adapter.send_message.return_value = MagicMock(success=True) return {"web": adapter} @pytest.fixture def worker(self, mock_outbox_repo, mock_adapters): return OutboxRetryWorker( outbox_repo=mock_outbox_repo, adapters=mock_adapters, poll_interval=1.0, ) @pytest.mark.asyncio async def test_start(self, worker): await worker.start() assert worker.is_running is True @pytest.mark.asyncio async def test_stop(self, worker): await worker.start() await worker.stop() assert worker.is_running is False @pytest.mark.asyncio async def test_is_running_property(self, worker): assert worker.is_running is False await worker.start() assert worker.is_running is True @pytest.mark.asyncio async def test_run_loop(self, worker, mock_outbox_repo): entry = MagicMock() entry.id = 1 entry.channel_type = "web" entry.payload = {"content": "Hello"} entry.retry_count = 0 mock_outbox_repo.dequeue_pending.return_value = [entry] mock_outbox_repo.mark_delivered.return_value = True await worker.start() await worker.stop() @pytest.mark.asyncio async def test_retry_success(self, worker, mock_outbox_repo, mock_adapters): entry = MagicMock() entry.id = 1 entry.channel_type = "web" entry.payload = {"content": "Hello"} entry.retry_count = 0 mock_outbox_repo.dequeue_pending.return_value = [entry] mock_outbox_repo.mark_delivered.return_value = True await worker.start() await worker.stop() mock_adapters["web"].send_message.assert_awaited_once() mock_outbox_repo.mark_delivered.assert_awaited_once_with(1) @pytest.mark.asyncio async def test_retry_failure(self, worker, mock_outbox_repo, mock_adapters): entry = MagicMock() entry.id = 1 entry.channel_type = "web" entry.payload = {"content": "Hello"} entry.retry_count = 0 mock_outbox_repo.dequeue_pending.return_value = [entry] mock_adapters["web"].send_message.return_value = MagicMock(success=False) mock_outbox_repo.mark_failed.return_value = True await worker.start() await worker.stop() mock_outbox_repo.mark_failed.assert_awaited_once_with(1, "retry") @pytest.mark.asyncio async def test_retry_no_adapter(self, worker, mock_outbox_repo): entry = MagicMock() entry.id = 1 entry.channel_type = "unknown" entry.payload = {"content": "Hello"} entry.retry_count = 0 mock_outbox_repo.dequeue_pending.return_value = [entry] mock_outbox_repo.mark_failed.return_value = True await worker.start() await worker.stop() mock_outbox_repo.mark_failed.assert_awaited_once_with(1, "no adapter") @pytest.mark.asyncio async def test_retry_exception(self, worker, mock_outbox_repo, mock_adapters): entry = MagicMock() entry.id = 1 entry.channel_type = "web" entry.payload = {"content": "Hello"} entry.retry_count = 0 mock_outbox_repo.dequeue_pending.return_value = [entry] mock_adapters["web"].send_message.side_effect = Exception("send error") mock_outbox_repo.mark_failed.return_value = True await worker.start() await worker.stop() mock_outbox_repo.mark_failed.assert_awaited_once_with(1, "send error")