feat: premium squads inside one tariff
This commit is contained in:
@@ -124,3 +124,134 @@ class TariffWorkerTests(unittest.IsolatedAsyncioTestCase):
|
||||
|
||||
panel_service.remove_users_from_internal_squad.assert_not_awaited()
|
||||
self.assertFalse(sub.is_throttled)
|
||||
|
||||
async def test_premium_limit_removes_only_premium_squad(self):
|
||||
payload = _tariffs_config_payload()
|
||||
payload["tariffs"][0]["premium_squad_uuids"] = ["premium-squad"]
|
||||
payload["tariffs"][0]["premium_monthly_gb"] = 1
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
config_path = Path(tmpdir) / "tariffs.json"
|
||||
config_path.write_text(json.dumps(payload), encoding="utf-8")
|
||||
|
||||
settings = Settings(
|
||||
_env_file=None,
|
||||
BOT_TOKEN="token",
|
||||
POSTGRES_USER="app_user",
|
||||
POSTGRES_PASSWORD="app_password",
|
||||
TARIFFS_CONFIG_PATH=str(config_path),
|
||||
)
|
||||
panel_service = AsyncMock(spec=PanelApiService)
|
||||
panel_service.get_internal_squad_accessible_nodes = AsyncMock(
|
||||
return_value=[{"uuid": "node-1", "name": "Premium"}]
|
||||
)
|
||||
panel_service.get_node_users_bandwidth_stats = AsyncMock(
|
||||
return_value={
|
||||
"topUsers": [
|
||||
{
|
||||
"user": {"uuid": "panel-uuid"},
|
||||
"total": 2 * (1024**3),
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
panel_service.update_user_details_on_panel = AsyncMock(return_value={"response": {}})
|
||||
subscription_service = SubscriptionService(settings, panel_service)
|
||||
worker = TariffTrafficWorker(
|
||||
settings=settings,
|
||||
session_factory=SimpleNamespace(),
|
||||
panel_service=panel_service,
|
||||
subscription_service=subscription_service,
|
||||
)
|
||||
sub = SimpleNamespace(
|
||||
subscription_id=1,
|
||||
user_id=123,
|
||||
panel_user_uuid="panel-uuid",
|
||||
premium_baseline_bytes=1 * (1024**3),
|
||||
premium_topup_balance_bytes=0,
|
||||
premium_topup_used_bytes=0,
|
||||
premium_used_bytes=0,
|
||||
premium_is_limited=False,
|
||||
premium_period_start_at=None,
|
||||
)
|
||||
tariff = settings.tariffs_config.require("standard")
|
||||
|
||||
with patch("bot.services.tariff_worker.tariff_dal.get_warning", new=AsyncMock(return_value=True)):
|
||||
await worker._sync_premium_squad_limit(AsyncMock(), sub, tariff, datetime.now(timezone.utc))
|
||||
|
||||
self.assertTrue(sub.premium_is_limited)
|
||||
panel_service.update_user_details_on_panel.assert_awaited_once()
|
||||
payload = panel_service.update_user_details_on_panel.await_args.args[1]
|
||||
self.assertEqual(payload["activeInternalSquads"], ["squad-1"])
|
||||
|
||||
async def test_premium_topup_balance_carries_over_and_is_spent_only_above_monthly_limit(self):
|
||||
payload = _tariffs_config_payload()
|
||||
payload["tariffs"][0]["premium_squad_uuids"] = ["premium-squad"]
|
||||
payload["tariffs"][0]["premium_monthly_gb"] = 1
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
config_path = Path(tmpdir) / "tariffs.json"
|
||||
config_path.write_text(json.dumps(payload), encoding="utf-8")
|
||||
|
||||
settings = Settings(
|
||||
_env_file=None,
|
||||
BOT_TOKEN="token",
|
||||
POSTGRES_USER="app_user",
|
||||
POSTGRES_PASSWORD="app_password",
|
||||
TARIFFS_CONFIG_PATH=str(config_path),
|
||||
TARIFF_TRAFFIC_WARNING_LEVELS="101",
|
||||
)
|
||||
panel_service = AsyncMock(spec=PanelApiService)
|
||||
panel_service.get_internal_squad_accessible_nodes = AsyncMock(return_value=[{"uuid": "node-1"}])
|
||||
panel_service.get_node_users_bandwidth_stats = AsyncMock(
|
||||
return_value={
|
||||
"topUsers": [
|
||||
{
|
||||
"user": {"uuid": "panel-uuid"},
|
||||
"total": int(1.5 * (1024**3)),
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
panel_service.update_user_details_on_panel = AsyncMock(return_value={"response": {}})
|
||||
subscription_service = SubscriptionService(settings, panel_service)
|
||||
worker = TariffTrafficWorker(
|
||||
settings=settings,
|
||||
session_factory=SimpleNamespace(),
|
||||
panel_service=panel_service,
|
||||
subscription_service=subscription_service,
|
||||
)
|
||||
now = datetime(2026, 5, 9, tzinfo=timezone.utc)
|
||||
sub = SimpleNamespace(
|
||||
subscription_id=1,
|
||||
user_id=123,
|
||||
panel_user_uuid="panel-uuid",
|
||||
premium_baseline_bytes=1 * (1024**3),
|
||||
premium_topup_balance_bytes=2 * (1024**3),
|
||||
premium_topup_used_bytes=0,
|
||||
premium_used_bytes=0,
|
||||
premium_is_limited=False,
|
||||
premium_period_start_at=datetime(2026, 5, 1, tzinfo=timezone.utc),
|
||||
)
|
||||
tariff = settings.tariffs_config.require("standard")
|
||||
|
||||
await worker._sync_premium_squad_limit(AsyncMock(), sub, tariff, now)
|
||||
|
||||
self.assertEqual(sub.premium_topup_balance_bytes, int(1.5 * (1024**3)))
|
||||
self.assertEqual(sub.premium_topup_used_bytes, int(0.5 * (1024**3)))
|
||||
self.assertFalse(sub.premium_is_limited)
|
||||
|
||||
panel_service.get_node_users_bandwidth_stats = AsyncMock(
|
||||
return_value={
|
||||
"topUsers": [
|
||||
{
|
||||
"user": {"uuid": "panel-uuid"},
|
||||
"total": int(0.1 * (1024**3)),
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
next_month = datetime(2026, 6, 2, tzinfo=timezone.utc)
|
||||
await worker._sync_premium_squad_limit(AsyncMock(), sub, tariff, next_month)
|
||||
|
||||
self.assertEqual(sub.premium_topup_balance_bytes, int(1.5 * (1024**3)))
|
||||
self.assertEqual(sub.premium_topup_used_bytes, 0)
|
||||
self.assertEqual(sub.premium_period_start_at, datetime(2026, 6, 1, tzinfo=timezone.utc))
|
||||
|
||||
@@ -53,6 +53,24 @@ class TariffsConfigTests(unittest.TestCase):
|
||||
self.assertEqual(config.default.key, "standard")
|
||||
self.assertEqual(config.require("traffic").rub_per_gb_for_conversion(), 19.9)
|
||||
|
||||
def test_period_tariff_without_topup_packages_has_no_topup(self):
|
||||
config = TariffsConfig.model_validate(_valid_config())
|
||||
|
||||
self.assertIsNone(config.topup_packages_for(config.require("standard")))
|
||||
|
||||
def test_period_tariff_uses_only_own_topup_packages(self):
|
||||
data = _valid_config()
|
||||
data["tariffs"][0]["topup_packages"] = {
|
||||
"rub": [{"gb": 25, "price": 199}],
|
||||
"stars": [],
|
||||
}
|
||||
config = TariffsConfig.model_validate(data)
|
||||
|
||||
packages = config.topup_packages_for(config.require("standard"))
|
||||
|
||||
self.assertIsNotNone(packages)
|
||||
self.assertEqual(packages.rub[0].gb, 25)
|
||||
|
||||
def test_missing_config_returns_none(self):
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
@@ -110,3 +128,26 @@ class TariffsConfigTests(unittest.TestCase):
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
TariffsConfig.model_validate(data)
|
||||
|
||||
def test_premium_squad_limit_and_topups_load(self):
|
||||
data = _valid_config()
|
||||
data["tariffs"][0]["premium_squad_uuids"] = [" premium-squad "]
|
||||
data["tariffs"][0]["premium_monthly_gb"] = 50
|
||||
data["tariffs"][0]["premium_topup_packages"] = {
|
||||
"rub": [{"gb": 10, "price": 99}],
|
||||
"stars": [],
|
||||
}
|
||||
|
||||
config = TariffsConfig.model_validate(data)
|
||||
tariff = config.require("standard")
|
||||
|
||||
self.assertEqual(tariff.premium_squad_uuids, ["premium-squad"])
|
||||
self.assertEqual(tariff.premium_monthly_bytes, 50 * 1024**3)
|
||||
self.assertTrue(tariff.has_premium_squad_limit())
|
||||
|
||||
def test_premium_limit_requires_premium_squad(self):
|
||||
data = _valid_config()
|
||||
data["tariffs"][0]["premium_monthly_gb"] = 50
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
TariffsConfig.model_validate(data)
|
||||
|
||||
@@ -126,6 +126,33 @@ class WebAppAssetTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(plans[0]["price"], 199.0)
|
||||
self.assertEqual(plans[1]["stars_price"], 2500)
|
||||
|
||||
def test_serialize_payment_methods_respects_runtime_provider_toggles(self):
|
||||
settings = Settings(
|
||||
_env_file=None,
|
||||
BOT_TOKEN="token",
|
||||
POSTGRES_USER="app_user",
|
||||
POSTGRES_PASSWORD="app_password",
|
||||
TARIFFS_CONFIG_PATH="missing-tariffs.json",
|
||||
CRYPTOPAY_ENABLED=False,
|
||||
FREEKASSA_ENABLED=False,
|
||||
SEVERPAY_ENABLED=False,
|
||||
YOOKASSA_ENABLED=False,
|
||||
PLATEGA_ENABLED=False,
|
||||
STARS_ENABLED=False,
|
||||
)
|
||||
configured_service = SimpleNamespace(configured=True)
|
||||
app = {
|
||||
"cryptopay_service": configured_service,
|
||||
"freekassa_service": configured_service,
|
||||
"severpay_service": configured_service,
|
||||
"yookassa_service": configured_service,
|
||||
"platega_service": configured_service,
|
||||
}
|
||||
|
||||
methods = subscription_webapp._serialize_payment_methods(settings, app)
|
||||
|
||||
self.assertEqual(methods, [])
|
||||
|
||||
def test_serialize_plans_includes_stars_only_subscription_options(self):
|
||||
settings = Settings(
|
||||
_env_file=None,
|
||||
|
||||
Reference in New Issue
Block a user