import time from datetime import datetime from unittest.mock import AsyncMock, MagicMock, Mock, patch import pytest from database.repositories.audio_repository import AudioRepository class TestAudioRepositoryNewSchema: """Тесты для 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 для тестов""" 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.mark.asyncio async def test_create_tables_new_schema(self, audio_repository): """Тест создания таблиц с новой схемой БД""" await audio_repository.create_tables() # Проверяем, что все три таблицы созданы assert audio_repository._execute_query.call_count == 3 # Получаем все вызовы calls = audio_repository._execute_query.call_args_list # Проверяем таблицу audio_message_reference audio_table_call = next( call for call in calls if "audio_message_reference" in str(call) ) assert "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT" in str(audio_table_call) assert "file_name TEXT NOT NULL UNIQUE" in str(audio_table_call) assert "author_id INTEGER NOT NULL" in str(audio_table_call) assert "date_added INTEGER NOT NULL" in str(audio_table_call) assert ( "FOREIGN KEY (author_id) REFERENCES our_users (user_id) ON DELETE CASCADE" in str(audio_table_call) ) # Проверяем таблицу user_audio_listens listens_table_call = next( call for call in calls if "user_audio_listens" in str(call) ) assert "file_name TEXT NOT NULL" in str(listens_table_call) assert "user_id INTEGER NOT NULL" in str(listens_table_call) assert "PRIMARY KEY (file_name, user_id)" in str(listens_table_call) assert ( "FOREIGN KEY (user_id) REFERENCES our_users (user_id) ON DELETE CASCADE" in str(listens_table_call) ) # Проверяем таблицу audio_moderate moderate_table_call = next( call for call in calls if "audio_moderate" in str(call) ) assert "user_id INTEGER NOT NULL" in str(moderate_table_call) assert "message_id INTEGER" in str(moderate_table_call) assert "PRIMARY KEY (user_id, message_id)" in str(moderate_table_call) assert ( "FOREIGN KEY (user_id) REFERENCES our_users (user_id) ON DELETE CASCADE" in str(moderate_table_call) ) @pytest.mark.asyncio async def test_add_audio_record_string_date_conversion(self, audio_repository): """Тест преобразования строковой даты в UNIX timestamp""" from database.models import AudioMessage audio_msg = AudioMessage( file_name="test_audio.ogg", author_id=12345, date_added="2025-01-15 14:30:00", file_id="test_file_id", listen_count=0, ) await audio_repository.add_audio_record(audio_msg) # Проверяем, что метод вызван call_args = audio_repository._execute_query.call_args params = call_args[0][1] # Проверяем параметры assert params[0] == "test_audio.ogg" assert params[1] == 12345 assert isinstance(params[2], int) # timestamp # Проверяем, что timestamp соответствует дате expected_timestamp = int(datetime(2025, 1, 15, 14, 30, 0).timestamp()) assert params[2] == expected_timestamp @pytest.mark.asyncio async def test_add_audio_record_datetime_conversion(self, audio_repository): """Тест преобразования datetime в UNIX timestamp""" from database.models import AudioMessage test_datetime = datetime(2025, 1, 20, 10, 15, 30) audio_msg = AudioMessage( file_name="test_audio.ogg", author_id=12345, date_added=test_datetime, file_id="test_file_id", listen_count=0, ) await audio_repository.add_audio_record(audio_msg) # Проверяем параметры call_args = audio_repository._execute_query.call_args params = call_args[0][1] expected_timestamp = int(test_datetime.timestamp()) assert params[2] == expected_timestamp @pytest.mark.asyncio async def test_add_audio_record_timestamp_no_conversion(self, audio_repository): """Тест что timestamp остается timestamp без преобразования""" from database.models import AudioMessage test_timestamp = int(time.time()) audio_msg = AudioMessage( file_name="test_audio.ogg", author_id=12345, date_added=test_timestamp, file_id="test_file_id", listen_count=0, ) await audio_repository.add_audio_record(audio_msg) # Проверяем параметры call_args = audio_repository._execute_query.call_args params = call_args[0][1] assert params[2] == test_timestamp @pytest.mark.asyncio async def test_add_audio_record_simple_string_date(self, audio_repository): """Тест упрощенного добавления со строковой датой""" await audio_repository.add_audio_record_simple( "test_audio.ogg", 12345, "2025-01-15 14:30:00" ) # Проверяем параметры call_args = audio_repository._execute_query.call_args params = call_args[0][1] assert params[0] == "test_audio.ogg" assert params[1] == 12345 assert isinstance(params[2], int) # timestamp # Проверяем timestamp expected_timestamp = int(datetime(2025, 1, 15, 14, 30, 0).timestamp()) assert params[2] == expected_timestamp @pytest.mark.asyncio async def test_add_audio_record_simple_datetime(self, audio_repository): """Тест упрощенного добавления с datetime""" test_datetime = datetime(2025, 1, 25, 16, 45, 0) await audio_repository.add_audio_record_simple( "test_audio.ogg", 12345, test_datetime ) # Проверяем параметры call_args = audio_repository._execute_query.call_args params = call_args[0][1] expected_timestamp = int(test_datetime.timestamp()) assert params[2] == expected_timestamp @pytest.mark.asyncio async def test_get_date_by_file_name_timestamp_conversion(self, audio_repository): """Тест преобразования UNIX timestamp в читаемую дату""" test_timestamp = 1642248600 # 2022-01-17 10:30:00 audio_repository._execute_query_with_result.return_value = [(test_timestamp,)] result = await audio_repository.get_date_by_file_name("test_audio.ogg") # Должна вернуться читаемая дата в формате dd.mm.yyyy HH:MM assert result == "15.01.2022 15:10" assert isinstance(result, str) @pytest.mark.asyncio async def test_get_date_by_file_name_different_timestamp(self, audio_repository): """Тест преобразования другого timestamp в читаемую дату""" test_timestamp = 1705312800 # 2024-01-16 12:00:00 audio_repository._execute_query_with_result.return_value = [(test_timestamp,)] result = await audio_repository.get_date_by_file_name("test_audio.ogg") assert result == "15.01.2024 13:00" @pytest.mark.asyncio async def test_get_date_by_file_name_midnight(self, audio_repository): """Тест преобразования timestamp для полуночи""" test_timestamp = 1705190400 # 2024-01-15 00:00:00 audio_repository._execute_query_with_result.return_value = [(test_timestamp,)] result = await audio_repository.get_date_by_file_name("test_audio.ogg") assert result == "14.01.2024 03:00" @pytest.mark.asyncio async def test_get_date_by_file_name_year_end(self, audio_repository): """Тест преобразования timestamp для конца года""" test_timestamp = 1704067200 # 2023-12-31 23:59:59 audio_repository._execute_query_with_result.return_value = [(test_timestamp,)] result = await audio_repository.get_date_by_file_name("test_audio.ogg") assert result == "01.01.2024 03:00" @pytest.mark.asyncio async def test_foreign_keys_enabled_called(self, audio_repository): """Тест что метод enable_foreign_keys вызывается""" await audio_repository.enable_foreign_keys() audio_repository._execute_query.assert_called_once_with( "PRAGMA foreign_keys = ON;" ) audio_repository.logger.info.assert_not_called() # Этот метод не логирует @pytest.mark.asyncio async def test_create_tables_logging(self, audio_repository): """Тест логирования при создании таблиц""" await audio_repository.create_tables() # Проверяем, что лог записан audio_repository.logger.info.assert_called_once_with( "Таблицы для аудио созданы" ) @pytest.mark.asyncio async def test_add_audio_record_logging_format(self, audio_repository): """Тест формата лога при добавлении аудио записи""" from database.models import AudioMessage audio_msg = AudioMessage( file_name="test_audio.ogg", author_id=12345, date_added="2025-01-15 14:30:00", file_id="test_file_id", listen_count=0, ) await audio_repository.add_audio_record(audio_msg) # Проверяем формат лога log_call = audio_repository.logger.info.call_args log_message = log_call[0][0] assert "Аудио добавлено:" in log_message assert "file_name=test_audio.ogg" in log_message assert "author_id=12345" in log_message @pytest.mark.asyncio async def test_add_audio_record_simple_logging_format(self, audio_repository): """Тест формата лога при упрощенном добавлении""" await audio_repository.add_audio_record_simple( "test_audio.ogg", 12345, "2025-01-15 14:30:00" ) # Проверяем формат лога log_call = audio_repository.logger.info.call_args log_message = log_call[0][0] assert "Аудио добавлено:" in log_message assert "file_name=test_audio.ogg" in log_message assert "user_id=12345" in log_message @pytest.mark.asyncio async def test_get_date_by_file_name_logging_format(self, audio_repository): """Тест формата лога при получении даты""" test_timestamp = 1642248600 # 2022-01-17 10:30:00 audio_repository._execute_query_with_result.return_value = [(test_timestamp,)] await audio_repository.get_date_by_file_name("test_audio.ogg") # Проверяем формат лога log_call = audio_repository.logger.info.call_args log_message = log_call[0][0] assert "Получена дата" in log_message assert "15.01.2022 15:10" in log_message assert "test_audio.ogg" in log_message class TestAudioRepositoryEdgeCases: """Тесты граничных случаев для AudioRepository""" @pytest.fixture def audio_repository(self): """Экземпляр AudioRepository для тестов""" with patch.object(AudioRepository, "__init__", return_value=None): repo = AudioRepository() repo._execute_query = AsyncMock() repo._execute_query_with_result = AsyncMock() repo.logger = Mock() return repo @pytest.mark.asyncio async def test_add_audio_record_empty_string_date(self, audio_repository): """Тест добавления с пустой строковой датой""" from database.models import AudioMessage audio_msg = AudioMessage( file_name="test_audio.ogg", author_id=12345, date_added="", file_id="test_file_id", listen_count=0, ) # Должно вызвать ValueError при парсинге пустой строки with pytest.raises(ValueError): await audio_repository.add_audio_record(audio_msg) @pytest.mark.asyncio async def test_add_audio_record_invalid_string_date(self, audio_repository): """Тест добавления с некорректной строковой датой""" from database.models import AudioMessage audio_msg = AudioMessage( file_name="test_audio.ogg", author_id=12345, date_added="invalid_date", file_id="test_file_id", listen_count=0, ) # Должно вызвать ValueError при парсинге некорректной даты with pytest.raises(ValueError): await audio_repository.add_audio_record(audio_msg) @pytest.mark.asyncio async def test_add_audio_record_none_date(self, audio_repository): """Тест добавления с None датой""" from database.models import AudioMessage audio_msg = AudioMessage( file_name="test_audio.ogg", author_id=12345, date_added=None, file_id="test_file_id", listen_count=0, ) # Метод обрабатывает None как timestamp без преобразования await audio_repository.add_audio_record(audio_msg) # Проверяем, что метод был вызван с None call_args = audio_repository._execute_query.call_args params = call_args[0][1] assert params[2] is None @pytest.mark.asyncio async def test_add_audio_record_simple_empty_string_date(self, audio_repository): """Тест упрощенного добавления с пустой строковой датой""" # Должно вызвать ValueError при парсинге пустой строки with pytest.raises(ValueError): await audio_repository.add_audio_record_simple("test_audio.ogg", 12345, "") @pytest.mark.asyncio async def test_add_audio_record_simple_invalid_string_date(self, audio_repository): """Тест упрощенного добавления с некорректной строковой датой""" # Должно вызвать ValueError при парсинге некорректной даты with pytest.raises(ValueError): await audio_repository.add_audio_record_simple( "test_audio.ogg", 12345, "invalid_date" ) @pytest.mark.asyncio async def test_add_audio_record_simple_none_date(self, audio_repository): """Тест упрощенного добавления с None датой""" # Метод обрабатывает None как timestamp без преобразования await audio_repository.add_audio_record_simple("test_audio.ogg", 12345, None) # Проверяем, что метод был вызван с None call_args = audio_repository._execute_query.call_args params = call_args[0][1] assert params[2] is None @pytest.mark.asyncio async def test_get_date_by_file_name_zero_timestamp(self, audio_repository): """Тест получения даты для timestamp = 0 (1970-01-01)""" audio_repository._execute_query_with_result.return_value = [(0,)] result = await audio_repository.get_date_by_file_name("test_audio.ogg") assert result == "01.01.1970 03:00" @pytest.mark.asyncio async def test_get_date_by_file_name_negative_timestamp(self, audio_repository): """Тест получения даты для отрицательного timestamp""" audio_repository._execute_query_with_result.return_value = [ (-3600,) ] # 1969-12-31 23:00:00 result = await audio_repository.get_date_by_file_name("test_audio.ogg") assert result == "01.01.1970 02:00" @pytest.mark.asyncio async def test_get_date_by_file_name_future_timestamp(self, audio_repository): """Тест получения даты для будущего timestamp""" future_timestamp = int(datetime(2030, 12, 31, 23, 59, 59).timestamp()) audio_repository._execute_query_with_result.return_value = [(future_timestamp,)] result = await audio_repository.get_date_by_file_name("test_audio.ogg") assert result == "31.12.2030 23:59"