fix: align traffic top-up flows and unlimited overrides
This commit is contained in:
@@ -1031,7 +1031,7 @@ async def admin_user_premium_override_route(request: web.Request) -> web.Respons
|
|||||||
|
|
||||||
|
|
||||||
async def admin_user_regular_traffic_override_route(request: web.Request) -> web.Response:
|
async def admin_user_regular_traffic_override_route(request: web.Request) -> web.Response:
|
||||||
"""Main (regular) traffic: unlimited-style ceiling + admin bonus GB."""
|
"""Main (regular) traffic: native unlimited panel limit + admin bonus GB."""
|
||||||
actor_id = _require_admin_user_id(request)
|
actor_id = _require_admin_user_id(request)
|
||||||
target_id = int(request.match_info["user_id"])
|
target_id = int(request.match_info["user_id"])
|
||||||
settings: Settings = request.app["settings"]
|
settings: Settings = request.app["settings"]
|
||||||
|
|||||||
@@ -112,15 +112,14 @@ class TariffMixin:
|
|||||||
regular_unlimited_override: bool,
|
regular_unlimited_override: bool,
|
||||||
traffic_used_bytes: int,
|
traffic_used_bytes: int,
|
||||||
) -> int:
|
) -> int:
|
||||||
"""Numeric cap sent to the panel; ``regular_unlimited_override`` uses a large practical ceiling.""" # noqa: E501
|
"""Numeric cap sent to the panel; Remnawave treats ``0`` as unlimited."""
|
||||||
floor = (
|
floor = (
|
||||||
int(tier_baseline_bytes or 0)
|
int(tier_baseline_bytes or 0)
|
||||||
+ max(0, int(topup_balance_bytes or 0))
|
+ max(0, int(topup_balance_bytes or 0))
|
||||||
+ max(0, int(regular_bonus_bytes or 0))
|
+ max(0, int(regular_bonus_bytes or 0))
|
||||||
)
|
)
|
||||||
if regular_unlimited_override:
|
if regular_unlimited_override:
|
||||||
used = max(0, int(traffic_used_bytes or 0))
|
return 0
|
||||||
return max(floor, used + 512 * (1024**3), 1024**5)
|
|
||||||
return floor
|
return floor
|
||||||
|
|
||||||
async def premium_access_for_tariff(self, tariff: Optional[Tariff]) -> Dict[str, Any]:
|
async def premium_access_for_tariff(self, tariff: Optional[Tariff]) -> Dict[str, Any]:
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
<X size={18} />
|
<X size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<ScrollArea class="dialog-body-scroll" maxHeight="none">
|
<ScrollArea class="dialog-body-scroll scroll-area--dialog" maxHeight="none">
|
||||||
<slot />
|
<slot />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -36,6 +36,10 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.scroll-area--dialog) {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
:global(.scroll-area__viewport) {
|
:global(.scroll-area__viewport) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -56,6 +60,10 @@
|
|||||||
width: 10px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.scroll-area--dialog .scroll-area__scrollbar[data-orientation="vertical"]) {
|
||||||
|
transform: translateX(12px);
|
||||||
|
}
|
||||||
|
|
||||||
:global(.scroll-area__scrollbar[data-orientation="horizontal"]) {
|
:global(.scroll-area__scrollbar[data-orientation="horizontal"]) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
|
|||||||
@@ -104957,10 +104957,9 @@ export const DEMO_DATASET = {
|
|||||||
updated_by: null,
|
updated_by: null,
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
base: "Unlimited-style ceiling and a persistent bonus on the main traffic limit.",
|
base: "Unlimited access and a persistent bonus on the main traffic limit.",
|
||||||
fallback: "Режим безлимита и постоянный бонус к лимиту основного трафика.",
|
fallback: "Режим безлимита и постоянный бонус к лимиту основного трафика.",
|
||||||
effective:
|
effective: "Unlimited access and a persistent bonus on the main traffic limit.",
|
||||||
"Unlimited-style ceiling and a persistent bonus on the main traffic limit.",
|
|
||||||
override: "",
|
override: "",
|
||||||
overridden: false,
|
overridden: false,
|
||||||
updated_at: null,
|
updated_at: null,
|
||||||
@@ -105241,22 +105240,22 @@ export const DEMO_DATASET = {
|
|||||||
audience: "internal",
|
audience: "internal",
|
||||||
values: {
|
values: {
|
||||||
ru: {
|
ru: {
|
||||||
base: "Дополнительный лимит основного и премиум-трафика поверх тарифа; безлимит только для премиум-сквадов.",
|
base: "Дополнительный лимит основного и премиум-трафика поверх тарифа; безлимит можно включить для основного трафика или премиум-сквадов.",
|
||||||
fallback:
|
fallback:
|
||||||
"Дополнительный лимит основного и премиум-трафика поверх тарифа; безлимит только для премиум-сквадов.",
|
"Дополнительный лимит основного и премиум-трафика поверх тарифа; безлимит можно включить для основного трафика или премиум-сквадов.",
|
||||||
effective:
|
effective:
|
||||||
"Дополнительный лимит основного и премиум-трафика поверх тарифа; безлимит только для премиум-сквадов.",
|
"Дополнительный лимит основного и премиум-трафика поверх тарифа; безлимит можно включить для основного трафика или премиум-сквадов.",
|
||||||
override: "",
|
override: "",
|
||||||
overridden: false,
|
overridden: false,
|
||||||
updated_at: null,
|
updated_at: null,
|
||||||
updated_by: null,
|
updated_by: null,
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
base: "Extra main or premium traffic limits on top of the tariff; unlimited applies to premium squads only.",
|
base: "Extra main or premium traffic limits on top of the tariff; unlimited can be applied to main traffic or premium squads.",
|
||||||
fallback:
|
fallback:
|
||||||
"Дополнительный лимит основного и премиум-трафика поверх тарифа; безлимит только для премиум-сквадов.",
|
"Дополнительный лимит основного и премиум-трафика поверх тарифа; безлимит можно включить для основного трафика или премиум-сквадов.",
|
||||||
effective:
|
effective:
|
||||||
"Extra main or premium traffic limits on top of the tariff; unlimited applies to premium squads only.",
|
"Extra main or premium traffic limits on top of the tariff; unlimited can be applied to main traffic or premium squads.",
|
||||||
override: "",
|
override: "",
|
||||||
overridden: false,
|
overridden: false,
|
||||||
updated_at: null,
|
updated_at: null,
|
||||||
@@ -134112,18 +134111,18 @@ export const DEMO_DATASET = {
|
|||||||
audience: "user",
|
audience: "user",
|
||||||
values: {
|
values: {
|
||||||
ru: {
|
ru: {
|
||||||
base: "Докупить премиум-трафик (мини-приложение)",
|
base: "Докупить премиум-трафик",
|
||||||
fallback: "Докупить премиум-трафик (мини-приложение)",
|
fallback: "Докупить премиум-трафик",
|
||||||
effective: "Докупить премиум-трафик (мини-приложение)",
|
effective: "Докупить премиум-трафик",
|
||||||
override: "",
|
override: "",
|
||||||
overridden: false,
|
overridden: false,
|
||||||
updated_at: null,
|
updated_at: null,
|
||||||
updated_by: null,
|
updated_by: null,
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
base: "Top up premium traffic (mini app)",
|
base: "Top up premium traffic",
|
||||||
fallback: "Докупить премиум-трафик (мини-приложение)",
|
fallback: "Докупить премиум-трафик",
|
||||||
effective: "Top up premium traffic (mini app)",
|
effective: "Top up premium traffic",
|
||||||
override: "",
|
override: "",
|
||||||
overridden: false,
|
overridden: false,
|
||||||
updated_at: null,
|
updated_at: null,
|
||||||
@@ -134136,18 +134135,18 @@ export const DEMO_DATASET = {
|
|||||||
audience: "user",
|
audience: "user",
|
||||||
values: {
|
values: {
|
||||||
ru: {
|
ru: {
|
||||||
base: "Докупить трафик (мини-приложение)",
|
base: "Докупить трафик",
|
||||||
fallback: "Докупить трафик (мини-приложение)",
|
fallback: "Докупить трафик",
|
||||||
effective: "Докупить трафик (мини-приложение)",
|
effective: "Докупить трафик",
|
||||||
override: "",
|
override: "",
|
||||||
overridden: false,
|
overridden: false,
|
||||||
updated_at: null,
|
updated_at: null,
|
||||||
updated_by: null,
|
updated_by: null,
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
base: "Top up traffic (mini app)",
|
base: "Top up traffic",
|
||||||
fallback: "Докупить трафик (мини-приложение)",
|
fallback: "Докупить трафик",
|
||||||
effective: "Top up traffic (mini app)",
|
effective: "Top up traffic",
|
||||||
override: "",
|
override: "",
|
||||||
overridden: false,
|
overridden: false,
|
||||||
updated_at: null,
|
updated_at: null,
|
||||||
|
|||||||
@@ -2760,15 +2760,6 @@
|
|||||||
max-height: min(100%, 760px);
|
max-height: min(100%, 760px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-user-dialog .dialog-body-scroll {
|
|
||||||
margin-right: -10px;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-user-dialog .dialog-body-scroll > .scroll-area__viewport {
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-user-dialog-body {
|
.admin-user-dialog-body {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
|||||||
+5
-5
@@ -374,8 +374,8 @@
|
|||||||
"traffic_warning_premium_depleted": "⛔️ The <b>premium</b> server quota for plan <b>{tariff_name}</b> is used up.\n\n<b>Current period:</b>\nused — <b>{used}</b>\navailable — <b>{remaining}</b>\nlimit — <b>{limit_total}</b>\n\nThis limit applies to:\n{servers}\n\nAccess may be limited until the period resets or you buy more premium traffic.\n\nUse the button below to top up.",
|
"traffic_warning_premium_depleted": "⛔️ The <b>premium</b> server quota for plan <b>{tariff_name}</b> is used up.\n\n<b>Current period:</b>\nused — <b>{used}</b>\navailable — <b>{remaining}</b>\nlimit — <b>{limit_total}</b>\n\nThis limit applies to:\n{servers}\n\nAccess may be limited until the period resets or you buy more premium traffic.\n\nUse the button below to top up.",
|
||||||
"traffic_warning_premium_generic_servers": "• servers with premium access under your plan",
|
"traffic_warning_premium_generic_servers": "• servers with premium access under your plan",
|
||||||
"traffic_warning_premium_servers_more": "… and {count} more servers",
|
"traffic_warning_premium_servers_more": "… and {count} more servers",
|
||||||
"traffic_warn_btn_topup_webapp_regular": "Top up traffic (mini app)",
|
"traffic_warn_btn_topup_webapp_regular": "Top up traffic",
|
||||||
"traffic_warn_btn_topup_webapp_premium": "Top up premium traffic (mini app)",
|
"traffic_warn_btn_topup_webapp_premium": "Top up premium traffic",
|
||||||
"traffic_warn_btn_topup_regular": "Top up traffic",
|
"traffic_warn_btn_topup_regular": "Top up traffic",
|
||||||
"traffic_warn_btn_topup_premium": "Top up premium traffic",
|
"traffic_warn_btn_topup_premium": "Top up premium traffic",
|
||||||
"log_promo_activation": "🎁 <b>Promo Code Activated</b>\n\n👤 User: {user_display}\n🏷 Code: <code>{promo_code}</code>\n🎯 Bonus: <b>+{bonus_days}d</b>\n🕐 Time: {timestamp}",
|
"log_promo_activation": "🎁 <b>Promo Code Activated</b>\n\n👤 User: {user_display}\n🏷 Code: <code>{promo_code}</code>\n🎯 Bonus: <b>+{bonus_days}d</b>\n🕐 Time: {timestamp}",
|
||||||
@@ -685,7 +685,7 @@
|
|||||||
"email_support_row_remaining": "Remaining",
|
"email_support_row_remaining": "Remaining",
|
||||||
"email_support_row_panel_status": "Panel status",
|
"email_support_row_panel_status": "Panel status",
|
||||||
"email_support_cta_open_ticket": "Open ticket",
|
"email_support_cta_open_ticket": "Open ticket",
|
||||||
"email_support_cta_open_mini_app": "Open in Mini App",
|
"email_support_cta_open_mini_app": "Open ticket",
|
||||||
"wa_loading": "Loading...",
|
"wa_loading": "Loading...",
|
||||||
"wa_back": "Back",
|
"wa_back": "Back",
|
||||||
"wa_next": "Next",
|
"wa_next": "Next",
|
||||||
@@ -1268,11 +1268,11 @@
|
|||||||
"user_premium_override_card_title": "Premium traffic",
|
"user_premium_override_card_title": "Premium traffic",
|
||||||
"user_premium_override_card_hint": "Unlimited access and extra volume for premium squads on top of the tariff.",
|
"user_premium_override_card_hint": "Unlimited access and extra volume for premium squads on top of the tariff.",
|
||||||
"user_regular_override_card_title": "Main traffic",
|
"user_regular_override_card_title": "Main traffic",
|
||||||
"user_regular_override_card_hint": "Unlimited-style ceiling and a persistent bonus on the main traffic limit.",
|
"user_regular_override_card_hint": "Unlimited access and a persistent bonus on the main traffic limit.",
|
||||||
"user_regular_override_status_unlimited": "Current: unlimited",
|
"user_regular_override_status_unlimited": "Current: unlimited",
|
||||||
"regular_override_saved": "Main traffic override saved",
|
"regular_override_saved": "Main traffic override saved",
|
||||||
"user_traffic_override_title": "Traffic overrides",
|
"user_traffic_override_title": "Traffic overrides",
|
||||||
"user_traffic_override_hint": "Extra main or premium traffic limits on top of the tariff; unlimited applies to premium squads only.",
|
"user_traffic_override_hint": "Extra main or premium traffic limits on top of the tariff; unlimited can be applied to main traffic or premium squads.",
|
||||||
"user_regular_override_bonus": "Extra main traffic, GB",
|
"user_regular_override_bonus": "Extra main traffic, GB",
|
||||||
"user_regular_override_bonus_hint": "Persistent bonus bytes added to the main traffic limit (not a balance top-up).",
|
"user_regular_override_bonus_hint": "Persistent bonus bytes added to the main traffic limit (not a balance top-up).",
|
||||||
"user_regular_override_status_bonus": "Main traffic now: +{gb} GB",
|
"user_regular_override_status_bonus": "Main traffic now: +{gb} GB",
|
||||||
|
|||||||
+4
-4
@@ -374,8 +374,8 @@
|
|||||||
"traffic_warning_premium_depleted": "⛔️ Лимит премиум-серверов тарифа <b>{tariff_name}</b> израсходован.\n\n<b>Текущий период:</b>\nизрасходовано — <b>{used}</b>\nдоступно — <b>{remaining}</b>\nлимит — <b>{limit_total}</b>\n\nЛимит действует на:\n{servers}\n\nДоступ может быть ограничен до сброса периода или докупки премиум-трафика.\n\nДокупить можно через кнопку ниже.",
|
"traffic_warning_premium_depleted": "⛔️ Лимит премиум-серверов тарифа <b>{tariff_name}</b> израсходован.\n\n<b>Текущий период:</b>\nизрасходовано — <b>{used}</b>\nдоступно — <b>{remaining}</b>\nлимит — <b>{limit_total}</b>\n\nЛимит действует на:\n{servers}\n\nДоступ может быть ограничен до сброса периода или докупки премиум-трафика.\n\nДокупить можно через кнопку ниже.",
|
||||||
"traffic_warning_premium_generic_servers": "• серверы с премиум-доступом по вашему тарифу",
|
"traffic_warning_premium_generic_servers": "• серверы с премиум-доступом по вашему тарифу",
|
||||||
"traffic_warning_premium_servers_more": "… и ещё серверов: {count}",
|
"traffic_warning_premium_servers_more": "… и ещё серверов: {count}",
|
||||||
"traffic_warn_btn_topup_webapp_regular": "Докупить трафик (мини-приложение)",
|
"traffic_warn_btn_topup_webapp_regular": "Докупить трафик",
|
||||||
"traffic_warn_btn_topup_webapp_premium": "Докупить премиум-трафик (мини-приложение)",
|
"traffic_warn_btn_topup_webapp_premium": "Докупить премиум-трафик",
|
||||||
"traffic_warn_btn_topup_regular": "Докупить трафик",
|
"traffic_warn_btn_topup_regular": "Докупить трафик",
|
||||||
"traffic_warn_btn_topup_premium": "Докупить премиум-трафик",
|
"traffic_warn_btn_topup_premium": "Докупить премиум-трафик",
|
||||||
"log_promo_activation": "🎁 <b>Активирован промокод</b>\n\n👤 Пользователь: {user_display}\n🏷 Код: <code>{promo_code}</code>\n🎯 Бонус: <b>+{bonus_days} дн.</b>\n🕐 Время: {timestamp}",
|
"log_promo_activation": "🎁 <b>Активирован промокод</b>\n\n👤 Пользователь: {user_display}\n🏷 Код: <code>{promo_code}</code>\n🎯 Бонус: <b>+{bonus_days} дн.</b>\n🕐 Время: {timestamp}",
|
||||||
@@ -685,7 +685,7 @@
|
|||||||
"email_support_row_remaining": "Осталось",
|
"email_support_row_remaining": "Осталось",
|
||||||
"email_support_row_panel_status": "Статус в панели",
|
"email_support_row_panel_status": "Статус в панели",
|
||||||
"email_support_cta_open_ticket": "Открыть тикет",
|
"email_support_cta_open_ticket": "Открыть тикет",
|
||||||
"email_support_cta_open_mini_app": "Открыть в Mini App",
|
"email_support_cta_open_mini_app": "Открыть обращение",
|
||||||
"wa_loading": "Загрузка...",
|
"wa_loading": "Загрузка...",
|
||||||
"wa_back": "Назад",
|
"wa_back": "Назад",
|
||||||
"wa_next": "Далее",
|
"wa_next": "Далее",
|
||||||
@@ -1272,7 +1272,7 @@
|
|||||||
"user_regular_override_status_unlimited": "Сейчас: безлимит",
|
"user_regular_override_status_unlimited": "Сейчас: безлимит",
|
||||||
"regular_override_saved": "Оверрайд основного трафика сохранён",
|
"regular_override_saved": "Оверрайд основного трафика сохранён",
|
||||||
"user_traffic_override_title": "Оверрайд трафика",
|
"user_traffic_override_title": "Оверрайд трафика",
|
||||||
"user_traffic_override_hint": "Дополнительный лимит основного и премиум-трафика поверх тарифа; безлимит только для премиум-сквадов.",
|
"user_traffic_override_hint": "Дополнительный лимит основного и премиум-трафика поверх тарифа; безлимит можно включить для основного трафика или премиум-сквадов.",
|
||||||
"user_regular_override_bonus": "Доп. основной трафик, GB",
|
"user_regular_override_bonus": "Доп. основной трафик, GB",
|
||||||
"user_regular_override_bonus_hint": "Постоянный бонус к лимиту основного трафика (не то же самое, что докупка ГБ).",
|
"user_regular_override_bonus_hint": "Постоянный бонус к лимиту основного трафика (не то же самое, что докупка ГБ).",
|
||||||
"user_regular_override_status_bonus": "Основной сейчас: +{gb} GB",
|
"user_regular_override_status_bonus": "Основной сейчас: +{gb} GB",
|
||||||
|
|||||||
@@ -133,6 +133,59 @@ class AdminGrantTopupTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
self.assertIsNone(await service.admin_grant_topup(AsyncMock(), 1, 0))
|
self.assertIsNone(await service.admin_grant_topup(AsyncMock(), 1, 0))
|
||||||
self.assertIsNone(await service.admin_grant_topup(AsyncMock(), 1, -10))
|
self.assertIsNone(await service.admin_grant_topup(AsyncMock(), 1, -10))
|
||||||
|
|
||||||
|
async def test_regular_unlimited_override_syncs_zero_panel_limit(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
settings = _make_settings(_tariffs_config_payload(), tmpdir)
|
||||||
|
panel_service = AsyncMock(spec=PanelApiService)
|
||||||
|
panel_service.update_user_details_on_panel = AsyncMock(return_value={"response": {}})
|
||||||
|
service = SubscriptionService(settings, panel_service)
|
||||||
|
|
||||||
|
db_user = SimpleNamespace(
|
||||||
|
user_id=42,
|
||||||
|
first_name="Tester",
|
||||||
|
last_name=None,
|
||||||
|
username="tester",
|
||||||
|
language_code="ru",
|
||||||
|
panel_user_uuid="panel-uuid",
|
||||||
|
email=None,
|
||||||
|
telegram_id=42,
|
||||||
|
)
|
||||||
|
sub = SimpleNamespace(
|
||||||
|
subscription_id=7,
|
||||||
|
user_id=42,
|
||||||
|
panel_user_uuid="panel-uuid",
|
||||||
|
end_date=datetime.now(timezone.utc) + timedelta(days=10),
|
||||||
|
tariff_key="standard",
|
||||||
|
tier_baseline_bytes=100 * (1024**3),
|
||||||
|
topup_balance_bytes=0,
|
||||||
|
traffic_limit_bytes=105 * (1024**3),
|
||||||
|
traffic_used_bytes=2 * (1024**5),
|
||||||
|
regular_bonus_bytes=0,
|
||||||
|
regular_unlimited_override=True,
|
||||||
|
is_throttled=True,
|
||||||
|
hwid_device_limit=3,
|
||||||
|
extra_hwid_devices=0,
|
||||||
|
premium_is_limited=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"bot.services.subscription_service.user_dal.get_user_by_id",
|
||||||
|
new=AsyncMock(return_value=db_user),
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"bot.services.subscription_service.subscription_dal.get_active_subscription_by_user_id",
|
||||||
|
new=AsyncMock(return_value=sub),
|
||||||
|
),
|
||||||
|
):
|
||||||
|
await service.sync_main_traffic_limit_to_panel(AsyncMock(), 42)
|
||||||
|
|
||||||
|
self.assertEqual(sub.traffic_limit_bytes, 0)
|
||||||
|
self.assertFalse(sub.is_throttled)
|
||||||
|
panel_service.update_user_details_on_panel.assert_awaited_once()
|
||||||
|
panel_payload = panel_service.update_user_details_on_panel.await_args.args[1]
|
||||||
|
self.assertEqual(panel_payload["trafficLimitBytes"], 0)
|
||||||
|
|
||||||
async def test_premium_grant_clears_limited_state_when_balance_covers_overuse(self):
|
async def test_premium_grant_clears_limited_state_when_balance_covers_overuse(self):
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
settings = _make_settings(_tariffs_config_payload(premium=True), tmpdir)
|
settings = _make_settings(_tariffs_config_payload(premium=True), tmpdir)
|
||||||
|
|||||||
@@ -46,6 +46,23 @@ class MiniAppUrlTests(unittest.TestCase):
|
|||||||
subscription_mini_app_topup_url(s, "regular"),
|
subscription_mini_app_topup_url(s, "regular"),
|
||||||
"https://app.example.com/webapp?topup=regular",
|
"https://app.example.com/webapp?topup=regular",
|
||||||
)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
subscription_mini_app_topup_url(s, "premium"),
|
||||||
|
"https://app.example.com/webapp?topup=premium",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_subscription_mini_app_topup_url_preserves_existing_query(self):
|
||||||
|
s = Settings(
|
||||||
|
_env_file=None,
|
||||||
|
BOT_TOKEN="x",
|
||||||
|
POSTGRES_USER="u",
|
||||||
|
POSTGRES_PASSWORD="p",
|
||||||
|
SUBSCRIPTION_MINI_APP_URL="https://app.example.com/webapp?lang=ru",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
subscription_mini_app_topup_url(s, "premium"),
|
||||||
|
"https://app.example.com/webapp?lang=ru&topup=premium",
|
||||||
|
)
|
||||||
|
|
||||||
def test_subscription_mini_app_path_url(self):
|
def test_subscription_mini_app_path_url(self):
|
||||||
s = Settings(
|
s = Settings(
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class SubscriptionServiceCalculationTests(unittest.TestCase):
|
|||||||
["fallback-a", "fallback-b"],
|
["fallback-a", "fallback-b"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_main_traffic_limit_includes_topup_bonus_and_unlimited_floor(self):
|
def test_main_traffic_limit_includes_topup_bonus_and_unlimited_zero(self):
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
settings = _make_settings(_tariffs_config_payload(), tmpdir)
|
settings = _make_settings(_tariffs_config_payload(), tmpdir)
|
||||||
service = _make_service(settings)
|
service = _make_service(settings)
|
||||||
@@ -117,7 +117,7 @@ class SubscriptionServiceCalculationTests(unittest.TestCase):
|
|||||||
regular_unlimited_override=True,
|
regular_unlimited_override=True,
|
||||||
traffic_used_bytes=2 * (1024**5),
|
traffic_used_bytes=2 * (1024**5),
|
||||||
)
|
)
|
||||||
self.assertEqual(unlimited_limit, 2 * (1024**5) + 512 * GIB)
|
self.assertEqual(unlimited_limit, 0)
|
||||||
|
|
||||||
def test_premium_effective_limit_ignores_negative_balances(self):
|
def test_premium_effective_limit_ignores_negative_balances(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ def test_support_ticket_closed_email_uses_user_language():
|
|||||||
assert '<html lang="ru"' in content.html
|
assert '<html lang="ru"' in content.html
|
||||||
assert "Ticket #7 was closed" not in content.text
|
assert "Ticket #7 was closed" not in content.text
|
||||||
assert "Тема: Проблема с подключением" in content.text
|
assert "Тема: Проблема с подключением" in content.text
|
||||||
assert "Открыть в Mini App" in content.html
|
assert "Открыть обращение" in content.html
|
||||||
|
|
||||||
|
|
||||||
def test_support_admin_email_localizes_snapshot_rows_for_recipient():
|
def test_support_admin_email_localizes_snapshot_rows_for_recipient():
|
||||||
|
|||||||
@@ -34,6 +34,31 @@ def _tariffs_config_payload() -> dict:
|
|||||||
|
|
||||||
|
|
||||||
class TariffWorkerTests(unittest.IsolatedAsyncioTestCase):
|
class TariffWorkerTests(unittest.IsolatedAsyncioTestCase):
|
||||||
|
def test_topup_webapp_button_labels_do_not_mention_mini_app(self):
|
||||||
|
class I18n:
|
||||||
|
def gettext(self, _lang, key, **_kwargs):
|
||||||
|
return {
|
||||||
|
"traffic_warn_btn_topup_webapp_regular": "Top up traffic",
|
||||||
|
"traffic_warn_btn_topup_webapp_premium": "Top up premium traffic",
|
||||||
|
}.get(key, key)
|
||||||
|
|
||||||
|
worker = TariffTrafficWorker(
|
||||||
|
settings=SimpleNamespace(SUBSCRIPTION_MINI_APP_URL="https://app.example.com"),
|
||||||
|
session_factory=SimpleNamespace(),
|
||||||
|
panel_service=SimpleNamespace(),
|
||||||
|
subscription_service=SimpleNamespace(),
|
||||||
|
bot=SimpleNamespace(),
|
||||||
|
i18n=I18n(),
|
||||||
|
)
|
||||||
|
|
||||||
|
regular = worker._traffic_topup_markup("en", "regular").inline_keyboard[0][0]
|
||||||
|
premium = worker._traffic_topup_markup("en", "premium").inline_keyboard[0][0]
|
||||||
|
|
||||||
|
self.assertEqual(regular.text, "Top up traffic")
|
||||||
|
self.assertEqual(regular.web_app.url, "https://app.example.com?topup=regular")
|
||||||
|
self.assertEqual(premium.text, "Top up premium traffic")
|
||||||
|
self.assertEqual(premium.web_app.url, "https://app.example.com?topup=premium")
|
||||||
|
|
||||||
async def test_db_tick_retries_deadlock_once(self):
|
async def test_db_tick_retries_deadlock_once(self):
|
||||||
class FakeSession:
|
class FakeSession:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user