Files
bot_vk_ikp_prodagi/tests/test_flow.py
T
Kenik 7a59b035ce Воронка БАЗА: приведение к ТЗ (метки, сохранённые данные, тексты, шаги)
Закрыты расхождения с ТЗ:
- Метки (§12) переименованы под спецификацию: role_player, game_dota2, format_help,
  interest_trial, interest_summer_intensive, manager_requested, source_main_menu,
  source_keyword_summer; убраны служебные age_*/level_* метки.
- Сохранённые данные (§13): кодовое слово, дата запуска бота (started_at), дата
  передачи менеджеру (manager_at), интересы (пробное/интенсив). Карточка менеджеру
  дополнена этими полями, заголовок «Новый запрос из бота».
- «Пока не знаю, нужен совет» (§7): показ собранных данных + свободный текст → менеджеру.
- «Задать вопрос» (§9): промпт «напишите одним сообщением» → BAZA_QUESTION → передача.
- Летний интенсив: свой набор уровней (§10.3), экран младше-12 (Передать менеджеру/
  Другие тренировки), двухшаговая оплата (Оплатить интенсив → ссылка) (§10.4),
  различение запуска по кодовому слову vs меню.
- Индивидуальная ветка без «пробного» (§6). Fallback-текст дословно по §15.
- «Выбрать абонемент» ведёт на страницу тарифов ссылкой (§8). Тексты веток — по ТЗ.

Тесты воронки обновлены и дополнены (интенсив-оплата, младше-12 интенсив, свободный
вопрос, «нужен совет»). pytest 30 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 13:55:34 +03:00

