from datetime import datetime, timedelta
from types import SimpleNamespace
from app.bot.ui.panel import (
PANEL_SECTION_ADMIN,
PANEL_SECTION_HOME,
PANEL_SECTION_REFERRAL,
PANEL_SECTION_SUBSCRIPTION,
PANEL_SECTION_SUPPORT,
PANEL_SECTION_TERMS,
PanelContext,
build_panel_keyboard,
render_panel_caption,
)
from app.services.sync_service import CachedUserView
def _button_texts(markup) -> list[str]:
return [button.text for row in markup.inline_keyboard for button in row]
def _cached_user_view(*, telegram_id: int = 123456789) -> CachedUserView:
return CachedUserView(
record=SimpleNamespace(
username="oreol-renew",
status="ACTIVE",
expire_at=datetime.now() + timedelta(days=10),
used_traffic_bytes=0,
traffic_limit_bytes=0,
short_uuid="renew999",
subscription_url="https://example.com/sub/renew999",
telegram_id=telegram_id,
),
internal_squads=["VPN"],
)
def test_home_panel_caption_without_subscription() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[],
api_error=None,
)
caption = render_panel_caption(
section=PANEL_SECTION_HOME,
context=context,
brand_name="OREOL VPN",
)
assert "Добро пожаловать в OREOL VPN" in caption
assert "Профиль" not in caption
assert "Подписка" in caption
assert "Статус: Не активна" in caption
assert "Срок: —" in caption
assert "Трафик: —" in caption
def test_home_panel_keyboard_has_expected_buttons() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[],
api_error=None,
)
markup = build_panel_keyboard(section=PANEL_SECTION_HOME, context=context)
button_texts = _button_texts(markup)
assert "Профиль" not in button_texts
assert "💳 Подписка" in button_texts
assert "🆘 Поддержка" in button_texts
assert "👥 Реферальная система" in button_texts
assert "📜 Условия использования" in button_texts
assert len(markup.inline_keyboard[0]) == 1
assert markup.inline_keyboard[0][0].text == "💳 Подписка"
assert markup.inline_keyboard[0][0].callback_data == "payment:menu"
def test_admin_home_panel_keyboard_has_admin_button() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=True,
users=[],
api_error=None,
)
markup = build_panel_keyboard(section=PANEL_SECTION_HOME, context=context)
button_texts = _button_texts(markup)
assert "🛠 Админ-панель" in button_texts
assert len(markup.inline_keyboard[1]) == 1
assert markup.inline_keyboard[1][0].text == "🛠 Админ-панель"
assert markup.inline_keyboard[1][0].callback_data == "panel:admin"
def test_admin_panel_caption_contains_dashboard_stats() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=True,
users=[],
admin_total_telegram_users=120,
admin_total_cached_users=80,
admin_active_cached_users=67,
admin_pending_orders=9,
admin_review_orders=4,
admin_open_tickets=3,
admin_pending_referral_bonuses=2,
api_error=None,
)
caption = render_panel_caption(
section=PANEL_SECTION_ADMIN,
context=context,
brand_name="OREOL VPN",
)
assert "🛠 Админ-панель OREOL VPN" in caption
assert "Пользователей в боте: 120" in caption
assert "Чеков на проверке: 4" in caption
assert "Открытых тикетов: 3" in caption
assert "Бонусов в ожидании: 2" in caption
def test_admin_panel_keyboard_has_expected_buttons() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=True,
users=[],
api_error=None,
)
markup = build_panel_keyboard(
section=PANEL_SECTION_ADMIN,
context=context,
payment_review_url="https://t.me/c/123/56/57",
support_ticket_url="https://t.me/c/123/2/3",
)
button_texts = _button_texts(markup)
assert "🔍 Найти пользователя" in button_texts
assert "👤 Пользователи" in button_texts
assert "🔄 Полная синхронизация" in button_texts
assert "💸 Очередь оплат" in button_texts
assert "🎫 Тикеты" in button_texts
assert "↻ Обновить" in button_texts
assert "🏠 Главное меню" in button_texts
def test_subscription_caption_without_subscription_but_with_payment() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[],
payment_enabled=True,
payment_plan_title="OREOL VPN на 30 дней",
payment_plan_description="Доступ к VPN на 30 дней",
payment_plan_price_stars=250,
payment_plan_duration_days=30,
payment_plan_traffic_limit_bytes=0,
api_error=None,
)
caption = render_panel_caption(
section=PANEL_SECTION_SUBSCRIPTION,
context=context,
brand_name="OREOL VPN",
)
assert "У вас пока нет активного доступа." in caption
assert "OREOL VPN на 30 дней" in caption
assert "Цена: 250 ₽" in caption
assert "После выбора тарифа бот покажет реквизиты" in caption
def test_subscription_caption_without_payment_uses_ui_instructions() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[],
payment_enabled=False,
api_error=None,
)
caption = render_panel_caption(
section=PANEL_SECTION_SUBSCRIPTION,
context=context,
brand_name="OREOL VPN",
)
assert "откройте поддержку" in caption
assert "short UUID" in caption
assert "/link" not in caption
def test_subscription_keyboard_has_buy_button() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[],
payment_enabled=True,
api_error=None,
)
markup = build_panel_keyboard(section=PANEL_SECTION_SUBSCRIPTION, context=context)
button_texts = _button_texts(markup)
assert "💳 Купить подписку" in button_texts
assert "🏠 Главное меню" in button_texts
def test_subscription_keyboard_has_renew_button_when_access_exists() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[_cached_user_view()],
payment_enabled=True,
api_error=None,
)
markup = build_panel_keyboard(section=PANEL_SECTION_SUBSCRIPTION, context=context)
button_texts = _button_texts(markup)
assert "💳 Продлить подписку" in button_texts
assert "💳 Купить подписку" not in button_texts
def test_subscription_caption_with_existing_access_mentions_renewal() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[_cached_user_view()],
payment_enabled=True,
api_error=None,
)
caption = render_panel_caption(
section=PANEL_SECTION_SUBSCRIPTION,
context=context,
brand_name="OREOL VPN",
)
assert "Продлить подписку" in caption
assert "автоматически добавит выбранный срок" in caption
def test_terms_panel_caption() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[],
api_error=None,
)
caption = render_panel_caption(
section=PANEL_SECTION_TERMS,
context=context,
brand_name="OREOL VPN",
)
assert "📜 Правила VPN" in caption
assert "использовать VPN только в законных целях" in caption
assert "не гарантирует полной анонимности" in caption
def test_support_panel_has_create_ticket_button() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[],
api_error=None,
)
markup = build_panel_keyboard(section=PANEL_SECTION_SUPPORT, context=context)
button_texts = _button_texts(markup)
assert "🎫 Создать тикет" in button_texts
assert "🏠 Главное меню" in button_texts
def test_support_panel_has_support_url_button() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[],
api_error=None,
)
markup = build_panel_keyboard(
section=PANEL_SECTION_SUPPORT,
context=context,
support_url="https://t.me/support_account",
)
button_texts = _button_texts(markup)
assert "Написать в поддержку" in button_texts
def test_support_panel_caption() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[],
api_error=None,
)
caption = render_panel_caption(
section=PANEL_SECTION_SUPPORT,
context=context,
brand_name="OREOL VPN",
)
assert "🆘 Помощь и сопровождение" in caption
assert "откройте тикет через кнопку ниже" in caption
def test_referral_panel_caption_with_link() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[],
referral_code="ABCD1234",
referral_count=3,
referral_link="https://t.me/oreol_vpn_bot?start=ref_ABCD1234",
referral_share_url="https://t.me/share/url?url=test",
referral_recent_names=["Alex", "Maria"],
referral_discount_percent=12,
referral_bonus_days=7,
api_error=None,
)
caption = render_panel_caption(
section=PANEL_SECTION_REFERRAL,
context=context,
brand_name="OREOL VPN",
)
assert "👥 Реферальная система" in caption
assert "Ваш реферальный код: ABCD1234" in caption
assert "Приглашено пользователей: 3" in caption
assert "+7 дней" in caption
assert "Чтобы получить скидку 12%" in caption
assert "https://t.me/oreol_vpn_bot?start=ref_ABCD1234" in caption
assert "Alex" in caption
assert "Maria" in caption
def test_referral_caption_uses_runtime_discount_for_applied_code() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[],
referral_code="ABCD1234",
referral_count=1,
applied_referral_code="PROMO777",
referral_discount_percent=17,
api_error=None,
)
caption = render_panel_caption(
section=PANEL_SECTION_REFERRAL,
context=context,
brand_name="OREOL VPN",
)
assert "Код: PROMO777" in caption
assert "Скидка перед оплатой: 17%" in caption
def test_admin_panel_keyboard_has_settings_button() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=True,
users=[],
api_error=None,
)
markup = build_panel_keyboard(section=PANEL_SECTION_ADMIN, context=context)
button_texts = _button_texts(markup)
assert any("Настрой" in text for text in button_texts)
def test_home_panel_hides_referral_when_disabled() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[],
referral_enabled=False,
api_error=None,
)
markup = build_panel_keyboard(section=PANEL_SECTION_HOME, context=context)
button_texts = _button_texts(markup)
assert all("Реферал" not in text for text in button_texts)
def test_referral_panel_caption_when_disabled() -> None:
context = PanelContext(
telegram_id=123456789,
display_name="Evgeniy Spirin",
username="evgeniy",
language_code="ru",
is_admin=False,
users=[],
referral_enabled=False,
api_error=None,
)
caption = render_panel_caption(
section=PANEL_SECTION_REFERRAL,
context=context,
brand_name="OREOL VPN",
)
assert "отключена" in caption