refactor: improve startup and sync performance

This commit is contained in:
3252a8
2026-05-20 23:04:29 +03:00
parent 8192eaf55b
commit a7f298743d
12 changed files with 973 additions and 97 deletions
+59
View File
@@ -0,0 +1,59 @@
import unittest
from types import SimpleNamespace
from unittest.mock import AsyncMock, patch
import bot.app.web.subscription_webapp # noqa: F401
from bot.app.web.admin_api_impl import stats as stats_module
class AdminPanelStatsCacheTests(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self):
stats_module._ADMIN_PANEL_STATS_CACHES.clear()
async def asyncTearDown(self):
stats_module._ADMIN_PANEL_STATS_CACHES.clear()
def _settings(self):
return SimpleNamespace(
ADMIN_PANEL_STATS_CACHE_TTL_SECONDS=15,
REDIS_URL="redis://redis:6379/0",
REDIS_KEY_PREFIX="shop",
)
def _panel_service(self):
return SimpleNamespace(
get_system_stats=AsyncMock(return_value={"users": {"totalUsers": 10}}),
get_bandwidth_stats=AsyncMock(return_value={"current": 123}),
get_nodes_statistics=AsyncMock(return_value={"nodes": []}),
get_nodes_bandwidth_usage=AsyncMock(return_value={"topNodes": []}),
get_nodes_online_lookups=AsyncMock(return_value={"byUuid": {}, "byName": {}}),
)
async def test_admin_panel_stats_are_cached_between_requests(self):
settings = self._settings()
panel_service = self._panel_service()
cache_store = {}
async def fake_get(_settings, key):
return cache_store.get(key)
async def fake_set(_settings, key, value, ttl):
cache_store[key] = value
with (
patch("bot.infra.redis.cache_get_json", fake_get),
patch("bot.infra.redis.cache_set_json", fake_set),
):
first = await stats_module._load_admin_panel_stats(None, settings, panel_service)
second = await stats_module._load_admin_panel_stats(None, settings, panel_service)
self.assertEqual(first, second)
panel_service.get_system_stats.assert_awaited_once()
panel_service.get_bandwidth_stats.assert_awaited_once()
panel_service.get_nodes_statistics.assert_awaited_once()
panel_service.get_nodes_bandwidth_usage.assert_awaited_once()
panel_service.get_nodes_online_lookups.assert_awaited_once()
if __name__ == "__main__":
unittest.main()
+71
View File
@@ -0,0 +1,71 @@
from datetime import datetime, timedelta, timezone
from bot.handlers.admin.sync_admin import (
_coerce_panel_telegram_id,
_description_matches,
_subscription_update_delta,
)
from db.models import Subscription
def test_description_match_ignores_whitespace_shape():
assert _description_matches("email@example.com username", "email@example.com\nusername")
def test_panel_telegram_id_is_coerced_to_int():
assert _coerce_panel_telegram_id("12345") == 12345
assert _coerce_panel_telegram_id("") is None
def test_subscription_update_delta_skips_unchanged_fields():
end_date = datetime(2026, 5, 20, 12, 0, tzinfo=timezone.utc)
subscription = Subscription(
user_id=1,
panel_user_uuid="panel-1",
panel_subscription_uuid="sub-1",
end_date=end_date,
is_active=True,
status_from_panel="ACTIVE",
)
assert (
_subscription_update_delta(
subscription,
{
"user_id": 1,
"panel_user_uuid": "panel-1",
"end_date": end_date + timedelta(milliseconds=500),
"is_active": True,
"status_from_panel": "ACTIVE",
},
)
== {}
)
def test_subscription_update_delta_returns_only_changed_fields():
end_date = datetime(2026, 5, 20, 12, 0, tzinfo=timezone.utc)
subscription = Subscription(
user_id=1,
panel_user_uuid="panel-1",
panel_subscription_uuid="sub-1",
end_date=end_date,
is_active=True,
status_from_panel="ACTIVE",
)
assert _subscription_update_delta(
subscription,
{
"user_id": 2,
"panel_user_uuid": "panel-1",
"end_date": end_date + timedelta(seconds=2),
"is_active": False,
"status_from_panel": "EXPIRED",
},
) == {
"user_id": 2,
"end_date": end_date + timedelta(seconds=2),
"is_active": False,
"status_from_panel": "EXPIRED",
}
+60
View File
@@ -1,3 +1,4 @@
import asyncio
import unittest
from types import SimpleNamespace
from unittest.mock import AsyncMock, patch
@@ -46,6 +47,65 @@ class PanelApiServiceLoggingTests(unittest.IsolatedAsyncioTestCase):
self.assertTrue(service._request.await_args.kwargs["log_full_response"])
async def test_get_user_by_uuid_uses_short_ttl_cache_and_update_invalidates(self):
service = self._make_service()
service._request = AsyncMock(return_value={"response": {"uuid": "user-uuid"}})
first = await service.get_user_by_uuid("user-uuid")
second = await service.get_user_by_uuid("user-uuid")
self.assertEqual(first, {"uuid": "user-uuid"})
self.assertEqual(second, {"uuid": "user-uuid"})
self.assertEqual(service._request.await_count, 1)
await service.update_user_details_on_panel("user-uuid", {"description": "updated"})
await service.get_user_by_uuid("user-uuid")
self.assertEqual(service._request.await_count, 3)
async def test_get_user_devices_uses_short_ttl_cache_and_disconnect_invalidates(self):
service = self._make_service()
service._request = AsyncMock(return_value={"response": [{"hwid": "device-1"}]})
first = await service.get_user_devices("user-uuid")
second = await service.get_user_devices("user-uuid")
self.assertEqual(first, [{"hwid": "device-1"}])
self.assertEqual(second, [{"hwid": "device-1"}])
self.assertEqual(service._request.await_count, 1)
await service.disconnect_device("user-uuid", "device-1")
await service.get_user_devices("user-uuid")
self.assertEqual(service._request.await_count, 3)
async def test_get_all_panel_users_uses_singleflight_cache_and_update_invalidates(self):
service = self._make_service()
get_calls = 0
async def fake_request(method, endpoint, **kwargs):
nonlocal get_calls
if method == "GET":
get_calls += 1
return {"response": {"users": [{"uuid": "user-uuid"}]}}
return {"response": {"uuid": "user-uuid"}}
service._request = AsyncMock(side_effect=fake_request)
first, second = await asyncio.gather(
service.get_all_panel_users(),
service.get_all_panel_users(),
)
self.assertEqual(first, [{"uuid": "user-uuid"}])
self.assertEqual(second, [{"uuid": "user-uuid"}])
self.assertEqual(get_calls, 1)
await service.update_user_details_on_panel("user-uuid", {"description": "updated"})
await service.get_all_panel_users()
self.assertEqual(get_calls, 2)
if __name__ == "__main__":
unittest.main()
+74
View File
@@ -0,0 +1,74 @@
import unittest
from types import SimpleNamespace
from unittest.mock import AsyncMock, patch
from bot.middlewares import profile_sync as profile_sync_module
from bot.middlewares.profile_sync import ProfileSyncMiddleware
class ProfileSyncMiddlewareCacheTests(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self):
profile_sync_module._LOCAL_PROFILE_SYNC_CHECKS.clear()
async def asyncTearDown(self):
profile_sync_module._LOCAL_PROFILE_SYNC_CHECKS.clear()
def _settings(self):
return SimpleNamespace(
PROFILE_SYNC_CACHE_TTL_SECONDS=900,
REDIS_URL="redis://redis:6379/0",
REDIS_KEY_PREFIX="shop",
)
async def test_profile_sync_skips_repeated_user_checks_inside_ttl(self):
middleware = ProfileSyncMiddleware()
handler = AsyncMock(return_value="ok")
event = SimpleNamespace()
tg_user = SimpleNamespace(
id=42,
username="alice",
first_name="Alice",
last_name="Smith",
)
db_user = SimpleNamespace(
user_id=42,
telegram_id=42,
username="alice",
first_name="Alice",
last_name="Smith",
email=None,
panel_user_uuid=None,
)
cache_store = {}
async def fake_get(_settings, key):
return cache_store.get(key)
async def fake_set(_settings, key, value, ttl):
cache_store[key] = value
data = {
"session": AsyncMock(),
"event_from_user": tg_user,
"settings": self._settings(),
}
with (
patch.object(profile_sync_module, "cache_get_json", fake_get),
patch.object(profile_sync_module, "cache_set_json", fake_set),
patch.object(
profile_sync_module.user_dal,
"get_user_by_telegram_id",
AsyncMock(return_value=db_user),
) as get_user,
):
first = await middleware(handler, event, data)
second = await middleware(handler, event, data)
self.assertEqual(first, "ok")
self.assertEqual(second, "ok")
get_user.assert_awaited_once()
self.assertEqual(handler.await_count, 2)
if __name__ == "__main__":
unittest.main()