fix: merge active sub email account with expired sub telegram account

This commit is contained in:
3252a8
2026-05-22 18:23:38 +03:00
parent 33a7dbc0e6
commit c4c5b8e3a0
3 changed files with 149 additions and 1 deletions
+4
View File
@@ -1042,7 +1042,11 @@ async def _sync_panel_identity_for_user(
if user.email: if user.email:
payload["email"] = user.email payload["email"] = user.email
if expire_at is not None: if expire_at is not None:
if expire_at.tzinfo is None:
expire_at = expire_at.replace(tzinfo=timezone.utc)
payload["expireAt"] = expire_at.isoformat(timespec="milliseconds").replace("+00:00", "Z") payload["expireAt"] = expire_at.isoformat(timespec="milliseconds").replace("+00:00", "Z")
if expire_at > datetime.now(timezone.utc):
payload["status"] = "ACTIVE"
try: try:
await subscription_service.panel_service.update_user_details_on_panel( await subscription_service.panel_service.update_user_details_on_panel(
+38
View File
@@ -1,5 +1,6 @@
import json import json
import unittest import unittest
from datetime import datetime, timedelta, timezone
from types import SimpleNamespace from types import SimpleNamespace
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
@@ -76,6 +77,43 @@ class AccountLinkingPanelTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual(payload["email"], "linked@example.com") self.assertEqual(payload["email"], "linked@example.com")
self.assertEqual(payload["telegramId"], 42) self.assertEqual(payload["telegramId"], 42)
async def test_merged_panel_identity_reactivates_expired_target_with_transferred_time(self):
expire_at = datetime.now(timezone.utc) + timedelta(days=30)
panel_service = SimpleNamespace(
delete_user_from_panel=AsyncMock(return_value=True),
update_user_details_on_panel=AsyncMock(return_value={"uuid": "panel-target"}),
)
request = SimpleNamespace(
app={"subscription_service": SimpleNamespace(panel_service=panel_service)}
)
user = SimpleNamespace(
user_id=42,
panel_user_uuid="panel-target",
telegram_id=42,
email="linked@example.com",
username="alice",
first_name="Alice",
last_name=None,
)
result = await _sync_merged_panel_identity_for_user(
request,
user,
source_panel_uuid="panel-email",
final_panel_uuid="panel-target",
expire_at=expire_at,
)
self.assertTrue(result)
panel_service.delete_user_from_panel.assert_awaited_once_with(
"panel-email",
log_response=False,
)
_, payload = panel_service.update_user_details_on_panel.await_args.args[:2]
expected_expire_at = expire_at.isoformat(timespec="milliseconds").replace("+00:00", "Z")
self.assertEqual(payload["expireAt"], expected_expire_at)
self.assertEqual(payload["status"], "ACTIVE")
async def test_telegram_merge_defers_panel_sync_until_source_cleanup(self): async def test_telegram_merge_defers_panel_sync_until_source_cleanup(self):
current_user = SimpleNamespace( current_user = SimpleNamespace(
user_id=-100, user_id=-100,
+107 -1
View File
@@ -1,5 +1,5 @@
import unittest import unittest
from datetime import datetime, timezone from datetime import datetime, timedelta, timezone
from types import SimpleNamespace from types import SimpleNamespace
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
@@ -131,3 +131,109 @@ class UserDalMergeTests(unittest.IsolatedAsyncioTestCase):
self.assertIn("user_payment_methods", delete_tables) self.assertIn("user_payment_methods", delete_tables)
self.assertIn("promo_code_activations", delete_tables) self.assertIn("promo_code_activations", delete_tables)
session.delete.assert_awaited_once_with(source) session.delete.assert_awaited_once_with(source)
async def test_merge_users_moves_active_email_subscription_onto_expired_telegram_account(self):
before = datetime.now(timezone.utc)
source = SimpleNamespace(
user_id=-100,
email="paid@example.com",
telegram_id=None,
panel_user_uuid="panel-email",
email_verified_at=before,
username=None,
first_name=None,
last_name=None,
language_code="ru",
telegram_photo_url=None,
channel_subscription_verified=False,
channel_subscription_checked_at=None,
channel_subscription_verified_for=None,
lifetime_used_traffic_bytes=0,
referred_by_id=None,
referral_code=None,
)
target = SimpleNamespace(
user_id=42,
email=None,
telegram_id=42,
panel_user_uuid="panel-telegram",
email_verified_at=None,
username="old",
first_name=None,
last_name=None,
language_code="ru",
telegram_photo_url=None,
channel_subscription_verified=False,
channel_subscription_checked_at=None,
channel_subscription_verified_for=None,
lifetime_used_traffic_bytes=0,
referred_by_id=None,
referral_code=None,
)
source_active_sub = SimpleNamespace(
end_date=before + timedelta(days=30),
is_active=True,
skip_notifications=False,
last_notification_sent=before,
status_from_panel="ACTIVE",
panel_user_uuid="panel-email",
)
expired_target_sub = SimpleNamespace(
end_date=before - timedelta(days=3),
is_active=False,
skip_notifications=False,
last_notification_sent=before,
status_from_panel="EXPIRED",
panel_user_uuid="panel-telegram",
)
session = SimpleNamespace(
execute=AsyncMock(side_effect=lambda stmt: FakeResult()),
delete=AsyncMock(),
flush=AsyncMock(),
refresh=AsyncMock(),
)
async def fake_get_user_by_id(_session, user_id):
if user_id == source.user_id:
return source
if user_id == target.user_id:
return target
return None
async def fake_get_active_subscription(_session, user_id, panel_user_uuid=None):
if user_id == source.user_id and panel_user_uuid == source.panel_user_uuid:
return source_active_sub
return None
async def fake_get_latest_subscription(_session, user_id, panel_user_uuid=None, **_kwargs):
if user_id == target.user_id and panel_user_uuid == target.panel_user_uuid:
return expired_target_sub
return None
with (
patch("db.dal.user_dal.get_user_by_id", side_effect=fake_get_user_by_id),
patch(
"db.dal.user_dal._get_active_subscription_for_user",
side_effect=fake_get_active_subscription,
),
patch(
"db.dal.user_dal._get_latest_subscription_for_user",
side_effect=fake_get_latest_subscription,
),
):
merged = await user_dal.merge_users(
session,
source_user_id=source.user_id,
target_user_id=target.user_id,
)
self.assertIs(merged, target)
self.assertEqual(target.email, "paid@example.com")
self.assertTrue(expired_target_sub.is_active)
self.assertEqual(expired_target_sub.status_from_panel, "ACTIVE_EXTENDED_BY_MERGE")
self.assertIsNone(expired_target_sub.last_notification_sent)
self.assertGreater(expired_target_sub.end_date, before + timedelta(days=29))
self.assertLess(expired_target_sub.end_date, before + timedelta(days=31))
self.assertFalse(source_active_sub.is_active)
self.assertTrue(source_active_sub.skip_notifications)
self.assertEqual(source_active_sub.status_from_panel, "MERGED_INTO_ACCOUNT")