import time from datetime import datetime from unittest.mock import AsyncMock, Mock, patch import pytest from database.models import BlacklistHistoryRecord from database.repositories.blacklist_history_repository import \ BlacklistHistoryRepository class TestBlacklistHistoryRepository: """Тесты для BlacklistHistoryRepository""" @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_history_repository(self, mock_db_connection): """Экземпляр BlacklistHistoryRepository для тестов""" # Патчим наследование от DatabaseConnection with patch.object(BlacklistHistoryRepository, "__init__", return_value=None): repo = BlacklistHistoryRepository() 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_history_record(self): """Тестовая запись истории бана""" current_time = int(time.time()) return BlacklistHistoryRecord( user_id=12345, message_for_user="Нарушение правил", date_ban=current_time, date_unban=None, ban_author=999, created_at=current_time, updated_at=current_time, ) @pytest.fixture def sample_history_record_with_unban(self): """Тестовая запись истории бана с датой разбана""" current_time = int(time.time()) return BlacklistHistoryRecord( user_id=12345, message_for_user="Нарушение правил", date_ban=current_time - 86400, # Бан был вчера date_unban=current_time, # Разбан сегодня ban_author=999, created_at=current_time - 86400, updated_at=current_time, ) @pytest.mark.asyncio async def test_create_tables(self, blacklist_history_repository): """Тест создания таблицы истории банов/разбанов""" await blacklist_history_repository.create_tables() # Проверяем, что метод вызван (4 раза: таблица + 3 индекса) assert blacklist_history_repository._execute_query.call_count == 4 calls = blacklist_history_repository._execute_query.call_args_list # Проверяем, что создается таблица с правильной структурой create_table_call = calls[0] assert "CREATE TABLE IF NOT EXISTS blacklist_history" in create_table_call[0][0] assert ( "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT" in create_table_call[0][0] ) assert "user_id INTEGER NOT NULL" in create_table_call[0][0] assert "message_for_user TEXT" in create_table_call[0][0] assert "date_ban INTEGER NOT NULL" in create_table_call[0][0] assert "date_unban INTEGER" in create_table_call[0][0] assert "ban_author INTEGER" in create_table_call[0][0] assert ( "created_at INTEGER DEFAULT (strftime('%s', 'now'))" in create_table_call[0][0] ) assert ( "updated_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] ) assert ( "FOREIGN KEY (ban_author) REFERENCES our_users(user_id) ON DELETE SET NULL" in create_table_call[0][0] ) # Проверяем создание индексов index_calls = calls[1:4] index_names = [call[0][0] for call in index_calls] assert any("idx_blacklist_history_user_id" in idx for idx in index_names) assert any("idx_blacklist_history_date_ban" in idx for idx in index_names) assert any("idx_blacklist_history_date_unban" in idx for idx in index_names) # Проверяем логирование blacklist_history_repository.logger.info.assert_called_once_with( "Таблица истории банов/разбанов создана" ) @pytest.mark.asyncio async def test_add_record_on_ban( self, blacklist_history_repository, sample_history_record ): """Тест добавления записи о бане в историю""" await blacklist_history_repository.add_record_on_ban(sample_history_record) # Проверяем, что метод вызван с правильными параметрами blacklist_history_repository._execute_query.assert_called_once() call_args = blacklist_history_repository._execute_query.call_args # Проверяем SQL запрос sql_query = call_args[0][0].replace("\n", " ").replace(" ", " ").strip() assert "INSERT INTO blacklist_history" in sql_query assert ( "user_id, message_for_user, date_ban, date_unban, ban_author, created_at, updated_at" in sql_query ) # Проверяем параметры params = call_args[0][1] assert params[0] == 12345 # user_id assert params[1] == "Нарушение правил" # message_for_user assert params[2] == sample_history_record.date_ban # date_ban assert params[3] is None # date_unban assert params[4] == 999 # ban_author assert params[5] == sample_history_record.created_at # created_at assert params[6] == sample_history_record.updated_at # updated_at # Проверяем логирование blacklist_history_repository.logger.info.assert_called_once() log_call = blacklist_history_repository.logger.info.call_args[0][0] assert "Запись о бане добавлена в историю" in log_call assert "user_id=12345" in log_call @pytest.mark.asyncio async def test_add_record_on_ban_with_defaults(self, blacklist_history_repository): """Тест добавления записи о бане с дефолтными значениями created_at и updated_at""" record = BlacklistHistoryRecord( user_id=12345, message_for_user="Тест", date_ban=int(time.time()), date_unban=None, ban_author=None, created_at=None, # Будет установлено автоматически updated_at=None, # Будет установлено автоматически ) await blacklist_history_repository.add_record_on_ban(record) # Проверяем, что метод вызван blacklist_history_repository._execute_query.assert_called_once() call_args = blacklist_history_repository._execute_query.call_args # Проверяем, что created_at и updated_at установлены (не None) params = call_args[0][1] assert params[5] is not None # created_at assert params[6] is not None # updated_at assert isinstance(params[5], int) assert isinstance(params[6], int) @pytest.mark.asyncio async def test_set_unban_date_success(self, blacklist_history_repository): """Тест успешного обновления даты разбана""" user_id = 12345 date_unban = int(time.time()) # Мокируем результат проверки - находим открытую запись blacklist_history_repository._execute_query_with_result.return_value = [ (100,) ] # id записи result = await blacklist_history_repository.set_unban_date(user_id, date_unban) # Проверяем, что сначала проверяется наличие записи assert blacklist_history_repository._execute_query_with_result.call_count == 1 check_call = blacklist_history_repository._execute_query_with_result.call_args assert "SELECT id FROM blacklist_history" in check_call[0][0] assert check_call[0][1] == (user_id,) # Проверяем, что затем обновляется запись assert blacklist_history_repository._execute_query.call_count == 1 update_call = blacklist_history_repository._execute_query.call_args update_query = ( update_call[0][0].replace("\n", " ").replace(" ", " ").strip() ) assert "UPDATE blacklist_history" in update_query assert "SET date_unban = ?" in update_query assert "updated_at = ?" in update_query # Проверяем параметры обновления update_params = update_call[0][1] assert update_params[0] == date_unban assert update_params[1] is not None # updated_at (текущее время) assert isinstance(update_params[1], int) assert update_params[2] == 100 # id записи # Проверяем результат assert result is True # Проверяем логирование blacklist_history_repository.logger.info.assert_called_once() log_call = blacklist_history_repository.logger.info.call_args[0][0] assert "Дата разбана обновлена в истории" in log_call assert f"user_id={user_id}" in log_call @pytest.mark.asyncio async def test_set_unban_date_no_open_record(self, blacklist_history_repository): """Тест обновления даты разбана когда нет открытой записи""" user_id = 12345 date_unban = int(time.time()) # Мокируем результат проверки - нет открытых записей blacklist_history_repository._execute_query_with_result.return_value = [] result = await blacklist_history_repository.set_unban_date(user_id, date_unban) # Проверяем, что проверка была выполнена assert blacklist_history_repository._execute_query_with_result.call_count == 1 # Проверяем, что UPDATE не был вызван (нет записей для обновления) blacklist_history_repository._execute_query.assert_not_called() # Проверяем результат assert result is False # Проверяем логирование предупреждения blacklist_history_repository.logger.warning.assert_called_once() log_call = blacklist_history_repository.logger.warning.call_args[0][0] assert "Не найдена открытая запись в истории для обновления" in log_call assert f"user_id={user_id}" in log_call @pytest.mark.asyncio async def test_set_unban_date_exception(self, blacklist_history_repository): """Тест обработки исключения при обновлении даты разбана""" user_id = 12345 date_unban = int(time.time()) # Мокируем исключение при проверке blacklist_history_repository._execute_query_with_result.side_effect = Exception( "Database error" ) result = await blacklist_history_repository.set_unban_date(user_id, date_unban) # Проверяем, что метод вернул False при ошибке assert result is False # Проверяем логирование ошибки blacklist_history_repository.logger.error.assert_called_once() log_call = blacklist_history_repository.logger.error.call_args[0][0] assert "Ошибка обновления даты разбана в истории" in log_call assert f"user_id={user_id}" in log_call @pytest.mark.asyncio async def test_set_unban_date_update_exception(self, blacklist_history_repository): """Тест обработки исключения при обновлении записи""" user_id = 12345 date_unban = int(time.time()) # Мокируем успешную проверку, но ошибку при обновлении blacklist_history_repository._execute_query_with_result.return_value = [(100,)] blacklist_history_repository._execute_query.side_effect = Exception( "Update error" ) result = await blacklist_history_repository.set_unban_date(user_id, date_unban) # Проверяем, что метод вернул False при ошибке assert result is False # Проверяем логирование ошибки blacklist_history_repository.logger.error.assert_called_once() log_call = blacklist_history_repository.logger.error.call_args[0][0] assert "Ошибка обновления даты разбана в истории" in log_call