SilverElixir commited on
Commit
102c1fa
·
1 Parent(s): 535be3d

Fix prod outage: single bot module namespace + skip fil locale in setMyCommands

Browse files
Files changed (3) hide show
  1. bot.py +16 -1
  2. tests/test_bot_commands.py +6 -0
  3. 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 SUPPORTED_LANGS if code != DEFAULT_LANG
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