mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-01-19 19:32:10 +00:00
1) Отображение скидки на кнопках (красивое!)
2) У промогрупп появится приоритет 3) У пользователя может быть несколько промогрупп, но влиять будет только с наивысшим приоритетом 4) Промокоды с промогруппой 5) При выводе пользователей с промогруппой будет также выводиться ссылка на каждого. Можно будет отследить сливы промокодов "для своих". Я в целом это добавлю во все места, где пользователь выводится в админке 6) Исправить баг исчезновения триалки при пополнении 7) Исправить падающие тесты и добавить новых 8) Трафик: 0 ГБ в тестовой подписке исправить на Трафик: Безлимит 9) При попытке изменить промогруппу "Пользователь не найден" - исправил
This commit is contained in:
@@ -300,7 +300,6 @@ async def test_auto_purchase_saved_cart_after_topup_extension(monkeypatch):
|
||||
create_transaction_mock.assert_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auto_purchase_trial_preserved_on_insufficient_balance(monkeypatch):
|
||||
"""Тест: триал сохраняется, если не хватает денег для автопокупки"""
|
||||
monkeypatch.setattr(settings, "AUTO_PURCHASE_AFTER_TOPUP_ENABLED", True)
|
||||
@@ -379,7 +378,6 @@ async def test_auto_purchase_trial_preserved_on_insufficient_balance(monkeypatch
|
||||
subtract_mock.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auto_purchase_trial_converted_after_successful_extension(monkeypatch):
|
||||
"""Тест: триал конвертируется в платную подписку ТОЛЬКО после успешного продления"""
|
||||
monkeypatch.setattr(settings, "AUTO_PURCHASE_AFTER_TOPUP_ENABLED", True)
|
||||
@@ -490,7 +488,6 @@ async def test_auto_purchase_trial_converted_after_successful_extension(monkeypa
|
||||
db_session.commit.assert_awaited() # Commit был вызван
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auto_purchase_trial_preserved_on_extension_failure(monkeypatch):
|
||||
"""Тест: триал НЕ конвертируется и вызывается rollback при ошибке в extend_subscription"""
|
||||
monkeypatch.setattr(settings, "AUTO_PURCHASE_AFTER_TOPUP_ENABLED", True)
|
||||
@@ -579,7 +576,6 @@ async def test_auto_purchase_trial_preserved_on_extension_failure(monkeypatch):
|
||||
db_session.rollback.assert_awaited() # ROLLBACK БЫЛ ВЫЗВАН!
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auto_purchase_trial_remaining_days_transferred(monkeypatch):
|
||||
"""Тест: остаток триала переносится на платную подписку при TRIAL_ADD_REMAINING_DAYS_TO_PAID=True"""
|
||||
monkeypatch.setattr(settings, "AUTO_PURCHASE_AFTER_TOPUP_ENABLED", True)
|
||||
|
||||
@@ -124,10 +124,13 @@ async def test_return_to_saved_cart_success(mock_callback_query, mock_state, moc
|
||||
patch('app.handlers.subscription.purchase._get_available_countries') as mock_get_countries, \
|
||||
patch('app.handlers.subscription.purchase.format_period_description') as mock_format_period, \
|
||||
patch('app.localization.texts.get_texts') as mock_get_texts, \
|
||||
patch('app.handlers.subscription.purchase.get_subscription_confirm_keyboard_with_cart') as mock_keyboard_func:
|
||||
patch('app.handlers.subscription.purchase.get_subscription_confirm_keyboard_with_cart') as mock_keyboard_func, \
|
||||
patch('app.handlers.subscription.purchase._prepare_subscription_summary') as mock_prepare_summary:
|
||||
|
||||
# Подготовим моки
|
||||
mock_cart_service.get_user_cart = AsyncMock(return_value=cart_data)
|
||||
mock_cart_service.save_user_cart = AsyncMock(return_value=True)
|
||||
mock_prepare_summary.return_value = ("summary", {})
|
||||
mock_get_countries.return_value = [{'uuid': 'ru', 'name': 'Russia'}, {'uuid': 'us', 'name': 'USA'}]
|
||||
mock_format_period.return_value = "30 дней"
|
||||
mock_keyboard = InlineKeyboardMarkup(
|
||||
@@ -146,8 +149,8 @@ async def test_return_to_saved_cart_success(mock_callback_query, mock_state, moc
|
||||
# Вызываем функцию
|
||||
await return_to_saved_cart(mock_callback_query, mock_state, mock_user, mock_db)
|
||||
|
||||
# Проверяем, что данные были загружены из корзины и установлены в FSM
|
||||
mock_state.set_data.assert_called_once_with(cart_data)
|
||||
# Проверяем, что корзина была загружена
|
||||
mock_cart_service.get_user_cart.assert_called_once_with(mock_user.id)
|
||||
|
||||
# Проверяем, что сообщение было отредактировано
|
||||
mock_callback_query.message.edit_text.assert_called_once()
|
||||
@@ -320,6 +323,7 @@ async def test_return_to_saved_cart_insufficient_funds(mock_callback_query, mock
|
||||
|
||||
# Подготовим моки
|
||||
mock_cart_service.get_user_cart = AsyncMock(return_value=cart_data)
|
||||
mock_cart_service.save_user_cart = AsyncMock(return_value=True)
|
||||
mock_keyboard = InlineKeyboardMarkup(
|
||||
inline_keyboard=[[InlineKeyboardButton(text="Пополнить", callback_data="topup")]]
|
||||
)
|
||||
|
||||
@@ -16,197 +16,13 @@ from app.localization.texts import _build_dynamic_values
|
||||
|
||||
|
||||
class TestBuildDynamicValues:
|
||||
"""Тесты для функции _build_dynamic_values из texts.py."""
|
||||
"""
|
||||
Тесты для функции _build_dynamic_values из texts.py.
|
||||
|
||||
@patch('app.localization.texts.settings')
|
||||
def test_russian_language_generates_period_keys(self, mock_settings: MagicMock) -> None:
|
||||
"""Русский язык должен генерировать все ключи периодов."""
|
||||
# Настройка моков
|
||||
mock_settings.PRICE_14_DAYS = 50000
|
||||
mock_settings.PRICE_30_DAYS = 99000
|
||||
mock_settings.PRICE_60_DAYS = 189000
|
||||
mock_settings.PRICE_90_DAYS = 269000
|
||||
mock_settings.PRICE_180_DAYS = 499000
|
||||
mock_settings.PRICE_360_DAYS = 899000
|
||||
mock_settings.get_base_promo_group_period_discount.return_value = 0
|
||||
mock_settings.format_price = lambda x: f"{x // 100} ₽"
|
||||
|
||||
# Мок для traffic цен
|
||||
mock_settings.PRICE_TRAFFIC_5GB = 10000
|
||||
mock_settings.PRICE_TRAFFIC_10GB = 20000
|
||||
mock_settings.PRICE_TRAFFIC_25GB = 30000
|
||||
mock_settings.PRICE_TRAFFIC_50GB = 40000
|
||||
mock_settings.PRICE_TRAFFIC_100GB = 50000
|
||||
mock_settings.PRICE_TRAFFIC_250GB = 60000
|
||||
mock_settings.PRICE_TRAFFIC_UNLIMITED = 70000
|
||||
|
||||
result = _build_dynamic_values("ru-RU")
|
||||
|
||||
assert "PERIOD_14_DAYS" in result
|
||||
assert "PERIOD_30_DAYS" in result
|
||||
assert "PERIOD_60_DAYS" in result
|
||||
assert "PERIOD_90_DAYS" in result
|
||||
assert "PERIOD_180_DAYS" in result
|
||||
assert "PERIOD_360_DAYS" in result
|
||||
|
||||
@patch('app.localization.texts.settings')
|
||||
def test_english_language_generates_period_keys(self, mock_settings: MagicMock) -> None:
|
||||
"""Английский язык должен генерировать все ключи периодов."""
|
||||
# Настройка моков
|
||||
mock_settings.PRICE_14_DAYS = 50000
|
||||
mock_settings.PRICE_30_DAYS = 99000
|
||||
mock_settings.PRICE_60_DAYS = 189000
|
||||
mock_settings.PRICE_90_DAYS = 269000
|
||||
mock_settings.PRICE_180_DAYS = 499000
|
||||
mock_settings.PRICE_360_DAYS = 899000
|
||||
mock_settings.get_base_promo_group_period_discount.return_value = 0
|
||||
mock_settings.format_price = lambda x: f"{x // 100} ₽"
|
||||
|
||||
# Мок для traffic цен
|
||||
mock_settings.PRICE_TRAFFIC_5GB = 10000
|
||||
mock_settings.PRICE_TRAFFIC_10GB = 20000
|
||||
mock_settings.PRICE_TRAFFIC_25GB = 30000
|
||||
mock_settings.PRICE_TRAFFIC_50GB = 40000
|
||||
mock_settings.PRICE_TRAFFIC_100GB = 50000
|
||||
mock_settings.PRICE_TRAFFIC_250GB = 60000
|
||||
mock_settings.PRICE_TRAFFIC_UNLIMITED = 70000
|
||||
|
||||
result = _build_dynamic_values("en-US")
|
||||
|
||||
assert "PERIOD_14_DAYS" in result
|
||||
assert "PERIOD_30_DAYS" in result
|
||||
assert "PERIOD_360_DAYS" in result
|
||||
# Проверяем, что используется "days" а не "дней"
|
||||
assert "days" in result["PERIOD_30_DAYS"]
|
||||
|
||||
@patch('app.localization.texts.settings')
|
||||
@patch('app.utils.pricing_utils.apply_percentage_discount')
|
||||
def test_period_with_discount_shows_strikethrough(
|
||||
self,
|
||||
mock_apply_discount: MagicMock,
|
||||
mock_settings: MagicMock
|
||||
) -> None:
|
||||
"""Период со скидкой должен показывать зачёркнутую цену."""
|
||||
# Настройка моков
|
||||
mock_settings.PRICE_30_DAYS = 99000
|
||||
mock_settings.get_base_promo_group_period_discount.return_value = 30
|
||||
mock_apply_discount.return_value = (69300, 29700) # 30% скидка
|
||||
mock_settings.format_price = lambda x: f"{x // 100} ₽"
|
||||
|
||||
# Остальные цены
|
||||
mock_settings.PRICE_14_DAYS = 50000
|
||||
mock_settings.PRICE_60_DAYS = 189000
|
||||
mock_settings.PRICE_90_DAYS = 269000
|
||||
mock_settings.PRICE_180_DAYS = 499000
|
||||
mock_settings.PRICE_360_DAYS = 899000
|
||||
mock_settings.PRICE_TRAFFIC_5GB = 10000
|
||||
mock_settings.PRICE_TRAFFIC_10GB = 20000
|
||||
mock_settings.PRICE_TRAFFIC_25GB = 30000
|
||||
mock_settings.PRICE_TRAFFIC_50GB = 40000
|
||||
mock_settings.PRICE_TRAFFIC_100GB = 50000
|
||||
mock_settings.PRICE_TRAFFIC_250GB = 60000
|
||||
mock_settings.PRICE_TRAFFIC_UNLIMITED = 70000
|
||||
|
||||
result = _build_dynamic_values("ru-RU")
|
||||
|
||||
# Проверяем, что есть зачёркивание и процент скидки
|
||||
assert "<s>990 ₽</s>" in result["PERIOD_30_DAYS"]
|
||||
assert "(-30%)" in result["PERIOD_30_DAYS"]
|
||||
|
||||
@patch('app.localization.texts.settings')
|
||||
def test_period_360_with_discount_has_fire_emojis(self, mock_settings: MagicMock) -> None:
|
||||
"""Период 360 дней со скидкой должен иметь огоньки 🔥."""
|
||||
# Настройка моков для 360 дней со скидкой
|
||||
mock_settings.PRICE_360_DAYS = 899000
|
||||
|
||||
def get_discount(period_days: int) -> int:
|
||||
return 30 if period_days == 360 else 0
|
||||
|
||||
mock_settings.get_base_promo_group_period_discount.side_effect = get_discount
|
||||
mock_settings.format_price = lambda x: f"{x // 100} ₽"
|
||||
|
||||
# Остальные цены
|
||||
mock_settings.PRICE_14_DAYS = 50000
|
||||
mock_settings.PRICE_30_DAYS = 99000
|
||||
mock_settings.PRICE_60_DAYS = 189000
|
||||
mock_settings.PRICE_90_DAYS = 269000
|
||||
mock_settings.PRICE_180_DAYS = 499000
|
||||
mock_settings.PRICE_TRAFFIC_5GB = 10000
|
||||
mock_settings.PRICE_TRAFFIC_10GB = 20000
|
||||
mock_settings.PRICE_TRAFFIC_25GB = 30000
|
||||
mock_settings.PRICE_TRAFFIC_50GB = 40000
|
||||
mock_settings.PRICE_TRAFFIC_100GB = 50000
|
||||
mock_settings.PRICE_TRAFFIC_250GB = 60000
|
||||
mock_settings.PRICE_TRAFFIC_UNLIMITED = 70000
|
||||
|
||||
result = _build_dynamic_values("ru-RU")
|
||||
|
||||
# Проверяем наличие огоньков
|
||||
assert result["PERIOD_360_DAYS"].startswith("🔥")
|
||||
assert result["PERIOD_360_DAYS"].endswith("🔥")
|
||||
assert result["PERIOD_360_DAYS"].count("🔥") == 2
|
||||
|
||||
@patch('app.localization.texts.settings')
|
||||
def test_period_360_without_discount_no_fire_emojis(self, mock_settings: MagicMock) -> None:
|
||||
"""Период 360 дней без скидки НЕ должен иметь огоньки 🔥."""
|
||||
# Настройка моков для 360 дней БЕЗ скидки
|
||||
mock_settings.PRICE_360_DAYS = 899000
|
||||
mock_settings.get_base_promo_group_period_discount.return_value = 0 # Нет скидки
|
||||
mock_settings.format_price = lambda x: f"{x // 100} ₽"
|
||||
|
||||
# Остальные цены
|
||||
mock_settings.PRICE_14_DAYS = 50000
|
||||
mock_settings.PRICE_30_DAYS = 99000
|
||||
mock_settings.PRICE_60_DAYS = 189000
|
||||
mock_settings.PRICE_90_DAYS = 269000
|
||||
mock_settings.PRICE_180_DAYS = 499000
|
||||
mock_settings.PRICE_TRAFFIC_5GB = 10000
|
||||
mock_settings.PRICE_TRAFFIC_10GB = 20000
|
||||
mock_settings.PRICE_TRAFFIC_25GB = 30000
|
||||
mock_settings.PRICE_TRAFFIC_50GB = 40000
|
||||
mock_settings.PRICE_TRAFFIC_100GB = 50000
|
||||
mock_settings.PRICE_TRAFFIC_250GB = 60000
|
||||
mock_settings.PRICE_TRAFFIC_UNLIMITED = 70000
|
||||
|
||||
result = _build_dynamic_values("ru-RU")
|
||||
|
||||
# Проверяем отсутствие огоньков
|
||||
assert "🔥" not in result["PERIOD_360_DAYS"]
|
||||
# Но должна быть просто цена
|
||||
assert "8990 ₽" in result["PERIOD_360_DAYS"]
|
||||
|
||||
@patch('app.localization.texts.settings')
|
||||
def test_other_periods_never_have_fire_emojis(self, mock_settings: MagicMock) -> None:
|
||||
"""Другие периоды (не 360) никогда не должны иметь огоньки, даже со скидкой."""
|
||||
# Настройка моков - 30 дней со скидкой
|
||||
mock_settings.PRICE_30_DAYS = 99000
|
||||
|
||||
def get_discount(period_days: int) -> int:
|
||||
return 30 if period_days == 30 else 0
|
||||
|
||||
mock_settings.get_base_promo_group_period_discount.side_effect = get_discount
|
||||
mock_settings.format_price = lambda x: f"{x // 100} ₽"
|
||||
|
||||
# Остальные цены
|
||||
mock_settings.PRICE_14_DAYS = 50000
|
||||
mock_settings.PRICE_60_DAYS = 189000
|
||||
mock_settings.PRICE_90_DAYS = 269000
|
||||
mock_settings.PRICE_180_DAYS = 499000
|
||||
mock_settings.PRICE_360_DAYS = 899000
|
||||
mock_settings.PRICE_TRAFFIC_5GB = 10000
|
||||
mock_settings.PRICE_TRAFFIC_10GB = 20000
|
||||
mock_settings.PRICE_TRAFFIC_25GB = 30000
|
||||
mock_settings.PRICE_TRAFFIC_50GB = 40000
|
||||
mock_settings.PRICE_TRAFFIC_100GB = 50000
|
||||
mock_settings.PRICE_TRAFFIC_250GB = 60000
|
||||
mock_settings.PRICE_TRAFFIC_UNLIMITED = 70000
|
||||
|
||||
result = _build_dynamic_values("ru-RU")
|
||||
|
||||
# 30 дней со скидкой не должно иметь огоньков
|
||||
assert "🔥" not in result["PERIOD_30_DAYS"]
|
||||
# Но должна быть скидка
|
||||
assert "<s>" in result["PERIOD_30_DAYS"]
|
||||
NOTE: PERIOD_*_DAYS константы были удалены из _build_dynamic_values,
|
||||
так как теперь кнопки периодов генерируются динамически в get_subscription_period_keyboard()
|
||||
с учетом персональных скидок пользователя.
|
||||
"""
|
||||
|
||||
@patch('app.localization.texts.settings')
|
||||
def test_returns_empty_dict_for_unknown_language(self, mock_settings: MagicMock) -> None:
|
||||
@@ -214,47 +30,10 @@ class TestBuildDynamicValues:
|
||||
result = _build_dynamic_values("fr-FR") # Французский не поддерживается
|
||||
assert result == {}
|
||||
|
||||
@patch('app.localization.texts.settings')
|
||||
def test_language_code_extraction_works(self, mock_settings: MagicMock) -> None:
|
||||
"""Должна корректно извлекаться языковая часть из locale."""
|
||||
# Настройка моков
|
||||
mock_settings.PRICE_14_DAYS = 50000
|
||||
mock_settings.PRICE_30_DAYS = 99000
|
||||
mock_settings.PRICE_60_DAYS = 189000
|
||||
mock_settings.PRICE_90_DAYS = 269000
|
||||
mock_settings.PRICE_180_DAYS = 499000
|
||||
mock_settings.PRICE_360_DAYS = 899000
|
||||
mock_settings.get_base_promo_group_period_discount.return_value = 0
|
||||
mock_settings.format_price = lambda x: f"{x // 100} ₽"
|
||||
mock_settings.PRICE_TRAFFIC_5GB = 10000
|
||||
mock_settings.PRICE_TRAFFIC_10GB = 20000
|
||||
mock_settings.PRICE_TRAFFIC_25GB = 30000
|
||||
mock_settings.PRICE_TRAFFIC_50GB = 40000
|
||||
mock_settings.PRICE_TRAFFIC_100GB = 50000
|
||||
mock_settings.PRICE_TRAFFIC_250GB = 60000
|
||||
mock_settings.PRICE_TRAFFIC_UNLIMITED = 70000
|
||||
|
||||
# Тест с полным locale кодом
|
||||
result1 = _build_dynamic_values("ru-RU")
|
||||
result2 = _build_dynamic_values("ru")
|
||||
result3 = _build_dynamic_values("RU-ru")
|
||||
|
||||
# Все должны вернуть русские значения
|
||||
assert "дней" in result1["PERIOD_30_DAYS"]
|
||||
assert "дней" in result2["PERIOD_30_DAYS"]
|
||||
assert "дней" in result3["PERIOD_30_DAYS"]
|
||||
|
||||
@patch('app.localization.texts.settings')
|
||||
def test_traffic_keys_also_generated(self, mock_settings: MagicMock) -> None:
|
||||
"""Должны генерироваться не только периоды, но и ключи трафика."""
|
||||
# Настройка моков
|
||||
mock_settings.PRICE_14_DAYS = 50000
|
||||
mock_settings.PRICE_30_DAYS = 99000
|
||||
mock_settings.PRICE_60_DAYS = 189000
|
||||
mock_settings.PRICE_90_DAYS = 269000
|
||||
mock_settings.PRICE_180_DAYS = 499000
|
||||
mock_settings.PRICE_360_DAYS = 899000
|
||||
mock_settings.get_base_promo_group_period_discount.return_value = 0
|
||||
"""Должны генерироваться ключи трафика и другие динамические значения."""
|
||||
# Настройка моков для traffic цен
|
||||
mock_settings.format_price = lambda x: f"{x // 100} ₽"
|
||||
mock_settings.PRICE_TRAFFIC_5GB = 10000
|
||||
mock_settings.PRICE_TRAFFIC_10GB = 20000
|
||||
|
||||
Reference in New Issue
Block a user