from __future__ import annotations from unittest.mock import AsyncMock, MagicMock, patch import pytest from yuxi.channels.adapters.telegram.adapter import TelegramAdapter from yuxi.channels.models import ChannelStatus class TestMessageReactionCount: @pytest.fixture def adapter(self): return TelegramAdapter(config={"bot_token": "123:abc"}) def test_reaction_count_passthrough(self, adapter): mr_chat = MagicMock() mr_chat.id = -10012345 mr_chat.type = "supergroup" mrc = MagicMock() mrc.chat = mr_chat mrc.message_id = 42 rc1 = MagicMock() rc1.type = MagicMock() rc1.type.__str__ = lambda s: "ReactionTypeEmoji" rc1.total_count = 5 rc1.emoji = "\U0001f44d" rc2 = MagicMock() rc2.type = MagicMock() rc2.type.__str__ = lambda s: "ReactionTypeEmoji" rc2.total_count = 3 rc2.emoji = "\u2764\ufe0f" mrc.reactions = [rc1, rc2] update = MagicMock() update.update_id = 1 update.message = None update.edited_message = None update.callback_query = None update.my_chat_member = None update.chat_member = None update.poll = None update.poll_answer = None update.message_reaction = None update.message_reaction_count = mrc result = adapter._make_passthrough_event(update) assert result.content == "(reaction count update)" assert result.metadata["message_id"] == "42" assert len(result.metadata["reactions"]) == 2 assert result.metadata["reactions"][0]["count"] == 5 assert result.metadata["reactions"][1]["count"] == 3 class TestStartPollingError: @pytest.fixture def adapter(self): return TelegramAdapter(config={"bot_token": "123:abc"}) @pytest.mark.asyncio async def test_polling_unexpected_error_sets_error_status(self, adapter): adapter._application = MagicMock() adapter._application.bot = AsyncMock() adapter._application.updater = MagicMock() adapter._application.updater.start_polling = AsyncMock( side_effect=ConnectionError("network gone") ) adapter._status = ChannelStatus.CONNECTED try: await adapter._application.updater.start_polling( poll_interval=1.0, timeout=10 ) except Exception: adapter._status = ChannelStatus.ERROR assert adapter._status == ChannelStatus.ERROR @pytest.mark.asyncio async def test_polling_cancelled_error_preserved(self, adapter): adapter._application = MagicMock() adapter._application.bot = AsyncMock() adapter._application.updater = MagicMock() adapter._application.updater.start_polling = AsyncMock( side_effect=ConnectionError("network gone") ) adapter._status = ChannelStatus.CONNECTED try: await adapter._application.updater.start_polling( poll_interval=1.0, timeout=10 ) except Exception: adapter._status = ChannelStatus.ERROR assert adapter._status == ChannelStatus.ERROR @pytest.mark.asyncio async def test_cancelled_error_does_not_change_status(self): import asyncio adapter = TelegramAdapter(config={"bot_token": "123:abc"}) adapter._status = ChannelStatus.CONNECTED try: raise asyncio.CancelledError() except asyncio.CancelledError: pass assert adapter._status == ChannelStatus.CONNECTED class TestDownloadMediaProtection: @pytest.fixture def adapter(self): return TelegramAdapter(config={"bot_token": "123:abc"}) @pytest.mark.asyncio async def test_download_large_file_raises_error(self, adapter): mock_file = MagicMock() mock_file.file_size = 200 * 1024 * 1024 adapter._application = MagicMock() adapter._application.bot = AsyncMock() adapter._application.bot.get_file = AsyncMock(return_value=mock_file) with pytest.raises(ValueError, match="209715200"): await adapter.download_media("file_id_123") @pytest.mark.asyncio async def test_download_small_file_allowed(self, adapter): mock_file = MagicMock() mock_file.file_size = 5 * 1024 * 1024 mock_file.download_as_bytearray = AsyncMock(return_value=b"data") adapter._application = MagicMock() adapter._application.bot = AsyncMock() adapter._application.bot.get_file = AsyncMock(return_value=mock_file) result = await adapter.download_media("file_id_123") assert result == b"data" @pytest.mark.asyncio async def test_download_no_size_info_allowed(self, adapter): mock_file = MagicMock() mock_file.file_size = 0 mock_file.download_as_bytearray = AsyncMock(return_value=b"ok") adapter._application = MagicMock() adapter._application.bot = AsyncMock() adapter._application.bot.get_file = AsyncMock(return_value=mock_file) result = await adapter.download_media("file_id_123") assert result == b"ok" @pytest.mark.asyncio async def test_download_none_size_allowed(self, adapter): mock_file = MagicMock() mock_file.file_size = None mock_file.download_as_bytearray = AsyncMock(return_value=b"ok") adapter._application = MagicMock() adapter._application.bot = AsyncMock() adapter._application.bot.get_file = AsyncMock(return_value=mock_file) result = await adapter.download_media("file_id_123") assert result == b"ok" class TestLeaseFallback: @pytest.mark.asyncio async def test_lease_acquire_without_redis_still_acquires(self): from yuxi.channels.adapters.telegram.lease import PollingLease with patch("yuxi.channels.adapters.telegram.lease.PollingLease.acquire", new=AsyncMock(return_value=True)): lease = PollingLease({"bot_token": "test123"}) lease._acquired = True assert lease.is_acquired is True