from types import SimpleNamespace import pytest from banban.service.pending_voice_message import PendingVoiceMessageService from services.scheduler import TaskScheduler class FakeSession: def __init__(self): self.commits = 0 self.rollbacks = 0 self.closed = False async def execute(self, statement, params=None): return SimpleNamespace(mappings=lambda: SimpleNamespace(all=lambda: [])) async def commit(self): self.commits += 1 async def rollback(self): self.rollbacks += 1 async def close(self): self.closed = True @pytest.mark.asyncio async def test_add_pending_message_sends_mqtt_notice_after_commit(monkeypatch): service = PendingVoiceMessageService() session = FakeSession() upserts = [] notices = [] async def fake_get_session(): return session async def fake_upsert_pending(self, **kwargs): upserts.append(kwargs) async def fake_notify_device_pending_message(*, target_device_id): notices.append(target_device_id) return True monkeypatch.setattr(service, "get_session", fake_get_session) monkeypatch.setattr( "banban.service.pending_voice_message.PendingVoiceMessageDAO.upsert_pending", fake_upsert_pending, ) monkeypatch.setattr(service, "notify_device_pending_message", fake_notify_device_pending_message) await service.add_pending_message( target_device_id=" TalkingQ_device001 ", sender_device_id=" TalkingQ_device002 ", im_message_id=123, media_file_key="messages/audio/demo.mp3", audio_url="http://example.test/demo.mp3", source="parent_child_voice", ) assert session.commits == 1 assert session.rollbacks == 0 assert session.closed is True assert upserts == [ { "target_device_id": "TalkingQ_device001", "sender_device_id": "TalkingQ_device002", "im_message_id": 123, "media_file_key": "messages/audio/demo.mp3", "audio_url": "http://example.test/demo.mp3", "source": "parent_child_voice", } ] assert notices == ["TalkingQ_device001"] @pytest.mark.asyncio async def test_scheduler_sends_pending_notice_without_websocket_check(monkeypatch): scheduler = TaskScheduler() calls = [] class FakePendingVoiceService: async def list_devices_with_pending(self): return ["TalkingQ_device001"] async def notify_device_pending_message(self, *, target_device_id): calls.append(target_device_id) return True class FakeOfflineAudioCache: async def get_all_audio_cache(self): return {} monkeypatch.setattr("services.scheduler.pending_voice_message_service", FakePendingVoiceService()) monkeypatch.setattr("services.scheduler.offline_audio_cache", FakeOfflineAudioCache()) await scheduler._execute_task() assert calls == ["TalkingQ_device001"]