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 AdminUserDeleteRouteTests(unittest.IsolatedAsyncioTestCase): def _request(self, session: FakeSession, app_overrides=None): app = { "settings": SimpleNamespace(), "async_session_factory": lambda: session, } app.update(app_overrides or {}) return SimpleNamespace( app=app, match_info={"user_id": "42"}, ) async def test_deletes_panel_users_before_bot_db_user(self): session = FakeSession() calls = [] async def delete_panel_user(panel_uuid, log_response=False): calls.append(("panel", panel_uuid, log_response)) return True async def delete_db_user(_session, user_id): calls.append(("db", user_id)) return True panel_service = SimpleNamespace( delete_user_from_panel=AsyncMock(side_effect=delete_panel_user), ) request = self._request(session, {"panel_service": panel_service}) user = SimpleNamespace(user_id=42, panel_user_uuid="panel-main") 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, "get_panel_user_uuids_for_user", AsyncMock(return_value=["panel-main", "panel-sub"]), ) as collect_uuids, patch.object( admin_users.user_dal, "delete_user_and_relations", AsyncMock(side_effect=delete_db_user), ), patch.object( admin_users.message_log_dal, "create_message_log_no_commit", AsyncMock(), ) as log_mock, patch.object(admin_users, "_invalidate_after_admin_user_mutation", AsyncMock()), ): response = await admin_users.admin_user_delete_route(request) self.assertEqual(response.status, 200) self.assertEqual(json.loads(response.text)["ok"], True) self.assertEqual( calls, [ ("panel", "panel-main", False), ("panel", "panel-sub", False), ("db", 42), ], ) collect_uuids.assert_awaited_once_with(session, 42, user=user) log_payload = log_mock.await_args.args[1] self.assertEqual(log_payload["event_type"], "admin_delete_user_webapp") self.assertIn("panel-main,panel-sub", log_payload["content"]) self.assertTrue(session.committed) self.assertFalse(session.rolled_back) async def test_aborts_when_panel_service_is_unavailable_for_panel_user(self): session = FakeSession() request = self._request(session) user = SimpleNamespace(user_id=42, panel_user_uuid="panel-main") 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, "get_panel_user_uuids_for_user", AsyncMock(return_value=["panel-main"]), ), patch.object( admin_users.user_dal, "delete_user_and_relations", AsyncMock(), ) as delete_db, ): response = await admin_users.admin_user_delete_route(request) self.assertEqual(response.status, 503) self.assertEqual(json.loads(response.text)["error"], "panel_service_unavailable") delete_db.assert_not_awaited() self.assertTrue(session.rolled_back) self.assertFalse(session.committed) async def test_aborts_when_panel_delete_fails(self): session = FakeSession() panel_service = SimpleNamespace( delete_user_from_panel=AsyncMock(return_value=False), ) request = self._request(session, {"panel_service": panel_service}) user = SimpleNamespace(user_id=42, panel_user_uuid="panel-main") 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, "get_panel_user_uuids_for_user", AsyncMock(return_value=["panel-main"]), ), patch.object( admin_users.user_dal, "delete_user_and_relations", AsyncMock(), ) as delete_db, ): response = await admin_users.admin_user_delete_route(request) self.assertEqual(response.status, 502) self.assertEqual(json.loads(response.text)["error"], "panel_delete_failed") panel_service.delete_user_from_panel.assert_awaited_once_with( "panel-main", log_response=False, ) delete_db.assert_not_awaited() self.assertTrue(session.rolled_back) self.assertFalse(session.committed)