"""Regression coverage for partial Telegram overflow delivery.""" from types import SimpleNamespace from unittest.mock import AsyncMock, MagicMock import pytest from gateway.config import PlatformConfig from gateway.platforms.base import SendResult from gateway.platforms.telegram import TelegramAdapter from gateway.stream_consumer import GatewayStreamConsumer def _message(message_id: int | str) -> SimpleNamespace: return SimpleNamespace(message_id=message_id) @pytest.fixture def telegram_adapter() -> TelegramAdapter: adapter = TelegramAdapter(PlatformConfig(enabled=True, token="fake-token")) adapter._bot = MagicMock() object.__setattr__(adapter, "MAX_MESSAGE_LENGTH", 160) return adapter @pytest.mark.asyncio async def test_edit_overflow_split_reports_success_when_all_continuations_land(telegram_adapter): """Complete overflow delivery keeps the existing successful contract.""" content = "word " * 120 telegram_adapter._bot.edit_message_text = AsyncMock(return_value=True) telegram_adapter._bot.send_message = AsyncMock( side_effect=[_message(202), _message(203), _message(204), _message(205)] ) result = await telegram_adapter._edit_overflow_split( "12345", "201", content, finalize=False, metadata={"thread_id": "77"} ) assert result.success is True assert result.message_id == result.continuation_message_ids[-1] assert result.raw_response is None assert telegram_adapter._bot.edit_message_text.await_count == 1 assert telegram_adapter._bot.send_message.await_count == len(result.continuation_message_ids) for call in telegram_adapter._bot.send_message.await_args_list: assert call.kwargs["message_thread_id"] == 77 @pytest.mark.asyncio async def test_edit_overflow_split_reports_later_partial_failure_after_some_continuations_land(telegram_adapter): """Partial metadata tracks the last delivered continuation before failure.""" content = "word " * 120 telegram_adapter._bot.edit_message_text = AsyncMock(return_value=True) telegram_adapter._bot.send_message = AsyncMock( side_effect=[ _message(202), RuntimeError("telegram send failed"), RuntimeError("telegram send failed"), ] ) result = await telegram_adapter._edit_overflow_split( "12345", "201", content, finalize=False, metadata={"thread_id": "77"} ) assert result.success is False assert result.message_id == "202" assert result.raw_response["partial_overflow"] is True assert result.raw_response["delivered_chunks"] == 2 assert result.raw_response["last_message_id"] == "202" assert result.continuation_message_ids == ("202",) @pytest.mark.asyncio async def test_edit_overflow_split_reports_partial_failure_when_continuation_fails(telegram_adapter): """A failed continuation must not be reported as final delivery.""" content = "word " * 120 telegram_adapter._bot.edit_message_text = AsyncMock(return_value=True) telegram_adapter._bot.send_message = AsyncMock( side_effect=[RuntimeError("telegram send failed"), RuntimeError("telegram send failed")] ) result = await telegram_adapter._edit_overflow_split( "12345", "201", content, finalize=False, metadata={"thread_id": "77"} ) assert result.success is False assert result.retryable is True assert result.error == "overflow_continuation_failed" assert result.message_id == "201" assert result.raw_response["partial_overflow"] is True assert result.raw_response["delivered_chunks"] == 1 assert result.raw_response["total_chunks"] > 1 assert result.raw_response["last_message_id"] == "201" assert result.raw_response["delivered_prefix"] assert result.continuation_message_ids == () @pytest.mark.asyncio async def test_stream_consumer_fallback_sends_tail_after_partial_overflow(): """A partial overflow edit enters fallback instead of marking final delivered.""" adapter = MagicMock() adapter.MAX_MESSAGE_LENGTH = 4096 adapter.edit_message = AsyncMock( return_value=SendResult( success=False, message_id="preview-1", error="overflow_continuation_failed", retryable=True, raw_response={ "partial_overflow": True, "delivered_chunks": 1, "total_chunks": 2, "last_message_id": "preview-1", "delivered_prefix": "hello ", }, ) ) adapter.send = AsyncMock(return_value=SendResult(success=True, message_id="tail-1")) adapter.delete_message = AsyncMock(return_value=True) consumer = GatewayStreamConsumer(adapter, "chat-1", metadata={"thread_id": "77"}) consumer._message_id = "preview-1" consumer._last_sent_text = "hello " ok = await consumer._send_or_edit("hello world", finalize=True) assert ok is False assert consumer.final_response_sent is False assert consumer.final_content_delivered is False assert consumer._fallback_final_send is True assert consumer._fallback_prefix == "hello " await consumer._send_fallback_final("hello world") adapter.send.assert_awaited_once() assert adapter.send.await_args.kwargs["content"] == "world" assert adapter.send.await_args.kwargs["metadata"] == {"thread_id": "77"} adapter.delete_message.assert_not_awaited() assert consumer.final_response_sent is True assert consumer.final_content_delivered is True