feat: add telegram guardrails for trials and referrals

This commit is contained in:
3252a8
2026-06-04 10:51:58 +03:00
parent 33707b7257
commit 2ad6b14513
26 changed files with 1944 additions and 125 deletions
@@ -55,6 +55,7 @@ ADMIN_TARIFF_SETTINGS_PAGE_KEYS = {
"admin_tariffs_trial_title",
"admin_tariffs_trial_subtitle",
"admin_tariffs_trial_enabled",
"admin_tariffs_trial_without_telegram_enabled",
"admin_tariffs_trial_days",
"admin_tariffs_trial_traffic",
"admin_tariffs_trial_strategy",
@@ -69,6 +70,18 @@ ADMIN_TARIFF_SETTINGS_PAGE_KEYS = {
"admin_tariffs_trial_group_reset_hint",
"admin_tariffs_trial_group_squads",
"admin_tariffs_trial_group_squads_hint",
"admin_tariffs_referral_title",
"admin_tariffs_referral_subtitle",
"admin_tariffs_referral_group_welcome",
"admin_tariffs_referral_group_welcome_hint",
"admin_tariffs_referral_welcome_bonus_days",
"admin_tariffs_referral_without_telegram",
"admin_tariffs_referral_group_rules",
"admin_tariffs_referral_group_rules_hint",
"admin_tariffs_referral_one_bonus_per_referee",
"admin_tariffs_referral_legacy_refs",
"admin_tariffs_referral_disposable_domains",
"admin_tariffs_referral_disposable_domains_hint",
"admin_tariffs_legacy_title",
"admin_tariffs_legacy_subtitle",
"admin_tariffs_legacy_period",
@@ -243,6 +256,7 @@ def test_trial_required_settings_reject_empty_values():
"TRIAL_DURATION_DAYS",
"TRIAL_TRAFFIC_LIMIT_GB",
"TRIAL_TRAFFIC_STRATEGY",
"TRIAL_WITHOUT_TELEGRAM_ENABLED",
):
with pytest.raises(ValueError):
coerce_value(get_field_by_key(key), "")
@@ -321,8 +335,20 @@ def test_legacy_tariff_settings_are_separated_from_payment_settings():
assert manifest["MONTH_1_ENABLED"]["section_order"] == 11
assert manifest["TRIAL_ENABLED"]["section"] == "pricing"
assert manifest["TRIAL_ENABLED"]["subsection"] == "trial"
assert manifest["TRIAL_WITHOUT_TELEGRAM_ENABLED"]["section"] == "pricing"
assert manifest["TRIAL_WITHOUT_TELEGRAM_ENABLED"]["subsection"] == "trial"
assert manifest["TRIAL_SQUAD_UUIDS"]["section"] == "pricing"
assert manifest["TRIAL_SQUAD_UUIDS"]["subsection"] == "trial"
assert manifest["REFERRAL_WELCOME_BONUS_DAYS"]["section"] == "pricing"
assert manifest["REFERRAL_WELCOME_BONUS_DAYS"]["subsection"] == "referral"
assert manifest["REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED"]["section"] == "pricing"
assert manifest["REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED"]["subsection"] == "referral"
assert manifest["REFERRAL_ONE_BONUS_PER_REFEREE"]["section"] == "pricing"
assert manifest["REFERRAL_ONE_BONUS_PER_REFEREE"]["subsection"] == "referral"
assert manifest["LEGACY_REFS"]["section"] == "pricing"
assert manifest["LEGACY_REFS"]["subsection"] == "referral"
assert manifest["DISPOSABLE_EMAIL_DOMAINS"]["section"] == "pricing"
assert manifest["DISPOSABLE_EMAIL_DOMAINS"]["subsection"] == "referral"
def test_platega_settings_share_one_admin_subsection():
@@ -0,0 +1,78 @@
from datetime import datetime, timezone
from types import SimpleNamespace
from unittest import IsolatedAsyncioTestCase
from unittest.mock import AsyncMock
import bot.app.web.subscription_webapp # noqa: F401
from bot.app.web.webapp import auth as auth_module
class WebAppReferralWelcomeBonusTests(IsolatedAsyncioTestCase):
async def test_disposable_email_referral_welcome_bonus_requires_telegram(self):
settings = SimpleNamespace(
REFERRAL_WELCOME_BONUS_DAYS=3,
REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED=True,
DISPOSABLE_EMAIL_DOMAINS="mailinator.com",
)
user = SimpleNamespace(
user_id=42,
referred_by_id=7,
telegram_id=None,
email="person@mailinator.com",
)
subscription_service = SimpleNamespace(
has_active_subscription=AsyncMock(return_value=False),
extend_active_subscription_days=AsyncMock(),
)
request = SimpleNamespace(
app={"settings": settings, "subscription_service": subscription_service}
)
result = await auth_module._apply_referral_welcome_bonus_if_needed(
request,
SimpleNamespace(),
user,
"ABC123",
)
self.assertIsNone(result)
subscription_service.has_active_subscription.assert_not_awaited()
subscription_service.extend_active_subscription_days.assert_not_awaited()
async def test_linked_telegram_allows_disposable_email_referral_welcome_bonus(self):
end_date = datetime(2026, 1, 9, 3, 4, tzinfo=timezone.utc)
settings = SimpleNamespace(
REFERRAL_WELCOME_BONUS_DAYS=3,
REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED=True,
DISPOSABLE_EMAIL_DOMAINS="mailinator.com",
)
user = SimpleNamespace(
user_id=42,
referred_by_id=7,
telegram_id=123456,
email="person@mailinator.com",
)
session = SimpleNamespace()
subscription_service = SimpleNamespace(
has_active_subscription=AsyncMock(return_value=False),
extend_active_subscription_days=AsyncMock(return_value=end_date),
)
request = SimpleNamespace(
app={"settings": settings, "subscription_service": subscription_service}
)
result = await auth_module._apply_referral_welcome_bonus_if_needed(
request,
session,
user,
"ABC123",
)
self.assertEqual(result, end_date)
subscription_service.has_active_subscription.assert_awaited_once_with(session, 42)
subscription_service.extend_active_subscription_days.assert_awaited_once_with(
session,
42,
3,
reason="referral_welcome_bonus",
)
+92
View File
@@ -115,3 +115,95 @@ class WebAppTrialActivationTests(IsolatedAsyncioTestCase):
mark_trial_activated.assert_awaited_once_with(session, 42)
self.assertEqual(session.commit_count, 2)
self.assertEqual(session.rollback_count, 0)
async def test_email_only_trial_activation_requires_telegram_when_disabled(self):
session = _Session()
settings = SimpleNamespace(
TRIAL_ENABLED=True,
TRIAL_DURATION_DAYS=7,
TRIAL_TRAFFIC_LIMIT_GB=10,
TRIAL_WITHOUT_TELEGRAM_ENABLED=False,
DISPOSABLE_EMAIL_DOMAINS="",
LOG_TRIAL_ACTIVATIONS=False,
)
db_user = SimpleNamespace(
user_id=42,
telegram_id=None,
is_banned=False,
email="email-only@example.com",
)
subscription_service = SimpleNamespace(activate_trial_subscription=AsyncMock())
request = SimpleNamespace(
app={
"settings": settings,
"async_session_factory": _SessionFactory(session),
"subscription_service": subscription_service,
}
)
with (
patch.object(billing_module, "_require_user_id", return_value=42),
patch.object(
billing_module,
"_enforce_webapp_rate_limit",
AsyncMock(return_value=None),
),
patch.object(
billing_module.user_dal,
"get_user_by_id",
AsyncMock(return_value=db_user),
),
):
response = await billing_module.activate_trial_route(request)
payload = json.loads(response.text)
self.assertEqual(response.status, 400)
self.assertEqual(payload["error"], "trial_telegram_required")
self.assertEqual(payload["message"], "telegram_required")
subscription_service.activate_trial_subscription.assert_not_awaited()
async def test_disposable_email_trial_activation_requires_telegram(self):
session = _Session()
settings = SimpleNamespace(
TRIAL_ENABLED=True,
TRIAL_DURATION_DAYS=7,
TRIAL_TRAFFIC_LIMIT_GB=10,
TRIAL_WITHOUT_TELEGRAM_ENABLED=True,
DISPOSABLE_EMAIL_DOMAINS="mailinator.com,temp-mail.org",
LOG_TRIAL_ACTIVATIONS=False,
)
db_user = SimpleNamespace(
user_id=42,
telegram_id=None,
is_banned=False,
email="person@mailinator.com",
)
subscription_service = SimpleNamespace(activate_trial_subscription=AsyncMock())
request = SimpleNamespace(
app={
"settings": settings,
"async_session_factory": _SessionFactory(session),
"subscription_service": subscription_service,
}
)
with (
patch.object(billing_module, "_require_user_id", return_value=42),
patch.object(
billing_module,
"_enforce_webapp_rate_limit",
AsyncMock(return_value=None),
),
patch.object(
billing_module.user_dal,
"get_user_by_id",
AsyncMock(return_value=db_user),
),
):
response = await billing_module.activate_trial_route(request)
payload = json.loads(response.text)
self.assertEqual(response.status, 400)
self.assertEqual(payload["error"], "trial_telegram_required")
self.assertEqual(payload["message"], "disposable_email")
subscription_service.activate_trial_subscription.assert_not_awaited()