Files
remnawave-minishop/tests/test_panel_api_service_logging.py
T

244 lines
9.2 KiB
Python

import asyncio
import unittest
from types import SimpleNamespace
from unittest.mock import AsyncMock, patch
from bot.services.panel_api_service import PanelApiService
class PanelApiServiceLoggingTests(unittest.IsolatedAsyncioTestCase):
def _make_service(self) -> PanelApiService:
return PanelApiService(
SimpleNamespace(
PANEL_API_URL="https://panel.example.test/api",
PANEL_API_KEY="panel-key",
USER_HWID_DEVICE_LIMIT=None,
)
)
async def test_update_user_details_does_not_log_full_response_by_default(self):
service = self._make_service()
service._request = AsyncMock(return_value={"response": {"uuid": "user-uuid"}})
with patch("bot.services.panel_api_service.logging.info") as info_log:
result = await service.update_user_details_on_panel(
"user-uuid",
{"description": "profile"},
)
self.assertEqual(result, {"uuid": "user-uuid"})
service._request.assert_awaited_once_with(
"PATCH",
"/users",
json={"description": "profile", "uuid": "user-uuid"},
log_full_response=False,
)
info_log.assert_not_called()
async def test_update_user_details_can_still_request_full_response_logging(self):
service = self._make_service()
service._request = AsyncMock(return_value={"response": {"uuid": "user-uuid"}})
await service.update_user_details_on_panel(
"user-uuid",
{"description": "profile"},
log_response=True,
)
self.assertTrue(service._request.await_args.kwargs["log_full_response"])
async def test_create_panel_user_omits_empty_description(self):
service = self._make_service()
service._request = AsyncMock(return_value={"response": {"uuid": "user-uuid"}})
await service.create_panel_user(
username_on_panel="tg_42",
telegram_id=42,
description="",
)
payload = service._request.await_args.kwargs["json"]
self.assertNotIn("description", payload)
self.assertEqual(payload["telegramId"], 42)
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_by_uuid_lookup_returns_success_payload(self):
service = self._make_service()
service._request = AsyncMock(return_value={"response": {"uuid": "user-uuid"}})
result = await service.get_user_by_uuid_lookup("user-uuid")
self.assertTrue(result["ok"])
self.assertFalse(result["not_found"])
self.assertIsNone(result["failure_reason"])
self.assertEqual(result["user"], {"uuid": "user-uuid"})
service._request.assert_awaited_once_with(
"GET",
"/users/user-uuid",
log_full_response=False,
)
async def test_get_user_by_uuid_lookup_keeps_transient_errors_separate_from_not_found(self):
service = self._make_service()
transient_response = {
"error": True,
"status_code": -1,
"message": "Connection error",
}
service._request = AsyncMock(return_value=transient_response)
result = await service.get_user_by_uuid_lookup("user-uuid")
self.assertFalse(result["ok"])
self.assertFalse(result["not_found"])
self.assertIsNone(result["user"])
self.assertIn("classification=panel_lookup_failed", result["failure_reason"])
self.assertIn("status_code=-1", result["failure_reason"])
self.assertIn("message=Connection error", result["failure_reason"])
self.assertEqual(result["response"], transient_response)
async def test_get_user_by_uuid_lookup_marks_confirmed_not_found(self):
service = self._make_service()
cases = [
{"error": True, "status_code": 404},
{"error": True, "status_code": 400, "details": {"errorCode": "A062"}},
]
for response in cases:
with self.subTest(response=response):
service._request = AsyncMock(return_value=response)
result = await service.get_user_by_uuid_lookup("missing-user")
self.assertFalse(result["ok"])
self.assertTrue(result["not_found"])
self.assertIsNone(result["user"])
self.assertIn("classification=confirmed_not_found", result["failure_reason"])
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_subscription_page_config_by_short_uuid_uses_panel_endpoint(self):
service = self._make_service()
panel_payload = {"config": {"version": "1"}}
service._request = AsyncMock(return_value={"response": panel_payload})
result = await service.get_subscription_page_config_by_short_uuid(
"short-uuid",
request_headers={"user-agent": "Mozilla/5.0"},
)
self.assertEqual(result, panel_payload)
service._request.assert_awaited_once_with(
"GET",
"/subscriptions/subpage-config/short-uuid",
json={"requestHeaders": {"user-agent": "Mozilla/5.0"}},
log_full_response=False,
)
async def test_get_subscription_page_config_list_uses_panel_endpoint(self):
service = self._make_service()
panel_payload = {"configs": [{"uuid": "default"}]}
service._request = AsyncMock(return_value={"response": panel_payload})
result = await service.get_subscription_page_config_list()
self.assertEqual(result, panel_payload)
service._request.assert_awaited_once_with(
"GET",
"/subscription-page-configs",
log_full_response=False,
)
async def test_get_subscription_page_config_by_uuid_uses_panel_endpoint(self):
service = self._make_service()
panel_payload = {"uuid": "default", "config": {"version": "1"}}
service._request = AsyncMock(return_value={"response": panel_payload})
result = await service.get_subscription_page_config_by_uuid("default")
self.assertEqual(result, panel_payload)
service._request.assert_awaited_once_with(
"GET",
"/subscription-page-configs/default",
log_full_response=False,
)
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)
async def test_get_all_panel_users_falls_back_to_100_when_large_page_fails(self):
service = self._make_service()
requested_sizes = []
async def fake_request(method, endpoint, **kwargs):
params = kwargs.get("params") or {}
size = params.get("size")
requested_sizes.append(size)
if size == 1000:
return {"error": True, "status_code": 400}
return {"response": {"users": [{"uuid": "user-uuid"}]}}
service._request = AsyncMock(side_effect=fake_request)
users = await service.get_all_panel_users()
self.assertEqual(users, [{"uuid": "user-uuid"}])
self.assertEqual(requested_sizes, [1000, 100])
if __name__ == "__main__":
unittest.main()