Files
bot_vk_ikp_prodagi/tests/test_flow.py
T
Kenik 12ce9c0807 Умный пропуск шагов воронки при уже сохранённых данных (§15)
- _baza_advance: воронка переходит к первому НЕзаполненному шагу, пропуская роль/
  возраст/игру/уровень, если они уже известны; формат (выбор действия) спрашивается всегда.
- На повторном входе — короткая подсказка, что известные ответы не переспрашиваются
  и их можно изменить кнопкой «Назад» (back-навигация остаётся линейной = override).
- Интенсив: известный возраст (из подбора) не переспрашивается — сразу уровень CS2.
- Данные из ветки «лето» сохраняются и переиспользуются в подборе, и наоборот.

Тесты: test_baza_smart_skip_known_fields, test_baza_intensive_skips_known_age. pytest 33 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 20:10:15 +03:00

212 lines
9.8 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_smart_skip_known_fields(app):
"""§15: уже сохранённые ответы не переспрашиваются."""
u = 70020
bot.db.set_profile_field(u, "role", "Игрок (пишет сам)")
bot.db.set_profile_field(u, "age", "16 лет и старше")
# роль и возраст известны → воронка стартует сразу с игры
app.handle_message(u, "Подобрать тренировку 🎯")
assert _state(u) == "BAZA_GAME"
# уровень известен → после игры сразу формат (пропуская уровень)
bot.db.set_profile_field(u, "level", "Играет для себя")
app.handle_message(u, "CS2")
assert _state(u) == "BAZA_FORMAT"
# «Назад» позволяет переопределить пропущенный уровень
app.handle_message(u, "Назад ⬅️")
assert _state(u) == "BAZA_LEVEL"
def test_baza_intensive_skips_known_age(app):
"""§15: в интенсиве известный возраст не переспрашивается."""
u = 70021
bot.db.set_profile_field(u, "age", "1415 лет")
app.handle_message(u, "Летний интенсив CS2 🔥")
app.handle_message(u, "Хочу участвовать 🔥")
assert _state(u) == "BAZA_INT_LEVEL" # возраст пропущен
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_edit_link(app):
"""Админ меняет ссылку кнопки → сохраняется в БД и подхватывается ботом."""
a = _admin()
app.handle_message(a, "Ссылки 🔗")
app.handle_message(a, "✏️ Изменить ссылку")
app.handle_message(a, "group_4. Групповой абонемент — 4 занятия")
app.handle_message(a, "не-ссылка") # отклоняется валидацией
assert bot.db.get_link("group_4") is None
app.handle_message(a, "https://infocyber.pro/pay/group4")
assert bot.db.get_link("group_4") == "https://infocyber.pro/pay/group4"
# бот отдаёт обновлённую ссылку (с UTM)
assert app._link("group_4") == "https://infocyber.pro/pay/group4"
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)