Files

419 lines
18 KiB
Python

"""
Тесты для административных обработчиков триггеров.
"""
import pytest
from unittest.mock import Mock, AsyncMock, patch
from telegram import Update, Message, User, CallbackQuery
from handlers.admin_handlers import AdminHandlers
class TestAdminTriggers:
"""Тесты административных обработчиков триггеров"""
@pytest.fixture
def mock_trigger_service(self):
"""Мок сервиса триггеров"""
service = Mock()
service.add_trigger = AsyncMock(return_value=1)
service.update_trigger = AsyncMock(return_value=True)
service.delete_trigger = AsyncMock(return_value=True)
service.toggle_trigger = AsyncMock(return_value=True)
service.get_all_triggers = AsyncMock(return_value=[])
service.get_trigger_by_id = AsyncMock(return_value=None)
return service
@pytest.fixture
def admin_handlers(self, mock_trigger_service):
"""Фикстура административных обработчиков"""
with patch('handlers.admin_handlers.TriggerService', return_value=mock_trigger_service):
handlers = AdminHandlers(
database_url=':memory:',
trigger_service=mock_trigger_service
)
return handlers
@pytest.fixture
def mock_update(self):
"""Мок-объект для Update"""
update = Mock(spec=Update)
message = Mock(spec=Message)
user = Mock(spec=User)
chat = Mock()
user.id = 123456
message.message_id = 789
chat.id = 987654
update.message = message
update.effective_user = user
update.effective_chat = chat
return update
@pytest.fixture
def mock_context(self):
"""Мок-объект для контекста"""
context = Mock()
context.args = []
context.user_data = {}
return context
@pytest.mark.asyncio
async def test_handle_trigger_add_success(self, admin_handlers, mock_update, mock_context):
"""Тест успешного добавления триггера"""
mock_update.message.text = "/trigger_add hello Привет!"
mock_context.args = ["hello", "Привет!"]
admin_handlers.send_response = AsyncMock()
await admin_handlers._handle_trigger_add(mock_update, mock_context)
# Проверяем, что сервис был вызван
admin_handlers.trigger_service.add_trigger.assert_called_once()
call_args = admin_handlers.trigger_service.add_trigger.call_args[0][0]
assert call_args['name'] == 'hello'
assert call_args['pattern'] == 'hello'
assert call_args['response_text'] == 'Привет!'
assert call_args['chat_type'] == 'group'
# Проверяем, что ответ был отправлен
admin_handlers.send_response.assert_called_once()
response_call = admin_handlers.send_response.call_args[0]
assert "успешно добавлен" in response_call[1]
@pytest.mark.asyncio
async def test_handle_trigger_add_invalid_args(self, admin_handlers, mock_update, mock_context):
"""Тест добавления триггера с некорректными аргументами"""
mock_update.message.text = "/trigger_add"
mock_context.args = []
admin_handlers.send_response = AsyncMock()
await admin_handlers._handle_trigger_add(mock_update, mock_context)
# Проверяем, что сервис не был вызван
admin_handlers.trigger_service.add_trigger.assert_not_called()
# Проверяем, что отправлено сообщение об ошибке
admin_handlers.send_response.assert_called_once()
response_call = admin_handlers.send_response.call_args[0]
assert "Использование:" in response_call[1]
@pytest.mark.asyncio
async def test_handle_trigger_add_validation_error(self, admin_handlers, mock_update, mock_context):
"""Тест добавления триггера с ошибкой валидации"""
mock_update.message.text = "/trigger_add test Тест"
mock_context.args = ["test", "Тест"]
admin_handlers.send_response = AsyncMock()
admin_handlers.trigger_service.add_trigger.side_effect = Exception("Validation error")
await admin_handlers._handle_trigger_add(mock_update, mock_context)
# Проверяем, что был вызван сервис
admin_handlers.trigger_service.add_trigger.assert_called_once()
# Проверяем, что отправлено сообщение об ошибке
admin_handlers.send_response.assert_called_once()
response_call = admin_handlers.send_response.call_args[0]
assert "Ошибка" in response_call[1] or "Не удалось" in response_call[1]
@pytest.mark.asyncio
async def test_handle_trigger_list_empty(self, admin_handlers, mock_update, mock_context):
"""Тест списка триггеров при их отсутствии"""
admin_handlers.send_response = AsyncMock()
await admin_handlers._handle_trigger_list(mock_update, mock_context)
# Проверяем, что отправлено сообщение о пустом списке
admin_handlers.send_response.assert_called_once()
response_call = admin_handlers.send_response.call_args[0]
assert "Нет созданных триггеров" in response_call[1]
@pytest.mark.asyncio
async def test_handle_trigger_list_with_triggers(self, admin_handlers, mock_update, mock_context):
"""Тест списка триггеров с данными"""
triggers = [
{
'id': 1,
'keywords': ['hello', 'hi'],
'response': 'Привет!',
'enabled': True
},
{
'id': 2,
'keywords': ['bye'],
'response': 'Пока!',
'enabled': False
}
]
admin_handlers.trigger_service.get_all_triggers.return_value = triggers
with patch('telegram.InlineKeyboardMarkup') as mock_markup, \
patch('telegram.InlineKeyboardButton') as mock_button:
admin_handlers.send_response = AsyncMock()
await admin_handlers._handle_trigger_list(mock_update, mock_context)
# Проверяем, что ответ был отправлен
admin_handlers.send_response.assert_called_once()
@pytest.mark.asyncio
async def test_handle_trigger_edit_valid_id(self, admin_handlers, mock_update, mock_context):
"""Тест редактирования триггера с валидным ID"""
mock_context.args = ["1"]
trigger_data = {
'id': 1,
'keywords': ['hello'],
'response': 'Привет!'
}
admin_handlers.trigger_service.get_trigger_by_id.return_value = trigger_data
admin_handlers.send_response = AsyncMock()
await admin_handlers._handle_trigger_edit(mock_update, mock_context)
# Проверяем, что триггер был найден
admin_handlers.trigger_service.get_trigger_by_id.assert_called_once_with(1)
# Проверяем, что ID сохранен в user_data
assert mock_context.user_data['editing_trigger_id'] == 1
# Проверяем, что отправлена форма редактирования
admin_handlers.send_response.assert_called_once()
response_call = admin_handlers.send_response.call_args[0]
assert "Редактирование триггера" in response_call[1]
@pytest.mark.asyncio
async def test_handle_trigger_edit_invalid_id(self, admin_handlers, mock_update, mock_context):
"""Тест редактирования триггера с невалидным ID"""
mock_context.args = ["abc"]
admin_handlers.send_response = AsyncMock()
await admin_handlers._handle_trigger_edit(mock_update, mock_context)
# Проверяем, что отправлено сообщение об ошибке
admin_handlers.send_response.assert_called_once()
response_call = admin_handlers.send_response.call_args[0]
assert "Необходимо указать корректный ID" in response_call[1]
@pytest.mark.asyncio
async def test_handle_trigger_edit_not_found(self, admin_handlers, mock_update, mock_context):
"""Тест редактирования несуществующего триггера"""
mock_context.args = ["999"]
admin_handlers.trigger_service.get_trigger_by_id.return_value = None
admin_handlers.send_response = AsyncMock()
await admin_handlers._handle_trigger_edit(mock_update, mock_context)
# Проверяем, что отправлено сообщение о ненайденном триггере
admin_handlers.send_response.assert_called_once()
response_call = admin_handlers.send_response.call_args[0]
assert "не найден" in response_call[1]
@pytest.mark.asyncio
async def test_handle_trigger_delete_valid_id(self, admin_handlers, mock_update, mock_context):
"""Тест удаления триггера с валидным ID"""
mock_context.args = ["1"]
trigger_data = {
'id': 1,
'keywords': ['hello'],
'response': 'Привет!'
}
admin_handlers.trigger_service.get_trigger_by_id.return_value = trigger_data
with patch('telegram.InlineKeyboardMarkup') as mock_markup, \
patch('telegram.InlineKeyboardButton') as mock_button:
admin_handlers.send_response = AsyncMock()
await admin_handlers._handle_trigger_delete(mock_update, mock_context)
# Проверяем, что отправлена клавиатура подтверждения
admin_handlers.send_response.assert_called_once()
response_call = admin_handlers.send_response.call_args[0]
assert "Подтверждение удаления" in response_call[1]
@pytest.mark.asyncio
async def test_handle_trigger_toggle_success(self, admin_handlers, mock_update, mock_context):
"""Тест успешного переключения статуса триггера"""
mock_context.args = ["1"]
admin_handlers.send_response = AsyncMock()
await admin_handlers._handle_trigger_toggle(mock_update, mock_context)
# Проверяем, что сервис был вызван
admin_handlers.trigger_service.toggle_trigger.assert_called_once_with(1, True)
# Проверяем, что отправлен ответ
admin_handlers.send_response.assert_called_once()
response_call = admin_handlers.send_response.call_args[0]
assert "успешно" in response_call[1]
@pytest.mark.asyncio
async def test_handle_trigger_toggle_not_found(self, admin_handlers, mock_update, mock_context):
"""Тест переключения статуса несуществующего триггера"""
mock_context.args = ["999"]
admin_handlers.trigger_service.toggle_trigger.return_value = False
admin_handlers.send_response = AsyncMock()
await admin_handlers._handle_trigger_toggle(mock_update, mock_context)
# Проверяем, что отправлено сообщение об ошибке
admin_handlers.send_response.assert_called_once()
response_call = admin_handlers.send_response.call_args[0]
assert "не найден" in response_call[1]
@pytest.mark.asyncio
async def test_handle_trigger_toggle_callback_success(self, admin_handlers):
"""Тест callback переключения статуса триггера"""
update = Mock(spec=Update)
callback_query = Mock(spec=CallbackQuery)
user = Mock(spec=User)
callback_query.data = 'trigger_toggle_1'
user.id = 123456
callback_query.from_user = user
update.callback_query = callback_query
context = Mock()
with patch('telegram.InlineKeyboardMarkup') as mock_markup, \
patch('telegram.InlineKeyboardButton') as mock_button:
callback_query.edit_message_text = AsyncMock()
await admin_handlers.handle_trigger_toggle_callback(update, context)
# Проверяем, что сервис был вызван для активации
admin_handlers.trigger_service.toggle_trigger.assert_called_once_with(1, True)
# Проверяем, что сообщение было обновлено
callback_query.edit_message_text.assert_called_once()
@pytest.mark.asyncio
async def test_handle_trigger_toggle_callback_deactivate(self, admin_handlers):
"""Тест callback переключения статуса триггера (деактивация)"""
update = Mock(spec=Update)
callback_query = Mock(spec=CallbackQuery)
user = Mock(spec=User)
callback_query.data = 'trigger_toggle_2'
user.id = 123456
callback_query.from_user = user
update.callback_query = callback_query
context = Mock()
with patch('telegram.InlineKeyboardMarkup') as mock_markup, \
patch('telegram.InlineKeyboardButton') as mock_button:
callback_query.edit_message_text = AsyncMock()
await admin_handlers.handle_trigger_toggle_callback(update, context)
# Проверяем, что сервис был вызван для деактивации
admin_handlers.trigger_service.toggle_trigger.assert_called_once_with(2, False)
# Проверяем, что сообщение было обновлено
callback_query.edit_message_text.assert_called_once()
@pytest.mark.asyncio
async def test_handle_trigger_toggle_callback_invalid_data(self, admin_handlers):
"""Тест callback с некорректными данными"""
update = Mock(spec=Update)
callback_query = Mock(spec=CallbackQuery)
callback_query.data = 'trigger_toggle_invalid'
update.callback_query = callback_query
context = Mock()
# Не должно делать ничего (нет исключений)
await admin_handlers.handle_trigger_toggle_callback(update, context)
# Сервис не должен вызываться
admin_handlers.trigger_service.toggle_trigger.assert_not_called()
@pytest.mark.asyncio
async def test_handle_trigger_delete_callback_confirm(self, admin_handlers):
"""Тест callback подтверждения удаления триггера"""
update = Mock(spec=Update)
callback_query = Mock(spec=CallbackQuery)
user = Mock(spec=User)
callback_query.data = 'trigger_confirm_delete_1'
user.id = 123456
callback_query.from_user = user
update.callback_query = callback_query
context = Mock()
with patch('telegram.InlineKeyboardMarkup') as mock_markup, \
patch('telegram.InlineKeyboardButton') as mock_button:
callback_query.edit_message_text = AsyncMock()
await admin_handlers.handle_trigger_delete_callback(update, context)
# Проверяем, что сервис был вызван для удаления
admin_handlers.trigger_service.delete_trigger.assert_called_once_with(1)
# Проверяем, что сообщение было обновлено
callback_query.edit_message_text.assert_called_once()
@pytest.mark.asyncio
async def test_handle_trigger_delete_callback_cancel(self, admin_handlers):
"""Тест callback отмены удаления триггера"""
update = Mock(spec=Update)
callback_query = Mock(spec=CallbackQuery)
callback_query.data = 'trigger_cancel_delete'
update.callback_query = callback_query
context = Mock()
with patch('telegram.InlineKeyboardMarkup') as mock_markup, \
patch('telegram.InlineKeyboardButton') as mock_button:
callback_query.edit_message_text = AsyncMock()
await admin_handlers.handle_trigger_delete_callback(update, context)
# Проверяем, что сервис не был вызван для удаления
admin_handlers.trigger_service.delete_trigger.assert_not_called()
# Проверяем, что сообщение было обновлено
callback_query.edit_message_text.assert_called_once()
def test_safe_execute_trigger_handlers(self, admin_handlers, mock_update, mock_context):
"""Тест защищенного выполнения обработчиков триггеров"""
async def test_handler(update, context):
return "success"
# Тестируем через публичный метод handle_trigger_add
admin_handlers._AdminHandlers__safe_execute = Mock()
admin_handlers.handle_trigger_add = Mock()
# Проверяем, что safe_execute вызывается
admin_handlers.handle_trigger_add(mock_update, mock_context)
# Проверяем, что safe_execute был вызван
assert admin_handlers._AdminHandlers__safe_execute.called