import pytest from unittest.mock import Mock, AsyncMock, patch, MagicMock from datetime import datetime import time from database.repositories.blacklist_repository import BlacklistRepository from database.models import BlacklistUser class TestBlacklistRepository: """Тесты для BlacklistRepository""" @pytest.fixture def mock_db_connection(self): """Мок для DatabaseConnection""" mock_connection = Mock() mock_connection._execute_query = AsyncMock() mock_connection._execute_query_with_result = AsyncMock() mock_connection.logger = Mock() return mock_connection @pytest.fixture def blacklist_repository(self, mock_db_connection): """Экземпляр BlacklistRepository для тестов""" # Патчим наследование от DatabaseConnection with patch.object(BlacklistRepository, '__init__', return_value=None): repo = BlacklistRepository() repo._execute_query = mock_db_connection._execute_query repo._execute_query_with_result = mock_db_connection._execute_query_with_result repo.logger = mock_db_connection.logger return repo @pytest.fixture def sample_blacklist_user(self): """Тестовый пользователь в черном списке""" return BlacklistUser( user_id=12345, message_for_user="Нарушение правил", date_to_unban=int(time.time()) + 86400, # +1 день created_at=int(time.time()), ban_author=999, ) @pytest.fixture def sample_blacklist_user_permanent(self): """Тестовый пользователь с постоянным баном""" return BlacklistUser( user_id=67890, message_for_user="Постоянный бан", date_to_unban=None, created_at=int(time.time()), ban_author=None, ) @pytest.mark.asyncio async def test_create_tables(self, blacklist_repository): """Тест создания таблицы черного списка""" await blacklist_repository.create_tables() # Проверяем, что метод вызван blacklist_repository._execute_query.assert_called() calls = blacklist_repository._execute_query.call_args_list # Проверяем, что создается таблица с правильной структурой create_table_call = calls[0] assert "CREATE TABLE IF NOT EXISTS blacklist" in create_table_call[0][0] assert "user_id INTEGER NOT NULL PRIMARY KEY" in create_table_call[0][0] assert "message_for_user TEXT" in create_table_call[0][0] assert "date_to_unban INTEGER" in create_table_call[0][0] assert "created_at INTEGER DEFAULT (strftime('%s', 'now'))" in create_table_call[0][0] assert "FOREIGN KEY (user_id) REFERENCES our_users (user_id) ON DELETE CASCADE" in create_table_call[0][0] # Проверяем логирование blacklist_repository.logger.info.assert_called_once_with("Таблица черного списка создана") @pytest.mark.asyncio async def test_add_user(self, blacklist_repository, sample_blacklist_user): """Тест добавления пользователя в черный список""" await blacklist_repository.add_user(sample_blacklist_user) # Проверяем, что метод вызван с правильными параметрами blacklist_repository._execute_query.assert_called_once() call_args = blacklist_repository._execute_query.call_args # Проверяем SQL запрос (учитываем форматирование) sql_query = call_args[0][0].replace('\n', ' ').replace(' ', ' ').replace(' ', ' ').strip() expected_sql = "INSERT INTO blacklist (user_id, message_for_user, date_to_unban, ban_author) VALUES (?, ?, ?, ?)" assert sql_query == expected_sql # Проверяем параметры assert call_args[0][1] == (12345, "Нарушение правил", sample_blacklist_user.date_to_unban, 999) # Проверяем логирование blacklist_repository.logger.info.assert_called_once_with( "Пользователь добавлен в черный список: user_id=12345" ) @pytest.mark.asyncio async def test_add_user_permanent_ban(self, blacklist_repository, sample_blacklist_user_permanent): """Тест добавления пользователя с постоянным баном""" await blacklist_repository.add_user(sample_blacklist_user_permanent) call_args = blacklist_repository._execute_query.call_args assert call_args[0][1] == (67890, "Постоянный бан", None, None) blacklist_repository.logger.info.assert_called_once_with( "Пользователь добавлен в черный список: user_id=67890" ) @pytest.mark.asyncio async def test_remove_user_success(self, blacklist_repository): """Тест успешного удаления пользователя из черного списка""" await blacklist_repository.remove_user(12345) # Проверяем, что метод вызван с правильными параметрами blacklist_repository._execute_query.assert_called_once() call_args = blacklist_repository._execute_query.call_args assert call_args[0][0] == "DELETE FROM blacklist WHERE user_id = ?" assert call_args[0][1] == (12345,) # Проверяем логирование blacklist_repository.logger.info.assert_called_once_with( "Пользователь с идентификатором 12345 успешно удален из черного списка." ) @pytest.mark.asyncio async def test_remove_user_failure(self, blacklist_repository): """Тест неудачного удаления пользователя из черного списка""" # Симулируем ошибку при удалении blacklist_repository._execute_query.side_effect = Exception("Database error") result = await blacklist_repository.remove_user(12345) # Проверяем, что возвращается False при ошибке assert result is False # Проверяем логирование ошибки blacklist_repository.logger.error.assert_called_once() error_log = blacklist_repository.logger.error.call_args[0][0] assert "Ошибка удаления пользователя с идентификатором 12345" in error_log assert "Database error" in error_log @pytest.mark.asyncio async def test_user_exists_true(self, blacklist_repository): """Тест проверки существования пользователя (пользователь существует)""" # Симулируем результат запроса - пользователь найден blacklist_repository._execute_query_with_result.return_value = [(1,)] result = await blacklist_repository.user_exists(12345) # Проверяем, что возвращается True assert result is True # Проверяем, что метод вызван с правильными параметрами blacklist_repository._execute_query_with_result.assert_called_once() call_args = blacklist_repository._execute_query_with_result.call_args assert call_args[0][0] == "SELECT 1 FROM blacklist WHERE user_id = ?" assert call_args[0][1] == (12345,) # Проверяем логирование blacklist_repository.logger.info.assert_called_once_with( "Существует ли пользователь: user_id=12345 Итог: [(1,)]" ) @pytest.mark.asyncio async def test_user_exists_false(self, blacklist_repository): """Тест проверки существования пользователя (пользователь не существует)""" # Симулируем результат запроса - пользователь не найден blacklist_repository._execute_query_with_result.return_value = [] result = await blacklist_repository.user_exists(12345) # Проверяем, что возвращается False assert result is False # Проверяем логирование blacklist_repository.logger.info.assert_called_once_with( "Существует ли пользователь: user_id=12345 Итог: []" ) @pytest.mark.asyncio async def test_get_user_success(self, blacklist_repository): """Тест успешного получения пользователя по ID""" # Симулируем результат запроса mock_row = (12345, "Нарушение правил", int(time.time()) + 86400, int(time.time()), 111) blacklist_repository._execute_query_with_result.return_value = [mock_row] result = await blacklist_repository.get_user(12345) # Проверяем, что возвращается правильный объект assert result is not None assert result.user_id == 12345 assert result.message_for_user == "Нарушение правил" assert result.date_to_unban == mock_row[2] assert result.created_at == mock_row[3] assert result.ban_author == mock_row[4] # Проверяем, что метод вызван с правильными параметрами blacklist_repository._execute_query_with_result.assert_called_once() call_args = blacklist_repository._execute_query_with_result.call_args assert "SELECT user_id, message_for_user, date_to_unban, created_at, ban_author" in call_args[0][0] assert call_args[0][1] == (12345,) @pytest.mark.asyncio async def test_get_user_not_found(self, blacklist_repository): """Тест получения пользователя по ID (пользователь не найден)""" # Симулируем результат запроса - пользователь не найден blacklist_repository._execute_query_with_result.return_value = [] result = await blacklist_repository.get_user(12345) # Проверяем, что возвращается None assert result is None @pytest.mark.asyncio async def test_get_all_users_with_limits(self, blacklist_repository): """Тест получения пользователей с лимитами""" # Симулируем результат запроса mock_rows = [ (12345, "Нарушение правил", int(time.time()) + 86400, int(time.time())), (67890, "Постоянный бан", None, int(time.time()) - 86400) ] blacklist_repository._execute_query_with_result.return_value = mock_rows result = await blacklist_repository.get_all_users(offset=0, limit=10) # Проверяем, что возвращается правильный список assert len(result) == 2 assert result[0].user_id == 12345 assert result[0].message_for_user == "Нарушение правил" assert result[1].user_id == 67890 assert result[1].message_for_user == "Постоянный бан" assert result[1].date_to_unban is None # Проверяем, что метод вызван с правильными параметрами blacklist_repository._execute_query_with_result.assert_called_once() call_args = blacklist_repository._execute_query_with_result.call_args assert call_args[0][0] == "SELECT user_id, message_for_user, date_to_unban, created_at FROM blacklist LIMIT ?, ?" assert call_args[0][1] == (0, 10) # Проверяем логирование blacklist_repository.logger.info.assert_called_once_with( "Получен список пользователей в черном списке (offset=0, limit=10): 2" ) @pytest.mark.asyncio async def test_get_all_users_no_limit(self, blacklist_repository): """Тест получения всех пользователей без лимитов""" # Симулируем результат запроса mock_rows = [ (12345, "Нарушение правил", int(time.time()) + 86400, int(time.time())), (67890, "Постоянный бан", None, int(time.time()) - 86400) ] blacklist_repository._execute_query_with_result.return_value = mock_rows result = await blacklist_repository.get_all_users_no_limit() # Проверяем, что возвращается правильный список assert len(result) == 2 # Проверяем, что метод вызван без лимитов blacklist_repository._execute_query_with_result.assert_called_once() call_args = blacklist_repository._execute_query_with_result.call_args assert call_args[0][0] == "SELECT user_id, message_for_user, date_to_unban, created_at FROM blacklist" # Проверяем, что параметры пустые (без лимитов) assert len(call_args[0]) == 1 # Только SQL запрос, без параметров # Проверяем логирование blacklist_repository.logger.info.assert_called_once_with( "Получен список всех пользователей в черном списке: 2" ) @pytest.mark.asyncio async def test_get_users_for_unblock_today(self, blacklist_repository): """Тест получения пользователей для разблокировки сегодня""" current_timestamp = int(time.time()) # Симулируем результат запроса - пользователи с истекшим сроком mock_rows = [(12345,), (67890,)] blacklist_repository._execute_query_with_result.return_value = mock_rows result = await blacklist_repository.get_users_for_unblock_today(current_timestamp) # Проверяем, что возвращается правильный словарь assert len(result) == 2 assert 12345 in result assert 67890 in result assert result[12345] == 12345 assert result[67890] == 67890 # Проверяем, что метод вызван с правильными параметрами blacklist_repository._execute_query_with_result.assert_called_once() call_args = blacklist_repository._execute_query_with_result.call_args assert call_args[0][0] == "SELECT user_id FROM blacklist WHERE date_to_unban IS NOT NULL AND date_to_unban <= ?" assert call_args[0][1] == (current_timestamp,) # Проверяем логирование blacklist_repository.logger.info.assert_called_once_with( "Получен список пользователей для разблокировки: {12345: 12345, 67890: 67890}" ) @pytest.mark.asyncio async def test_get_users_for_unblock_today_empty(self, blacklist_repository): """Тест получения пользователей для разблокировки (пустой результат)""" current_timestamp = int(time.time()) # Симулируем пустой результат запроса blacklist_repository._execute_query_with_result.return_value = [] result = await blacklist_repository.get_users_for_unblock_today(current_timestamp) # Проверяем, что возвращается пустой словарь assert result == {} # Проверяем логирование blacklist_repository.logger.info.assert_called_once_with( "Получен список пользователей для разблокировки: {}" ) @pytest.mark.asyncio async def test_get_count(self, blacklist_repository): """Тест получения количества пользователей в черном списке""" # Симулируем результат запроса blacklist_repository._execute_query_with_result.return_value = [(5,)] result = await blacklist_repository.get_count() # Проверяем, что возвращается правильное количество assert result == 5 # Проверяем, что метод вызван с правильными параметрами blacklist_repository._execute_query_with_result.assert_called_once() call_args = blacklist_repository._execute_query_with_result.call_args assert call_args[0][0] == "SELECT COUNT(*) FROM blacklist" # Проверяем, что параметры пустые assert len(call_args[0]) == 1 # Только SQL запрос, без параметров @pytest.mark.asyncio async def test_get_count_zero(self, blacklist_repository): """Тест получения количества пользователей (0 пользователей)""" # Симулируем пустой результат запроса blacklist_repository._execute_query_with_result.return_value = [] result = await blacklist_repository.get_count() # Проверяем, что возвращается 0 assert result == 0 @pytest.mark.asyncio async def test_get_count_none_result(self, blacklist_repository): """Тест получения количества пользователей (None результат)""" # Симулируем None результат запроса blacklist_repository._execute_query_with_result.return_value = None result = await blacklist_repository.get_count() # Проверяем, что возвращается 0 assert result == 0 @pytest.mark.asyncio async def test_error_handling_in_get_user(self, blacklist_repository): """Тест обработки ошибок при получении пользователя""" # Симулируем ошибку базы данных blacklist_repository._execute_query_with_result.side_effect = Exception("Database connection failed") # Проверяем, что исключение пробрасывается with pytest.raises(Exception) as exc_info: await blacklist_repository.get_user(12345) assert "Database connection failed" in str(exc_info.value) @pytest.mark.asyncio async def test_error_handling_in_get_all_users(self, blacklist_repository): """Тест обработки ошибок при получении всех пользователей""" # Симулируем ошибку базы данных blacklist_repository._execute_query_with_result.side_effect = Exception("Database connection failed") # Проверяем, что исключение пробрасывается with pytest.raises(Exception) as exc_info: await blacklist_repository.get_all_users() assert "Database connection failed" in str(exc_info.value) @pytest.mark.asyncio async def test_error_handling_in_get_count(self, blacklist_repository): """Тест обработки ошибок при получении количества""" # Симулируем ошибку базы данных blacklist_repository._execute_query_with_result.side_effect = Exception("Database connection failed") # Проверяем, что исключение пробрасывается with pytest.raises(Exception) as exc_info: await blacklist_repository.get_count() assert "Database connection failed" in str(exc_info.value) @pytest.mark.asyncio async def test_error_handling_in_get_users_for_unblock_today(self, blacklist_repository): """Тест обработки ошибок при получении пользователей для разблокировки""" # Симулируем ошибку базы данных blacklist_repository._execute_query_with_result.side_effect = Exception("Database connection failed") # Проверяем, что исключение пробрасывается with pytest.raises(Exception) as exc_info: await blacklist_repository.get_users_for_unblock_today(int(time.time())) assert "Database connection failed" in str(exc_info.value) # TODO: 20-й тест - test_integration_workflow # Этот тест должен проверять полный рабочий процесс: # 1. Добавление пользователя в черный список # 2. Проверка существования пользователя # 3. Получение информации о пользователе # 4. Получение общего количества пользователей # 5. Удаление пользователя из черного списка # 6. Проверка, что пользователь больше не существует # # Проблема: тест падает из-за сложности мокирования возвращаемых значений # при создании объектов BlacklistUser из результатов запросов к БД. # Требует более сложной настройки моков для корректной работы.