172 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import bot
def _state(uid):
return bot.db.get_user_data(uid)["state"]
def _prof(uid):
return bot.db.get_profile(uid)
def test_baza_group_funnel_to_payment(app):
"""Полный подбор → групповой абонемент → шаг оплаты (BAZA_PAY)."""
u = 70001
app.handle_message(u, "Подобрать тренировку 🎯"); assert _state(u) == "BAZA_FORWHO"
app.handle_message(u, "Для подростка"); assert _state(u) == "BAZA_AGE"
app.handle_message(u, "1415 лет"); assert _state(u) == "BAZA_GAME"
app.handle_message(u, "CS2"); assert _state(u) == "BAZA_LEVEL"
app.handle_message(u, "Играет для себя"); assert _state(u) == "BAZA_FORMAT"
app.handle_message(u, "Групповые тренировки"); assert _state(u) == "BAZA_GROUP"
app.handle_message(u, "Купить 4 тренировки"); assert _state(u) == "BAZA_PAY"
p = _prof(u)
assert p["role"].startswith("Родитель") and p["game"] == "CS2"
assert p["format"] == "Групповые тренировки" and "4" in p["tariff"]
assert p.get("started_at") # дата запуска сохранена (§13)
assert {"role_parent", "game_cs2", "format_group", "clicked_payment", "lead_hot"} <= set(p["metki"])
def test_baza_individual_and_back(app):
"""Индивидуальная ветка + кнопка «Назад» возвращает на предыдущий шаг."""
u = 70011
app.handle_message(u, "Подобрать тренировку 🎯")
app.handle_message(u, "Для себя"); assert _state(u) == "BAZA_AGE"
app.handle_message(u, "16 лет и старше"); assert _state(u) == "BAZA_GAME"
app.handle_message(u, "Назад ⬅️"); assert _state(u) == "BAZA_AGE"
app.handle_message(u, "16 лет и старше")
app.handle_message(u, "Dota 2"); assert _state(u) == "BAZA_LEVEL"
app.handle_message(u, "Есть рейтинг или ранг")
app.handle_message(u, "Индивидуальные тренировки"); assert _state(u) == "BAZA_INDIVIDUAL"
app.handle_message(u, "Купить 8 тренировок"); assert _state(u) == "BAZA_PAY"
assert "8" in _prof(u)["tariff"]
def test_baza_under12_goes_to_manager(app):
u = 70012
app.handle_message(u, "Подобрать тренировку 🎯")
app.handle_message(u, "Для подростка")
app.handle_message(u, "Младше 12 лет"); assert _state(u) == "BAZA_YOUNG"
app.handle_message(u, "Обсудить с менеджером 💬")
assert _state(u) == "OPERATOR_CHAT"
assert _prof(u)["age"] == "Младше 12 лет"
assert "manager_requested" in _prof(u)["metki"]
def test_baza_other_game_goes_to_manager(app):
u = 70013
app.handle_message(u, "Подобрать тренировку 🎯")
app.handle_message(u, "Для себя")
app.handle_message(u, "16 лет и старше")
app.handle_message(u, "Другая игра"); assert _state(u) == "BAZA_GAME_OTHER"
app.handle_message(u, "Valorant"); assert _state(u) == "OPERATOR_CHAT"
assert "Valorant" in _prof(u)["game"]
def test_baza_intensive_by_keyword(app):
"""Кодовое слово «лето» запускает интенсив; ветка доходит до оплаты."""
u = 70014
app.handle_message(u, "лето"); assert _state(u) == "BAZA_INT_INFO"
app.handle_message(u, "Хочу участвовать 🔥"); assert _state(u) == "BAZA_INT_AGE"
app.handle_message(u, "1415 лет"); assert _state(u) == "BAZA_INT_LEVEL"
app.handle_message(u, "Играю регулярно"); assert _state(u) == "BAZA_INT_PAY_INTRO"
app.handle_message(u, "Оплатить интенсив 💳"); assert _state(u) == "BAZA_PAY"
p = _prof(u)
assert p["keyword"] == "лето" and p.get("interest_summer")
assert {"source_keyword_summer", "interest_summer_intensive", "clicked_payment"} <= set(p["metki"])
def test_baza_under12_intensive_to_manager(app):
u = 70017
app.handle_message(u, "Летний интенсив CS2 🔥")
app.handle_message(u, "Хочу участвовать 🔥")
app.handle_message(u, "Младше 12 лет"); assert _state(u) == "BAZA_INT_YOUNG"
app.handle_message(u, "Передать менеджеру 💬"); assert _state(u) == "OPERATOR_CHAT"
def test_baza_question_button_handoff(app):
"""«Задать вопрос» (§9): сначала промпт, затем сообщение → менеджеру."""
u = 70015
app.handle_message(u, "Задать вопрос 💬"); assert _state(u) == "BAZA_QUESTION"
app.handle_message(u, "Когда занятия?"); assert _state(u) == "OPERATOR_CHAT"
assert _prof(u)["message"] == "Когда занятия?"
assert "manager_requested" in _prof(u)["metki"]
def test_baza_help_free_text_to_manager(app):
"""§7: «Пока не знаю» → свободный текст → менеджеру, format_help."""
u = 70018
app.handle_message(u, "Подобрать тренировку 🎯")
app.handle_message(u, "Для себя")
app.handle_message(u, "1415 лет")
app.handle_message(u, "CS2")
app.handle_message(u, "Только начинает")
app.handle_message(u, "Пока не знаю, нужен совет"); assert _state(u) == "BAZA_HELP"
app.handle_message(u, "важно расписание"); assert _state(u) == "OPERATOR_CHAT"
assert _prof(u)["message"] == "важно расписание"
assert "format_help" in _prof(u)["metki"]
def test_baza_prices_and_menu(app):
u = 70016
app._sent.clear()
app.handle_message(u, "Посмотреть цены 💰")
assert _state(u) == "FREE_CHAT"
assert any("2 790" in s["msg"] for s in app._sent)
def test_flood_does_not_crash(app):
u = 70002
for _ in range(25):
app.handle_message(u, "спам")
# бот не падает; пользователь остаётся в FREE_CHAT (флуд/бан только игнорят)
assert _state(u) == "FREE_CHAT"
def test_admin_panel_visible_to_super(app):
admin = bot.config.SUPER_ADMIN_IDS[0]
app._sent.clear()
app.handle_message(admin, "Админка ⚙️")
assert any("Статистика" in (s["kb"] or "") for s in app._sent)
def test_non_admin_cannot_open_panel(app):
app._sent.clear()
app.handle_message(424242, "Админка ⚙️")
assert not any("Админ-панель" in s["msg"] for s in app._sent)
def _admin():
return bot.config.SUPER_ADMIN_IDS[0]
def test_admin_faq_crud(app):
a = _admin()
app.handle_message(a, "Быстрые ответы 💬")
app.handle_message(a, " Добавить ответ")
app.handle_message(a, "вопрос | ответ")
assert any(kw == "вопрос" for _, kw, _ in bot.db.get_custom_faq())
def test_admin_add_remove_admin(app):
a = _admin()
app.handle_message(a, "Администраторы 👑")
app.handle_message(a, " Добавить админа")
app.handle_message(a, "888001")
assert 888001 in bot.db.get_admins()
assert app._is_admin(888001)
def test_broadcast_preview_and_cancel(app):
a = _admin()
app.handle_message(a, "рассылка Привет, акция!")
assert a in app._pending_broadcasts
app.handle_message(a, "Отмена ❌")
assert a not in app._pending_broadcasts
def test_stats_runs(app):
a = _admin()
app._sent.clear()
app.handle_message(a, "Статистика 📊")
assert any("Статистика" in s["msg"] for s in app._sent)