import time from datetime import datetime from unittest.mock import AsyncMock, MagicMock, Mock, patch import pytest from database.models import AudioListenRecord, AudioMessage, AudioModerate from database.repositories.audio_repository import AudioRepository class TestAudioRepository: """Тесты для AudioRepository""" @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 audio_repository(self, mock_db_connection): """Экземпляр AudioRepository для тестов""" # Патчим наследование от DatabaseConnection with patch.object(AudioRepository, "__init__", return_value=None): repo = AudioRepository() 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_audio_message(self): """Тестовое аудио сообщение""" return AudioMessage( file_name="test_audio_123.ogg", author_id=12345, date_added="2025-01-15 14:30:00", file_id="test_file_id", listen_count=0, ) @pytest.fixture def sample_datetime(self): """Тестовая дата""" return datetime(2025, 1, 15, 14, 30, 0) @pytest.fixture def sample_timestamp(self): """Тестовый UNIX timestamp""" return int(time.mktime(datetime(2025, 1, 15, 14, 30, 0).timetuple())) @pytest.mark.asyncio async def test_enable_foreign_keys(self, audio_repository): """Тест включения внешних ключей""" await audio_repository.enable_foreign_keys() audio_repository._execute_query.assert_called_once_with( "PRAGMA foreign_keys = ON;" ) @pytest.mark.asyncio async def test_create_tables(self, audio_repository): """Тест создания таблиц""" await audio_repository.create_tables() # Проверяем, что все три таблицы созданы assert audio_repository._execute_query.call_count == 3 # Проверяем вызовы для каждой таблицы calls = audio_repository._execute_query.call_args_list assert any("audio_message_reference" in str(call) for call in calls) assert any("user_audio_listens" in str(call) for call in calls) assert any("audio_moderate" in str(call) for call in calls) @pytest.mark.asyncio async def test_add_audio_record_with_string_date( self, audio_repository, sample_audio_message ): """Тест добавления аудио записи со строковой датой""" await audio_repository.add_audio_record(sample_audio_message) # Проверяем, что метод вызван с правильными параметрами audio_repository._execute_query.assert_called_once() call_args = audio_repository._execute_query.call_args assert call_args[0][0] == """ INSERT INTO audio_message_reference (file_name, author_id, date_added) VALUES (?, ?, ?) """ # Проверяем, что date_added преобразован в timestamp assert call_args[0][1][0] == "test_audio_123.ogg" assert call_args[0][1][1] == 12345 assert isinstance(call_args[0][1][2], int) # timestamp @pytest.mark.asyncio async def test_add_audio_record_with_datetime_date(self, audio_repository): """Тест добавления аудио записи с datetime датой""" audio_msg = AudioMessage( file_name="test_audio_456.ogg", author_id=67890, date_added=datetime(2025, 1, 20, 10, 15, 0), file_id="test_file_id_2", listen_count=0, ) await audio_repository.add_audio_record(audio_msg) # Проверяем, что date_added преобразован в timestamp call_args = audio_repository._execute_query.call_args assert isinstance(call_args[0][1][2], int) # timestamp @pytest.mark.asyncio async def test_add_audio_record_with_timestamp_date(self, audio_repository): """Тест добавления аудио записи с timestamp датой""" timestamp = int(time.time()) audio_msg = AudioMessage( file_name="test_audio_789.ogg", author_id=11111, date_added=timestamp, file_id="test_file_id_3", listen_count=0, ) await audio_repository.add_audio_record(audio_msg) # Проверяем, что date_added остался timestamp call_args = audio_repository._execute_query.call_args assert call_args[0][1][2] == timestamp @pytest.mark.asyncio async def test_add_audio_record_simple_with_string_date(self, audio_repository): """Тест упрощенного добавления аудио записи со строковой датой""" await audio_repository.add_audio_record_simple( "test_audio.ogg", 12345, "2025-01-15 14:30:00" ) # Проверяем, что метод вызван audio_repository._execute_query.assert_called_once() call_args = audio_repository._execute_query.call_args assert call_args[0][1][0] == "test_audio.ogg" # file_name assert call_args[0][1][1] == 12345 # user_id assert isinstance(call_args[0][1][2], int) # timestamp @pytest.mark.asyncio async def test_add_audio_record_simple_with_datetime_date( self, audio_repository, sample_datetime ): """Тест упрощенного добавления аудио записи с datetime датой""" await audio_repository.add_audio_record_simple( "test_audio.ogg", 12345, sample_datetime ) # Проверяем, что date_added преобразован в timestamp call_args = audio_repository._execute_query.call_args assert isinstance(call_args[0][1][2], int) # timestamp @pytest.mark.asyncio async def test_get_last_date_audio(self, audio_repository): """Тест получения даты последнего аудио""" expected_timestamp = 1642248600 # 2022-01-17 10:30:00 audio_repository._execute_query_with_result.return_value = [ (expected_timestamp,) ] result = await audio_repository.get_last_date_audio() assert result == expected_timestamp audio_repository._execute_query_with_result.assert_called_once_with( "SELECT date_added FROM audio_message_reference ORDER BY date_added DESC LIMIT 1" ) @pytest.mark.asyncio async def test_get_last_date_audio_no_records(self, audio_repository): """Тест получения даты последнего аудио когда записей нет""" audio_repository._execute_query_with_result.return_value = [] result = await audio_repository.get_last_date_audio() assert result is None @pytest.mark.asyncio async def test_get_user_audio_records_count(self, audio_repository): """Тест получения количества аудио записей пользователя""" audio_repository._execute_query_with_result.return_value = [(5,)] result = await audio_repository.get_user_audio_records_count(12345) assert result == 5 audio_repository._execute_query_with_result.assert_called_once_with( "SELECT COUNT(*) FROM audio_message_reference WHERE author_id = ?", (12345,) ) @pytest.mark.asyncio async def test_get_path_for_audio_record(self, audio_repository): """Тест получения пути к аудио записи пользователя""" audio_repository._execute_query_with_result.return_value = [("test_audio.ogg",)] result = await audio_repository.get_path_for_audio_record(12345) assert result == "test_audio.ogg" audio_repository._execute_query_with_result.assert_called_once_with( """ SELECT file_name FROM audio_message_reference WHERE author_id = ? ORDER BY date_added DESC LIMIT 1 """, (12345,), ) @pytest.mark.asyncio async def test_get_path_for_audio_record_no_records(self, audio_repository): """Тест получения пути к аудио записи когда записей нет""" audio_repository._execute_query_with_result.return_value = [] result = await audio_repository.get_path_for_audio_record(12345) assert result is None @pytest.mark.asyncio async def test_check_listen_audio(self, audio_repository): """Тест проверки непрослушанных аудио""" # Мокаем результаты запросов audio_repository._execute_query_with_result.side_effect = [ [("audio1.ogg",), ("audio2.ogg",)], # прослушанные [("audio1.ogg",), ("audio2.ogg",), ("audio3.ogg",)], # все аудио ] result = await audio_repository.check_listen_audio(12345) # Должно вернуться только непрослушанные (audio3.ogg) assert result == ["audio3.ogg"] assert audio_repository._execute_query_with_result.call_count == 2 @pytest.mark.asyncio async def test_mark_listened_audio(self, audio_repository): """Тест отметки аудио как прослушанного""" await audio_repository.mark_listened_audio("test_audio.ogg", 12345) audio_repository._execute_query.assert_called_once_with( "INSERT OR IGNORE INTO user_audio_listens (file_name, user_id) VALUES (?, ?)", ("test_audio.ogg", 12345), ) @pytest.mark.asyncio async def test_get_user_id_by_file_name(self, audio_repository): """Тест получения user_id по имени файла""" audio_repository._execute_query_with_result.return_value = [(12345,)] result = await audio_repository.get_user_id_by_file_name("test_audio.ogg") assert result == 12345 audio_repository._execute_query_with_result.assert_called_once_with( "SELECT author_id FROM audio_message_reference WHERE file_name = ?", ("test_audio.ogg",), ) @pytest.mark.asyncio async def test_get_user_id_by_file_name_not_found(self, audio_repository): """Тест получения user_id по имени файла когда файл не найден""" audio_repository._execute_query_with_result.return_value = [] result = await audio_repository.get_user_id_by_file_name("nonexistent.ogg") assert result is None @pytest.mark.asyncio async def test_get_date_by_file_name(self, audio_repository): """Тест получения даты по имени файла""" timestamp = 1642404600 # 2022-01-17 10:30:00 audio_repository._execute_query_with_result.return_value = [(timestamp,)] result = await audio_repository.get_date_by_file_name("test_audio.ogg") # Должна вернуться читаемая дата assert result == "17.01.2022 10:30" audio_repository._execute_query_with_result.assert_called_once_with( "SELECT date_added FROM audio_message_reference WHERE file_name = ?", ("test_audio.ogg",), ) @pytest.mark.asyncio async def test_get_date_by_file_name_not_found(self, audio_repository): """Тест получения даты по имени файла когда файл не найден""" audio_repository._execute_query_with_result.return_value = [] result = await audio_repository.get_date_by_file_name("nonexistent.ogg") assert result is None @pytest.mark.asyncio async def test_refresh_listen_audio(self, audio_repository): """Тест очистки записей прослушивания пользователя""" await audio_repository.refresh_listen_audio(12345) audio_repository._execute_query.assert_called_once_with( "DELETE FROM user_audio_listens WHERE user_id = ?", (12345,) ) @pytest.mark.asyncio async def test_delete_listen_count_for_user(self, audio_repository): """Тест удаления данных о прослушанных аудио пользователя""" await audio_repository.delete_listen_count_for_user(12345) audio_repository._execute_query.assert_called_once_with( "DELETE FROM user_audio_listens WHERE user_id = ?", (12345,) ) @pytest.mark.asyncio async def test_set_user_id_and_message_id_for_voice_bot_success( self, audio_repository ): """Тест успешной установки связи для voice bot""" result = await audio_repository.set_user_id_and_message_id_for_voice_bot( 123, 456 ) assert result is True audio_repository._execute_query.assert_called_once_with( "INSERT OR IGNORE INTO audio_moderate (user_id, message_id) VALUES (?, ?)", (456, 123), ) @pytest.mark.asyncio async def test_set_user_id_and_message_id_for_voice_bot_exception( self, audio_repository ): """Тест установки связи для voice bot при ошибке""" audio_repository._execute_query.side_effect = Exception("Database error") result = await audio_repository.set_user_id_and_message_id_for_voice_bot( 123, 456 ) assert result is False @pytest.mark.asyncio async def test_get_user_id_by_message_id_for_voice_bot(self, audio_repository): """Тест получения user_id по message_id для voice bot""" audio_repository._execute_query_with_result.return_value = [(456,)] result = await audio_repository.get_user_id_by_message_id_for_voice_bot(123) assert result == 456 audio_repository._execute_query_with_result.assert_called_once_with( "SELECT user_id FROM audio_moderate WHERE message_id = ?", (123,) ) @pytest.mark.asyncio async def test_get_user_id_by_message_id_for_voice_bot_not_found( self, audio_repository ): """Тест получения user_id по message_id когда связь не найдена""" audio_repository._execute_query_with_result.return_value = [] result = await audio_repository.get_user_id_by_message_id_for_voice_bot(123) assert result is None @pytest.mark.asyncio async def test_delete_audio_moderate_record(self, audio_repository): """Тест удаления записи из таблицы audio_moderate""" message_id = 12345 await audio_repository.delete_audio_moderate_record(message_id) audio_repository._execute_query.assert_called_once_with( "DELETE FROM audio_moderate WHERE message_id = ?", (message_id,) ) audio_repository.logger.info.assert_called_once_with( f"Удалена запись из audio_moderate для message_id {message_id}" ) @pytest.mark.asyncio async def test_add_audio_record_logging( self, audio_repository, sample_audio_message ): """Тест логирования при добавлении аудио записи""" await audio_repository.add_audio_record(sample_audio_message) # Проверяем, что лог записан audio_repository.logger.info.assert_called_once() log_message = audio_repository.logger.info.call_args[0][0] assert "Аудио добавлено" in log_message assert "test_audio_123.ogg" in log_message assert "12345" in log_message @pytest.mark.asyncio async def test_add_audio_record_simple_logging(self, audio_repository): """Тест логирования при упрощенном добавлении аудио записи""" await audio_repository.add_audio_record_simple( "test_audio.ogg", 12345, "2025-01-15 14:30:00" ) # Проверяем, что лог записан audio_repository.logger.info.assert_called_once() log_message = audio_repository.logger.info.call_args[0][0] assert "Аудио добавлено" in log_message assert "test_audio.ogg" in log_message assert "12345" in log_message @pytest.mark.asyncio async def test_get_date_by_file_name_logging(self, audio_repository): """Тест логирования при получении даты по имени файла""" timestamp = 1642404600 # 2022-01-17 10:30:00 audio_repository._execute_query_with_result.return_value = [(timestamp,)] await audio_repository.get_date_by_file_name("test_audio.ogg") # Проверяем, что лог записан audio_repository.logger.info.assert_called_once() log_message = audio_repository.logger.info.call_args[0][0] assert "Получена дата" in log_message assert "17.01.2022 10:30" in log_message assert "test_audio.ogg" in log_message class TestAudioRepositoryIntegration: """Интеграционные тесты для AudioRepository""" @pytest.fixture def real_audio_repository(self): """Реальный экземпляр AudioRepository для интеграционных тестов""" # Здесь можно создать реальное подключение к тестовой БД # Но для простоты используем мок return Mock() @pytest.mark.asyncio async def test_full_audio_workflow(self, real_audio_repository): """Тест полного рабочего процесса с аудио""" # Этот тест можно расширить для реальной БД assert True # Placeholder для будущих интеграционных тестов @pytest.mark.asyncio async def test_foreign_keys_enabled(self, real_audio_repository): """Тест что внешние ключи включены""" # Этот тест можно расширить для реальной БД assert True # Placeholder для будущих интеграционных тестов