From 46a7d2571f98e2fb75b665fd4b93ef36d608e978 Mon Sep 17 00:00:00 2001 From: Kenik Date: Wed, 24 Jun 2026 21:53:41 +0300 Subject: [PATCH] =?UTF-8?q?Pytest-=D0=BD=D0=B0=D0=B1=D0=BE=D1=80:=20=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0=D1=82=D0=BE=D1=80=D1=8B,=20?= =?UTF-8?q?=D0=91=D0=94,=20=D0=B8=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=BE=D0=BD=D0=BD=D1=8B=D0=B5=20=D1=84=D0=BB=D0=BE?= =?UTF-8?q?=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tests/ — 19 тестов: validators (телефон/имя/формат), database (услуги, FAQ, вакансии, отклики, админы, лиды, подписчики), интеграция (полный флоу заявки, карьера игрока, анти-флуд не роняет бота, доступ к админке). Работают на временной БД, реальную не трогают. Запуск: python -m pytest tests/ -q Co-Authored-By: Claude Opus 4.8 --- tests/conftest.py | 34 +++++++++++++++++++++++ tests/test_database.py | 58 ++++++++++++++++++++++++++++++++++++++++ tests/test_flow.py | 48 +++++++++++++++++++++++++++++++++ tests/test_validators.py | 34 +++++++++++++++++++++++ 4 files changed, 174 insertions(+) create mode 100644 tests/conftest.py create mode 100644 tests/test_database.py create mode 100644 tests/test_flow.py create mode 100644 tests/test_validators.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..4e3518b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,34 @@ +import os +import sys + +import pytest + +# Корень проекта в путь импорта +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if ROOT not in sys.path: + sys.path.insert(0, ROOT) + +from database import DatabaseManager + + +@pytest.fixture +def tmp_db(tmp_path): + """Чистая БД во временном файле.""" + return DatabaseManager(str(tmp_path / "test.db")) + + +@pytest.fixture +def app(tmp_path, monkeypatch): + """Экземпляр бота в режиме эмуляции с временной БД и перехватом отправки.""" + import bot + bot.config.VK_TOKEN = "your_vk_community_token_here" # эмулятор, без сети + test_db = bot.DatabaseManager(str(tmp_path / "bot.db")) + monkeypatch.setattr(bot, "db", test_db) + application = bot.VKBotApplication() + sent = [] + application.send_message = ( + lambda peer_id, message, keyboard=None, attachment=None, template=None: + sent.append({"to": peer_id, "msg": message, "kb": keyboard}) + ) + application._sent = sent + return application diff --git a/tests/test_database.py b/tests/test_database.py new file mode 100644 index 0000000..1f7cfda --- /dev/null +++ b/tests/test_database.py @@ -0,0 +1,58 @@ +def test_services_crud(tmp_db): + assert len(tmp_db.get_services()) >= 1 # сид + nid = tmp_db.add_service("X-услуга", "100 ₽", "описание") + assert nid in tmp_db.get_services() + tmp_db.update_service(nid, "price", "200 ₽") + assert tmp_db.get_services()[nid]["price"] == "200 ₽" + tmp_db.delete_service(nid) + assert nid not in tmp_db.get_services() + + +def test_custom_faq(tmp_db): + fid = tmp_db.add_custom_faq("привет", "Здравствуйте!") + assert tmp_db.match_custom_faq("ну привет всем") == "Здравствуйте!" + assert tmp_db.delete_custom_faq(fid) + assert tmp_db.match_custom_faq("привет") is None + + +def test_vacancy_options(tmp_db): + assert len(tmp_db.get_vacancy_options("player")) == 3 + vid = tmp_db.add_vacancy_option("worker", "Аналитик") + assert any(l == "Аналитик" for _, l in tmp_db.get_vacancy_options("worker")) + tmp_db.delete_vacancy_option(vid) + assert not any(l == "Аналитик" for _, l in tmp_db.get_vacancy_options("worker")) + + +def test_applications_and_stats(tmp_db): + tmp_db.add_application(1, "player", { + "discipline": "CS2", "rank": "8", "role": "awp", "experience": "x", "contact": "@a"}) + st = tmp_db.get_application_stats() + assert st["players"] == 1 and st["total"] == 1 + rows = tmp_db.get_recent_applications() + assert tmp_db.set_application_status(rows[0]["id"], "принят") + + +def test_admins(tmp_db): + assert tmp_db.add_admin(555) + assert 555 in tmp_db.get_admins() + assert not tmp_db.add_admin(555) # дубль + assert tmp_db.remove_admin(555) + assert 555 not in tmp_db.get_admins() + + +def test_leads_and_vac_data(tmp_db): + tmp_db.add_lead(user_id=1, source="bot", name="A", phone="+79990001122", + service="CS2", call_time="завтра", promo=None, marketing=True) + s = tmp_db.get_lead_stats() + assert s["total"] == 1 and s["from_bot"] == 1 and s["marketing_yes"] == 1 + tmp_db.set_vac_field(2, "discipline", "CS2") + assert tmp_db.get_vac_data(2)["discipline"] == "CS2" + tmp_db.clear_vac_data(2) + assert tmp_db.get_vac_data(2) == {} + + +def test_marketing_subscribers(tmp_db): + tmp_db.update_user_field(10, "consent_2", "Согласен") + tmp_db.update_user_field(11, "consent_2", "Не согласен") + subs = tmp_db.get_marketing_subscribers() + assert 10 in subs and 11 not in subs diff --git a/tests/test_flow.py b/tests/test_flow.py new file mode 100644 index 0000000..a80c908 --- /dev/null +++ b/tests/test_flow.py @@ -0,0 +1,48 @@ +import bot + + +def _state(uid): + return bot.db.get_user_data(uid)["state"] + + +def test_full_lead_flow(app): + u = 70001 + assert (app.handle_message(u, "Заполнить заявку 📝"), _state(u))[1] == "FORM_NAME" + app.handle_message(u, "Иван"); assert _state(u) == "FORM_PHONE" + app.handle_message(u, "89990001122"); assert _state(u) == "FORM_QUESTION" + app.handle_message(u, "кс"); assert _state(u) == "FORM_CALLDATE" + app.handle_message(u, "Завтра"); assert _state(u) == "FORM_CALLTIME" + app.handle_message(u, "14:00"); assert _state(u) == "FORM_REVIEW" + app.handle_message(u, "Да, всё верно! ✅"); assert _state(u) == "FORM_CONSENT_1" + app.handle_message(u, "Согласен(а) и подтверждаю ✅"); assert _state(u) == "FORM_CONSENT_2" + app.handle_message(u, "Без рассылки ➡️"); assert _state(u) == "FREE_CHAT" + stats = bot.db.get_lead_stats() + assert stats["total"] == 1 and stats["from_bot"] == 1 + + +def test_career_player_flow(app): + u = 70003 + app.handle_message(u, "Карьера в ИКП 💼"); assert _state(u) == "VAC_CHOOSE" + app.handle_message(u, "Игроком в состав 🎮"); assert _state(u) == "VAC_P_DISCIPLINE" + app.handle_message(u, "Counter-Strike 2 🔫"); assert _state(u) == "VAC_P_RANK" + + +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) diff --git a/tests/test_validators.py b/tests/test_validators.py new file mode 100644 index 0000000..34539ac --- /dev/null +++ b/tests/test_validators.py @@ -0,0 +1,34 @@ +from validators import normalize_phone, format_phone_display, is_valid_name + + +def test_normalize_ru_8(): + assert normalize_phone("89991234567") == "+79991234567" + + +def test_normalize_ru_formatted(): + assert normalize_phone("+7 (999) 123-45-67") == "+79991234567" + + +def test_normalize_international(): + assert normalize_phone("+380501234567") == "+380501234567" + + +def test_normalize_invalid(): + assert normalize_phone("12345") is None + assert normalize_phone("abcdef") is None + + +def test_format_phone_display(): + assert format_phone_display("+79991234567") == "+7 (999) 123-45-67" + assert format_phone_display("+380501234567") == "+380501234567" # межд. как есть + + +def test_name_valid(): + assert is_valid_name("Иван-Петров") + assert is_valid_name("John") + + +def test_name_invalid(): + assert not is_valid_name("Иван123") + assert not is_valid_name("!!!") + assert not is_valid_name("a")