Spaces:
Running
Running
SilverElixir commited on
Commit ·
102c1fa
1
Parent(s): 535be3d
Fix prod outage: single bot module namespace + skip fil locale in setMyCommands
Browse files- bot.py +16 -1
- tests/test_bot_commands.py +6 -0
- tests/test_bot_state.py +9 -0
bot.py
CHANGED
|
@@ -6,6 +6,16 @@ Lumen — телеграм-бот на Gemini/OpenRouter, webhook-режим.
|
|
| 6 |
|
| 7 |
from __future__ import annotations
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
import asyncio
|
| 10 |
import atexit
|
| 11 |
import contextlib
|
|
@@ -47,6 +57,11 @@ from lumen_lang import (
|
|
| 47 |
t as _lang_t,
|
| 48 |
)
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
# логирование
|
| 51 |
|
| 52 |
LOG_FILE_PATH = Path(os.getenv("BOT_LOG_PATH", "/app/bot.log"))
|
|
@@ -1492,7 +1507,7 @@ async def _webhook_startup() -> None:
|
|
| 1492 |
BotCommand(command="lang", description=_lang_t(code, "cmd_desc_lang")),
|
| 1493 |
],
|
| 1494 |
)
|
| 1495 |
-
for code in
|
| 1496 |
]
|
| 1497 |
|
| 1498 |
async def try_setup():
|
|
|
|
| 6 |
|
| 7 |
from __future__ import annotations
|
| 8 |
|
| 9 |
+
# КРИТИЧНО (прод-инцидент, сентябрь 2026): прод запускается как `python bot.py`
|
| 10 |
+
# (`__main__`), а тесты — через `import bot`. Без этой строки любой отложенный
|
| 11 |
+
# `import bot` внутри функций (их десятки после распила P2) при прод-запуске
|
| 12 |
+
# ЗАНОВО выполнял весь bot.py как отдельный модуль: второе приложение, пустые
|
| 13 |
+
# chat_state/квоты и вечный bot=None → все апдейты уходили в 503, бот молчал.
|
| 14 |
+
# Алиас делает `import bot` везде тем же объектом, что и запущенный модуль.
|
| 15 |
+
import sys as _sys
|
| 16 |
+
_sys.modules.setdefault("bot", _sys.modules[__name__])
|
| 17 |
+
del _sys
|
| 18 |
+
|
| 19 |
import asyncio
|
| 20 |
import atexit
|
| 21 |
import contextlib
|
|
|
|
| 57 |
t as _lang_t,
|
| 58 |
)
|
| 59 |
|
| 60 |
+
# setMyCommands принимает только двухбуквенные ISO 639-1 коды (прод-инцидент:
|
| 61 |
+
# "fil" ронял регистрацию с 400 Bad Request). У филиппинского такого кода нет
|
| 62 |
+
# (fil — ISO 639-2), эти пользователи видят команды на английском по умолчанию.
|
| 63 |
+
COMMAND_LOCALES = [c for c in SUPPORTED_LANGS if c != DEFAULT_LANG and c != "fil"]
|
| 64 |
+
|
| 65 |
# логирование
|
| 66 |
|
| 67 |
LOG_FILE_PATH = Path(os.getenv("BOT_LOG_PATH", "/app/bot.log"))
|
|
|
|
| 1507 |
BotCommand(command="lang", description=_lang_t(code, "cmd_desc_lang")),
|
| 1508 |
],
|
| 1509 |
)
|
| 1510 |
+
for code in COMMAND_LOCALES
|
| 1511 |
]
|
| 1512 |
|
| 1513 |
async def try_setup():
|
tests/test_bot_commands.py
CHANGED
|
@@ -620,3 +620,9 @@ def test_lang_menu_lists_languages_alphabetically(monkeypatch):
|
|
| 620 |
texts = [cb.text for row in markup.inline_keyboard for cb in row]
|
| 621 |
assert texts == [f"{lumen_lang.LANG_NAMES[c]}{' ✓' if c == 'ru' else ''}" for c in codes]
|
| 622 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 620 |
texts = [cb.text for row in markup.inline_keyboard for cb in row]
|
| 621 |
assert texts == [f"{lumen_lang.LANG_NAMES[c]}{' ✓' if c == 'ru' else ''}" for c in codes]
|
| 622 |
|
| 623 |
+
|
| 624 |
+
def test_command_locales_excludes_filipino_without_iso_639_1_code():
|
| 625 |
+
# Прод-инцидент: "fil" ронял setMyCommands с 400 (Bot API принимает только
|
| 626 |
+
# двухбуквенные ISO 639-1 коды, которых у филиппинского нет).
|
| 627 |
+
assert "fil" not in bot.COMMAND_LOCALES
|
| 628 |
+
assert set(bot.COMMAND_LOCALES) == (set(bot.SUPPORTED_LANGS) - {bot.DEFAULT_LANG, "fil"})
|
tests/test_bot_state.py
CHANGED
|
@@ -12,6 +12,7 @@ import json
|
|
| 12 |
import logging
|
| 13 |
import lumen_chat_state
|
| 14 |
import lumen_limits
|
|
|
|
| 15 |
import sentry_sdk
|
| 16 |
import sys
|
| 17 |
import time
|
|
@@ -1093,3 +1094,11 @@ def test_serialize_chat_state_persists_lang():
|
|
| 1093 |
snap2 = lumen_state_storage._serialize_chat_state({"history": []})
|
| 1094 |
assert snap2["lang"] == "en"
|
| 1095 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
import logging
|
| 13 |
import lumen_chat_state
|
| 14 |
import lumen_limits
|
| 15 |
+
import pathlib
|
| 16 |
import sentry_sdk
|
| 17 |
import sys
|
| 18 |
import time
|
|
|
|
| 1094 |
snap2 = lumen_state_storage._serialize_chat_state({"history": []})
|
| 1095 |
assert snap2["lang"] == "en"
|
| 1096 |
|
| 1097 |
+
|
| 1098 |
+
def test_module_alias_for_prod_entry():
|
| 1099 |
+
# Прод-инцидент: прод запускается как `python bot.py` (__main__), а десятки
|
| 1100 |
+
# отложенных `import bot` внутри функций без алиаса выполняли весь bot.py
|
| 1101 |
+
# вторым модулем — пустое состояние и вечный bot=None (все апдейты в 503).
|
| 1102 |
+
# Тест-канарейка: алиас обязан оставаться в голове bot.py, проверяется текстом.
|
| 1103 |
+
src = pathlib.Path(bot.__file__).read_text(encoding="utf-8")
|
| 1104 |
+
assert 'sys.modules.setdefault("bot"' in src or "sys.modules.setdefault('bot'" in src
|