db: add composite indexes and merge-user optimizations
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import unittest
|
||||
from datetime import datetime, timezone
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from sqlalchemy.dialects import postgresql
|
||||
from sqlalchemy.sql.dml import Delete, Update
|
||||
|
||||
from db.dal import user_dal
|
||||
|
||||
|
||||
class FakeResult:
|
||||
def __init__(self, scalar_value=None, rowcount=1):
|
||||
self._scalar_value = scalar_value
|
||||
self.rowcount = rowcount
|
||||
|
||||
def scalar_one_or_none(self):
|
||||
return self._scalar_value
|
||||
|
||||
def scalars(self):
|
||||
return self
|
||||
|
||||
def all(self):
|
||||
if self._scalar_value is None:
|
||||
return []
|
||||
if isinstance(self._scalar_value, list):
|
||||
return self._scalar_value
|
||||
return [self._scalar_value]
|
||||
|
||||
|
||||
class UserDalMergeTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_get_user_ids_without_active_subscription_uses_left_join_null_check(self):
|
||||
session = SimpleNamespace(
|
||||
execute=AsyncMock(return_value=FakeResult([2, 3])),
|
||||
)
|
||||
|
||||
result = await user_dal.get_user_ids_without_active_subscription(session)
|
||||
|
||||
self.assertEqual(result, [2, 3])
|
||||
stmt = session.execute.await_args.args[0]
|
||||
sql = str(
|
||||
stmt.compile(
|
||||
dialect=postgresql.dialect(),
|
||||
compile_kwargs={"literal_binds": True},
|
||||
)
|
||||
).upper()
|
||||
self.assertIn("LEFT OUTER JOIN", sql)
|
||||
self.assertIn("IS NULL", sql)
|
||||
|
||||
async def test_merge_users_uses_bulk_updates_for_related_tables(self):
|
||||
source = SimpleNamespace(
|
||||
user_id=1,
|
||||
email="source@example.com",
|
||||
telegram_id=111,
|
||||
panel_user_uuid="panel-source",
|
||||
email_verified_at=datetime.now(timezone.utc),
|
||||
username="source-user",
|
||||
first_name="Source",
|
||||
last_name="User",
|
||||
language_code="ru",
|
||||
telegram_photo_url="https://example.com/source.jpg",
|
||||
channel_subscription_verified=True,
|
||||
channel_subscription_checked_at=datetime.now(timezone.utc),
|
||||
channel_subscription_verified_for=1,
|
||||
lifetime_used_traffic_bytes=512,
|
||||
referred_by_id=999,
|
||||
referral_code="SRC123",
|
||||
)
|
||||
target = SimpleNamespace(
|
||||
user_id=2,
|
||||
email=None,
|
||||
telegram_id=None,
|
||||
panel_user_uuid=None,
|
||||
email_verified_at=None,
|
||||
username=None,
|
||||
first_name=None,
|
||||
last_name=None,
|
||||
language_code=None,
|
||||
telegram_photo_url=None,
|
||||
channel_subscription_verified=False,
|
||||
channel_subscription_checked_at=None,
|
||||
channel_subscription_verified_for=None,
|
||||
lifetime_used_traffic_bytes=128,
|
||||
referred_by_id=None,
|
||||
referral_code=None,
|
||||
)
|
||||
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
|
||||
|
||||
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", return_value=None),
|
||||
patch("db.dal.user_dal._get_latest_subscription_for_user", return_value=None),
|
||||
):
|
||||
merged = await user_dal.merge_users(
|
||||
session,
|
||||
source_user_id=source.user_id,
|
||||
target_user_id=target.user_id,
|
||||
)
|
||||
|
||||
self.assertIs(merged, target)
|
||||
|
||||
update_tables = []
|
||||
delete_tables = []
|
||||
for call in session.execute.await_args_list:
|
||||
stmt = call.args[0]
|
||||
if isinstance(stmt, Update):
|
||||
update_tables.append(stmt.table.name)
|
||||
elif isinstance(stmt, Delete):
|
||||
delete_tables.append(stmt.table.name)
|
||||
|
||||
self.assertIn("user_billing", update_tables)
|
||||
self.assertIn("ad_attributions", update_tables)
|
||||
self.assertIn("subscriptions", update_tables)
|
||||
self.assertIn("payments", update_tables)
|
||||
self.assertIn("promo_code_activations", update_tables)
|
||||
self.assertIn("user_payment_methods", update_tables)
|
||||
self.assertIn("message_logs", update_tables)
|
||||
self.assertIn("users", update_tables)
|
||||
self.assertIn("user_payment_methods", delete_tables)
|
||||
self.assertIn("promo_code_activations", delete_tables)
|
||||
session.delete.assert_awaited_once_with(source)
|
||||
Reference in New Issue
Block a user