fix(telegram): handle expired callback queries gracefully
Parse Telegram HTTP error bodies so the real description is logged, fall back to a normal message when answerCallbackQuery rejects an expired callback ID, and remove inline buttons after a successful action.
This commit is contained in:
1 parent
e2e4dc3336
commit
2d42d8ed55
3 files changed
+96
-8
No files matched your search
+51
-2
@@ -12,6 +12,7 @@ sys.stdout.reconfigure(encoding="utf-8")
|
||||
|
||||
from reaction_board.notify.telegram import ( # noqa: E402
|
||||
TelegramClient,
|
||||
TelegramError,
|
||||
format_duration,
|
||||
)
|
||||
from reaction_board.services.favorites import ActionResult # noqa: E402
|
||||
@@ -94,20 +95,54 @@ def test_get_updates_and_answer():
|
||||
assert transport.calls[0]["url"].endswith("/answerCallbackQuery")
|
||||
assert transport.calls[0]["data"]["callback_query_id"] == "cb1"
|
||||
|
||||
transport = FakeTransport(result=True)
|
||||
bot = TelegramClient("token", 42, transport=transport)
|
||||
bot.edit_reply_markup(77)
|
||||
assert transport.calls[0]["url"].endswith("/editMessageReplyMarkup")
|
||||
assert transport.calls[0]["data"]["message_id"] == 77
|
||||
assert json.loads(transport.calls[0]["data"]["reply_markup"]) == {
|
||||
"inline_keyboard": []
|
||||
}
|
||||
|
||||
|
||||
def test_telegram_error_description_preserved():
|
||||
class ErrorTransport:
|
||||
def post(self, url, data, timeout=30):
|
||||
return {"ok": False, "description": "query is too old"}
|
||||
|
||||
bot = TelegramClient("token", 42, transport=ErrorTransport())
|
||||
try:
|
||||
bot.answer_callback("cb1", "x")
|
||||
except TelegramError as exc:
|
||||
assert "too old" in str(exc)
|
||||
else:
|
||||
raise AssertionError("Telegram 业务错误应抛出描述")
|
||||
|
||||
|
||||
class FakeBot:
|
||||
chat_id = 42
|
||||
|
||||
def __init__(self, updates):
|
||||
def __init__(self, updates, *, fail_answer=False):
|
||||
self.updates = updates
|
||||
self.fail_answer = fail_answer
|
||||
self.answers = []
|
||||
self.sent = []
|
||||
self.edits = []
|
||||
|
||||
def get_updates(self, offset=None):
|
||||
return list(self.updates)
|
||||
|
||||
def answer_callback(self, callback_id, text=""):
|
||||
if self.fail_answer:
|
||||
raise RuntimeError("HTTP Error 400: Bad Request")
|
||||
self.answers.append((callback_id, text))
|
||||
|
||||
def send_text(self, text):
|
||||
self.sent.append(text)
|
||||
|
||||
def edit_reply_markup(self, message_id):
|
||||
self.edits.append(message_id)
|
||||
|
||||
|
||||
class FakeActions:
|
||||
def __init__(self):
|
||||
@@ -133,7 +168,7 @@ def update(update_id, data, *, chat_id=42, from_id=42):
|
||||
"id": f"cb{update_id}",
|
||||
"data": data,
|
||||
"from": {"id": from_id},
|
||||
"message": {"chat": {"id": chat_id}},
|
||||
"message": {"message_id": update_id * 10, "chat": {"id": chat_id}},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -154,6 +189,18 @@ def test_poller_dispatch_and_authorization():
|
||||
assert bot.answers[0] == ("cb1", "已加入收藏夹(位置 #3)")
|
||||
assert bot.answers[3] == ("cb4", "无权限")
|
||||
assert bot.answers[4] == ("cb5", "未知操作")
|
||||
assert bot.edits == [10, 20, 30]
|
||||
|
||||
|
||||
def test_answer_failure_falls_back_to_text():
|
||||
bot = FakeBot([update(1, "fav:9")], fail_answer=True)
|
||||
actions = FakeActions()
|
||||
poller = TelegramPoller(bot, actions)
|
||||
poller.run_once(None)
|
||||
assert actions.calls == [("fav", 9)]
|
||||
assert bot.answers == []
|
||||
assert bot.sent == ["已加入收藏夹(位置 #3)"]
|
||||
assert bot.edits == [10]
|
||||
|
||||
|
||||
def main():
|
||||
@@ -161,7 +208,9 @@ def main():
|
||||
test_send_candidates_and_chunking()
|
||||
test_send_candidates_dedupes_by_aid()
|
||||
test_get_updates_and_answer()
|
||||
test_telegram_error_description_preserved()
|
||||
test_poller_dispatch_and_authorization()
|
||||
test_answer_failure_falls_back_to_text()
|
||||
print("OK: Telegram 消息、分片、长轮询与回调授权测试全部通过")
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user