chore: cover can_topup_devices flag derivation
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
"""Tests for the ``can_topup_devices`` flag emitted by ``_serialize_subscription``.
|
||||
|
||||
The flag drives whether the Web App shows the "buy HWID devices" button on the
|
||||
Devices screen. Before this flag existed, the button was rendered for any
|
||||
active subscription with a finite ``max_devices``, even if the user's tariff
|
||||
had no HWID device packages configured. Clicking led the user into an empty
|
||||
modal with "no options available" — confusing.
|
||||
|
||||
These tests pin the visibility contract:
|
||||
|
||||
* unlimited subscribers (max_devices == 0) → flag is False (no point in top-up);
|
||||
* tariff without HWID packages → flag is False;
|
||||
* tariff with rub-only or stars-only packages → flag is True;
|
||||
* legacy mode without a tariffs catalog → flag is False;
|
||||
* malformed tariff lookup → flag is False (and does not raise).
|
||||
"""
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
# Importing the facade populates ``webapp.serializers`` with helpers like
|
||||
# ``_format_remaining`` that come from ``_runtime`` via the facade init dance.
|
||||
import bot.app.web.subscription_webapp # noqa: F401
|
||||
from bot.app.web.webapp.serializers import _serialize_subscription
|
||||
from config.settings import Settings
|
||||
|
||||
|
||||
def _tariffs_payload(*, hwid_rub=None, hwid_stars=None, has_premium=False) -> dict:
|
||||
tariff: Dict[str, Any] = {
|
||||
"key": "standard",
|
||||
"names": {"en": "Standard"},
|
||||
"descriptions": {"en": "Base"},
|
||||
"squad_uuids": ["main"],
|
||||
"billing_model": "period",
|
||||
"monthly_gb": 100,
|
||||
"prices_rub": {"1": 100},
|
||||
"prices_stars": {"1": 0},
|
||||
"enabled_periods": [1],
|
||||
"hwid_device_limit": 3,
|
||||
"enabled": True,
|
||||
}
|
||||
if has_premium:
|
||||
tariff.update(
|
||||
premium_squad_uuids=["premium-squad"],
|
||||
premium_monthly_gb=20,
|
||||
)
|
||||
if hwid_rub or hwid_stars:
|
||||
tariff["hwid_device_packages"] = {
|
||||
"rub": hwid_rub or [],
|
||||
"stars": hwid_stars or [],
|
||||
}
|
||||
return {"default_tariff": "standard", "tariffs": [tariff]}
|
||||
|
||||
|
||||
def _make_settings(tmpdir: str, payload: Optional[dict] = None, **overrides: Any) -> Settings:
|
||||
values: Dict[str, Any] = {
|
||||
"_env_file": None,
|
||||
"BOT_TOKEN": "token",
|
||||
"POSTGRES_USER": "u",
|
||||
"POSTGRES_PASSWORD": "p",
|
||||
}
|
||||
if payload is not None:
|
||||
config_path = Path(tmpdir) / "tariffs.json"
|
||||
config_path.write_text(json.dumps(payload), encoding="utf-8")
|
||||
values["TARIFFS_CONFIG_PATH"] = str(config_path)
|
||||
values.update(overrides)
|
||||
return Settings(**values)
|
||||
|
||||
|
||||
def _active(**overrides) -> Dict[str, Any]:
|
||||
"""Build a minimal ``active`` dict shaped like ``get_active_subscription_details``."""
|
||||
base: Dict[str, Any] = {
|
||||
"tariff_key": "standard",
|
||||
"status_from_panel": "ACTIVE",
|
||||
"end_date": datetime.now(timezone.utc) + timedelta(days=30),
|
||||
"traffic_limit_bytes": 0,
|
||||
"traffic_used_bytes": 0,
|
||||
"premium_baseline_bytes": 0,
|
||||
"premium_topup_balance_bytes": 0,
|
||||
"premium_topup_used_bytes": 0,
|
||||
"premium_used_bytes": 0,
|
||||
"premium_limit_bytes": 0,
|
||||
"max_devices": 3,
|
||||
"base_hwid_device_limit": 3,
|
||||
"extra_hwid_devices": 0,
|
||||
"billing_model": "period",
|
||||
"tariff_name": "Standard",
|
||||
"tariff_description": "Base",
|
||||
"premium_title": None,
|
||||
"config_link": None,
|
||||
"connect_button_url": None,
|
||||
"tier_baseline_bytes": 0,
|
||||
"topup_balance_bytes": 0,
|
||||
"premium_bonus_bytes": 0,
|
||||
"regular_bonus_bytes": 0,
|
||||
"regular_unlimited_override": False,
|
||||
"premium_unlimited_override": False,
|
||||
"premium_is_limited": False,
|
||||
"premium_squad_labels": [],
|
||||
"premium_node_labels": [],
|
||||
"period_start_at": None,
|
||||
"is_throttled": False,
|
||||
"traffic_limit_strategy": "",
|
||||
}
|
||||
base.update(overrides)
|
||||
return base
|
||||
|
||||
|
||||
class CanTopupDevicesFlagTests(unittest.TestCase):
|
||||
def test_flag_is_true_when_tariff_has_rub_packages_and_limit_is_finite(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(
|
||||
tmpdir,
|
||||
_tariffs_payload(hwid_rub=[{"count": 1, "price": 50}]),
|
||||
)
|
||||
payload = _serialize_subscription(settings, _active(max_devices=3), None, "en")
|
||||
self.assertTrue(payload["can_topup_devices"])
|
||||
|
||||
def test_flag_is_true_when_only_stars_packages_are_configured(self):
|
||||
# The bot only renders rub packages today, but the Web App grid renders
|
||||
# both. The flag drives the Web App, so stars-only must still be True.
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(
|
||||
tmpdir,
|
||||
_tariffs_payload(hwid_stars=[{"count": 1, "price": 100}]),
|
||||
)
|
||||
payload = _serialize_subscription(settings, _active(max_devices=3), None, "en")
|
||||
self.assertTrue(payload["can_topup_devices"])
|
||||
|
||||
def test_flag_is_false_when_max_devices_is_zero(self):
|
||||
# Unlimited subscribers: hwid_devices topup is a dead end.
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(
|
||||
tmpdir,
|
||||
_tariffs_payload(hwid_rub=[{"count": 1, "price": 50}]),
|
||||
)
|
||||
payload = _serialize_subscription(settings, _active(max_devices=0), None, "en")
|
||||
self.assertFalse(payload["can_topup_devices"])
|
||||
|
||||
def test_flag_is_false_when_tariff_has_no_hwid_packages(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(tmpdir, _tariffs_payload())
|
||||
payload = _serialize_subscription(settings, _active(), None, "en")
|
||||
self.assertFalse(payload["can_topup_devices"])
|
||||
|
||||
def test_flag_is_false_when_packages_dict_is_empty(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(
|
||||
tmpdir,
|
||||
_tariffs_payload(hwid_rub=[], hwid_stars=[]),
|
||||
)
|
||||
payload = _serialize_subscription(settings, _active(), None, "en")
|
||||
self.assertFalse(payload["can_topup_devices"])
|
||||
|
||||
def test_flag_is_false_in_legacy_mode_without_tariff_catalog(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(tmpdir, payload=None) # no tariffs catalog
|
||||
payload = _serialize_subscription(
|
||||
settings, _active(tariff_key=None, max_devices=3), None, "en"
|
||||
)
|
||||
self.assertFalse(payload["can_topup_devices"])
|
||||
|
||||
def test_flag_is_false_when_tariff_key_does_not_resolve(self):
|
||||
# If the user's stored ``tariff_key`` was renamed/removed, the helper
|
||||
# raises inside ``settings.tariffs_config.require``; we should swallow
|
||||
# that and emit False, not 500.
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(
|
||||
tmpdir,
|
||||
_tariffs_payload(hwid_rub=[{"count": 1, "price": 50}]),
|
||||
)
|
||||
payload = _serialize_subscription(
|
||||
settings, _active(tariff_key="missing-tariff"), None, "en"
|
||||
)
|
||||
self.assertFalse(payload["can_topup_devices"])
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user