fix(telegram): report cron topic fallback

This commit is contained in:
analista
2026-05-14 11:25:44 +09:00
committed by Teknium
parent 16d8e44f7a
commit d81b888807
4 changed files with 129 additions and 4 deletions

View File

@@ -2396,6 +2396,65 @@ class TestDeliverResultTimeoutCancelsFuture:
assert result is None, f"expected successful delivery, got error: {result!r}"
standalone_send.assert_awaited_once()
def test_live_adapter_thread_fallback_records_delivery_error(self):
"""A cron target with an explicit topic must not be marked clean if
Telegram falls back to the base chat after "thread not found".
"""
from gateway.config import Platform
from gateway.platforms.base import SendResult
from concurrent.futures import Future
send_result = SendResult(
success=True,
message_id="42",
raw_response={
"requested_thread_id": 7072,
"thread_fallback": True,
},
)
adapter = MagicMock()
adapter.send = AsyncMock(return_value=send_result)
pconfig = MagicMock()
pconfig.enabled = True
mock_cfg = MagicMock()
mock_cfg.platforms = {Platform.TELEGRAM: pconfig}
loop = MagicMock()
loop.is_running.return_value = True
job = {
"id": "thread-fallback-job",
"deliver": "telegram:226252250:7072",
}
completed_future = Future()
completed_future.set_result(send_result)
def fake_run_coro(coro, _loop):
coro.close()
return completed_future
with patch("gateway.config.load_gateway_config", return_value=mock_cfg), \
patch("cron.scheduler.load_config", return_value={"cron": {"wrap_response": False}}), \
patch("asyncio.run_coroutine_threadsafe", side_effect=fake_run_coro):
result = _deliver_result(
job,
"Hello world",
adapters={Platform.TELEGRAM: adapter},
loop=loop,
)
assert result == (
"configured thread_id 7072 for telegram:226252250 was not found; "
"delivered without thread_id"
)
adapter.send.assert_called_once_with(
"226252250",
"Hello world",
metadata={"thread_id": "7072"},
)
class TestSendMediaTimeoutCancelsFuture:
"""Same orphan-coroutine guarantee for _send_media_via_adapter's

View File

@@ -372,7 +372,7 @@ async def test_send_typing_falls_back_without_thread_on_bad_request():
@pytest.mark.asyncio
async def test_send_retries_without_thread_on_thread_not_found():
"""When message_thread_id causes 'thread not found', retry without it."""
"""When message_thread_id keeps failing, retry once then fall back."""
adapter = _make_adapter()
call_log = []
@@ -394,10 +394,43 @@ async def test_send_retries_without_thread_on_thread_not_found():
assert result.success is True
assert result.message_id == "42"
# First call has thread_id, second call retries without
assert result.raw_response["requested_thread_id"] == 99999
assert result.raw_response["thread_fallback"] is True
# First two calls keep the configured thread, then final fallback drops it.
assert len(call_log) == 3
assert call_log[0]["message_thread_id"] == 99999
assert call_log[1]["message_thread_id"] == 99999
assert call_log[2]["message_thread_id"] is None
@pytest.mark.asyncio
async def test_send_retries_transient_thread_not_found_before_fallback():
"""A one-off Telegram thread-not-found response should still land in the topic."""
adapter = _make_adapter()
call_log = []
async def mock_send_message(**kwargs):
call_log.append(dict(kwargs))
if len(call_log) == 1:
raise FakeBadRequest("Message thread not found")
return SimpleNamespace(message_id=43)
adapter._bot = SimpleNamespace(send_message=mock_send_message)
result = await adapter.send(
chat_id="123",
content="test message",
metadata={"thread_id": "99999"},
)
assert result.success is True
assert result.message_id == "43"
assert result.raw_response["requested_thread_id"] == 99999
assert result.raw_response["thread_fallback"] is False
assert len(call_log) == 2
assert call_log[0]["message_thread_id"] == 99999
assert call_log[1]["message_thread_id"] is None
assert call_log[1]["message_thread_id"] == 99999
@pytest.mark.asyncio
@@ -1079,6 +1112,7 @@ async def test_send_without_thread_id_unaffected():
)
assert result.success is True
assert result.raw_response["thread_fallback"] is False
assert len(call_log) == 1
assert call_log[0]["message_thread_id"] is None