288 lines
12 KiB
Python
288 lines
12 KiB
Python
"""
|
||
Тесты для helper_bot.handlers.admin.admin_handlers: хендлеры админ-панели с моками.
|
||
"""
|
||
|
||
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
||
import pytest
|
||
from aiogram import types
|
||
from aiogram.fsm.context import FSMContext
|
||
|
||
from helper_bot.handlers.admin.admin_handlers import (
|
||
admin_panel,
|
||
cancel_ban_process,
|
||
confirm_ban,
|
||
get_banned_users,
|
||
get_last_users,
|
||
get_ml_stats,
|
||
process_ban_duration,
|
||
process_ban_reason,
|
||
process_ban_target,
|
||
start_ban_process,
|
||
)
|
||
from helper_bot.handlers.admin.services import User as AdminUser
|
||
|
||
|
||
@pytest.mark.unit
|
||
@pytest.mark.asyncio
|
||
class TestAdminHandlers:
|
||
"""Тесты хендлеров admin_handlers с моками."""
|
||
|
||
@pytest.fixture
|
||
def mock_message(self):
|
||
"""Мок сообщения."""
|
||
msg = MagicMock(spec=types.Message)
|
||
msg.from_user = MagicMock()
|
||
msg.from_user.id = 123
|
||
msg.from_user.full_name = "Admin"
|
||
msg.text = "Бан по нику"
|
||
msg.answer = AsyncMock()
|
||
msg.reply = AsyncMock()
|
||
return msg
|
||
|
||
@pytest.fixture
|
||
def mock_state(self):
|
||
"""Мок FSMContext."""
|
||
state = MagicMock(spec=FSMContext)
|
||
state.set_state = AsyncMock()
|
||
state.get_state = AsyncMock(return_value="ADMIN")
|
||
state.get_data = AsyncMock(return_value={})
|
||
state.update_data = AsyncMock()
|
||
return state
|
||
|
||
@pytest.fixture
|
||
def mock_bot_db(self):
|
||
"""Мок БД."""
|
||
db = MagicMock()
|
||
db.get_last_users = AsyncMock(return_value=[("User One", 1), ("User Two", 2)])
|
||
db.get_banned_users_from_db = AsyncMock(return_value=[])
|
||
db.get_username = AsyncMock(return_value="user")
|
||
db.get_full_name_by_id = AsyncMock(return_value="Full Name")
|
||
return db
|
||
|
||
@patch("helper_bot.handlers.admin.admin_handlers.get_reply_keyboard_admin")
|
||
async def test_admin_panel_sets_state_and_answers(
|
||
self, mock_keyboard, mock_message, mock_state
|
||
):
|
||
"""admin_panel устанавливает состояние ADMIN и отправляет приветствие."""
|
||
mock_keyboard.return_value = MagicMock()
|
||
|
||
await admin_panel(mock_message, mock_state)
|
||
|
||
mock_state.set_state.assert_awaited_once_with("ADMIN")
|
||
mock_message.answer.assert_awaited_once()
|
||
assert (
|
||
"админк" in mock_message.answer.call_args[0][0].lower()
|
||
or "добро" in mock_message.answer.call_args[0][0].lower()
|
||
)
|
||
|
||
@patch(
|
||
"helper_bot.handlers.admin.admin_handlers.return_to_admin_menu",
|
||
new_callable=AsyncMock,
|
||
)
|
||
async def test_cancel_ban_process_returns_to_menu(
|
||
self, mock_return, mock_message, mock_state
|
||
):
|
||
"""cancel_ban_process вызывает return_to_admin_menu."""
|
||
mock_state.get_state = AsyncMock(return_value="AWAIT_BAN_TARGET")
|
||
|
||
await cancel_ban_process(mock_message, mock_state)
|
||
|
||
mock_return.assert_awaited_once_with(mock_message, mock_state)
|
||
|
||
@patch("helper_bot.handlers.admin.admin_handlers.create_keyboard_with_pagination")
|
||
@patch("helper_bot.handlers.admin.admin_handlers.AdminService")
|
||
async def test_get_last_users_answers_with_keyboard(
|
||
self, mock_service_cls, mock_keyboard, mock_message, mock_state, mock_bot_db
|
||
):
|
||
"""get_last_users получает пользователей и отправляет клавиатуру."""
|
||
mock_service = MagicMock()
|
||
mock_service.get_last_users = AsyncMock(
|
||
return_value=[
|
||
AdminUser(1, "u1", "User One"),
|
||
AdminUser(2, "u2", "User Two"),
|
||
]
|
||
)
|
||
mock_service_cls.return_value = mock_service
|
||
mock_keyboard.return_value = MagicMock()
|
||
|
||
await get_last_users(mock_message, mock_state, bot_db=mock_bot_db)
|
||
|
||
mock_service.get_last_users.assert_awaited_once()
|
||
mock_keyboard.assert_called_once()
|
||
mock_message.answer.assert_awaited_once()
|
||
assert (
|
||
"Список пользователей" in mock_message.answer.call_args[1]["text"]
|
||
or "пользователей" in mock_message.answer.call_args[1]["text"]
|
||
)
|
||
|
||
@patch("helper_bot.handlers.admin.admin_handlers.create_keyboard_with_pagination")
|
||
@patch("helper_bot.handlers.admin.admin_handlers.AdminService")
|
||
async def test_get_banned_users_empty_answers_no_list(
|
||
self, mock_service_cls, mock_keyboard, mock_message, mock_state, mock_bot_db
|
||
):
|
||
"""get_banned_users при пустом списке отправляет сообщение 'никого нет'."""
|
||
mock_service = MagicMock()
|
||
mock_service.get_banned_users_for_display = AsyncMock(
|
||
return_value=("Текст", [])
|
||
)
|
||
mock_service_cls.return_value = mock_service
|
||
|
||
await get_banned_users(mock_message, mock_state, bot_db=mock_bot_db)
|
||
|
||
mock_message.answer.assert_awaited_once()
|
||
assert (
|
||
"никого нет" in mock_message.answer.call_args[1]["text"]
|
||
or "заблокированных" in mock_message.answer.call_args[1]["text"]
|
||
)
|
||
|
||
@patch("helper_bot.handlers.admin.admin_handlers.get_global_instance")
|
||
async def test_get_ml_stats_disabled_answers_message(
|
||
self, mock_get_global, mock_message, mock_state
|
||
):
|
||
"""get_ml_stats при отключённом scoring_manager отправляет сообщение об отключении."""
|
||
mock_bdf = MagicMock()
|
||
mock_bdf.get_scoring_manager.return_value = None
|
||
mock_get_global.return_value = mock_bdf
|
||
|
||
await get_ml_stats(mock_message, mock_state)
|
||
|
||
mock_message.answer.assert_awaited_once()
|
||
assert (
|
||
"ML" in mock_message.answer.call_args[0][0]
|
||
or "RAG" in mock_message.answer.call_args[0][0]
|
||
or "отключен" in mock_message.answer.call_args[0][0].lower()
|
||
)
|
||
|
||
@patch("helper_bot.handlers.admin.admin_handlers.get_global_instance")
|
||
async def test_get_ml_stats_with_rag_and_deepseek(
|
||
self, mock_get_global, mock_message, mock_state
|
||
):
|
||
"""get_ml_stats при включённом scoring возвращает статистику."""
|
||
mock_scoring = MagicMock()
|
||
mock_scoring.get_stats = AsyncMock(
|
||
return_value={
|
||
"rag": {
|
||
"model_loaded": True,
|
||
"vector_store": {
|
||
"positive_count": 1,
|
||
"negative_count": 0,
|
||
"total_count": 1,
|
||
},
|
||
},
|
||
"deepseek": {"enabled": True, "model": "test", "timeout": 30},
|
||
}
|
||
)
|
||
mock_bdf = MagicMock()
|
||
mock_bdf.get_scoring_manager.return_value = mock_scoring
|
||
mock_get_global.return_value = mock_bdf
|
||
|
||
await get_ml_stats(mock_message, mock_state)
|
||
|
||
mock_message.answer.assert_awaited_once()
|
||
text = mock_message.answer.call_args[0][0]
|
||
assert "ML" in text or "RAG" in text or "DeepSeek" in text
|
||
|
||
async def test_start_ban_process_by_nick_sets_state_await_target(
|
||
self, mock_message, mock_state
|
||
):
|
||
"""start_ban_process при 'Бан по нику' устанавливает ban_type username и AWAIT_BAN_TARGET."""
|
||
mock_message.text = "Бан по нику"
|
||
|
||
await start_ban_process(mock_message, mock_state)
|
||
|
||
mock_state.update_data.assert_awaited_once()
|
||
call_kw = mock_state.update_data.call_args[1]
|
||
assert call_kw.get("ban_type") == "username"
|
||
mock_state.set_state.assert_awaited_once_with("AWAIT_BAN_TARGET")
|
||
mock_message.answer.assert_awaited_once()
|
||
assert (
|
||
"username" in mock_message.answer.call_args[0][0].lower()
|
||
or "ник" in mock_message.answer.call_args[0][0].lower()
|
||
)
|
||
|
||
async def test_start_ban_process_by_id_sets_ban_type_id(
|
||
self, mock_message, mock_state
|
||
):
|
||
"""start_ban_process при 'Бан по ID' устанавливает ban_type id."""
|
||
mock_message.text = "Бан по ID"
|
||
|
||
await start_ban_process(mock_message, mock_state)
|
||
|
||
call_kw = mock_state.update_data.call_args[1]
|
||
assert call_kw.get("ban_type") == "id"
|
||
|
||
@patch("helper_bot.handlers.admin.admin_handlers.create_keyboard_for_ban_reason")
|
||
@patch("helper_bot.handlers.admin.admin_handlers.format_user_info")
|
||
@patch(
|
||
"helper_bot.handlers.admin.admin_handlers.return_to_admin_menu",
|
||
new_callable=AsyncMock,
|
||
)
|
||
@patch("helper_bot.handlers.admin.admin_handlers.AdminService")
|
||
async def test_process_ban_target_user_not_found_returns_to_menu(
|
||
self,
|
||
mock_service_cls,
|
||
mock_return,
|
||
mock_format,
|
||
mock_keyboard,
|
||
mock_message,
|
||
mock_state,
|
||
mock_bot_db,
|
||
):
|
||
"""process_ban_target при ненайденном пользователе по username возвращает в меню."""
|
||
mock_service = MagicMock()
|
||
mock_service.get_user_by_username = AsyncMock(return_value=None)
|
||
mock_service_cls.return_value = mock_service
|
||
mock_state.get_data = AsyncMock(return_value={"ban_type": "username"})
|
||
mock_message.text = "unknown_user"
|
||
mock_format.return_value = "User info"
|
||
mock_keyboard.return_value = MagicMock()
|
||
|
||
await process_ban_target(mock_message, mock_state, bot_db=mock_bot_db)
|
||
|
||
mock_message.answer.assert_called()
|
||
mock_return.assert_awaited_once()
|
||
|
||
@patch("helper_bot.handlers.admin.admin_handlers.create_keyboard_for_ban_reason")
|
||
@patch("helper_bot.handlers.admin.admin_handlers.format_user_info")
|
||
@patch("helper_bot.handlers.admin.admin_handlers.AdminService")
|
||
async def test_process_ban_reason_sets_state_await_duration(
|
||
self, mock_service_cls, mock_format, mock_keyboard, mock_message, mock_state
|
||
):
|
||
"""process_ban_reason сохраняет причину и переводит в AWAIT_BAN_DURATION."""
|
||
mock_state.get_state = AsyncMock(return_value="AWAIT_BAN_DETAILS")
|
||
mock_state.get_data = AsyncMock(return_value={})
|
||
mock_message.text = "Спам"
|
||
mock_format.return_value = "Спам"
|
||
mock_keyboard.return_value = MagicMock()
|
||
|
||
await process_ban_reason(mock_message, mock_state)
|
||
|
||
mock_state.update_data.assert_awaited_once_with(ban_reason="Спам")
|
||
mock_state.set_state.assert_awaited_once_with("AWAIT_BAN_DURATION")
|
||
mock_message.answer.assert_awaited_once()
|
||
|
||
@patch("helper_bot.handlers.admin.admin_handlers.create_keyboard_for_approve_ban")
|
||
@patch("helper_bot.handlers.admin.admin_handlers.format_ban_confirmation")
|
||
async def test_process_ban_duration_forever_sets_ban_days_none(
|
||
self, mock_format, mock_keyboard, mock_message, mock_state
|
||
):
|
||
"""process_ban_duration при 'Навсегда' устанавливает ban_days=None."""
|
||
mock_state.get_data = AsyncMock(
|
||
return_value={
|
||
"target_user_id": 1,
|
||
"target_username": "u",
|
||
"target_full_name": "U",
|
||
"ban_reason": "Спам",
|
||
}
|
||
)
|
||
mock_message.text = "Навсегда"
|
||
mock_format.return_value = "Подтверждение"
|
||
mock_keyboard.return_value = MagicMock()
|
||
|
||
await process_ban_duration(mock_message, mock_state)
|
||
|
||
mock_state.update_data.assert_awaited_once()
|
||
assert mock_state.update_data.call_args[1].get("ban_days") is None
|
||
mock_state.set_state.assert_awaited_once_with("BAN_CONFIRMATION")
|