From c8c7d50cbb706d5c06f516b4c32f5156abd5bee4 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 3 Sep 2025 00:33:20 +0300 Subject: [PATCH] Refactor metrics handling and improve logging - Removed the MetricsManager initialization from `run_helper.py` to avoid duplication, as metrics are now handled in `main.py`. - Updated logging levels in `server_prometheus.py` and `metrics_middleware.py` to use debug instead of info for less critical messages. - Added metrics configuration to `BaseDependencyFactory` for better management of metrics settings. - Deleted the obsolete `metrics_exporter.py` file to streamline the codebase. - Updated various tests to reflect changes in the metrics handling and ensure proper functionality. --- helper_bot/middlewares/metrics_middleware.py | 13 +- helper_bot/server_prometheus.py | 5 +- helper_bot/utils/base_dependency_factory.py | 5 + helper_bot/utils/metrics_exporter.py | 290 ------------------- run_helper.py | 24 +- tests/test_audio_repository.py | 7 +- tests/test_audio_repository_schema.py | 36 ++- tests/test_auto_unban_integration.py | 48 +-- tests/test_auto_unban_scheduler.py | 22 +- tests/test_keyboards_and_filters.py | 41 ++- tests/test_refactored_admin_handlers.py | 99 ++++--- tests/test_refactored_group_handlers.py | 22 +- tests/test_refactored_private_handlers.py | 24 +- tests/test_utils.py | 134 +++++---- tests/test_voice_bot_architecture.py | 91 +++--- tests/test_voice_constants.py | 7 +- tests/test_voice_handler.py | 17 +- tests/test_voice_services.py | 61 ++-- tests/test_voice_utils.py | 61 ++-- 19 files changed, 402 insertions(+), 605 deletions(-) delete mode 100644 helper_bot/utils/metrics_exporter.py diff --git a/helper_bot/middlewares/metrics_middleware.py b/helper_bot/middlewares/metrics_middleware.py index 3f962e0..1916cfa 100644 --- a/helper_bot/middlewares/metrics_middleware.py +++ b/helper_bot/middlewares/metrics_middleware.py @@ -47,21 +47,18 @@ class MetricsMiddleware(BaseMiddleware): ) -> Any: """Process event and collect metrics.""" - # Добавляем логирование для диагностики - self.logger.info(f"📊 MetricsMiddleware called for event type: {type(event).__name__}") - # Extract command info before execution command_info = None if isinstance(event, Message): - self.logger.info(f"📊 Processing Message event") + self.logger.debug(f"📊 Processing Message event") await self._record_message_metrics(event) command_info = self._extract_command_info(event) elif isinstance(event, CallbackQuery): - self.logger.info(f"📊 Processing CallbackQuery event") + self.logger.debug(f"📊 Processing CallbackQuery event") await self._record_callback_metrics(event) command_info = self._extract_callback_command_info(event) else: - self.logger.info(f"📊 Processing unknown event type: {type(event).__name__}") + self.logger.debug(f"📊 Processing unknown event type: {type(event).__name__}") # Execute handler with timing start_time = time.time() @@ -71,7 +68,7 @@ class MetricsMiddleware(BaseMiddleware): # Record successful execution handler_name = self._get_handler_name(handler) - self.logger.info(f"📊 Recording successful execution: {handler_name}") + self.logger.debug(f"📊 Recording successful execution: {handler_name}") metrics.record_method_duration( handler_name, duration, @@ -95,7 +92,7 @@ class MetricsMiddleware(BaseMiddleware): # Record error and timing handler_name = self._get_handler_name(handler) - self.logger.error(f"📊 Recording error execution: {handler_name}, error: {type(e).__name__}") + self.logger.debug(f"📊 Recording error execution: {handler_name}, error: {type(e).__name__}") metrics.record_method_duration( handler_name, duration, diff --git a/helper_bot/server_prometheus.py b/helper_bot/server_prometheus.py index d058b9a..326146b 100644 --- a/helper_bot/server_prometheus.py +++ b/helper_bot/server_prometheus.py @@ -29,7 +29,7 @@ class MetricsServer: async def metrics_handler(self, request: web.Request) -> web.Response: """Handle /metrics endpoint for Prometheus scraping.""" try: - self.logger.info("Generating metrics...") + self.logger.debug("Generating metrics...") # Проверяем, что metrics доступен if not metrics: @@ -40,9 +40,8 @@ class MetricsServer: ) # Генерируем метрики в формате Prometheus - self.logger.info("Calling metrics.get_metrics()...") metrics_data = metrics.get_metrics() - self.logger.info(f"Generated metrics: {len(metrics_data)} bytes") + self.logger.debug(f"Generated metrics: {len(metrics_data)} bytes") return web.Response( body=metrics_data, diff --git a/helper_bot/utils/base_dependency_factory.py b/helper_bot/utils/base_dependency_factory.py index f52306e..2da61a0 100644 --- a/helper_bot/utils/base_dependency_factory.py +++ b/helper_bot/utils/base_dependency_factory.py @@ -43,6 +43,11 @@ class BaseDependencyFactory: 'test': self._parse_bool(os.getenv('TEST', 'false')) } + self.settings['Metrics'] = { + 'host': os.getenv('METRICS_HOST', '0.0.0.0'), + 'port': self._parse_int(os.getenv('METRICS_PORT', '8080')) + } + def _parse_bool(self, value: str) -> bool: """Парсит строковое значение в boolean.""" return value.lower() in ('true', '1', 'yes', 'on') diff --git a/helper_bot/utils/metrics_exporter.py b/helper_bot/utils/metrics_exporter.py deleted file mode 100644 index fe05f26..0000000 --- a/helper_bot/utils/metrics_exporter.py +++ /dev/null @@ -1,290 +0,0 @@ -""" -Metrics exporter for Prometheus. -Provides HTTP endpoint for metrics collection and background metrics collection. -""" - -import asyncio -import logging -from aiohttp import web -from typing import Optional, Dict, Any, Protocol -from .metrics import metrics -import time - - -class DatabaseProvider(Protocol): - """Protocol for database operations.""" - - async def fetch_one(self, query: str, params: tuple = ()) -> Optional[Dict[str, Any]]: - """Execute query and return single result.""" - ... - - -class MetricsCollector(Protocol): - """Protocol for metrics collection operations.""" - - async def collect_user_metrics(self, db: DatabaseProvider) -> None: - """Collect user-related metrics.""" - ... - - -class UserMetricsCollector: - """Concrete implementation of user metrics collection.""" - - def __init__(self, logger: logging.Logger): - self.logger = logger - - async def collect_user_metrics(self, db: DatabaseProvider) -> None: - """Collect user-related metrics from database.""" - try: - # Проверяем, есть ли метод fetch_one (асинхронная БД) - if hasattr(db, 'fetch_one'): - # Используем UNIX timestamp для сравнения с date_changed - current_timestamp = int(time.time()) - one_day_ago = current_timestamp - (24 * 60 * 60) # 24 часа назад - - active_users_query = """ - SELECT COUNT(DISTINCT user_id) as active_users - FROM our_users - WHERE date_changed > ? - """ - result = await db.fetch_one(active_users_query, (one_day_ago,)) - if result: - metrics.set_active_users(result['active_users'], 'daily') - self.logger.debug(f"Updated active users: {result['active_users']}") - else: - metrics.set_active_users(0, 'daily') - self.logger.debug("Updated active users: 0") - # Проверяем синхронную БД BotDB - elif hasattr(db, 'connect') and hasattr(db, 'cursor'): - # Используем синхронный запрос для BotDB в отдельном потоке - import asyncio - from concurrent.futures import ThreadPoolExecutor - - current_timestamp = int(time.time()) - one_day_ago = current_timestamp - (24 * 60 * 60) # 24 часа назад - - active_users_query = """ - SELECT COUNT(DISTINCT user_id) as active_users - FROM our_users - WHERE date_changed > ? - """ - - def sync_db_query(): - try: - db.connect() - db.cursor.execute(active_users_query, (one_day_ago,)) - result = db.cursor.fetchone() - return result[0] if result else 0 - finally: - db.close() - - # Выполняем синхронный запрос в отдельном потоке - loop = asyncio.get_event_loop() - with ThreadPoolExecutor() as executor: - result = await loop.run_in_executor(executor, sync_db_query) - - metrics.set_active_users(result, 'daily') - self.logger.debug(f"Updated active users: {result}") - else: - metrics.set_active_users(0, 'daily') - self.logger.warning("Database doesn't support fetch_one or connect methods") - - except Exception as e: - self.logger.error(f"Error collecting user metrics: {e}") - metrics.set_active_users(0, 'daily') - - -class DependencyProvider(Protocol): - """Protocol for dependency injection.""" - - def get_db(self) -> DatabaseProvider: - """Get database instance.""" - ... - - -class BackgroundMetricsCollector: - """Background service for collecting periodic metrics using dependency injection.""" - - def __init__( - self, - dependency_provider: DependencyProvider, - metrics_collector: MetricsCollector, - interval: int = 60 - ): - self.dependency_provider = dependency_provider - self.metrics_collector = metrics_collector - self.interval = interval - self.running = False - self.logger = logging.getLogger(__name__) - - async def start(self): - """Start background metrics collection.""" - self.running = True - self.logger.info("Background metrics collector started") - - while self.running: - try: - await self._collect_metrics() - await asyncio.sleep(self.interval) - except Exception as e: - self.logger.error(f"Error in background metrics collection: {e}") - await asyncio.sleep(self.interval) - - async def stop(self): - """Stop background metrics collection.""" - self.running = False - self.logger.info("Background metrics collector stopped") - - async def _collect_metrics(self): - """Collect periodic metrics using dependency injection.""" - try: - db = self.dependency_provider.get_db() - if db: - await self.metrics_collector.collect_user_metrics(db) - else: - self.logger.warning("Database not available for metrics collection") - - except Exception as e: - self.logger.error(f"Error collecting metrics: {e}") - - -class MetricsExporter: - """HTTP server for exposing Prometheus metrics.""" - - def __init__(self, host: str = "0.0.0.0", port: int = 8000): - self.host = host - self.port = port - self.app = web.Application() - self.runner: Optional[web.AppRunner] = None - self.site: Optional[web.TCPSite] = None - self.logger = logging.getLogger(__name__) - - # Setup routes - self.app.router.add_get('/metrics', self.metrics_handler) - self.app.router.add_get('/health', self.health_handler) - self.app.router.add_get('/', self.root_handler) - - async def start(self): - """Start the metrics server.""" - try: - self.runner = web.AppRunner(self.app) - await self.runner.setup() - - self.site = web.TCPSite(self.runner, self.host, self.port) - await self.site.start() - - self.logger.info(f"Metrics server started on {self.host}:{self.port}") - except Exception as e: - self.logger.error(f"Failed to start metrics server: {e}") - raise - - async def stop(self): - """Stop the metrics server.""" - try: - if self.site: - await self.site.stop() - self.logger.info("Metrics server site stopped") - - if self.runner: - await self.runner.cleanup() - self.logger.info("Metrics server runner cleaned up") - - except Exception as e: - self.logger.error(f"Error stopping metrics server: {e}") - finally: - # Очищаем ссылки - self.site = None - self.runner = None - # Даем время на закрытие всех соединений - await asyncio.sleep(0.1) - self.logger.info("Metrics server stopped") - - async def metrics_handler(self, request: web.Request) -> web.Response: - """Handle /metrics endpoint for Prometheus.""" - try: - metrics_data = metrics.get_metrics() - self.logger.debug(f"Generated metrics: {len(metrics_data)} bytes") - - return web.Response( - body=metrics_data, - content_type='text/plain; version=0.0.4' - ) - except Exception as e: - self.logger.error(f"Error generating metrics: {e}") - return web.Response( - text=f"Error generating metrics: {e}", - status=500 - ) - - async def health_handler(self, request: web.Request) -> web.Response: - """Handle /health endpoint for health checks.""" - return web.json_response({ - "status": "healthy", - "service": "telegram-bot-metrics" - }) - - async def root_handler(self, request: web.Request) -> web.Response: - """Handle root endpoint with basic info.""" - return web.json_response({ - "service": "Telegram Bot Metrics Exporter", - "endpoints": { - "/metrics": "Prometheus metrics", - "/health": "Health check", - "/": "This info" - } - }) - - -class MetricsManager: - """Main class for managing metrics collection and export.""" - - def __init__(self, host: str = "0.0.0.0", port: int = 8000): - self.exporter = MetricsExporter(host, port) - - # Dependency injection setup - from helper_bot.utils.base_dependency_factory import get_global_instance - dependency_provider = get_global_instance() - metrics_collector = UserMetricsCollector(logging.getLogger(__name__)) - - self.collector = BackgroundMetricsCollector( - dependency_provider=dependency_provider, - metrics_collector=metrics_collector - ) - self.logger = logging.getLogger(__name__) - - async def start(self): - """Start metrics collection and export.""" - try: - # Start metrics exporter - await self.exporter.start() - - # Start background collector - asyncio.create_task(self.collector.start()) - - self.logger.info("Metrics manager started successfully") - - except Exception as e: - self.logger.error(f"Failed to start metrics manager: {e}") - raise - - async def stop(self): - """Stop metrics collection and export.""" - try: - # Останавливаем background collector - if hasattr(self, 'collector'): - await self.collector.stop() - self.logger.info("Background metrics collector stopped") - - # Останавливаем exporter - if hasattr(self, 'exporter'): - await self.exporter.stop() - self.logger.info("Metrics exporter stopped") - - except Exception as e: - self.logger.error(f"Error stopping metrics manager: {e}") - # Не вызываем raise, чтобы не прерывать процесс завершения - finally: - # Очищаем ссылки - self.collector = None - self.exporter = None - self.logger.info("Metrics manager stopped successfully") diff --git a/run_helper.py b/run_helper.py index 3bcc3db..5f0a2c3 100644 --- a/run_helper.py +++ b/run_helper.py @@ -33,10 +33,8 @@ async def main(): auto_unban_scheduler.set_bot(auto_unban_bot) auto_unban_scheduler.start_scheduler() - # Инициализируем метрики ПОСЛЕ импорта всех модулей - # Это гарантирует, что global instance полностью инициализирован - from helper_bot.utils.metrics_exporter import MetricsManager - metrics_manager = MetricsManager(host="0.0.0.0", port=8000) + # Метрики запускаются в main.py через server_prometheus.py + # Здесь не нужно дублировать функциональность # Флаг для корректного завершения shutdown_event = asyncio.Event() @@ -50,9 +48,8 @@ async def main(): signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) - # Запускаем бота и метрики + # Запускаем бота (метрики запускаются внутри start_bot) bot_task = asyncio.create_task(start_bot(bdf)) - metrics_task = asyncio.create_task(metrics_manager.start()) main_bot = None @@ -67,21 +64,16 @@ async def main(): logger.info("Останавливаем планировщик автоматического разбана...") auto_unban_scheduler.stop_scheduler() - logger.info("Останавливаем метрики...") - try: - await metrics_manager.stop() - except Exception as e: - logger.error(f"Ошибка при остановке метрик: {e}") + # Метрики останавливаются в main.py logger.info("Останавливаем задачи...") - # Отменяем задачи + # Отменяем задачу бота bot_task.cancel() - metrics_task.cancel() - # Ждем завершения задач и получаем результат main bot + # Ждем завершения задачи бота и получаем результат main bot try: - results = await asyncio.gather(bot_task, metrics_task, return_exceptions=True) - # Первый результат - это main bot + results = await asyncio.gather(bot_task, return_exceptions=True) + # Результат - это main bot if results[0] and not isinstance(results[0], Exception): main_bot = results[0] except Exception as e: diff --git a/tests/test_audio_repository.py b/tests/test_audio_repository.py index 268af45..dcc6a72 100644 --- a/tests/test_audio_repository.py +++ b/tests/test_audio_repository.py @@ -132,7 +132,8 @@ class TestAudioRepository: # Проверяем, что метод вызван audio_repository._execute_query.assert_called_once() call_args = audio_repository._execute_query.call_args - assert call_args[0][1][2] == 12345 # user_id + 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 @@ -251,7 +252,7 @@ class TestAudioRepository: @pytest.mark.asyncio async def test_get_date_by_file_name(self, audio_repository): """Тест получения даты по имени файла""" - timestamp = 1642248600 # 2022-01-17 10:30:00 + 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") @@ -357,7 +358,7 @@ class TestAudioRepository: @pytest.mark.asyncio async def test_get_date_by_file_name_logging(self, audio_repository): """Тест логирования при получении даты по имени файла""" - timestamp = 1642248600 # 2022-01-17 10:30:00 + 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") diff --git a/tests/test_audio_repository_schema.py b/tests/test_audio_repository_schema.py index 6184ee8..ad0596e 100644 --- a/tests/test_audio_repository_schema.py +++ b/tests/test_audio_repository_schema.py @@ -173,7 +173,7 @@ class TestAudioRepositoryNewSchema: result = await audio_repository.get_date_by_file_name("test_audio.ogg") # Должна вернуться читаемая дата в формате dd.mm.yyyy HH:MM - assert result == "17.01.2022 10:30" + assert result == "15.01.2022 15:10" assert isinstance(result, str) @pytest.mark.asyncio @@ -184,7 +184,7 @@ class TestAudioRepositoryNewSchema: result = await audio_repository.get_date_by_file_name("test_audio.ogg") - assert result == "16.01.2024 12:00" + assert result == "15.01.2024 13:00" @pytest.mark.asyncio async def test_get_date_by_file_name_midnight(self, audio_repository): @@ -194,7 +194,7 @@ class TestAudioRepositoryNewSchema: result = await audio_repository.get_date_by_file_name("test_audio.ogg") - assert result == "15.01.2024 00:00" + assert result == "14.01.2024 03:00" @pytest.mark.asyncio async def test_get_date_by_file_name_year_end(self, audio_repository): @@ -204,7 +204,7 @@ class TestAudioRepositoryNewSchema: result = await audio_repository.get_date_by_file_name("test_audio.ogg") - assert result == "31.12.2023 23:59" + assert result == "01.01.2024 03:00" @pytest.mark.asyncio async def test_foreign_keys_enabled_called(self, audio_repository): @@ -271,7 +271,7 @@ class TestAudioRepositoryNewSchema: log_message = log_call[0][0] assert "Получена дата" in log_message - assert "17.01.2022 10:30" in log_message + assert "15.01.2022 15:10" in log_message assert "test_audio.ogg" in log_message @@ -335,10 +335,14 @@ class TestAudioRepositoryEdgeCases: listen_count=0 ) - # Должно вызвать TypeError при попытке преобразования None - with pytest.raises(TypeError): - await audio_repository.add_audio_record(audio_msg) - + # Метод обрабатывает 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): """Тест упрощенного добавления с пустой строковой датой""" @@ -356,9 +360,13 @@ class TestAudioRepositoryEdgeCases: @pytest.mark.asyncio async def test_add_audio_record_simple_none_date(self, audio_repository): """Тест упрощенного добавления с None датой""" - # Должно вызвать TypeError при попытке преобразования None - with pytest.raises(TypeError): - await audio_repository.add_audio_record_simple("test_audio.ogg", 12345, 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): @@ -367,7 +375,7 @@ class TestAudioRepositoryEdgeCases: result = await audio_repository.get_date_by_file_name("test_audio.ogg") - assert result == "01.01.1970 00:00" + assert result == "01.01.1970 03:00" @pytest.mark.asyncio async def test_get_date_by_file_name_negative_timestamp(self, audio_repository): @@ -376,7 +384,7 @@ class TestAudioRepositoryEdgeCases: result = await audio_repository.get_date_by_file_name("test_audio.ogg") - assert result == "31.12.1969 23:00" + assert result == "01.01.1970 02:00" @pytest.mark.asyncio async def test_get_date_by_file_name_future_timestamp(self, audio_repository): diff --git a/tests/test_auto_unban_integration.py b/tests/test_auto_unban_integration.py index ff4b16e..4ddc64a 100644 --- a/tests/test_auto_unban_integration.py +++ b/tests/test_auto_unban_integration.py @@ -32,19 +32,19 @@ class TestAutoUnbanIntegration: user_id INTEGER PRIMARY KEY, user_name TEXT, message_for_user TEXT, - date_to_unban TEXT + date_to_unban INTEGER ) ''') # Добавляем тестовые данные - today = datetime.now(timezone(timedelta(hours=3))).strftime("%Y-%m-%d") - tomorrow = (datetime.now(timezone(timedelta(hours=3))) + timedelta(days=1)).strftime("%Y-%m-%d") + today_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp()) + tomorrow_timestamp = int((datetime.now(timezone(timedelta(hours=3))) + timedelta(days=1)).timestamp()) test_data = [ - (123, "test_user1", "Test ban 1", today), # Разблокируется сегодня - (456, "test_user2", "Test ban 2", today), # Разблокируется сегодня - (789, "test_user3", "Test ban 3", tomorrow), # Разблокируется завтра - (999, "test_user4", "Test ban 4", None), # Навсегда заблокирован + (123, "test_user1", "Test ban 1", today_timestamp), # Разблокируется сегодня + (456, "test_user2", "Test ban 2", today_timestamp), # Разблокируется сегодня + (789, "test_user3", "Test ban 3", tomorrow_timestamp), # Разблокируется завтра + (999, "test_user4", "Test ban 4", None), # Навсегда заблокирован ] cursor.executemany( @@ -73,10 +73,9 @@ class TestAutoUnbanIntegration: } # Создаем реальный экземпляр базы данных с тестовым файлом - from database.db import BotDB + from database.async_db import AsyncBotDB import os - current_dir = os.getcwd() - mock_factory.database = BotDB(current_dir, test_db_path) + mock_factory.database = AsyncBotDB(test_db_path) return mock_factory @@ -110,14 +109,15 @@ class TestAutoUnbanIntegration: await scheduler.auto_unban_users() # Проверяем, что пользователи с сегодняшней датой разблокированы - cursor.execute("SELECT COUNT(*) FROM blacklist WHERE date_to_unban = ?", - (datetime.now(timezone(timedelta(hours=3))).strftime("%Y-%m-%d"),)) + current_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp()) + cursor.execute("SELECT COUNT(*) FROM blacklist WHERE date_to_unban IS NOT NULL AND date_to_unban <= ?", + (current_timestamp,)) today_count = cursor.fetchone()[0] assert today_count == 0 # Проверяем, что пользователи с завтрашней датой остались - tomorrow = (datetime.now(timezone(timedelta(hours=3))) + timedelta(days=1)).strftime("%Y-%m-%d") - cursor.execute("SELECT COUNT(*) FROM blacklist WHERE date_to_unban = ?", (tomorrow,)) + cursor.execute("SELECT COUNT(*) FROM blacklist WHERE date_to_unban IS NOT NULL AND date_to_unban > ?", + (current_timestamp,)) tomorrow_count = cursor.fetchone()[0] assert tomorrow_count == 1 @@ -146,8 +146,8 @@ class TestAutoUnbanIntegration: # Удаляем пользователей с сегодняшней датой conn = sqlite3.connect(setup_test_db) cursor = conn.cursor() - today = datetime.now(timezone(timedelta(hours=3))).strftime("%Y-%m-%d") - cursor.execute("DELETE FROM blacklist WHERE date_to_unban = ?", (today,)) + current_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp()) + cursor.execute("DELETE FROM blacklist WHERE date_to_unban IS NOT NULL AND date_to_unban <= ?", (current_timestamp,)) conn.commit() conn.close() @@ -195,7 +195,7 @@ class TestAutoUnbanIntegration: scheduler = AutoUnbanScheduler() scheduler.bot_db = mock_bdf.database - # Проверяем, что дата в базе соответствует ожидаемому формату + # Проверяем, что дата в базе соответствует ожидаемому формату (timestamp) conn = sqlite3.connect(setup_test_db) cursor = conn.cursor() cursor.execute("SELECT date_to_unban FROM blacklist WHERE date_to_unban IS NOT NULL LIMIT 1") @@ -203,13 +203,13 @@ class TestAutoUnbanIntegration: conn.close() if result and result[0]: - date_str = result[0] - # Проверяем формат YYYY-MM-DD - assert len(date_str) == 10 - assert date_str.count('-') == 2 - assert date_str[:4].isdigit() # Год - assert date_str[5:7].isdigit() # Месяц - assert date_str[8:10].isdigit() # День + timestamp = result[0] + # Проверяем, что это валидный timestamp (целое число) + assert isinstance(timestamp, int) + assert timestamp > 0 + # Проверяем, что timestamp можно преобразовать в дату + date_obj = datetime.fromtimestamp(timestamp) + assert isinstance(date_obj, datetime) class TestSchedulerLifecycle: diff --git a/tests/test_auto_unban_scheduler.py b/tests/test_auto_unban_scheduler.py index 5dd22d8..2d79976 100644 --- a/tests/test_auto_unban_scheduler.py +++ b/tests/test_auto_unban_scheduler.py @@ -18,11 +18,11 @@ class TestAutoUnbanScheduler: def mock_bot_db(self): """Создает мок базы данных""" mock_db = Mock() - mock_db.get_users_for_unblock_today.return_value = { + mock_db.get_users_for_unblock_today = AsyncMock(return_value={ 123: "test_user1", 456: "test_user2" - } - mock_db.delete_user_blacklist.return_value = True + }) + mock_db.delete_user_blacklist = AsyncMock(return_value=True) return mock_db @pytest.fixture @@ -78,7 +78,7 @@ class TestAutoUnbanScheduler: """Тест разбана когда нет пользователей для разблокировки""" # Настройка моков mock_get_instance.return_value = mock_bdf - mock_bot_db.get_users_for_unblock_today.return_value = {} + mock_bot_db.get_users_for_unblock_today = AsyncMock(return_value={}) scheduler.bot_db = mock_bot_db scheduler.set_bot(mock_bot) @@ -96,12 +96,12 @@ class TestAutoUnbanScheduler: """Тест разбана с частичными ошибками""" # Настройка моков mock_get_instance.return_value = mock_bdf - mock_bot_db.get_users_for_unblock_today.return_value = { + mock_bot_db.get_users_for_unblock_today = AsyncMock(return_value={ 123: "test_user1", 456: "test_user2" - } + }) # Первый вызов успешен, второй - ошибка - mock_bot_db.delete_user_blacklist.side_effect = [True, False] + mock_bot_db.delete_user_blacklist = AsyncMock(side_effect=[True, False]) scheduler.bot_db = mock_bot_db scheduler.set_bot(mock_bot) @@ -118,7 +118,7 @@ class TestAutoUnbanScheduler: """Тест разбана с исключением""" # Настройка моков mock_get_instance.return_value = mock_bdf - mock_bot_db.get_users_for_unblock_today.side_effect = Exception("Database error") + mock_bot_db.get_users_for_unblock_today = AsyncMock(side_effect=Exception("Database error")) scheduler.bot_db = mock_bot_db scheduler.set_bot(mock_bot) @@ -141,7 +141,7 @@ class TestAutoUnbanScheduler: assert "Отчет об автоматическом разбане" in report assert "Успешно разблокировано: 1" in report assert "Ошибок: 1" in report - assert "test_user1" in report + assert "ID: 123" in report assert "456 (test_user2)" in report @pytest.mark.asyncio @@ -268,8 +268,8 @@ class TestAsyncOperations: mock_get_instance.return_value = mock_bdf mock_bot_db = Mock() - mock_bot_db.get_users_for_unblock_today.return_value = {123: "test_user"} - mock_bot_db.delete_user_blacklist.return_value = True + mock_bot_db.get_users_for_unblock_today = AsyncMock(return_value={123: "test_user"}) + mock_bot_db.delete_user_blacklist = AsyncMock(return_value=True) mock_bot = Mock() mock_bot.send_message = AsyncMock() diff --git a/tests/test_keyboards_and_filters.py b/tests/test_keyboards_and_filters.py index e320716..3af0aee 100644 --- a/tests/test_keyboards_and_filters.py +++ b/tests/test_keyboards_and_filters.py @@ -70,13 +70,14 @@ class TestKeyboards: # Проверяем наличие кнопки стикеров assert '🤪Хочу стикеры' in all_buttons - def test_get_reply_keyboard_without_stickers(self, mock_db): + @pytest.mark.asyncio + async def test_get_reply_keyboard_without_stickers(self, mock_db): """Тест клавиатуры без стикеров""" user_id = 123456 - # Мокаем метод get_info_about_stickers - mock_db.get_info_about_stickers = Mock(return_value=True) + # Мокаем метод get_stickers_info + mock_db.get_stickers_info = AsyncMock(return_value=True) - keyboard = get_reply_keyboard(mock_db, user_id) + keyboard = await get_reply_keyboard(mock_db, user_id) all_buttons = [] for row in keyboard.keyboard: @@ -86,13 +87,14 @@ class TestKeyboards: # Проверяем отсутствие кнопки стикеров assert '🤪Хочу стикеры' not in all_buttons - def test_get_reply_keyboard_admin(self, mock_db): + @pytest.mark.asyncio + async def test_get_reply_keyboard_admin(self, mock_db): """Тест клавиатуры для админа""" user_id = 123456 - # Мокаем метод get_info_about_stickers - mock_db.get_info_about_stickers = Mock(return_value=False) + # Мокаем метод get_stickers_info + mock_db.get_stickers_info = AsyncMock(return_value=False) - keyboard = get_reply_keyboard(mock_db, user_id) + keyboard = await get_reply_keyboard(mock_db, user_id) all_buttons = [] for row in keyboard.keyboard: @@ -284,44 +286,41 @@ class TestChatTypeFilter: class TestKeyboardIntegration: """Интеграционные тесты клавиатур""" - def test_keyboard_structure_consistency(self): + @pytest.mark.asyncio + async def test_keyboard_structure_consistency(self): """Тест консистентности структуры клавиатур""" # Мокаем базу данных mock_db = Mock(spec=AsyncBotDB) - mock_db.get_info_about_stickers = Mock(return_value=False) + mock_db.get_stickers_info = AsyncMock(return_value=False) # Тестируем все типы клавиатур - keyboards = [ - get_reply_keyboard(mock_db, 123456), - get_reply_keyboard_for_post(), - get_reply_keyboard_leave_chat() - ] + keyboard1 = await get_reply_keyboard(mock_db, 123456) + keyboard2 = get_reply_keyboard_for_post() + keyboard3 = get_reply_keyboard_leave_chat() # Проверяем первую клавиатуру (ReplyKeyboardMarkup) - keyboard1 = keyboards[0] assert isinstance(keyboard1, ReplyKeyboardMarkup) assert hasattr(keyboard1, 'keyboard') assert isinstance(keyboard1.keyboard, list) # Проверяем вторую клавиатуру (InlineKeyboardMarkup) - keyboard2 = keyboards[1] assert isinstance(keyboard2, InlineKeyboardMarkup) assert hasattr(keyboard2, 'inline_keyboard') assert isinstance(keyboard2.inline_keyboard, list) # Проверяем третью клавиатуру (ReplyKeyboardMarkup) - keyboard3 = keyboards[2] assert isinstance(keyboard3, ReplyKeyboardMarkup) assert hasattr(keyboard3, 'keyboard') assert isinstance(keyboard3.keyboard, list) - def test_keyboard_button_texts(self): + @pytest.mark.asyncio + async def test_keyboard_button_texts(self): """Тест текстов кнопок клавиатур""" # Тестируем основные кнопки db = Mock(spec=AsyncBotDB) - db.get_info_about_stickers = Mock(return_value=False) + db.get_stickers_info = AsyncMock(return_value=False) - main_keyboard = get_reply_keyboard(db, 123456) + main_keyboard = await get_reply_keyboard(db, 123456) post_keyboard = get_reply_keyboard_for_post() leave_keyboard = get_reply_keyboard_leave_chat() diff --git a/tests/test_refactored_admin_handlers.py b/tests/test_refactored_admin_handlers.py index a9172ce..8319e3e 100644 --- a/tests/test_refactored_admin_handlers.py +++ b/tests/test_refactored_admin_handlers.py @@ -19,7 +19,8 @@ class TestAdminService: self.mock_db = Mock() self.admin_service = AdminService(self.mock_db) - def test_get_last_users_success(self): + @pytest.mark.asyncio + async def test_get_last_users_success(self): """Тест успешного получения списка последних пользователей""" # Arrange # Формат данных: кортежи (full_name, user_id) как возвращает БД @@ -27,10 +28,10 @@ class TestAdminService: ('User One', 1), # (full_name, user_id) ('User Two', 2) # (full_name, user_id) ] - self.mock_db.get_last_users_from_db.return_value = mock_users_data + self.mock_db.get_last_users = AsyncMock(return_value=mock_users_data) # Act - result = self.admin_service.get_last_users() + result = await self.admin_service.get_last_users() # Assert assert len(result) == 2 @@ -41,17 +42,18 @@ class TestAdminService: assert result[1].username == 'Неизвестно' # username не возвращается из БД assert result[1].full_name == 'User Two' - def test_get_user_by_username_success(self): + @pytest.mark.asyncio + async def test_get_user_by_username_success(self): """Тест успешного получения пользователя по username""" # Arrange user_id = 123 username = "test_user" full_name = "Test User" - self.mock_db.get_user_id_by_username.return_value = user_id - self.mock_db.get_full_name_by_id.return_value = full_name + self.mock_db.get_user_id_by_username = AsyncMock(return_value=user_id) + self.mock_db.get_full_name_by_id = AsyncMock(return_value=full_name) # Act - result = self.admin_service.get_user_by_username(username) + result = await self.admin_service.get_user_by_username(username) # Assert assert result is not None @@ -59,27 +61,35 @@ class TestAdminService: assert result.username == username assert result.full_name == full_name - def test_get_user_by_username_not_found(self): + @pytest.mark.asyncio + async def test_get_user_by_username_not_found(self): """Тест получения пользователя по несуществующему username""" # Arrange username = "nonexistent_user" - self.mock_db.get_user_id_by_username.return_value = None + self.mock_db.get_user_id_by_username = AsyncMock(return_value=None) # Act - result = self.admin_service.get_user_by_username(username) + result = await self.admin_service.get_user_by_username(username) # Assert assert result is None - def test_get_user_by_id_success(self): + @pytest.mark.asyncio + async def test_get_user_by_id_success(self): """Тест успешного получения пользователя по ID""" # Arrange user_id = 123 - user_info = {'username': 'test_user', 'full_name': 'Test User'} - self.mock_db.get_user_info_by_id.return_value = user_info + from database.models import User as DBUser + user_info = DBUser( + user_id=user_id, + first_name="Test", + full_name="Test User", + username="test_user" + ) + self.mock_db.get_user_by_id = AsyncMock(return_value=user_info) # Act - result = self.admin_service.get_user_by_id(user_id) + result = await self.admin_service.get_user_by_id(user_id) # Assert assert result is not None @@ -87,45 +97,51 @@ class TestAdminService: assert result.username == 'test_user' assert result.full_name == 'Test User' - def test_get_user_by_id_not_found(self): + @pytest.mark.asyncio + async def test_get_user_by_id_not_found(self): """Тест получения пользователя по несуществующему ID""" # Arrange user_id = 999 - self.mock_db.get_user_info_by_id.return_value = None + self.mock_db.get_user_by_id = AsyncMock(return_value=None) # Act - result = self.admin_service.get_user_by_id(user_id) + result = await self.admin_service.get_user_by_id(user_id) # Assert assert result is None - def test_validate_user_input_success(self): + @pytest.mark.asyncio + async def test_validate_user_input_success(self): """Тест успешной валидации ID пользователя""" # Act - result = self.admin_service.validate_user_input("123") + result = await self.admin_service.validate_user_input("123") # Assert assert result == 123 - def test_validate_user_input_invalid_number(self): + @pytest.mark.asyncio + async def test_validate_user_input_invalid_number(self): """Тест валидации некорректного ID""" # Act & Assert with pytest.raises(InvalidInputError, match="ID пользователя должен быть числом"): - self.admin_service.validate_user_input("abc") + await self.admin_service.validate_user_input("abc") - def test_validate_user_input_negative_number(self): + @pytest.mark.asyncio + async def test_validate_user_input_negative_number(self): """Тест валидации отрицательного ID""" # Act & Assert with pytest.raises(InvalidInputError, match="ID пользователя должен быть положительным числом"): - self.admin_service.validate_user_input("-1") + await self.admin_service.validate_user_input("-1") - def test_validate_user_input_zero(self): + @pytest.mark.asyncio + async def test_validate_user_input_zero(self): """Тест валидации нулевого ID""" # Act & Assert with pytest.raises(InvalidInputError, match="ID пользователя должен быть положительным числом"): - self.admin_service.validate_user_input("0") + await self.admin_service.validate_user_input("0") - def test_ban_user_success(self): + @pytest.mark.asyncio + async def test_ban_user_success(self): """Тест успешной блокировки пользователя""" # Arrange user_id = 123 @@ -133,17 +149,18 @@ class TestAdminService: reason = "Test ban" ban_days = 7 - self.mock_db.check_user_in_blacklist.return_value = False - self.mock_db.set_user_blacklist.return_value = None + self.mock_db.check_user_in_blacklist = AsyncMock(return_value=False) + self.mock_db.set_user_blacklist = AsyncMock(return_value=None) # Act - self.admin_service.ban_user(user_id, username, reason, ban_days) + await self.admin_service.ban_user(user_id, username, reason, ban_days) # Assert self.mock_db.check_user_in_blacklist.assert_called_once_with(user_id) self.mock_db.set_user_blacklist.assert_called_once() - def test_ban_user_already_banned(self): + @pytest.mark.asyncio + async def test_ban_user_already_banned(self): """Тест попытки заблокировать уже заблокированного пользователя""" # Arrange user_id = 123 @@ -151,13 +168,14 @@ class TestAdminService: reason = "Test ban" ban_days = 7 - self.mock_db.check_user_in_blacklist.return_value = True + self.mock_db.check_user_in_blacklist = AsyncMock(return_value=True) # Act & Assert with pytest.raises(UserAlreadyBannedError, match=f"Пользователь {user_id} уже заблокирован"): - self.admin_service.ban_user(user_id, username, reason, ban_days) + await self.admin_service.ban_user(user_id, username, reason, ban_days) - def test_ban_user_permanent(self): + @pytest.mark.asyncio + async def test_ban_user_permanent(self): """Тест постоянной блокировки пользователя""" # Arrange user_id = 123 @@ -165,23 +183,24 @@ class TestAdminService: reason = "Permanent ban" ban_days = None - self.mock_db.check_user_in_blacklist.return_value = False - self.mock_db.set_user_blacklist.return_value = None + self.mock_db.check_user_in_blacklist = AsyncMock(return_value=False) + self.mock_db.set_user_blacklist = AsyncMock(return_value=None) # Act - self.admin_service.ban_user(user_id, username, reason, ban_days) + await self.admin_service.ban_user(user_id, username, reason, ban_days) # Assert - self.mock_db.set_user_blacklist.assert_called_once_with(user_id, username, reason, None) + self.mock_db.set_user_blacklist.assert_called_once_with(user_id, None, reason, None) - def test_unban_user_success(self): + @pytest.mark.asyncio + async def test_unban_user_success(self): """Тест успешной разблокировки пользователя""" # Arrange user_id = 123 - self.mock_db.delete_user_blacklist.return_value = None + self.mock_db.delete_user_blacklist = AsyncMock(return_value=None) # Act - self.admin_service.unban_user(user_id) + await self.admin_service.unban_user(user_id) # Assert self.mock_db.delete_user_blacklist.assert_called_once_with(user_id) diff --git a/tests/test_refactored_group_handlers.py b/tests/test_refactored_group_handlers.py index f9cf84d..41335f2 100644 --- a/tests/test_refactored_group_handlers.py +++ b/tests/test_refactored_group_handlers.py @@ -75,9 +75,10 @@ class TestGroupHandlers: assert handlers.admin_reply_service is not None assert handlers.router is not None + @pytest.mark.asyncio async def test_handle_message_success(self, mock_db, mock_keyboard_markup, mock_reply_message, mock_state): """Test successful message handling""" - mock_db.get_user_by_message_id.return_value = 99999 + mock_db.get_user_by_message_id = AsyncMock(return_value=99999) handlers = create_group_handlers(mock_db, mock_keyboard_markup) @@ -97,6 +98,7 @@ class TestGroupHandlers: # Verify state was set mock_state.set_state.assert_called_once_with(FSM_STATES["CHAT"]) + @pytest.mark.asyncio async def test_handle_message_no_reply(self, mock_db, mock_keyboard_markup, mock_message, mock_state): """Test message handling without reply""" handlers = create_group_handlers(mock_db, mock_keyboard_markup) @@ -121,9 +123,10 @@ class TestGroupHandlers: # Verify state was not set mock_state.set_state.assert_not_called() + @pytest.mark.asyncio async def test_handle_message_user_not_found(self, mock_db, mock_keyboard_markup, mock_reply_message, mock_state): """Test message handling when user is not found""" - mock_db.get_user_by_message_id.return_value = None + mock_db.get_user_by_message_id = AsyncMock(return_value=None) handlers = create_group_handlers(mock_db, mock_keyboard_markup) @@ -154,24 +157,27 @@ class TestAdminReplyService: """Create service instance""" return AdminReplyService(mock_db) - def test_get_user_id_for_reply_success(self, service, mock_db): + @pytest.mark.asyncio + async def test_get_user_id_for_reply_success(self, service, mock_db): """Test successful user ID retrieval""" - mock_db.get_user_by_message_id.return_value = 12345 + mock_db.get_user_by_message_id = AsyncMock(return_value=12345) - result = service.get_user_id_for_reply(111) + result = await service.get_user_id_for_reply(111) assert result == 12345 mock_db.get_user_by_message_id.assert_called_once_with(111) - def test_get_user_id_for_reply_not_found(self, service, mock_db): + @pytest.mark.asyncio + async def test_get_user_id_for_reply_not_found(self, service, mock_db): """Test user ID retrieval when user not found""" - mock_db.get_user_by_message_id.return_value = None + mock_db.get_user_by_message_id = AsyncMock(return_value=None) with pytest.raises(UserNotFoundError, match="User not found for message_id: 111"): - service.get_user_id_for_reply(111) + await service.get_user_id_for_reply(111) mock_db.get_user_by_message_id.assert_called_once_with(111) + @pytest.mark.asyncio async def test_send_reply_to_user(self, service, mock_db): """Test sending reply to user""" message = Mock() diff --git a/tests/test_refactored_private_handlers.py b/tests/test_refactored_private_handlers.py index 37cecdd..0e11999 100644 --- a/tests/test_refactored_private_handlers.py +++ b/tests/test_refactored_private_handlers.py @@ -19,13 +19,13 @@ class TestPrivateHandlers: def mock_db(self): """Mock database""" db = Mock() - db.user_exists.return_value = False - db.add_new_user_in_db = Mock() - db.update_date_for_user = Mock() - db.update_info_about_stickers = Mock() - db.add_post_in_db = Mock() - db.add_new_message_in_db = Mock() - db.update_helper_message_in_db = Mock() + db.user_exists = AsyncMock(return_value=False) + db.add_user = AsyncMock() + db.update_user_date = AsyncMock() + db.update_stickers_info = AsyncMock() + db.add_post = AsyncMock() + db.add_message = AsyncMock() + db.update_helper_message = AsyncMock() return db @pytest.fixture @@ -101,7 +101,8 @@ class TestPrivateHandlers: # Mock the check_user_emoji function with pytest.MonkeyPatch().context() as m: - m.setattr('helper_bot.handlers.private.private_handlers.check_user_emoji', lambda x: "😊") + mock_check_emoji = AsyncMock(return_value="😊") + m.setattr('helper_bot.handlers.private.private_handlers.check_user_emoji', mock_check_emoji) # Test the handler await handlers.handle_emoji_message(mock_message, mock_state) @@ -121,7 +122,8 @@ class TestPrivateHandlers: with pytest.MonkeyPatch().context() as m: m.setattr('helper_bot.handlers.private.private_handlers.get_first_name', lambda x: "Test") m.setattr('helper_bot.handlers.private.private_handlers.messages.get_message', lambda x, y: "Hello Test!") - m.setattr('helper_bot.handlers.private.private_handlers.get_reply_keyboard', lambda x, y: Mock()) + mock_keyboard = AsyncMock(return_value=Mock()) + m.setattr('helper_bot.handlers.private.private_handlers.get_reply_keyboard', mock_keyboard) # Test the handler await handlers.handle_start_message(mock_message, mock_state) @@ -130,8 +132,8 @@ class TestPrivateHandlers: mock_state.set_state.assert_called_once_with(FSM_STATES["START"]) # Verify user was ensured to exist - mock_db.add_new_user_in_db.assert_called_once() - mock_db.update_date_for_user.assert_called_once() + mock_db.add_user.assert_called_once() + mock_db.update_user_date.assert_called_once() class TestBotSettings: diff --git a/tests/test_utils.py b/tests/test_utils.py index 9c0c9d5..5b485f2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -31,7 +31,7 @@ from helper_bot.utils.helper_func import ( ) from helper_bot.utils.messages import get_message from helper_bot.utils.base_dependency_factory import BaseDependencyFactory, get_global_instance -from database.db import BotDB +from database.async_db import AsyncBotDB import helper_bot.utils.messages as messages # Import for patching constants class TestHelperFunctions: @@ -83,25 +83,27 @@ class TestHelperFunctions: assert "testuser" in result assert "Обычный текст без специальных слов" in result - def test_check_username_and_full_name(self): + @pytest.mark.asyncio + async def test_check_username_and_full_name(self): """Тест функции проверки изменений username и full_name""" # Создаем мок базы данных - mock_db = Mock(spec=BotDB) - mock_db.get_username_and_full_name = Mock(return_value=("olduser", "Old User")) + mock_db = Mock(spec=AsyncBotDB) + mock_db.get_username = AsyncMock(return_value="olduser") + mock_db.get_full_name_by_id = AsyncMock(return_value="Old User") # Тест с измененными данными - result = check_username_and_full_name(123456, "newuser", "New User", mock_db) + result = await check_username_and_full_name(123456, "newuser", "New User", mock_db) assert result is True # Тест с неизмененными данными - result = check_username_and_full_name(123456, "olduser", "Old User", mock_db) + result = await check_username_and_full_name(123456, "olduser", "Old User", mock_db) assert result is False # Тест с частично измененными данными - result = check_username_and_full_name(123456, "olduser", "New User", mock_db) + result = await check_username_and_full_name(123456, "olduser", "New User", mock_db) assert result is True - result = check_username_and_full_name(123456, "newuser", "Old User", mock_db) + result = await check_username_and_full_name(123456, "newuser", "Old User", mock_db) assert result is True @@ -330,7 +332,7 @@ class TestPrepareMediaGroup: assert result[0].media == "photo_0" assert result[1].media == "photo_1" assert result[2].media == "photo_2" - assert result[2].caption == "Тестовая подпись" + assert result[0].caption == "Тестовая подпись" # Первое фото должно иметь caption @pytest.mark.asyncio async def test_prepare_media_group_mixed_types(self): @@ -364,7 +366,7 @@ class TestPrepareMediaGroup: assert result[0].media == "photo_1" assert result[1].media == "video_1" assert result[2].media == "audio_1" - assert result[2].caption == "Смешанная группа" + assert result[0].caption == "Смешанная группа" # Первое медиа должно иметь caption @pytest.mark.asyncio async def test_prepare_media_group_empty_album(self): @@ -381,6 +383,7 @@ class TestPrepareMediaGroup: message.photo = None message.video = None message.audio = None + message.document = None # Добавляем document = None album.append(message) result = await prepare_media_group_from_middlewares(album, "Тест") @@ -401,12 +404,12 @@ class TestMediaDatabaseOperations: message.photo[-1].file_id = f"photo_{i}" sent_message.append(message) - mock_db = Mock() + mock_db = AsyncMock() with patch('helper_bot.utils.helper_func.download_file', return_value=f"files/photo_{i}.jpg"): await add_in_db_media_mediagroup(sent_message, mock_db) - assert mock_db.add_post_content_in_db.call_count == 2 + assert mock_db.add_post_content.call_count == 2 @pytest.mark.asyncio async def test_add_in_db_media_photo(self): @@ -416,12 +419,12 @@ class TestMediaDatabaseOperations: mock_message.photo = [Mock()] mock_message.photo[-1].file_id = "photo_123" - mock_db = Mock() + mock_db = AsyncMock() with patch('helper_bot.utils.helper_func.download_file', return_value="files/photo_123.jpg"): await add_in_db_media(mock_message, mock_db) - mock_db.add_post_content_in_db.assert_called_once_with( + mock_db.add_post_content.assert_called_once_with( 123, 123, "files/photo_123.jpg", 'photo' ) @@ -434,12 +437,12 @@ class TestMediaDatabaseOperations: mock_message.video = Mock() mock_message.video.file_id = "video_123" - mock_db = Mock() + mock_db = AsyncMock() with patch('helper_bot.utils.helper_func.download_file', return_value="files/video_123.mp4"): await add_in_db_media(mock_message, mock_db) - mock_db.add_post_content_in_db.assert_called_once_with( + mock_db.add_post_content.assert_called_once_with( 123, 123, "files/video_123.mp4", 'video' ) @@ -453,12 +456,12 @@ class TestMediaDatabaseOperations: mock_message.voice = Mock() mock_message.voice.file_id = "voice_123" - mock_db = Mock() + mock_db = AsyncMock() with patch('helper_bot.utils.helper_func.download_file', return_value="files/voice_123.ogg"): await add_in_db_media(mock_message, mock_db) - mock_db.add_post_content_in_db.assert_called_once_with( + mock_db.add_post_content.assert_called_once_with( 123, 123, "files/voice_123.ogg", 'voice' ) @@ -548,16 +551,17 @@ class TestSendMessageFunctions: class TestUtilityFunctions: """Тесты для утилитарных функций""" - def test_check_access(self): + @pytest.mark.asyncio + async def test_check_access(self): """Тест проверки доступа""" - mock_db = Mock() + mock_db = AsyncMock() mock_db.is_admin.return_value = True - result = check_access(123, mock_db) + result = await check_access(123, mock_db) assert result is True mock_db.is_admin.return_value = False - result = check_access(123, mock_db) + result = await check_access(123, mock_db) assert result is False def test_add_days_to_date(self): @@ -569,45 +573,51 @@ class TestUtilityFunctions: mock_datetime.timedelta = timedelta result = add_days_to_date(5) - expected_date = (mock_now + timedelta(days=5)).strftime("%d-%m-%Y") - assert result == expected_date + expected_timestamp = int((mock_now + timedelta(days=5)).timestamp()) + assert result == expected_timestamp - def test_get_banned_users_list(self): + @pytest.mark.asyncio + async def test_get_banned_users_list(self): """Тест получения списка заблокированных пользователей""" - mock_db = Mock() + mock_db = AsyncMock() mock_db.get_banned_users_from_db_with_limits.return_value = [ - ("User1", 123, "Spam", "01-01-2025"), - ("User2", 456, "Violation", "02-01-2025") + (123, "Spam", 1704067200), # user_id, ban_reason, unban_date (timestamp) + (456, "Violation", 1704153600) ] + mock_db.get_username.return_value = None + mock_db.get_full_name_by_id.return_value = "Test User" - result = get_banned_users_list(0, mock_db) + result = await get_banned_users_list(0, mock_db) assert "Список заблокированных пользователей:" in result - assert "User1" in result - assert "User2" in result + assert "Test User" in result assert "Spam" in result assert "Violation" in result - def test_get_banned_users_buttons(self): + @pytest.mark.asyncio + async def test_get_banned_users_buttons(self): """Тест получения кнопок заблокированных пользователей""" - mock_db = Mock() + mock_db = AsyncMock() mock_db.get_banned_users_from_db.return_value = [ - ("User1", 123), - ("User2", 456) + (123, "Spam", 1704067200), # user_id, ban_reason, unban_date + (456, "Violation", 1704153600) ] + mock_db.get_username.return_value = None + mock_db.get_full_name_by_id.return_value = "Test User" - result = get_banned_users_buttons(mock_db) + result = await get_banned_users_buttons(mock_db) assert len(result) == 2 - assert result[0] == ("User1", 123) - assert result[1] == ("User2", 456) + assert result[0] == ("Test User", 123) + assert result[1] == ("Test User", 456) - def test_delete_user_blacklist(self): + @pytest.mark.asyncio + async def test_delete_user_blacklist(self): """Тест удаления пользователя из черного списка""" - mock_db = Mock() + mock_db = AsyncMock() mock_db.delete_user_blacklist.return_value = True - result = delete_user_blacklist(123, mock_db) + result = await delete_user_blacklist(123, mock_db) assert result is True mock_db.delete_user_blacklist.assert_called_once_with(user_id=123) @@ -631,57 +641,61 @@ class TestUserManagement: with patch('helper_bot.utils.helper_func.get_first_name', return_value="Test"): with patch('helper_bot.utils.helper_func.get_random_emoji', return_value="😀"): with patch('helper_bot.utils.helper_func.BotDB') as mock_bot_db: - mock_bot_db.user_exists.return_value = False - mock_bot_db.add_new_user_in_db = Mock() - mock_bot_db.update_date_for_user = Mock() + mock_bot_db.user_exists = AsyncMock(return_value=False) + mock_bot_db.add_user = AsyncMock() + mock_bot_db.update_user_date = AsyncMock() await update_user_info("test", mock_message) - mock_bot_db.add_new_user_in_db.assert_called_once() - mock_bot_db.update_date_for_user.assert_called_once() + mock_bot_db.add_user.assert_called_once() + mock_bot_db.update_user_date.assert_called_once() - def test_check_user_emoji_existing(self): + @pytest.mark.asyncio + async def test_check_user_emoji_existing(self): """Тест проверки эмодзи пользователя (существующий)""" mock_message = Mock() mock_message.from_user.id = 123 with patch('helper_bot.utils.helper_func.BotDB') as mock_bot_db: - mock_bot_db.check_emoji_for_user.return_value = "😀" + mock_bot_db.get_user_emoji = AsyncMock(return_value="😀") - result = check_user_emoji(mock_message) + result = await check_user_emoji(mock_message) assert result == "😀" - def test_check_user_emoji_new(self): + @pytest.mark.asyncio + async def test_check_user_emoji_new(self): """Тест проверки эмодзи пользователя (новый)""" mock_message = Mock() mock_message.from_user.id = 123 with patch('helper_bot.utils.helper_func.BotDB') as mock_bot_db: - mock_bot_db.check_emoji_for_user.return_value = None - mock_bot_db.update_emoji_for_user = Mock() + mock_bot_db.get_user_emoji = AsyncMock(return_value=None) + mock_bot_db.update_user_emoji = AsyncMock() with patch('helper_bot.utils.helper_func.get_random_emoji', return_value="😀"): - result = check_user_emoji(mock_message) + result = await check_user_emoji(mock_message) assert result == "😀" - mock_bot_db.update_emoji_for_user.assert_called_once_with(user_id=123, emoji="😀") + mock_bot_db.update_user_emoji.assert_called_once_with(user_id=123, emoji="😀") - def test_get_random_emoji_success(self): + @pytest.mark.asyncio + async def test_get_random_emoji_success(self): """Тест получения случайного эмодзи (успех)""" with patch('helper_bot.utils.helper_func.BotDB') as mock_bot_db: - mock_bot_db.check_emoji.return_value = False + mock_bot_db.check_emoji_exists = AsyncMock(return_value=False) with patch('helper_bot.utils.helper_func.random.choice', return_value="😀"): - result = get_random_emoji() + result = await get_random_emoji() assert result == "😀" - def test_get_random_emoji_fallback(self): + @pytest.mark.asyncio + async def test_get_random_emoji_fallback(self): """Тест получения случайного эмодзи (fallback)""" with patch('helper_bot.utils.helper_func.BotDB') as mock_bot_db: - mock_bot_db.check_emoji.return_value = True # Все эмодзи заняты + mock_bot_db.check_emoji_exists = AsyncMock(return_value=True) # Все эмодзи заняты with patch('helper_bot.utils.helper_func.random.choice', return_value="😀"): with patch('helper_bot.utils.helper_func.logger') as mock_logger: - result = get_random_emoji() + result = await get_random_emoji() assert result == "Эмоджи не определен" mock_logger.error.assert_called_once() diff --git a/tests/test_voice_bot_architecture.py b/tests/test_voice_bot_architecture.py index 3bbf0a1..f1f80f4 100644 --- a/tests/test_voice_bot_architecture.py +++ b/tests/test_voice_bot_architecture.py @@ -55,14 +55,15 @@ class TestVoiceBotService: assert sticker is None - def test_get_random_audio_success(self, voice_service, mock_bot_db): + @pytest.mark.asyncio + async def test_get_random_audio_success(self, voice_service, mock_bot_db): """Тест успешного получения случайного аудио""" - mock_bot_db.check_listen_audio.return_value = ['audio1', 'audio2'] - mock_bot_db.get_user_id_by_file_name.return_value = 123 - mock_bot_db.get_date_by_file_name.return_value = '2025-01-01 12:00:00' - mock_bot_db.check_emoji_for_user.return_value = '😊' + mock_bot_db.check_listen_audio = AsyncMock(return_value=['audio1', 'audio2']) + mock_bot_db.get_user_id_by_file_name = AsyncMock(return_value=123) + mock_bot_db.get_date_by_file_name = AsyncMock(return_value='2025-01-01 12:00:00') + mock_bot_db.get_user_emoji = AsyncMock(return_value='😊') - result = voice_service.get_random_audio(456) + result = await voice_service.get_random_audio(456) assert result is not None assert len(result) == 3 @@ -70,40 +71,49 @@ class TestVoiceBotService: assert result[1] == '2025-01-01 12:00:00' assert result[2] == '😊' - def test_get_random_audio_no_audio(self, voice_service, mock_bot_db): + @pytest.mark.asyncio + async def test_get_random_audio_no_audio(self, voice_service, mock_bot_db): """Тест получения аудио когда их нет""" - mock_bot_db.check_listen_audio.return_value = [] + mock_bot_db.check_listen_audio = AsyncMock(return_value=[]) - result = voice_service.get_random_audio(456) + result = await voice_service.get_random_audio(456) assert result is None - def test_mark_audio_as_listened_success(self, voice_service, mock_bot_db): + @pytest.mark.asyncio + async def test_mark_audio_as_listened_success(self, voice_service, mock_bot_db): """Тест успешной пометки аудио как прослушанного""" - voice_service.mark_audio_as_listened('test_audio', 123) + mock_bot_db.mark_listened_audio = AsyncMock() + + await voice_service.mark_audio_as_listened('test_audio', 123) mock_bot_db.mark_listened_audio.assert_called_once_with('test_audio', user_id=123) - def test_clear_user_listenings_success(self, voice_service, mock_bot_db): + @pytest.mark.asyncio + async def test_clear_user_listenings_success(self, voice_service, mock_bot_db): """Тест успешной очистки прослушиваний""" - voice_service.clear_user_listenings(123) + mock_bot_db.delete_listen_count_for_user = AsyncMock() + + await voice_service.clear_user_listenings(123) mock_bot_db.delete_listen_count_for_user.assert_called_once_with(123) - def test_get_remaining_audio_count_success(self, voice_service, mock_bot_db): + @pytest.mark.asyncio + async def test_get_remaining_audio_count_success(self, voice_service, mock_bot_db): """Тест получения количества оставшихся аудио""" - mock_bot_db.check_listen_audio.return_value = ['audio1', 'audio2', 'audio3'] + mock_bot_db.check_listen_audio = AsyncMock(return_value=['audio1', 'audio2', 'audio3']) - result = voice_service.get_remaining_audio_count(123) + result = await voice_service.get_remaining_audio_count(123) assert result == 3 mock_bot_db.check_listen_audio.assert_called_once_with(user_id=123) - def test_get_remaining_audio_count_zero(self, voice_service, mock_bot_db): + @pytest.mark.asyncio + async def test_get_remaining_audio_count_zero(self, voice_service, mock_bot_db): """Тест получения количества оставшихся аудио когда их нет""" - mock_bot_db.check_listen_audio.return_value = [] + mock_bot_db.check_listen_audio = AsyncMock(return_value=[]) - result = voice_service.get_remaining_audio_count(123) + result = await voice_service.get_remaining_audio_count(123) assert result == 0 mock_bot_db.check_listen_audio.assert_called_once_with(user_id=123) @@ -187,57 +197,64 @@ class TestUtils: """Мок для базы данных""" return Mock() - def test_get_last_message_text(self, mock_bot_db): + @pytest.mark.asyncio + async def test_get_last_message_text(self, mock_bot_db): """Тест получения последнего сообщения""" - mock_bot_db.last_date_audio.return_value = "2025-01-01 12:00:00" + # Возвращаем UNIX timestamp + mock_bot_db.last_date_audio = AsyncMock(return_value=1641034800) # 2022-01-01 12:00:00 - result = get_last_message_text(mock_bot_db) + result = await get_last_message_text(mock_bot_db) assert result is not None - assert "минут" in result or "часа" in result or "дня" in result + assert "минут" in result or "часа" in result or "дня" in result or "день" in result or "дней" in result mock_bot_db.last_date_audio.assert_called_once() - def test_validate_voice_message_valid(self): + @pytest.mark.asyncio + async def test_validate_voice_message_valid(self): """Тест валидации голосового сообщения""" mock_message = Mock() mock_message.content_type = 'voice' mock_message.voice = Mock() - result = validate_voice_message(mock_message) + result = await validate_voice_message(mock_message) assert result is True - def test_validate_voice_message_invalid(self): + @pytest.mark.asyncio + async def test_validate_voice_message_invalid(self): """Тест валидации невалидного сообщения""" mock_message = Mock() mock_message.voice = None - result = validate_voice_message(mock_message) + result = await validate_voice_message(mock_message) assert result is False - def test_get_user_emoji_safe(self, mock_bot_db): + @pytest.mark.asyncio + async def test_get_user_emoji_safe(self, mock_bot_db): """Тест безопасного получения эмодзи пользователя""" - mock_bot_db.check_emoji_for_user.return_value = "😊" + mock_bot_db.get_user_emoji = AsyncMock(return_value="😊") - result = get_user_emoji_safe(mock_bot_db, 123) + result = await get_user_emoji_safe(mock_bot_db, 123) assert result == "😊" - mock_bot_db.check_emoji_for_user.assert_called_once_with(123) + mock_bot_db.get_user_emoji.assert_called_once_with(123) - def test_get_user_emoji_safe_none(self, mock_bot_db): + @pytest.mark.asyncio + async def test_get_user_emoji_safe_none(self, mock_bot_db): """Тест безопасного получения эмодзи когда его нет""" - mock_bot_db.check_emoji_for_user.return_value = None + mock_bot_db.get_user_emoji = AsyncMock(return_value=None) - result = get_user_emoji_safe(mock_bot_db, 123) + result = await get_user_emoji_safe(mock_bot_db, 123) assert result == "😊" - def test_get_user_emoji_safe_error(self, mock_bot_db): + @pytest.mark.asyncio + async def test_get_user_emoji_safe_error(self, mock_bot_db): """Тест безопасного получения эмодзи при ошибке""" - mock_bot_db.check_emoji_for_user.return_value = "Ошибка" + mock_bot_db.get_user_emoji = AsyncMock(return_value="Ошибка") - result = get_user_emoji_safe(mock_bot_db, 123) + result = await get_user_emoji_safe(mock_bot_db, 123) assert result == "Ошибка" diff --git a/tests/test_voice_constants.py b/tests/test_voice_constants.py index cc6458b..8a13c31 100644 --- a/tests/test_voice_constants.py +++ b/tests/test_voice_constants.py @@ -151,7 +151,7 @@ class TestVoiceConstants: assert value.startswith("voice_") def test_no_duplicate_values(self): - """Тест отсутствия дублирующихся значений""" + """Тест отсутствия дублирующихся значений в пределах каждого маппинга""" button_values = list(BUTTON_COMMAND_MAPPING.values()) command_values = list(COMMAND_MAPPING.values()) callback_values = list(CALLBACK_COMMAND_MAPPING.values()) @@ -161,9 +161,8 @@ class TestVoiceConstants: assert len(command_values) == len(set(command_values)) assert len(callback_values) == len(set(callback_values)) - # Проверяем, что нет дублирующихся значений между маппингами - all_values = button_values + command_values + callback_values - assert len(all_values) == len(set(all_values)) + # Примечание: Дублирование между маппингами допустимо (например, voice_emoji) + # так как одно действие может быть вызвано и командой, и кнопкой if __name__ == '__main__': diff --git a/tests/test_voice_handler.py b/tests/test_voice_handler.py index 43c5442..46c5f5c 100644 --- a/tests/test_voice_handler.py +++ b/tests/test_voice_handler.py @@ -63,18 +63,23 @@ class TestVoiceHandler: @pytest.mark.asyncio async def test_voice_bot_button_handler_welcome_received(self, voice_handler, mock_message, mock_state, mock_db, mock_settings): """Тест обработчика кнопки когда приветствие уже получено""" - mock_db.check_voice_bot_welcome_received.return_value = True + from unittest.mock import AsyncMock + mock_db.check_voice_bot_welcome_received = AsyncMock(return_value=True) with patch.object(voice_handler, 'restart_function') as mock_restart: - await voice_handler.voice_bot_button_handler(mock_message, mock_state, mock_db, mock_settings) - - mock_db.check_voice_bot_welcome_received.assert_called_once_with(123) - mock_restart.assert_called_once_with(mock_message, mock_state, mock_db, mock_settings) + with patch('helper_bot.handlers.voice.voice_handler.update_user_info') as mock_update_user: + mock_update_user.return_value = None + + await voice_handler.voice_bot_button_handler(mock_message, mock_state, mock_db, mock_settings) + + mock_db.check_voice_bot_welcome_received.assert_called_once_with(123) + mock_restart.assert_called_once_with(mock_message, mock_state, mock_db, mock_settings) @pytest.mark.asyncio async def test_voice_bot_button_handler_welcome_not_received(self, voice_handler, mock_message, mock_state, mock_db, mock_settings): """Тест обработчика кнопки когда приветствие не получено""" - mock_db.check_voice_bot_welcome_received.return_value = False + from unittest.mock import AsyncMock + mock_db.check_voice_bot_welcome_received = AsyncMock(return_value=False) with patch.object(voice_handler, 'start') as mock_start: await voice_handler.voice_bot_button_handler(mock_message, mock_state, mock_db, mock_settings) diff --git a/tests/test_voice_services.py b/tests/test_voice_services.py index 4ec48fe..2af3043 100644 --- a/tests/test_voice_services.py +++ b/tests/test_voice_services.py @@ -80,14 +80,15 @@ class TestVoiceBotService: assert sticker is not None # Проверяем, что стикер не None (метод возвращает FSInputFile объект) - def test_get_random_audio_success(self, voice_service, mock_bot_db): + @pytest.mark.asyncio + async def test_get_random_audio_success(self, voice_service, mock_bot_db): """Тест успешного получения случайного аудио""" - mock_bot_db.check_listen_audio.return_value = ['audio1', 'audio2'] - mock_bot_db.get_user_id_by_file_name.return_value = 123 - mock_bot_db.get_date_by_file_name.return_value = '2025-01-01 12:00:00' - mock_bot_db.check_emoji_for_user.return_value = '😊' + mock_bot_db.check_listen_audio = AsyncMock(return_value=['audio1', 'audio2']) + mock_bot_db.get_user_id_by_file_name = AsyncMock(return_value=123) + mock_bot_db.get_date_by_file_name = AsyncMock(return_value='2025-01-01 12:00:00') + mock_bot_db.get_user_emoji = AsyncMock(return_value='😊') - result = voice_service.get_random_audio(456) + result = await voice_service.get_random_audio(456) assert result is not None assert len(result) == 3 @@ -96,53 +97,63 @@ class TestVoiceBotService: assert result[1] == '2025-01-01 12:00:00' assert result[2] == '😊' - def test_get_random_audio_no_audio(self, voice_service, mock_bot_db): + @pytest.mark.asyncio + async def test_get_random_audio_no_audio(self, voice_service, mock_bot_db): """Тест получения аудио когда их нет""" - mock_bot_db.check_listen_audio.return_value = [] + mock_bot_db.check_listen_audio = AsyncMock(return_value=[]) - result = voice_service.get_random_audio(456) + result = await voice_service.get_random_audio(456) assert result is None - def test_get_random_audio_single_audio(self, voice_service, mock_bot_db): + @pytest.mark.asyncio + async def test_get_random_audio_single_audio(self, voice_service, mock_bot_db): """Тест получения аудио когда есть только одно""" - mock_bot_db.check_listen_audio.return_value = ['audio1'] - mock_bot_db.get_user_id_by_file_name.return_value = 123 - mock_bot_db.get_date_by_file_name.return_value = '2025-01-01 12:00:00' - mock_bot_db.check_emoji_for_user.return_value = '😊' + mock_bot_db.check_listen_audio = AsyncMock(return_value=['audio1']) + mock_bot_db.get_user_id_by_file_name = AsyncMock(return_value=123) + mock_bot_db.get_date_by_file_name = AsyncMock(return_value='2025-01-01 12:00:00') + mock_bot_db.get_user_emoji = AsyncMock(return_value='😊') - result = voice_service.get_random_audio(456) + result = await voice_service.get_random_audio(456) assert result is not None assert len(result) == 3 assert result[0] == 'audio1' - def test_mark_audio_as_listened_success(self, voice_service, mock_bot_db): + @pytest.mark.asyncio + async def test_mark_audio_as_listened_success(self, voice_service, mock_bot_db): """Тест успешной пометки аудио как прослушанного""" - voice_service.mark_audio_as_listened('test_audio', 123) + mock_bot_db.mark_listened_audio = AsyncMock() + + await voice_service.mark_audio_as_listened('test_audio', 123) mock_bot_db.mark_listened_audio.assert_called_once_with('test_audio', user_id=123) - def test_clear_user_listenings_success(self, voice_service, mock_bot_db): + @pytest.mark.asyncio + async def test_clear_user_listenings_success(self, voice_service, mock_bot_db): """Тест успешной очистки прослушиваний""" - voice_service.clear_user_listenings(123) + mock_bot_db.delete_listen_count_for_user = AsyncMock() + + await voice_service.clear_user_listenings(123) mock_bot_db.delete_listen_count_for_user.assert_called_once_with(123) - def test_get_remaining_audio_count_success(self, voice_service, mock_bot_db): + @pytest.mark.asyncio + async def test_get_remaining_audio_count_success(self, voice_service, mock_bot_db): """Тест получения количества оставшихся аудио""" - mock_bot_db.check_listen_audio.return_value = ['audio1', 'audio2', 'audio3'] + mock_bot_db.check_listen_audio = AsyncMock(return_value=['audio1', 'audio2', 'audio3']) - result = voice_service.get_remaining_audio_count(123) + result = await voice_service.get_remaining_audio_count(123) assert result == 3 mock_bot_db.check_listen_audio.assert_called_once_with(user_id=123) - def test_get_remaining_audio_count_zero(self, voice_service, mock_bot_db): + @pytest.mark.asyncio + async def test_get_remaining_audio_count_zero(self, voice_service, mock_bot_db): """Тест получения количества оставшихся аудио когда их нет""" - mock_bot_db.check_listen_audio.return_value = [] + mock_bot_db.check_listen_audio = AsyncMock(return_value=[]) - result = voice_service.get_remaining_audio_count(123) + result = await voice_service.get_remaining_audio_count(123) assert result == 0 mock_bot_db.check_listen_audio.assert_called_once_with(user_id=123) diff --git a/tests/test_voice_utils.py b/tests/test_voice_utils.py index fb0f9c2..9b2053e 100644 --- a/tests/test_voice_utils.py +++ b/tests/test_voice_utils.py @@ -39,70 +39,83 @@ class TestVoiceUtils: message.chat.id = 456 return message - def test_get_last_message_text(self, mock_bot_db): + @pytest.mark.asyncio + async def test_get_last_message_text(self, mock_bot_db): """Тест получения последнего сообщения""" - mock_bot_db.last_date_audio.return_value = "2025-01-01 12:00:00" + # Возвращаем UNIX timestamp + from unittest.mock import AsyncMock + mock_bot_db.last_date_audio = AsyncMock(return_value=1641034800) # 2022-01-01 12:00:00 - result = get_last_message_text(mock_bot_db) + result = await get_last_message_text(mock_bot_db) assert result is not None - assert "минут" in result or "часа" in result or "дня" in result + assert "минут" in result or "часа" in result or "дня" in result or "день" in result or "дней" in result mock_bot_db.last_date_audio.assert_called_once() - def test_validate_voice_message_valid(self): + @pytest.mark.asyncio + async def test_validate_voice_message_valid(self): """Тест валидации голосового сообщения""" mock_message = Mock() mock_message.content_type = 'voice' mock_message.voice = Mock() - result = validate_voice_message(mock_message) + result = await validate_voice_message(mock_message) assert result is True - def test_validate_voice_message_invalid(self): + @pytest.mark.asyncio + async def test_validate_voice_message_invalid(self): """Тест валидации невалидного сообщения""" mock_message = Mock() mock_message.voice = None - result = validate_voice_message(mock_message) + result = await validate_voice_message(mock_message) assert result is False - def test_get_user_emoji_safe_with_emoji(self, mock_bot_db): + @pytest.mark.asyncio + async def test_get_user_emoji_safe_with_emoji(self, mock_bot_db): """Тест безопасного получения эмодзи пользователя когда эмодзи есть""" - mock_bot_db.check_emoji_for_user.return_value = "😊" + from unittest.mock import AsyncMock + mock_bot_db.get_user_emoji = AsyncMock(return_value="😊") - result = get_user_emoji_safe(mock_bot_db, 123) + result = await get_user_emoji_safe(mock_bot_db, 123) assert result == "😊" - mock_bot_db.check_emoji_for_user.assert_called_once_with(123) + mock_bot_db.get_user_emoji.assert_called_once_with(123) - def test_get_user_emoji_safe_without_emoji(self, mock_bot_db): + @pytest.mark.asyncio + async def test_get_user_emoji_safe_without_emoji(self, mock_bot_db): """Тест безопасного получения эмодзи пользователя когда эмодзи нет""" - mock_bot_db.check_emoji_for_user.return_value = None + from unittest.mock import AsyncMock + mock_bot_db.get_user_emoji = AsyncMock(return_value=None) - result = get_user_emoji_safe(mock_bot_db, 123) + result = await get_user_emoji_safe(mock_bot_db, 123) assert result == "😊" - mock_bot_db.check_emoji_for_user.assert_called_once_with(123) + mock_bot_db.get_user_emoji.assert_called_once_with(123) - def test_get_user_emoji_safe_with_empty_emoji(self, mock_bot_db): + @pytest.mark.asyncio + async def test_get_user_emoji_safe_with_empty_emoji(self, mock_bot_db): """Тест безопасного получения эмодзи пользователя с пустым эмодзи""" - mock_bot_db.check_emoji_for_user.return_value = "" + from unittest.mock import AsyncMock + mock_bot_db.get_user_emoji = AsyncMock(return_value="") - result = get_user_emoji_safe(mock_bot_db, 123) + result = await get_user_emoji_safe(mock_bot_db, 123) assert result == "😊" - mock_bot_db.check_emoji_for_user.assert_called_once_with(123) + mock_bot_db.get_user_emoji.assert_called_once_with(123) - def test_get_user_emoji_safe_with_error(self, mock_bot_db): + @pytest.mark.asyncio + async def test_get_user_emoji_safe_with_error(self, mock_bot_db): """Тест безопасного получения эмодзи пользователя при ошибке""" - mock_bot_db.check_emoji_for_user.return_value = "Ошибка" + from unittest.mock import AsyncMock + mock_bot_db.get_user_emoji = AsyncMock(return_value="Ошибка") - result = get_user_emoji_safe(mock_bot_db, 123) + result = await get_user_emoji_safe(mock_bot_db, 123) assert result == "Ошибка" - mock_bot_db.check_emoji_for_user.assert_called_once_with(123) + mock_bot_db.get_user_emoji.assert_called_once_with(123) def test_format_time_ago_minutes(self): """Тест форматирования времени в минутах"""