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.
This commit is contained in:
2025-09-03 00:33:20 +03:00
parent 6fcecff97c
commit c8c7d50cbb
19 changed files with 402 additions and 605 deletions

View File

@@ -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")

View File

@@ -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):

View File

@@ -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:

View File

@@ -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()

View File

@@ -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()

View File

@@ -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)

View File

@@ -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()

View File

@@ -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:

View File

@@ -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()

View File

@@ -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 == "Ошибка"

View File

@@ -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__':

View File

@@ -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)

View File

@@ -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)

View File

@@ -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):
"""Тест форматирования времени в минутах"""