fix: вход в любой режим закрывает обращение поддержки

Деактивация была только в /start и «Главное меню». После перехода в
«Обучение» флаг is_active оставался, и текст уезжал оператору, а не в
обучение — сообщение уходило не туда.

Правило теперь: ушёл из контекста поддержки — обращение неактивно.
_close_support_case() вызывается из enter_mode() (любой режим),
show_main_menu() и start(). «🆘 Поддержка» — не режим, флаг не снимает.
Топик и история сохраняются, следующее обращение продолжает переписку.

Три точки вызова сведены в один helper вместо трёх копий try/except.

Тесты: 230 OK (3 новых). Регрессия проверена на старом коде: после входа
в «Обучение» is_active=True и relay=1 (текст ушёл оператору); с фиксом
is_active=False, relay=0 (текст обработал бот).

VERSION: 0.1.0+55 -> 0.1.0+56
This commit is contained in:
hermes committed 2026-10-11 19:48:57 +02:00
1 parent 12ef2c6b44
commit 3dd2924d5e
5 files changed
+93 -21

No files matched your search

+60
View File
@@ -399,6 +399,59 @@ class StartClosesSupportCaseTests(_TempDbCase):
await bot.start(message, _StubState("lobby"))
self.assertFalse(support.is_support_active(42, self.db_path))
async def test_enter_any_mode_closes_support_case(self):
"""Вход в любой режим = уход из поддержки, флаг снимается.
Регрессия: деактивация была только в /start и «Главное меню», поэтому
после перехода в «Обучение» текст уезжал оператору, а не в обучение.
"""
for mode in ("training", "generation", "subscription", "help"):
with patch.dict("os.environ", {"SUPPORT_GROUP_ID": "-100123"}, clear=False):
support.set_topic(42, 777, self.db_path)
support.set_active(42, True, self.db_path)
state = _StubState("help")
message = self._message("/training")
with patch.object(bot, "send_rich_message", new=AsyncMock()):
await bot.enter_mode(message, state, mode)
self.assertFalse(
support.is_support_active(42, self.db_path),
f"режим {mode} не закрыл обращение",
)
# Топик сохраняется: следующее обращение продолжает переписку.
self.assertEqual(support.get_topic(42, self.db_path), 777, mode)
async def test_mode_switch_then_text_goes_to_bot_not_operator(self):
"""После перехода в режим обычный текст не должен уходить оператору."""
with patch.dict("os.environ", {"SUPPORT_GROUP_ID": "-100123"}, clear=False):
support.set_topic(42, 777, self.db_path)
support.set_active(42, True, self.db_path)
state = _StubState("help")
message = self._message("/training")
with patch.object(bot, "send_rich_message", new=AsyncMock()):
await bot.enter_mode(message, state, "training")
# Middleware теперь должен пропустить текст в обычные обработчики.
event = Message(
message_id=2,
date=datetime.datetime.now(),
chat=Chat(id=555, type="private"),
from_user=User(id=42, is_bot=False, first_name="Test"),
text="кофейня в Калининграде для семей с детьми",
)
with patch.object(support, "relay_user_message", new=AsyncMock()) as relay:
handled = await self._call_middleware(event)
self.assertEqual(handled, "handled")
relay.assert_not_awaited()
async def _call_middleware(self, event):
async def handler(_event, _data):
return "handled"
return await support.SupportRelayMiddleware()(handler, event, {})
async def test_enter_mode_no_longer_blocks_other_modes(self):
"""Вход в режим из открытого обращения разрешён — пользователя нельзя запирать."""
support.set_topic(42, 777, self.db_path)
@@ -413,6 +466,13 @@ class StartClosesSupportCaseTests(_TempDbCase):
texts = " ".join(text for text, _ in message.answers)
self.assertNotIn("Идёт обращение в поддержку", texts)
async def test_main_menu_closes_support_case(self):
with patch.dict("os.environ", {"SUPPORT_GROUP_ID": "-100123"}, clear=False):
support.set_topic(42, 777, self.db_path)
support.set_active(42, True, self.db_path)
await bot.show_main_menu(self._message("🏠 Главное меню"), _StubState("help"))
self.assertFalse(support.is_support_active(42, self.db_path))
class SupportTopicTests(_TempDbCase):
def _tg_user(self, **kwargs):