fix: reset trial eligibility from web admin
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import json
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from bot.app.web.admin_api_impl import users as admin_users
|
||||
|
||||
|
||||
class FakeSession:
|
||||
def __init__(self):
|
||||
self.committed = False
|
||||
self.rolled_back = False
|
||||
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
return False
|
||||
|
||||
async def commit(self):
|
||||
self.committed = True
|
||||
|
||||
async def rollback(self):
|
||||
self.rolled_back = True
|
||||
|
||||
|
||||
class AdminUserResetTrialRouteTests(unittest.IsolatedAsyncioTestCase):
|
||||
def _request(self, session: FakeSession):
|
||||
return SimpleNamespace(
|
||||
app={
|
||||
"settings": SimpleNamespace(),
|
||||
"async_session_factory": lambda: session,
|
||||
},
|
||||
match_info={"user_id": "42"},
|
||||
)
|
||||
|
||||
async def test_marks_trial_reset_without_deleting_subscription_history(self):
|
||||
session = FakeSession()
|
||||
request = self._request(session)
|
||||
user = SimpleNamespace(user_id=42)
|
||||
|
||||
with (
|
||||
patch.object(admin_users, "_require_admin_user_id", return_value=100),
|
||||
patch.object(admin_users.user_dal, "get_user_by_id", AsyncMock(return_value=user)),
|
||||
patch.object(
|
||||
admin_users.user_dal,
|
||||
"mark_trial_eligibility_reset",
|
||||
AsyncMock(return_value=object()),
|
||||
) as mark_reset,
|
||||
patch.object(
|
||||
admin_users.subscription_dal,
|
||||
"delete_all_user_subscriptions",
|
||||
AsyncMock(),
|
||||
) as delete_all,
|
||||
patch.object(
|
||||
admin_users.message_log_dal, "create_message_log_no_commit", AsyncMock()
|
||||
) as log,
|
||||
patch.object(
|
||||
admin_users, "_invalidate_after_admin_user_mutation", AsyncMock()
|
||||
) as invalidate,
|
||||
):
|
||||
response = await admin_users.admin_user_reset_trial_route(request)
|
||||
|
||||
self.assertEqual(response.status, 200)
|
||||
self.assertEqual(json.loads(response.text)["ok"], True)
|
||||
mark_reset.assert_awaited_once_with(session, 42)
|
||||
delete_all.assert_not_awaited()
|
||||
log_payload = log.await_args.args[1]
|
||||
self.assertEqual(log_payload["event_type"], "admin_reset_trial_webapp")
|
||||
self.assertEqual(log_payload["target_user_id"], 42)
|
||||
invalidate.assert_awaited_once()
|
||||
self.assertTrue(session.committed)
|
||||
self.assertFalse(session.rolled_back)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -202,7 +202,7 @@ class SubscriptionServiceActivationDispatchTests(unittest.IsolatedAsyncioTestCas
|
||||
TRIAL_SQUAD_UUIDS="trial-squad",
|
||||
)
|
||||
service = _make_service(settings)
|
||||
service.has_had_any_subscription = AsyncMock(return_value=False)
|
||||
service.has_trial_blocking_subscription = AsyncMock(return_value=False)
|
||||
service._get_or_create_panel_user_link_details = AsyncMock(
|
||||
return_value=("panel-user", "panel-sub", "short", True)
|
||||
)
|
||||
@@ -255,7 +255,7 @@ class SubscriptionServiceActivationDispatchTests(unittest.IsolatedAsyncioTestCas
|
||||
TRIAL_SQUAD_UUIDS=" , ",
|
||||
)
|
||||
service = _make_service(settings)
|
||||
service.has_had_any_subscription = AsyncMock(return_value=False)
|
||||
service.has_trial_blocking_subscription = AsyncMock(return_value=False)
|
||||
service._get_or_create_panel_user_link_details = AsyncMock(
|
||||
return_value=("panel-user", "panel-sub", "short", True)
|
||||
)
|
||||
@@ -292,6 +292,47 @@ class SubscriptionServiceActivationDispatchTests(unittest.IsolatedAsyncioTestCas
|
||||
panel_payload = service.panel_service.update_user_details_on_panel.await_args.args[1]
|
||||
self.assertEqual(panel_payload["activeInternalSquads"], ["fallback-a", "fallback-b"])
|
||||
|
||||
async def test_activate_trial_rejects_users_with_blocking_subscription_history(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(
|
||||
_tariffs_config_payload(),
|
||||
tmpdir,
|
||||
TRIAL_ENABLED=True,
|
||||
TRIAL_DURATION_DAYS=3,
|
||||
)
|
||||
service = _make_service(settings)
|
||||
service.has_trial_blocking_subscription = AsyncMock(return_value=True)
|
||||
service._get_or_create_panel_user_link_details = AsyncMock()
|
||||
service.panel_service.update_user_details_on_panel = AsyncMock()
|
||||
session = AsyncMock()
|
||||
db_user = SimpleNamespace(
|
||||
user_id=42,
|
||||
telegram_id=42,
|
||||
email=None,
|
||||
username="trial-user",
|
||||
first_name="Trial",
|
||||
last_name="User",
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.trial.user_dal.get_user_by_id",
|
||||
AsyncMock(return_value=db_user),
|
||||
),
|
||||
patch(
|
||||
"bot.services.subscription_service_impl.trial.subscription_dal.upsert_subscription",
|
||||
AsyncMock(),
|
||||
) as upsert_subscription,
|
||||
):
|
||||
result = await service.activate_trial_subscription(session, user_id=42)
|
||||
|
||||
self.assertFalse(result["activated"])
|
||||
self.assertFalse(result["eligible"])
|
||||
self.assertEqual(result["message_key"], "trial_already_had_subscription_or_trial")
|
||||
service._get_or_create_panel_user_link_details.assert_not_awaited()
|
||||
service.panel_service.update_user_details_on_panel.assert_not_awaited()
|
||||
upsert_subscription.assert_not_awaited()
|
||||
|
||||
async def test_activate_subscription_dispatches_traffic_sale_mode(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
settings = _make_settings(_tariffs_config_payload(), tmpdir)
|
||||
|
||||
@@ -29,3 +29,13 @@ def test_support_models_expose_expected_tables():
|
||||
|
||||
def test_user_model_tracks_lifetime_traffic_sync_timestamp():
|
||||
assert "lifetime_used_traffic_synced_at" in User.__table__.columns
|
||||
|
||||
|
||||
def test_trial_eligibility_reset_migration_and_model_are_registered():
|
||||
ids = [migration.id for migration in MIGRATIONS]
|
||||
|
||||
assert "0033_add_trial_eligibility_reset_marker" in ids
|
||||
assert ids.index("0033_add_trial_eligibility_reset_marker") > ids.index(
|
||||
"0032_add_telegram_notification_status"
|
||||
)
|
||||
assert "trial_eligibility_reset_at" in User.__table__.columns
|
||||
|
||||
+39
-1
@@ -6,7 +6,7 @@ from unittest.mock import AsyncMock, patch
|
||||
from sqlalchemy.dialects import postgresql
|
||||
from sqlalchemy.sql.dml import Delete, Update
|
||||
|
||||
from db.dal import user_dal
|
||||
from db.dal import subscription_dal, user_dal
|
||||
|
||||
|
||||
class FakeResult:
|
||||
@@ -122,6 +122,44 @@ class UserDalReferralTests(unittest.IsolatedAsyncioTestCase):
|
||||
|
||||
self.assertIs(result, referrer)
|
||||
|
||||
async def test_mark_trial_eligibility_reset_updates_user_marker(self):
|
||||
reset_at = datetime(2026, 6, 1, tzinfo=timezone.utc)
|
||||
session = SimpleNamespace(execute=AsyncMock(return_value=FakeResult(rowcount=1)))
|
||||
|
||||
result = await user_dal.mark_trial_eligibility_reset(session, 42, reset_at=reset_at)
|
||||
|
||||
self.assertEqual(result, reset_at)
|
||||
stmt = session.execute.await_args.args[0]
|
||||
sql = str(
|
||||
stmt.compile(
|
||||
dialect=postgresql.dialect(),
|
||||
compile_kwargs={"literal_binds": True},
|
||||
)
|
||||
).upper()
|
||||
self.assertIn("UPDATE USERS", sql)
|
||||
self.assertIn("TRIAL_ELIGIBILITY_RESET_AT", sql)
|
||||
self.assertIn("USER_ID = 42", sql)
|
||||
|
||||
|
||||
class SubscriptionDalTrialEligibilityTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_trial_blocking_history_honors_user_reset_marker(self):
|
||||
session = SimpleNamespace(execute=AsyncMock(return_value=FakeResult(7)))
|
||||
|
||||
result = await subscription_dal.has_trial_blocking_subscription_for_user(session, 42)
|
||||
|
||||
self.assertTrue(result)
|
||||
stmt = session.execute.await_args.args[0]
|
||||
sql = str(
|
||||
stmt.compile(
|
||||
dialect=postgresql.dialect(),
|
||||
compile_kwargs={"literal_binds": True},
|
||||
)
|
||||
).upper()
|
||||
self.assertIn("TRIAL_ELIGIBILITY_RESET_AT", sql)
|
||||
self.assertIn("SUBSCRIPTIONS.IS_ACTIVE = TRUE", sql)
|
||||
self.assertIn("COALESCE(SUBSCRIPTIONS.START_DATE, SUBSCRIPTIONS.END_DATE)", sql)
|
||||
self.assertIn("SUBSCRIPTIONS.USER_ID = 42", sql)
|
||||
|
||||
|
||||
class UserDalMergeTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_get_panel_user_uuids_for_user_includes_subscription_fallbacks_once(self):
|
||||
|
||||
Reference in New Issue
Block a user