import unittest import asyncio from types import SimpleNamespace from unittest.mock import AsyncMock, patch from app.integrations.telegram_satellite_service import TelegramSatelliteService class _DummySession: def close(self): return None class TelegramMultimodalTests(unittest.IsolatedAsyncioTestCase): async def asyncTearDown(self): service = getattr(self, "_service_under_test", None) if service is not None: await service._shutdown_chat_workers() async def test_process_message_uses_extracted_image_message(self): service = TelegramSatelliteService("token-teste") self._service_under_test = service tools_db = _DummySession() mock_db = _DummySession() with patch("app.integrations.telegram_satellite_service.SessionLocal", return_value=tools_db), patch( "app.integrations.telegram_satellite_service.SessionMockLocal", return_value=mock_db, ), patch("app.integrations.telegram_satellite_service.UserService") as user_service_cls, patch( "app.integrations.telegram_satellite_service.OrquestradorService" ) as orchestrator_cls, patch.object( service, "_build_orchestration_message_from_image", AsyncMock(return_value="[imagem recebida no telegram]\nDados extraidos da imagem: Registrar multa de aluguel: placa ABC1D23; valor 293,47; auto_infracao A123456."), ): user_service_cls.return_value.get_or_create.return_value = SimpleNamespace(id=7) orchestrator_cls.return_value.handle_message = AsyncMock(return_value="ok") answer = await service._process_message( text="segue a multa", sender={"id": 99, "first_name": "Vitor"}, chat_id=99, image_attachments=[{"mime_type": "image/jpeg", "data": b"123"}], ) self.assertEqual(answer, "ok") orchestrator_cls.return_value.handle_message.assert_awaited_once() kwargs = orchestrator_cls.return_value.handle_message.await_args.kwargs self.assertIn("Registrar multa de aluguel", kwargs["message"]) self.assertEqual(kwargs["user_id"], 7) async def test_process_message_returns_direct_failure_for_unreadable_image(self): service = TelegramSatelliteService("token-teste") self._service_under_test = service tools_db = _DummySession() mock_db = _DummySession() with patch("app.integrations.telegram_satellite_service.SessionLocal", return_value=tools_db), patch( "app.integrations.telegram_satellite_service.SessionMockLocal", return_value=mock_db, ), patch("app.integrations.telegram_satellite_service.UserService") as user_service_cls, patch( "app.integrations.telegram_satellite_service.OrquestradorService" ) as orchestrator_cls, patch.object( service, "_build_orchestration_message_from_image", AsyncMock(return_value="Nao consegui identificar os dados da imagem. Descreva o documento ou envie uma foto mais nitida."), ): user_service_cls.return_value.get_or_create.return_value = SimpleNamespace(id=7) orchestrator_cls.return_value.handle_message = AsyncMock() answer = await service._process_message( text="", sender={"id": 99}, chat_id=99, image_attachments=[{"mime_type": "image/jpeg", "data": b"123"}], ) self.assertIn("Nao consegui identificar os dados da imagem", answer) self.assertFalse(orchestrator_cls.return_value.handle_message.await_count) async def test_process_message_returns_direct_failure_for_receipt_without_watermark(self): service = TelegramSatelliteService("token-teste") self._service_under_test = service tools_db = _DummySession() mock_db = _DummySession() with patch("app.integrations.telegram_satellite_service.SessionLocal", return_value=tools_db), patch( "app.integrations.telegram_satellite_service.SessionMockLocal", return_value=mock_db, ), patch("app.integrations.telegram_satellite_service.UserService") as user_service_cls, patch( "app.integrations.telegram_satellite_service.OrquestradorService" ) as orchestrator_cls, patch.object( service, "_build_orchestration_message_from_image", AsyncMock(return_value="O comprovante enviado nao e valido. Envie um comprovante valido com a marca d'agua SysaltiIA visivel."), ): user_service_cls.return_value.get_or_create.return_value = SimpleNamespace(id=7) orchestrator_cls.return_value.handle_message = AsyncMock() answer = await service._process_message( text="segue o comprovante", sender={"id": 99}, chat_id=99, image_attachments=[{"mime_type": "image/jpeg", "data": b"123"}], ) self.assertIn("marca d'agua SysaltiIA visivel", answer) self.assertFalse(orchestrator_cls.return_value.handle_message.await_count) async def test_schedule_update_processing_allows_parallel_chats(self): service = TelegramSatelliteService("token-teste") self._service_under_test = service release_first_chat = asyncio.Event() chat_one_started = asyncio.Event() started_chats: list[int] = [] async def fake_handle_update(*, session, update): chat_id = update["message"]["chat"]["id"] started_chats.append(chat_id) if chat_id == 1: chat_one_started.set() await release_first_chat.wait() with patch.object(service, "_handle_update", new=fake_handle_update): await service._schedule_update_processing( session=SimpleNamespace(), update={"update_id": 1, "message": {"chat": {"id": 1}, "text": "primeiro"}}, ) await chat_one_started.wait() await service._schedule_update_processing( session=SimpleNamespace(), update={"update_id": 2, "message": {"chat": {"id": 2}, "text": "segundo"}}, ) await asyncio.sleep(0) self.assertEqual(started_chats, [1, 2]) release_first_chat.set() await asyncio.sleep(0) async def test_schedule_update_processing_preserves_order_per_chat(self): service = TelegramSatelliteService("token-teste") self._service_under_test = service first_started = asyncio.Event() allow_first_to_finish = asyncio.Event() second_started = asyncio.Event() started_updates: list[int] = [] async def fake_handle_update(*, session, update): update_id = update["update_id"] started_updates.append(update_id) if update_id == 1: first_started.set() await allow_first_to_finish.wait() return second_started.set() with patch.object(service, "_handle_update", new=fake_handle_update): await service._schedule_update_processing( session=SimpleNamespace(), update={"update_id": 1, "message": {"chat": {"id": 1}, "text": "primeiro"}}, ) await first_started.wait() await service._schedule_update_processing( session=SimpleNamespace(), update={"update_id": 2, "message": {"chat": {"id": 1}, "text": "segundo"}}, ) await asyncio.sleep(0) self.assertFalse(second_started.is_set()) allow_first_to_finish.set() await asyncio.wait_for(second_started.wait(), timeout=1) self.assertEqual(started_updates, [1, 2])