fix: bind HWID top-ups to subscription periods
This commit is contained in:
@@ -56,6 +56,22 @@ def _tariffs_payload(*, hwid_rub=None, hwid_stars=None, has_premium=False) -> di
|
||||
return {"default_tariff": "standard", "tariffs": [tariff]}
|
||||
|
||||
|
||||
def _traffic_tariffs_payload(*, hwid_rub=None) -> dict:
|
||||
tariff: Dict[str, Any] = {
|
||||
"key": "traffic",
|
||||
"names": {"en": "Traffic"},
|
||||
"descriptions": {"en": "Traffic"},
|
||||
"squad_uuids": ["main"],
|
||||
"billing_model": "traffic",
|
||||
"traffic_packages": {"rub": [{"gb": 100, "price": 100}], "stars": []},
|
||||
"hwid_device_limit": 3,
|
||||
"enabled": True,
|
||||
}
|
||||
if hwid_rub:
|
||||
tariff["hwid_device_packages"] = {"rub": hwid_rub, "stars": []}
|
||||
return {"default_tariff": "traffic", "tariffs": [tariff]}
|
||||
|
||||
|
||||
def _make_settings(tmpdir: str, payload: Optional[dict] = None, **overrides: Any) -> Settings:
|
||||
values: Dict[str, Any] = {
|
||||
"_env_file": None,
|
||||
@@ -178,6 +194,20 @@ class CanTopupDevicesFlagTests(unittest.TestCase):
|
||||
)
|
||||
self.assertFalse(payload["can_topup_devices"])
|
||||
|
||||
def test_flag_is_false_for_traffic_tariff_even_with_packages(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(
|
||||
tmpdir,
|
||||
_traffic_tariffs_payload(hwid_rub=[{"count": 1, "price": 50}]),
|
||||
)
|
||||
payload = _serialize_subscription(
|
||||
settings,
|
||||
_active(tariff_key="traffic", billing_model="traffic"),
|
||||
None,
|
||||
"en",
|
||||
)
|
||||
self.assertFalse(payload["can_topup_devices"])
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
unittest.main()
|
||||
|
||||
@@ -9,8 +9,9 @@ recent fix). These tests pin that:
|
||||
returns a meaningful payload;
|
||||
* the panel call uses ``hwidDeviceLimit = base + extra``, not just ``base``;
|
||||
* a panel failure returns ``None`` and DOES NOT write the audit row;
|
||||
* the happy path persists ``extra_hwid_devices = old + purchased`` and
|
||||
records the device purchase.
|
||||
* the happy path records a validity window ending at the subscription end;
|
||||
* renewal top-ups start after the current HWID entitlement and do not double
|
||||
the active device limit before that date.
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -75,6 +76,8 @@ def _make_sub(*, hwid_device_limit=3, extra_hwid_devices=0):
|
||||
panel_subscription_uuid="panel-sub",
|
||||
tariff_key="standard",
|
||||
end_date=datetime(2099, 1, 1, tzinfo=timezone.utc),
|
||||
start_date=datetime(2098, 12, 1, tzinfo=timezone.utc),
|
||||
duration_months=1,
|
||||
hwid_device_limit=hwid_device_limit,
|
||||
extra_hwid_devices=extra_hwid_devices,
|
||||
)
|
||||
@@ -135,8 +138,90 @@ class HwidDeviceTopupInputTests(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
async def test_rejects_traffic_tariff(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(
|
||||
tmpdir,
|
||||
_tariffs_config_payload(
|
||||
billing_model="traffic",
|
||||
traffic_packages={"rub": [{"gb": 100, "price": 100}], "stars": []},
|
||||
),
|
||||
)
|
||||
service = _make_service(settings)
|
||||
sub = _make_sub()
|
||||
user = _make_user()
|
||||
with (
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.user_dal.get_user_by_id",
|
||||
AsyncMock(return_value=user),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.subscription_dal.get_active_subscription_by_user_id",
|
||||
AsyncMock(return_value=sub),
|
||||
),
|
||||
):
|
||||
result = await service.activate_hwid_device_topup(
|
||||
session=AsyncMock(),
|
||||
user_id=42,
|
||||
device_count=1,
|
||||
payment_amount=50,
|
||||
payment_db_id=1,
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
||||
class HwidDeviceTopupBehaviourTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_quote_prorates_period_price_for_remaining_subscription_window(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(
|
||||
tmpdir,
|
||||
_tariffs_config_payload(
|
||||
hwid_device_packages={
|
||||
"rub": [
|
||||
{
|
||||
"count": 1,
|
||||
"price": 100,
|
||||
"prices": {"1": 100},
|
||||
"min_price": 10,
|
||||
}
|
||||
],
|
||||
"stars": [],
|
||||
}
|
||||
),
|
||||
)
|
||||
service = _make_service(settings)
|
||||
sub = _make_sub()
|
||||
sub.start_date = datetime(2099, 1, 1, tzinfo=timezone.utc)
|
||||
sub.end_date = datetime(2099, 1, 31, tzinfo=timezone.utc)
|
||||
user = _make_user()
|
||||
|
||||
with (
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.user_dal.get_user_by_id",
|
||||
AsyncMock(return_value=user),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.subscription_dal.get_active_subscription_by_user_id",
|
||||
AsyncMock(return_value=sub),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.tariff_dal.get_hwid_device_entitlement_summary",
|
||||
AsyncMock(return_value={"active_devices": 0, "active_until": None}),
|
||||
),
|
||||
):
|
||||
quote = await service.quote_hwid_device_topup(
|
||||
session=AsyncMock(),
|
||||
user_id=42,
|
||||
device_count=1,
|
||||
tariff_key="standard",
|
||||
currency="rub",
|
||||
now=datetime(2099, 1, 16, tzinfo=timezone.utc),
|
||||
)
|
||||
|
||||
self.assertIsNotNone(quote)
|
||||
self.assertEqual(quote["price"], 50)
|
||||
self.assertAlmostEqual(quote["proration_ratio"], 0.5)
|
||||
|
||||
async def test_unlimited_subscriber_returns_noop_payload(self):
|
||||
# hwid_device_limit == 0 means unlimited — top-up makes no sense and must skip.
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
@@ -170,7 +255,7 @@ class HwidDeviceTopupBehaviourTests(unittest.IsolatedAsyncioTestCase):
|
||||
# Unlimited subscriber: no audit row, no panel touch.
|
||||
create_purchase.assert_not_awaited()
|
||||
|
||||
async def test_panel_payload_uses_effective_limit_with_extras(self):
|
||||
async def test_panel_payload_uses_effective_limit_with_active_entitlements(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(tmpdir, _tariffs_config_payload())
|
||||
service = _make_service(settings)
|
||||
@@ -196,6 +281,10 @@ class HwidDeviceTopupBehaviourTests(unittest.IsolatedAsyncioTestCase):
|
||||
"bot.services.subscription_service_impl.devices.subscription_dal.update_subscription",
|
||||
AsyncMock(return_value=updated_sub),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.tariff_dal.get_hwid_device_entitlement_summary",
|
||||
AsyncMock(return_value={"active_devices": 2, "active_until": sub.end_date}),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.payments.payment_dal.get_payment_by_db_id",
|
||||
AsyncMock(return_value=SimpleNamespace()),
|
||||
@@ -203,7 +292,7 @@ class HwidDeviceTopupBehaviourTests(unittest.IsolatedAsyncioTestCase):
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.tariff_dal.create_hwid_device_purchase",
|
||||
AsyncMock(),
|
||||
),
|
||||
) as create_purchase,
|
||||
):
|
||||
result = await service.activate_hwid_device_topup(
|
||||
session=AsyncMock(),
|
||||
@@ -217,10 +306,132 @@ class HwidDeviceTopupBehaviourTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(result["hwid_device_limit"], 6)
|
||||
self.assertEqual(result["extra_hwid_devices"], 3)
|
||||
self.assertEqual(result["purchased_hwid_devices"], 1)
|
||||
create_purchase.assert_awaited_once()
|
||||
purchase_kwargs = create_purchase.await_args.kwargs
|
||||
self.assertEqual(purchase_kwargs["valid_until"], sub.end_date)
|
||||
self.assertLess(purchase_kwargs["valid_from"], sub.end_date)
|
||||
# Panel must see the full effective limit, not just the base.
|
||||
panel_payload = service.panel_service.update_user_details_on_panel.await_args.args[1]
|
||||
self.assertEqual(panel_payload["hwidDeviceLimit"], 6)
|
||||
|
||||
async def test_activation_uses_frozen_payment_validity_window(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(tmpdir, _tariffs_config_payload())
|
||||
service = _make_service(settings)
|
||||
sub = _make_sub(hwid_device_limit=3, extra_hwid_devices=0)
|
||||
sub.end_date = datetime(2099, 3, 1, tzinfo=timezone.utc)
|
||||
user = _make_user()
|
||||
frozen_until = datetime(2099, 2, 1, tzinfo=timezone.utc)
|
||||
payment = SimpleNamespace(
|
||||
hwid_valid_from=datetime(2099, 1, 1, tzinfo=timezone.utc),
|
||||
hwid_valid_until=frozen_until,
|
||||
hwid_pricing_period_months=1,
|
||||
hwid_proration_ratio=1.0,
|
||||
hwid_full_price=50,
|
||||
)
|
||||
updated_sub = SimpleNamespace(subscription_id=11, end_date=sub.end_date)
|
||||
service.panel_service.update_user_details_on_panel = AsyncMock(
|
||||
return_value={"ok": True}
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.user_dal.get_user_by_id",
|
||||
AsyncMock(return_value=user),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.subscription_dal.get_active_subscription_by_user_id",
|
||||
AsyncMock(return_value=sub),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.subscription_dal.update_subscription",
|
||||
AsyncMock(return_value=updated_sub),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.tariff_dal.get_hwid_device_entitlement_summary",
|
||||
AsyncMock(return_value={"active_devices": 0, "active_until": None}),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.payments.payment_dal.get_payment_by_db_id",
|
||||
AsyncMock(return_value=payment),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.tariff_dal.create_hwid_device_purchase",
|
||||
AsyncMock(),
|
||||
) as create_purchase,
|
||||
):
|
||||
result = await service.activate_hwid_device_topup(
|
||||
session=AsyncMock(),
|
||||
user_id=42,
|
||||
device_count=1,
|
||||
payment_amount=50,
|
||||
payment_db_id=1,
|
||||
)
|
||||
|
||||
self.assertEqual(result["hwid_devices_valid_until"], frozen_until)
|
||||
purchase_kwargs = create_purchase.await_args.kwargs
|
||||
self.assertEqual(purchase_kwargs["valid_until"], frozen_until)
|
||||
|
||||
async def test_renewal_topup_starts_after_existing_entitlement(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(tmpdir, _tariffs_config_payload())
|
||||
service = _make_service(settings)
|
||||
current_entitlement_end = datetime(2099, 1, 1, tzinfo=timezone.utc)
|
||||
renewed_end = datetime(2099, 2, 1, tzinfo=timezone.utc)
|
||||
sub = _make_sub(hwid_device_limit=3, extra_hwid_devices=2)
|
||||
sub.end_date = renewed_end
|
||||
user = _make_user()
|
||||
updated_sub = SimpleNamespace(subscription_id=11, end_date=renewed_end)
|
||||
service.panel_service.update_user_details_on_panel = AsyncMock(
|
||||
return_value={"ok": True}
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.user_dal.get_user_by_id",
|
||||
AsyncMock(return_value=user),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.subscription_dal.get_active_subscription_by_user_id",
|
||||
AsyncMock(return_value=sub),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.subscription_dal.update_subscription",
|
||||
AsyncMock(return_value=updated_sub),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.tariff_dal.get_hwid_device_entitlement_summary",
|
||||
AsyncMock(
|
||||
return_value={
|
||||
"active_devices": 2,
|
||||
"active_until": current_entitlement_end,
|
||||
}
|
||||
),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.payments.payment_dal.get_payment_by_db_id",
|
||||
AsyncMock(return_value=SimpleNamespace()),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.tariff_dal.create_hwid_device_purchase",
|
||||
AsyncMock(),
|
||||
) as create_purchase,
|
||||
):
|
||||
result = await service.activate_hwid_device_topup(
|
||||
session=AsyncMock(),
|
||||
user_id=42,
|
||||
device_count=1,
|
||||
payment_amount=50,
|
||||
payment_db_id=1,
|
||||
renewal=True,
|
||||
)
|
||||
|
||||
self.assertEqual(result["extra_hwid_devices"], 2)
|
||||
self.assertEqual(result["hwid_device_limit"], 5)
|
||||
purchase_kwargs = create_purchase.await_args.kwargs
|
||||
self.assertEqual(purchase_kwargs["valid_from"], current_entitlement_end)
|
||||
self.assertEqual(purchase_kwargs["valid_until"], renewed_end)
|
||||
panel_payload = service.panel_service.update_user_details_on_panel.await_args.args[1]
|
||||
self.assertEqual(panel_payload["hwidDeviceLimit"], 5)
|
||||
|
||||
async def test_panel_failure_returns_none_and_skips_audit(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(tmpdir, _tariffs_config_payload())
|
||||
@@ -242,6 +453,10 @@ class HwidDeviceTopupBehaviourTests(unittest.IsolatedAsyncioTestCase):
|
||||
"bot.services.subscription_service_impl.devices.subscription_dal.update_subscription",
|
||||
AsyncMock(return_value=updated_sub),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.devices.tariff_dal.get_hwid_device_entitlement_summary",
|
||||
AsyncMock(return_value={"active_devices": 0, "active_until": None}),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.payments.payment_dal.get_payment_by_db_id",
|
||||
AsyncMock(return_value=SimpleNamespace()),
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import unittest
|
||||
from datetime import datetime, timezone
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from bot.services.tariff_worker import TariffTrafficWorker
|
||||
|
||||
|
||||
class _Service:
|
||||
def _base_hwid_limit_for_tariff(self, tariff):
|
||||
return tariff.hwid_device_limit
|
||||
|
||||
@staticmethod
|
||||
def _effective_hwid_limit(base_limit, extra_devices=0):
|
||||
if base_limit is None:
|
||||
return None
|
||||
base_int = max(0, int(base_limit))
|
||||
if base_int == 0:
|
||||
return 0
|
||||
return base_int + max(0, int(extra_devices or 0))
|
||||
|
||||
@staticmethod
|
||||
def _build_panel_update_payload(
|
||||
*,
|
||||
panel_user_uuid=None,
|
||||
expire_at=None,
|
||||
hwid_device_limit=None,
|
||||
include_default_squads=True,
|
||||
**_kwargs,
|
||||
):
|
||||
payload = {}
|
||||
if panel_user_uuid:
|
||||
payload["uuid"] = panel_user_uuid
|
||||
if expire_at:
|
||||
payload["expireAt"] = expire_at.isoformat(timespec="milliseconds").replace(
|
||||
"+00:00", "Z"
|
||||
)
|
||||
if hwid_device_limit is not None:
|
||||
payload["hwidDeviceLimit"] = int(hwid_device_limit)
|
||||
return payload
|
||||
|
||||
|
||||
class HwidDeviceWorkerTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_worker_resets_expired_hwid_entitlement_on_panel(self):
|
||||
panel = SimpleNamespace(
|
||||
update_user_details_on_panel=AsyncMock(return_value={"ok": True})
|
||||
)
|
||||
worker = TariffTrafficWorker(
|
||||
settings=SimpleNamespace(),
|
||||
session_factory=None,
|
||||
panel_service=panel,
|
||||
subscription_service=_Service(),
|
||||
)
|
||||
sub = SimpleNamespace(
|
||||
subscription_id=11,
|
||||
panel_user_uuid="panel-user",
|
||||
end_date=datetime(2099, 1, 1, tzinfo=timezone.utc),
|
||||
hwid_device_limit=3,
|
||||
extra_hwid_devices=2,
|
||||
)
|
||||
tariff = SimpleNamespace(hwid_device_limit=3)
|
||||
|
||||
with patch(
|
||||
"bot.services.tariff_worker.tariff_dal.sum_active_hwid_devices",
|
||||
AsyncMock(return_value=0),
|
||||
):
|
||||
await worker._sync_hwid_device_limit(
|
||||
session=AsyncMock(),
|
||||
sub=sub,
|
||||
tariff=tariff,
|
||||
panel_data={"hwidDeviceLimit": 5},
|
||||
)
|
||||
|
||||
self.assertEqual(sub.extra_hwid_devices, 0)
|
||||
panel_payload = panel.update_user_details_on_panel.await_args.args[1]
|
||||
self.assertEqual(panel_payload["hwidDeviceLimit"], 3)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
unittest.main()
|
||||
@@ -0,0 +1,190 @@
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from bot.services.panel_api_service import PanelApiService
|
||||
from bot.services.subscription_service import SubscriptionService
|
||||
from config.settings import Settings
|
||||
|
||||
|
||||
def _settings(tmpdir: str) -> Settings:
|
||||
payload = {
|
||||
"default_tariff": "basic",
|
||||
"tariffs": [
|
||||
{
|
||||
"key": "basic",
|
||||
"names": {"en": "Basic"},
|
||||
"descriptions": {"en": "Basic"},
|
||||
"squad_uuids": ["basic"],
|
||||
"billing_model": "period",
|
||||
"monthly_gb": 100,
|
||||
"prices_rub": {"1": 100},
|
||||
"enabled_periods": [1],
|
||||
"hwid_device_limit": 3,
|
||||
"enabled": True,
|
||||
},
|
||||
{
|
||||
"key": "pro",
|
||||
"names": {"en": "Pro"},
|
||||
"descriptions": {"en": "Pro"},
|
||||
"squad_uuids": ["pro"],
|
||||
"billing_model": "period",
|
||||
"monthly_gb": 200,
|
||||
"prices_rub": {"1": 200},
|
||||
"enabled_periods": [1],
|
||||
"hwid_device_limit": 5,
|
||||
"enabled": True,
|
||||
},
|
||||
],
|
||||
}
|
||||
config_path = Path(tmpdir) / "tariffs.json"
|
||||
config_path.write_text(json.dumps(payload), encoding="utf-8")
|
||||
return Settings(
|
||||
_env_file=None,
|
||||
BOT_TOKEN="token",
|
||||
POSTGRES_USER="u",
|
||||
POSTGRES_PASSWORD="p",
|
||||
TARIFFS_CONFIG_PATH=str(config_path),
|
||||
)
|
||||
|
||||
|
||||
def _service(settings: Settings) -> SubscriptionService:
|
||||
panel = AsyncMock(spec=PanelApiService)
|
||||
panel.update_user_details_on_panel = AsyncMock(return_value={"ok": True})
|
||||
return SubscriptionService(settings, panel)
|
||||
|
||||
|
||||
class HwidTariffSwitchConversionTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_hwid_remaining_rub_value_is_converted_to_target_tariff_days(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _settings(tmpdir)
|
||||
service = _service(settings)
|
||||
now = datetime(2099, 1, 15, tzinfo=timezone.utc)
|
||||
sub = SimpleNamespace(subscription_id=11)
|
||||
|
||||
with patch(
|
||||
"bot.services.subscription_service_impl.tariffs.tariff_dal.get_hwid_device_value_entries",
|
||||
AsyncMock(
|
||||
return_value=[
|
||||
{
|
||||
"purchase_id": 7,
|
||||
"purchased_devices": 1,
|
||||
"valid_from": now - timedelta(days=15),
|
||||
"valid_until": now + timedelta(days=15),
|
||||
"created_at": now - timedelta(days=15),
|
||||
"amount": 100,
|
||||
"currency": "RUB",
|
||||
}
|
||||
]
|
||||
),
|
||||
):
|
||||
credit = await service._hwid_conversion_credit(
|
||||
AsyncMock(),
|
||||
sub,
|
||||
at=now,
|
||||
)
|
||||
|
||||
self.assertEqual(credit["purchase_ids"], [7])
|
||||
self.assertAlmostEqual(credit["value_rub"], 50)
|
||||
|
||||
async def test_switch_expires_converted_hwid_purchases_and_audits_value(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _settings(tmpdir)
|
||||
service = _service(settings)
|
||||
user = SimpleNamespace(
|
||||
user_id=42,
|
||||
telegram_id=42,
|
||||
panel_user_uuid="panel-user",
|
||||
email=None,
|
||||
username="u",
|
||||
first_name="U",
|
||||
last_name="L",
|
||||
)
|
||||
sub = SimpleNamespace(
|
||||
subscription_id=11,
|
||||
user_id=42,
|
||||
panel_user_uuid="panel-user",
|
||||
panel_subscription_uuid="panel-sub",
|
||||
tariff_key="basic",
|
||||
start_date=datetime(2099, 1, 1, tzinfo=timezone.utc),
|
||||
end_date=datetime(2099, 2, 1, tzinfo=timezone.utc),
|
||||
effective_monthly_price_rub=100,
|
||||
premium_topup_balance_bytes=0,
|
||||
premium_topup_used_bytes=0,
|
||||
premium_used_bytes=0,
|
||||
topup_balance_bytes=0,
|
||||
regular_bonus_bytes=0,
|
||||
regular_unlimited_override=False,
|
||||
traffic_used_bytes=0,
|
||||
extra_hwid_devices=1,
|
||||
hwid_device_limit=3,
|
||||
)
|
||||
updated = SimpleNamespace(**{**sub.__dict__, "tariff_key": "pro"})
|
||||
updated.hwid_device_limit = 5
|
||||
updated.extra_hwid_devices = 0
|
||||
updated.traffic_limit_bytes = 200 * (1024**3)
|
||||
updated.premium_is_limited = False
|
||||
updated.effective_monthly_price_rub = 200
|
||||
|
||||
with (
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.lifecycle.user_dal.get_user_by_id",
|
||||
AsyncMock(return_value=user),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.lifecycle.subscription_dal.get_active_subscription_by_user_id",
|
||||
AsyncMock(return_value=sub),
|
||||
),
|
||||
patch.object(
|
||||
service,
|
||||
"calculate_tariff_switch_options_with_hwid",
|
||||
AsyncMock(
|
||||
return_value={
|
||||
"mode": "period_to_period",
|
||||
"remaining_days": 20,
|
||||
"recalc_days": 25,
|
||||
"paid_diff_rub": 0,
|
||||
"target_monthly_rub": 200,
|
||||
"converted_hwid_value_rub": 50,
|
||||
"converted_hwid_days": 7,
|
||||
"convertible_hwid_purchase_ids": [7],
|
||||
}
|
||||
),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.lifecycle.tariff_dal.expire_hwid_device_purchases",
|
||||
AsyncMock(return_value=1),
|
||||
) as expire_purchases,
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.lifecycle.tariff_dal.sum_active_hwid_devices",
|
||||
AsyncMock(return_value=0),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.lifecycle.subscription_dal.update_subscription",
|
||||
AsyncMock(return_value=updated),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.lifecycle.tariff_dal.create_tariff_change",
|
||||
AsyncMock(),
|
||||
) as create_change,
|
||||
):
|
||||
result = await service.switch_tariff_without_payment(
|
||||
AsyncMock(),
|
||||
user_id=42,
|
||||
target_tariff_key="pro",
|
||||
mode="recalc_days",
|
||||
)
|
||||
|
||||
self.assertEqual(result["tariff_key"], "pro")
|
||||
expire_purchases.assert_awaited_once()
|
||||
change_payload = create_change.await_args.args[1]
|
||||
self.assertEqual(change_payload["converted_hwid_value_rub"], 50)
|
||||
self.assertEqual(change_payload["converted_hwid_days"], 7)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
unittest.main()
|
||||
@@ -110,7 +110,7 @@ class TariffsConfigTests(unittest.TestCase):
|
||||
data = _valid_config()
|
||||
data["tariffs"][0]["hwid_device_limit"] = 5
|
||||
data["tariffs"][0]["hwid_device_packages"] = {
|
||||
"rub": [{"count": 1, "price": 99}],
|
||||
"rub": [{"count": 1, "price": 99, "prices": {"3": 249}, "min_price": 20}],
|
||||
"stars": [{"count": 1, "price": 2500}],
|
||||
}
|
||||
|
||||
@@ -120,6 +120,9 @@ class TariffsConfigTests(unittest.TestCase):
|
||||
self.assertEqual(tariff.hwid_device_limit, 5)
|
||||
self.assertTrue(tariff.has_hwid_device_packages())
|
||||
self.assertEqual(tariff.hwid_device_packages.rub[0].count, 1)
|
||||
self.assertEqual(tariff.hwid_device_packages.rub[0].price_for_period(3), 249)
|
||||
self.assertEqual(tariff.hwid_device_packages.rub[0].price_for_period(6), 594)
|
||||
self.assertEqual(tariff.hwid_device_packages.rub[0].min_price, 20)
|
||||
|
||||
def test_negative_hwid_device_limit_rejected(self):
|
||||
data = _valid_config()
|
||||
|
||||
Reference in New Issue
Block a user