diff --git a/tests/gateway/test_interrupt_key_match.py b/tests/gateway/test_interrupt_key_match.py index ece3878dd0..445a16f7a1 100644 --- a/tests/gateway/test_interrupt_key_match.py +++ b/tests/gateway/test_interrupt_key_match.py @@ -11,7 +11,7 @@ import asyncio import pytest from gateway.config import Platform, PlatformConfig -from gateway.platforms.base import BasePlatformAdapter, MessageEvent, SendResult +from gateway.platforms.base import BasePlatformAdapter, MessageEvent, MessageType, SendResult from gateway.session import SessionSource, build_session_key @@ -122,3 +122,29 @@ class TestInterruptKeyConsistency: # Interrupt event was set assert adapter._active_sessions[session_key].is_set() + + @pytest.mark.asyncio + async def test_photo_followup_is_queued_without_interrupt(self): + """Photo follow-ups should queue behind the active run instead of interrupting it.""" + adapter = StubAdapter() + adapter.set_message_handler(lambda event: asyncio.sleep(0, result=None)) + + source = _source("-1001234", "group") + session_key = build_session_key(source) + interrupt_event = asyncio.Event() + adapter._active_sessions[session_key] = interrupt_event + + event = MessageEvent( + text="caption", + source=source, + message_type=MessageType.PHOTO, + message_id="2", + media_urls=["/tmp/photo-a.jpg"], + media_types=["image/jpeg"], + ) + await adapter.handle_message(event) + + queued = adapter._pending_messages[session_key] + assert queued is event + assert queued.media_urls == ["/tmp/photo-a.jpg"] + assert interrupt_event.is_set() is False diff --git a/tests/gateway/test_telegram_documents.py b/tests/gateway/test_telegram_documents.py index e26b4a6a41..6fe9a24532 100644 --- a/tests/gateway/test_telegram_documents.py +++ b/tests/gateway/test_telegram_documents.py @@ -352,6 +352,26 @@ class TestDocumentDownloadBlock: # --------------------------------------------------------------------------- class TestMediaGroups: + @pytest.mark.asyncio + async def test_non_album_photo_burst_is_buffered_and_combined(self, adapter): + first_photo = _make_photo(_make_file_obj(b"first")) + second_photo = _make_photo(_make_file_obj(b"second")) + + msg1 = _make_message(caption="two images", photo=[first_photo]) + msg2 = _make_message(photo=[second_photo]) + + with patch("gateway.platforms.telegram.cache_image_from_bytes", side_effect=["/tmp/burst-one.jpg", "/tmp/burst-two.jpg"]): + await adapter._handle_media_message(_make_update(msg1), MagicMock()) + await adapter._handle_media_message(_make_update(msg2), MagicMock()) + assert adapter.handle_message.await_count == 0 + await asyncio.sleep(adapter.MEDIA_GROUP_WAIT_SECONDS + 0.05) + + adapter.handle_message.assert_awaited_once() + event = adapter.handle_message.await_args.args[0] + assert event.text == "two images" + assert event.media_urls == ["/tmp/burst-one.jpg", "/tmp/burst-two.jpg"] + assert len(event.media_types) == 2 + @pytest.mark.asyncio async def test_photo_album_is_buffered_and_combined(self, adapter): first_photo = _make_photo(_make_file_obj(b"first")) diff --git a/tests/gateway/test_telegram_photo_interrupts.py b/tests/gateway/test_telegram_photo_interrupts.py new file mode 100644 index 0000000000..9235e539db --- /dev/null +++ b/tests/gateway/test_telegram_photo_interrupts.py @@ -0,0 +1,49 @@ +import asyncio +from unittest.mock import MagicMock + +import pytest + +from gateway.config import GatewayConfig, Platform, PlatformConfig +from gateway.platforms.base import MessageEvent, MessageType +from gateway.session import SessionSource, build_session_key +from gateway.run import GatewayRunner + + +class _PendingAdapter: + def __init__(self): + self._pending_messages = {} + + +def _make_runner(): + runner = object.__new__(GatewayRunner) + runner.config = GatewayConfig(platforms={Platform.TELEGRAM: PlatformConfig(enabled=True, token="***")}) + runner.adapters = {Platform.TELEGRAM: _PendingAdapter()} + runner._running_agents = {} + runner._pending_messages = {} + runner._pending_approvals = {} + runner._voice_mode = {} + runner._is_user_authorized = lambda _source: True + return runner + + +@pytest.mark.asyncio +async def test_handle_message_does_not_priority_interrupt_photo_followup(): + runner = _make_runner() + source = SessionSource(platform=Platform.TELEGRAM, chat_id="12345", chat_type="dm") + session_key = build_session_key(source) + running_agent = MagicMock() + runner._running_agents[session_key] = running_agent + + event = MessageEvent( + text="caption", + message_type=MessageType.PHOTO, + source=source, + media_urls=["/tmp/photo-a.jpg"], + media_types=["image/jpeg"], + ) + + result = await runner._handle_message(event) + + assert result is None + running_agent.interrupt.assert_not_called() + assert runner.adapters[Platform.TELEGRAM]._pending_messages[session_key] is event