fix: preserve premium topup squad access

This commit is contained in:
3252a8
2026-05-25 12:18:58 +03:00
parent 340afe1b80
commit ba7811621e
6 changed files with 466 additions and 26 deletions
+73
View File
@@ -209,6 +209,79 @@ class AdminGrantTopupTests(unittest.IsolatedAsyncioTestCase):
sub_update_payload = upd.await_args.args[2]
self.assertFalse(sub_update_payload["premium_is_limited"])
async def test_premium_grant_skips_panel_patch_when_squads_already_match(self):
with tempfile.TemporaryDirectory() as tmpdir:
settings = _make_settings(_tariffs_config_payload(premium=True), tmpdir)
panel_service = AsyncMock(spec=PanelApiService)
panel_service.get_user_by_uuid = AsyncMock(
return_value={
"activeInternalSquads": [
{"uuid": "squad-1"},
{"uuid": "premium-squad"},
]
}
)
panel_service.update_user_details_on_panel = AsyncMock(return_value={"response": {}})
service = SubscriptionService(settings, panel_service)
db_user = SimpleNamespace(
user_id=77,
first_name="Premium",
last_name=None,
username="premium",
language_code="ru",
panel_user_uuid="panel-uuid",
email=None,
telegram_id=77,
)
sub = SimpleNamespace(
subscription_id=9,
user_id=77,
panel_user_uuid="panel-uuid",
tariff_key="standard",
premium_baseline_bytes=25 * (1024**3),
premium_topup_balance_bytes=0,
premium_topup_used_bytes=0,
premium_used_bytes=30 * (1024**3),
premium_is_limited=True,
premium_period_start_at=datetime.now(timezone.utc).replace(
day=1, hour=0, minute=0, second=0, microsecond=0
),
premium_unlimited_override=False,
premium_bonus_bytes=0,
)
updated_sub = SimpleNamespace(**vars(sub))
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),
),
patch(
"bot.services.subscription_service.subscription_dal.update_subscription",
new=AsyncMock(return_value=updated_sub),
) as upd,
patch(
"bot.services.subscription_service.tariff_dal.create_traffic_topup",
new=AsyncMock(),
) as topup_log,
):
result = await service.admin_grant_premium_topup(AsyncMock(), 77, 20.0)
self.assertIsNotNone(result)
self.assertFalse(result["premium_is_limited"])
upd.assert_awaited_once()
topup_log.assert_awaited_once()
panel_service.get_user_by_uuid.assert_awaited_once_with(
"panel-uuid",
log_response=False,
)
panel_service.update_user_details_on_panel.assert_not_awaited()
async def test_premium_grant_fails_when_tariff_has_no_premium_squads(self):
with tempfile.TemporaryDirectory() as tmpdir:
settings = _make_settings(_tariffs_config_payload(premium=False), tmpdir)
+75
View File
@@ -277,6 +277,81 @@ class TariffWorkerTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual(sub.premium_topup_used_bytes, 0)
self.assertEqual(sub.premium_period_start_at, datetime(2026, 6, 1, tzinfo=timezone.utc))
async def test_premium_topup_ledger_repairs_missing_balance_before_limiting(self):
payload = _tariffs_config_payload()
payload["tariffs"][0]["premium_squad_uuids"] = ["premium-squad"]
payload["tariffs"][0]["premium_monthly_gb"] = 25
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": [
{"username": "tg_123", "total": 40 * (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,
)
worker._premium_topup_ledger_total = AsyncMock(return_value=20 * (1024**3))
now = datetime(2026, 5, 9, tzinfo=timezone.utc)
sub = SimpleNamespace(
subscription_id=1,
user_id=123,
panel_user_uuid="panel-uuid",
premium_baseline_bytes=25 * (1024**3),
premium_topup_balance_bytes=0,
premium_topup_used_bytes=0,
premium_used_bytes=40 * (1024**3),
premium_is_limited=True,
premium_period_start_at=datetime(2026, 5, 1, tzinfo=timezone.utc),
premium_unlimited_override=False,
premium_bonus_bytes=0,
)
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,
now,
panel_username="tg_123",
panel_user_dict={
"activeInternalSquads": [
{"uuid": "squad-1"},
{"uuid": "premium-squad"},
]
},
)
self.assertEqual(sub.premium_topup_balance_bytes, 5 * (1024**3))
self.assertEqual(sub.premium_topup_used_bytes, 15 * (1024**3))
self.assertFalse(sub.premium_is_limited)
panel_service.update_user_details_on_panel.assert_not_awaited()
async def test_premium_usage_update_does_not_patch_panel_when_access_state_unchanged(self):
payload = _tariffs_config_payload()
payload["tariffs"][0]["premium_squad_uuids"] = ["premium-squad"]
+61
View File
@@ -270,6 +270,67 @@ class ActivatePremiumTopupPanelFailureTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual(result["tariff_key"], "standard")
create_topup.assert_awaited_once()
async def test_skips_panel_patch_when_premium_squads_already_match(self):
with tempfile.TemporaryDirectory() as tmpdir:
settings = _make_settings(tmpdir)
service = _make_service(settings)
sub = _make_sub(
premium_topup_balance_bytes=0,
premium_used_bytes=30 * GIB,
premium_is_limited=True,
)
user = _make_user()
service.panel_service.get_user_by_uuid = AsyncMock(
return_value={
"activeInternalSquads": [
{"uuid": "main-squad"},
{"uuid": "premium-squad"},
]
}
)
service.panel_service.update_user_details_on_panel = AsyncMock(
return_value={"ok": True, "uuid": "panel-uuid"}
)
with (
patch(
"bot.services.subscription_service_impl.payments.payment_dal.get_payment_by_db_id",
AsyncMock(return_value=SimpleNamespace()),
),
patch(
"bot.services.subscription_service_impl.traffic.user_dal.get_user_by_id",
AsyncMock(return_value=user),
),
patch(
"bot.services.subscription_service_impl.traffic.subscription_dal.get_active_subscription_by_user_id",
AsyncMock(return_value=sub),
),
patch(
"bot.services.subscription_service_impl.traffic.subscription_dal.update_subscription",
AsyncMock(),
),
patch(
"bot.services.subscription_service_impl.traffic.tariff_dal.create_traffic_topup",
AsyncMock(),
) as create_topup,
):
result = await service.activate_premium_topup(
session=AsyncMock(),
user_id=42,
tariff_key="standard",
traffic_gb=10,
payment_amount=100,
payment_db_id=2,
)
self.assertIsNotNone(result)
create_topup.assert_awaited_once()
service.panel_service.get_user_by_uuid.assert_awaited_once_with(
"panel-uuid",
log_response=False,
)
service.panel_service.update_user_details_on_panel.assert_not_awaited()
class SwitchTariffPanelFailureTests(unittest.IsolatedAsyncioTestCase):
"""Free tariff switch. The bug was lifecycle.py:121 ignoring panel result — the tariff_key