fix quality code
This commit is contained in:
@@ -6,13 +6,13 @@ from unittest.mock import AsyncMock, Mock, patch
|
||||
import pytest
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import Chat, Message, User
|
||||
from database.async_db import AsyncBotDB
|
||||
|
||||
# Импортируем моки в самом начале
|
||||
import tests.mocks
|
||||
from database.async_db import AsyncBotDB
|
||||
|
||||
# Настройка pytest-asyncio
|
||||
pytest_plugins = ('pytest_asyncio',)
|
||||
pytest_plugins = ("pytest_asyncio",)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@@ -93,19 +93,16 @@ def mock_dispatcher():
|
||||
def test_settings():
|
||||
"""Возвращает тестовые настройки"""
|
||||
return {
|
||||
'Telegram': {
|
||||
'bot_token': 'test_token_123',
|
||||
'preview_link': False,
|
||||
'group_for_posts': '-1001234567890',
|
||||
'group_for_message': '-1001234567891',
|
||||
'main_public': '-1001234567892',
|
||||
'group_for_logs': '-1001234567893',
|
||||
'important_logs': '-1001234567894'
|
||||
"Telegram": {
|
||||
"bot_token": "test_token_123",
|
||||
"preview_link": False,
|
||||
"group_for_posts": "-1001234567890",
|
||||
"group_for_message": "-1001234567891",
|
||||
"main_public": "-1001234567892",
|
||||
"group_for_logs": "-1001234567893",
|
||||
"important_logs": "-1001234567894",
|
||||
},
|
||||
'Settings': {
|
||||
'logs': True,
|
||||
'test': False
|
||||
}
|
||||
"Settings": {"logs": True, "test": False},
|
||||
}
|
||||
|
||||
|
||||
@@ -122,71 +119,71 @@ def mock_factory(test_settings, mock_db):
|
||||
@pytest.fixture
|
||||
def sample_photo_message(mock_message):
|
||||
"""Создает сообщение с фото для тестов"""
|
||||
mock_message.content_type = 'photo'
|
||||
mock_message.caption = 'Тестовое фото'
|
||||
mock_message.content_type = "photo"
|
||||
mock_message.caption = "Тестовое фото"
|
||||
mock_message.media_group_id = None
|
||||
mock_message.photo = [Mock()]
|
||||
mock_message.photo[-1].file_id = 'photo_file_id'
|
||||
mock_message.photo[-1].file_id = "photo_file_id"
|
||||
return mock_message
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_video_message(mock_message):
|
||||
"""Создает сообщение с видео для тестов"""
|
||||
mock_message.content_type = 'video'
|
||||
mock_message.caption = 'Тестовое видео'
|
||||
mock_message.content_type = "video"
|
||||
mock_message.caption = "Тестовое видео"
|
||||
mock_message.media_group_id = None
|
||||
mock_message.video = Mock()
|
||||
mock_message.video.file_id = 'video_file_id'
|
||||
mock_message.video.file_id = "video_file_id"
|
||||
return mock_message
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_audio_message(mock_message):
|
||||
"""Создает сообщение с аудио для тестов"""
|
||||
mock_message.content_type = 'audio'
|
||||
mock_message.caption = 'Тестовое аудио'
|
||||
mock_message.content_type = "audio"
|
||||
mock_message.caption = "Тестовое аудио"
|
||||
mock_message.media_group_id = None
|
||||
mock_message.audio = Mock()
|
||||
mock_message.audio.file_id = 'audio_file_id'
|
||||
mock_message.audio.file_id = "audio_file_id"
|
||||
return mock_message
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_voice_message(mock_message):
|
||||
"""Создает голосовое сообщение для тестов"""
|
||||
mock_message.content_type = 'voice'
|
||||
mock_message.content_type = "voice"
|
||||
mock_message.media_group_id = None
|
||||
mock_message.voice = Mock()
|
||||
mock_message.voice.file_id = 'voice_file_id'
|
||||
mock_message.voice.file_id = "voice_file_id"
|
||||
return mock_message
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_video_note_message(mock_message):
|
||||
"""Создает видеокружок для тестов"""
|
||||
mock_message.content_type = 'video_note'
|
||||
mock_message.content_type = "video_note"
|
||||
mock_message.media_group_id = None
|
||||
mock_message.video_note = Mock()
|
||||
mock_message.video_note.file_id = 'video_note_file_id'
|
||||
mock_message.video_note.file_id = "video_note_file_id"
|
||||
return mock_message
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_media_group(mock_message):
|
||||
"""Создает медиагруппу для тестов"""
|
||||
mock_message.media_group_id = 'group_123'
|
||||
mock_message.content_type = 'photo'
|
||||
mock_message.media_group_id = "group_123"
|
||||
mock_message.content_type = "photo"
|
||||
album = [mock_message]
|
||||
album[0].caption = 'Подпись к медиагруппе'
|
||||
album[0].caption = "Подпись к медиагруппе"
|
||||
return album
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_text_message(mock_message):
|
||||
"""Создает текстовое сообщение для тестов"""
|
||||
mock_message.content_type = 'text'
|
||||
mock_message.text = 'Тестовое текстовое сообщение'
|
||||
mock_message.content_type = "text"
|
||||
mock_message.text = "Тестовое текстовое сообщение"
|
||||
mock_message.media_group_id = None
|
||||
return mock_message
|
||||
|
||||
@@ -194,7 +191,7 @@ def sample_text_message(mock_message):
|
||||
@pytest.fixture
|
||||
def sample_document_message(mock_message):
|
||||
"""Создает сообщение с документом для тестов"""
|
||||
mock_message.content_type = 'document'
|
||||
mock_message.content_type = "document"
|
||||
mock_message.media_group_id = None
|
||||
return mock_message
|
||||
|
||||
@@ -202,18 +199,10 @@ def sample_document_message(mock_message):
|
||||
# Маркеры для категоризации тестов
|
||||
def pytest_configure(config):
|
||||
"""Настройка маркеров pytest"""
|
||||
config.addinivalue_line(
|
||||
"markers", "asyncio: mark test as async"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "slow: mark test as slow"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "integration: mark test as integration test"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "unit: mark test as unit test"
|
||||
)
|
||||
config.addinivalue_line("markers", "asyncio: mark test as async")
|
||||
config.addinivalue_line("markers", "slow: mark test as slow")
|
||||
config.addinivalue_line("markers", "integration: mark test as integration test")
|
||||
config.addinivalue_line("markers", "unit: mark test as unit test")
|
||||
|
||||
|
||||
# Автоматическая маркировка тестов
|
||||
@@ -223,15 +212,15 @@ def pytest_collection_modifyitems(config, items):
|
||||
# Маркируем асинхронные тесты
|
||||
if "async" in item.name or "Async" in item.name:
|
||||
item.add_marker(pytest.mark.asyncio)
|
||||
|
||||
|
||||
# Маркируем интеграционные тесты
|
||||
if "integration" in item.name.lower() or "Integration" in str(item.cls):
|
||||
item.add_marker(pytest.mark.integration)
|
||||
|
||||
|
||||
# Маркируем unit тесты
|
||||
if "unit" in item.name.lower() or "Unit" in str(item.cls):
|
||||
item.add_marker(pytest.mark.unit)
|
||||
|
||||
|
||||
# Маркируем медленные тесты
|
||||
if "slow" in item.name.lower() or "Slow" in str(item.cls):
|
||||
item.add_marker(pytest.mark.slow)
|
||||
|
||||
@@ -3,6 +3,7 @@ import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from database.models import UserMessage
|
||||
from database.repositories.message_repository import MessageRepository
|
||||
|
||||
@@ -10,11 +11,11 @@ from database.repositories.message_repository import MessageRepository
|
||||
@pytest.fixture(scope="session")
|
||||
def test_db_path():
|
||||
"""Фикстура для пути к тестовой БД (сессионная область)."""
|
||||
with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
|
||||
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
|
||||
temp_path = f.name
|
||||
|
||||
|
||||
yield temp_path
|
||||
|
||||
|
||||
# Очистка после всех тестов
|
||||
try:
|
||||
os.unlink(temp_path)
|
||||
@@ -32,26 +33,26 @@ def message_repository(test_db_path):
|
||||
def sample_messages():
|
||||
"""Фикстура для набора тестовых сообщений."""
|
||||
base_timestamp = int(datetime.now().timestamp())
|
||||
|
||||
|
||||
return [
|
||||
UserMessage(
|
||||
message_text="Первое тестовое сообщение",
|
||||
user_id=1001,
|
||||
telegram_message_id=2001,
|
||||
date=base_timestamp
|
||||
date=base_timestamp,
|
||||
),
|
||||
UserMessage(
|
||||
message_text="Второе тестовое сообщение",
|
||||
user_id=1002,
|
||||
telegram_message_id=2002,
|
||||
date=base_timestamp + 1
|
||||
date=base_timestamp + 1,
|
||||
),
|
||||
UserMessage(
|
||||
message_text="Третье тестовое сообщение",
|
||||
user_id=1003,
|
||||
telegram_message_id=2003,
|
||||
date=base_timestamp + 2
|
||||
)
|
||||
date=base_timestamp + 2,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -62,7 +63,7 @@ def message_without_date():
|
||||
message_text="Сообщение без даты",
|
||||
user_id=1004,
|
||||
telegram_message_id=2004,
|
||||
date=None
|
||||
date=None,
|
||||
)
|
||||
|
||||
|
||||
@@ -73,7 +74,7 @@ def message_with_zero_date():
|
||||
message_text="Сообщение с нулевой датой",
|
||||
user_id=1005,
|
||||
telegram_message_id=2005,
|
||||
date=0
|
||||
date=0,
|
||||
)
|
||||
|
||||
|
||||
@@ -84,7 +85,7 @@ def message_with_special_chars():
|
||||
message_text="Сообщение с 'кавычками', \"двойными кавычками\" и эмодзи 😊\nНовая строка",
|
||||
user_id=1006,
|
||||
telegram_message_id=2006,
|
||||
date=int(datetime.now().timestamp())
|
||||
date=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
@@ -96,7 +97,7 @@ def long_message():
|
||||
message_text=long_text,
|
||||
user_id=1007,
|
||||
telegram_message_id=2007,
|
||||
date=int(datetime.now().timestamp())
|
||||
date=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
@@ -107,7 +108,7 @@ def message_with_unicode():
|
||||
message_text="Сообщение с Unicode: 你好世界 🌍 Привет мир",
|
||||
user_id=1008,
|
||||
telegram_message_id=2008,
|
||||
date=int(datetime.now().timestamp())
|
||||
date=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from datetime import datetime
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from database.models import MessageContentLink, PostContent, TelegramPost
|
||||
from database.repositories.post_repository import PostRepository
|
||||
|
||||
@@ -37,7 +38,7 @@ def sample_telegram_post():
|
||||
text="Тестовый пост для unit тестов",
|
||||
author_id=67890,
|
||||
helper_text_message_id=None,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
@@ -49,7 +50,7 @@ def sample_telegram_post_with_helper():
|
||||
text="Тестовый пост с helper сообщением",
|
||||
author_id=67890,
|
||||
helper_text_message_id=99999,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
@@ -61,7 +62,7 @@ def sample_telegram_post_no_date():
|
||||
text="Тестовый пост без даты",
|
||||
author_id=67890,
|
||||
helper_text_message_id=None,
|
||||
created_at=None
|
||||
created_at=None,
|
||||
)
|
||||
|
||||
|
||||
@@ -69,19 +70,14 @@ def sample_telegram_post_no_date():
|
||||
def sample_post_content():
|
||||
"""Создает тестовый объект PostContent"""
|
||||
return PostContent(
|
||||
message_id=12345,
|
||||
content_name="/path/to/test/file.jpg",
|
||||
content_type="photo"
|
||||
message_id=12345, content_name="/path/to/test/file.jpg", content_type="photo"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_message_content_link():
|
||||
"""Создает тестовый объект MessageContentLink"""
|
||||
return MessageContentLink(
|
||||
post_id=12345,
|
||||
message_id=67890
|
||||
)
|
||||
return MessageContentLink(post_id=12345, message_id=67890)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -105,11 +101,11 @@ def mock_logger():
|
||||
@pytest.fixture
|
||||
def temp_db_file():
|
||||
"""Создает временный файл БД для интеграционных тестов"""
|
||||
with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as tmp_file:
|
||||
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp_file:
|
||||
db_path = tmp_file.name
|
||||
|
||||
|
||||
yield db_path
|
||||
|
||||
|
||||
# Очищаем временный файл после тестов
|
||||
try:
|
||||
os.unlink(db_path)
|
||||
@@ -132,22 +128,22 @@ def sample_posts_batch():
|
||||
text="Первый тестовый пост",
|
||||
author_id=11111,
|
||||
helper_text_message_id=None,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
),
|
||||
TelegramPost(
|
||||
message_id=10002,
|
||||
text="Второй тестовый пост",
|
||||
author_id=22222,
|
||||
helper_text_message_id=None,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
),
|
||||
TelegramPost(
|
||||
message_id=10003,
|
||||
text="Третий тестовый пост",
|
||||
author_id=33333,
|
||||
helper_text_message_id=88888,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
)
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -159,7 +155,7 @@ def sample_content_batch():
|
||||
(10002, "/path/to/video1.mp4", "video"),
|
||||
(10003, "/path/to/audio1.mp3", "audio"),
|
||||
(10004, "/path/to/photo2.jpg", "photo"),
|
||||
(10005, "/path/to/video2.mp4", "video")
|
||||
(10005, "/path/to/video2.mp4", "video"),
|
||||
]
|
||||
|
||||
|
||||
@@ -195,19 +191,19 @@ def sample_author_ids():
|
||||
def mock_sql_queries():
|
||||
"""Создает мок для SQL запросов"""
|
||||
return {
|
||||
'create_tables': [
|
||||
"create_tables": [
|
||||
"CREATE TABLE IF NOT EXISTS post_from_telegram_suggest",
|
||||
"CREATE TABLE IF NOT EXISTS content_post_from_telegram",
|
||||
"CREATE TABLE IF NOT EXISTS message_link_to_content"
|
||||
"CREATE TABLE IF NOT EXISTS message_link_to_content",
|
||||
],
|
||||
'add_post': "INSERT INTO post_from_telegram_suggest",
|
||||
'add_post_status': "status",
|
||||
'update_helper': "UPDATE post_from_telegram_suggest SET helper_text_message_id",
|
||||
'update_status': "UPDATE post_from_telegram_suggest SET status = ?",
|
||||
'add_content': "INSERT OR IGNORE INTO content_post_from_telegram",
|
||||
'add_link': "INSERT OR IGNORE INTO message_link_to_content",
|
||||
'get_content': "SELECT cpft.content_name, cpft.content_type",
|
||||
'get_text': "SELECT text FROM post_from_telegram_suggest",
|
||||
'get_ids': "SELECT mltc.message_id",
|
||||
'get_author': "SELECT author_id FROM post_from_telegram_suggest"
|
||||
"add_post": "INSERT INTO post_from_telegram_suggest",
|
||||
"add_post_status": "status",
|
||||
"update_helper": "UPDATE post_from_telegram_suggest SET helper_text_message_id",
|
||||
"update_status": "UPDATE post_from_telegram_suggest SET status = ?",
|
||||
"add_content": "INSERT OR IGNORE INTO content_post_from_telegram",
|
||||
"add_link": "INSERT OR IGNORE INTO message_link_to_content",
|
||||
"get_content": "SELECT cpft.content_name, cpft.content_type",
|
||||
"get_text": "SELECT text FROM post_from_telegram_suggest",
|
||||
"get_ids": "SELECT mltc.message_id",
|
||||
"get_author": "SELECT author_id FROM post_from_telegram_suggest",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Моки для тестового окружения
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import Mock, patch
|
||||
@@ -11,33 +12,34 @@ def setup_test_mocks():
|
||||
"""Настройка моков для тестов"""
|
||||
# Мокаем os.getenv
|
||||
mock_env_vars = {
|
||||
'BOT_TOKEN': 'test_token_123',
|
||||
'LISTEN_BOT_TOKEN': '',
|
||||
'TEST_BOT_TOKEN': '',
|
||||
'PREVIEW_LINK': 'False',
|
||||
'MAIN_PUBLIC': '@test',
|
||||
'GROUP_FOR_POSTS': '-1001234567890',
|
||||
'GROUP_FOR_MESSAGE': '-1001234567891',
|
||||
'GROUP_FOR_LOGS': '-1001234567893',
|
||||
'IMPORTANT_LOGS': '-1001234567894',
|
||||
'TEST_GROUP': '-1001234567895',
|
||||
'LOGS': 'True',
|
||||
'TEST': 'False',
|
||||
'DATABASE_PATH': 'database/test.db'
|
||||
"BOT_TOKEN": "test_token_123",
|
||||
"LISTEN_BOT_TOKEN": "",
|
||||
"TEST_BOT_TOKEN": "",
|
||||
"PREVIEW_LINK": "False",
|
||||
"MAIN_PUBLIC": "@test",
|
||||
"GROUP_FOR_POSTS": "-1001234567890",
|
||||
"GROUP_FOR_MESSAGE": "-1001234567891",
|
||||
"GROUP_FOR_LOGS": "-1001234567893",
|
||||
"IMPORTANT_LOGS": "-1001234567894",
|
||||
"TEST_GROUP": "-1001234567895",
|
||||
"LOGS": "True",
|
||||
"TEST": "False",
|
||||
"DATABASE_PATH": "database/test.db",
|
||||
}
|
||||
|
||||
def mock_getenv(key, default=None):
|
||||
return mock_env_vars.get(key, default)
|
||||
|
||||
env_patcher = patch('os.getenv', side_effect=mock_getenv)
|
||||
env_patcher = patch("os.getenv", side_effect=mock_getenv)
|
||||
env_patcher.start()
|
||||
|
||||
# Мокаем AsyncBotDB
|
||||
mock_db = Mock()
|
||||
db_patcher = patch('helper_bot.utils.base_dependency_factory.AsyncBotDB', mock_db)
|
||||
db_patcher = patch("helper_bot.utils.base_dependency_factory.AsyncBotDB", mock_db)
|
||||
db_patcher.start()
|
||||
|
||||
|
||||
return env_patcher, db_patcher
|
||||
|
||||
|
||||
# Настраиваем моки при импорте модуля
|
||||
env_patcher, db_patcher = setup_test_mocks()
|
||||
env_patcher, db_patcher = setup_test_mocks()
|
||||
|
||||
@@ -3,13 +3,14 @@ from datetime import datetime
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from database.models import Admin
|
||||
from database.repositories.admin_repository import AdminRepository
|
||||
|
||||
|
||||
class TestAdminRepository:
|
||||
"""Тесты для AdminRepository"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db_connection(self):
|
||||
"""Мок для DatabaseConnection"""
|
||||
@@ -18,137 +19,142 @@ class TestAdminRepository:
|
||||
mock_connection._execute_query_with_result = AsyncMock()
|
||||
mock_connection.logger = Mock()
|
||||
return mock_connection
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def admin_repository(self, mock_db_connection):
|
||||
"""Экземпляр AdminRepository для тестов"""
|
||||
# Патчим наследование от DatabaseConnection
|
||||
with patch.object(AdminRepository, '__init__', return_value=None):
|
||||
with patch.object(AdminRepository, "__init__", return_value=None):
|
||||
repo = AdminRepository()
|
||||
repo._execute_query = mock_db_connection._execute_query
|
||||
repo._execute_query_with_result = mock_db_connection._execute_query_with_result
|
||||
repo._execute_query_with_result = (
|
||||
mock_db_connection._execute_query_with_result
|
||||
)
|
||||
repo.logger = mock_db_connection.logger
|
||||
return repo
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_admin(self):
|
||||
"""Тестовый администратор"""
|
||||
return Admin(
|
||||
user_id=12345,
|
||||
role="admin"
|
||||
)
|
||||
|
||||
return Admin(user_id=12345, role="admin")
|
||||
|
||||
@pytest.fixture
|
||||
def sample_admin_with_created_at(self):
|
||||
"""Тестовый администратор с датой создания"""
|
||||
return Admin(
|
||||
user_id=12345,
|
||||
role="admin",
|
||||
created_at="1705312200" # UNIX timestamp
|
||||
user_id=12345, role="admin", created_at="1705312200" # UNIX timestamp
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_tables(self, admin_repository):
|
||||
"""Тест создания таблицы администраторов"""
|
||||
await admin_repository.create_tables()
|
||||
|
||||
|
||||
# Проверяем, что включены внешние ключи
|
||||
admin_repository._execute_query.assert_called()
|
||||
calls = admin_repository._execute_query.call_args_list
|
||||
|
||||
|
||||
# Первый вызов должен быть для включения внешних ключей
|
||||
assert calls[0][0][0] == "PRAGMA foreign_keys = ON"
|
||||
|
||||
|
||||
# Второй вызов должен быть для создания таблицы
|
||||
create_table_call = calls[1]
|
||||
assert "CREATE TABLE IF NOT EXISTS admins" in create_table_call[0][0]
|
||||
assert "user_id INTEGER NOT NULL PRIMARY KEY" in create_table_call[0][0]
|
||||
assert "role TEXT DEFAULT 'admin'" in create_table_call[0][0]
|
||||
assert "created_at INTEGER DEFAULT (strftime('%s', 'now'))" in create_table_call[0][0]
|
||||
assert "FOREIGN KEY (user_id) REFERENCES our_users (user_id) ON DELETE CASCADE" in create_table_call[0][0]
|
||||
|
||||
assert (
|
||||
"created_at INTEGER DEFAULT (strftime('%s', 'now'))"
|
||||
in create_table_call[0][0]
|
||||
)
|
||||
assert (
|
||||
"FOREIGN KEY (user_id) REFERENCES our_users (user_id) ON DELETE CASCADE"
|
||||
in create_table_call[0][0]
|
||||
)
|
||||
|
||||
# Проверяем логирование
|
||||
admin_repository.logger.info.assert_called_once_with("Таблица администраторов создана")
|
||||
|
||||
admin_repository.logger.info.assert_called_once_with(
|
||||
"Таблица администраторов создана"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_admin(self, admin_repository, sample_admin):
|
||||
"""Тест добавления администратора"""
|
||||
await admin_repository.add_admin(sample_admin)
|
||||
|
||||
|
||||
# Проверяем, что метод вызван с правильными параметрами
|
||||
admin_repository._execute_query.assert_called_once()
|
||||
call_args = admin_repository._execute_query.call_args
|
||||
|
||||
|
||||
assert call_args[0][0] == "INSERT INTO admins (user_id, role) VALUES (?, ?)"
|
||||
assert call_args[0][1] == (12345, "admin")
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
admin_repository.logger.info.assert_called_once_with(
|
||||
"Администратор добавлен: user_id=12345, role=admin"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_admin_with_custom_role(self, admin_repository):
|
||||
"""Тест добавления администратора с кастомной ролью"""
|
||||
admin = Admin(user_id=67890, role="super_admin")
|
||||
await admin_repository.add_admin(admin)
|
||||
|
||||
|
||||
call_args = admin_repository._execute_query.call_args
|
||||
assert call_args[0][1] == (67890, "super_admin")
|
||||
|
||||
|
||||
admin_repository.logger.info.assert_called_once_with(
|
||||
"Администратор добавлен: user_id=67890, role=super_admin"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remove_admin(self, admin_repository):
|
||||
"""Тест удаления администратора"""
|
||||
user_id = 12345
|
||||
await admin_repository.remove_admin(user_id)
|
||||
|
||||
|
||||
# Проверяем, что метод вызван с правильными параметрами
|
||||
admin_repository._execute_query.assert_called_once()
|
||||
call_args = admin_repository._execute_query.call_args
|
||||
|
||||
|
||||
assert call_args[0][0] == "DELETE FROM admins WHERE user_id = ?"
|
||||
assert call_args[0][1] == (user_id,)
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
admin_repository.logger.info.assert_called_once_with(
|
||||
"Администратор удален: user_id=12345"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_is_admin_true(self, admin_repository):
|
||||
"""Тест проверки администратора - пользователь является администратором"""
|
||||
user_id = 12345
|
||||
# Мокаем результат запроса - пользователь найден
|
||||
admin_repository._execute_query_with_result.return_value = [(1,)]
|
||||
|
||||
|
||||
result = await admin_repository.is_admin(user_id)
|
||||
|
||||
|
||||
# Проверяем, что метод вызван с правильными параметрами
|
||||
admin_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = admin_repository._execute_query_with_result.call_args
|
||||
|
||||
|
||||
assert call_args[0][0] == "SELECT 1 FROM admins WHERE user_id = ?"
|
||||
assert call_args[0][1] == (user_id,)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_is_admin_false(self, admin_repository):
|
||||
"""Тест проверки администратора - пользователь не является администратором"""
|
||||
user_id = 12345
|
||||
# Мокаем результат запроса - пользователь не найден
|
||||
admin_repository._execute_query_with_result.return_value = []
|
||||
|
||||
|
||||
result = await admin_repository.is_admin(user_id)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_admin_found(self, admin_repository):
|
||||
"""Тест получения информации об администраторе - администратор найден"""
|
||||
@@ -157,138 +163,143 @@ class TestAdminRepository:
|
||||
admin_repository._execute_query_with_result.return_value = [
|
||||
(12345, "admin", "1705312200")
|
||||
]
|
||||
|
||||
|
||||
result = await admin_repository.get_admin(user_id)
|
||||
|
||||
|
||||
# Проверяем, что метод вызван с правильными параметрами
|
||||
admin_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = admin_repository._execute_query_with_result.call_args
|
||||
|
||||
assert call_args[0][0] == "SELECT user_id, role, created_at FROM admins WHERE user_id = ?"
|
||||
|
||||
assert (
|
||||
call_args[0][0]
|
||||
== "SELECT user_id, role, created_at FROM admins WHERE user_id = ?"
|
||||
)
|
||||
assert call_args[0][1] == (user_id,)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result is not None
|
||||
assert result.user_id == 12345
|
||||
assert result.role == "admin"
|
||||
assert result.created_at == "1705312200"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_admin_not_found(self, admin_repository):
|
||||
"""Тест получения информации об администраторе - администратор не найден"""
|
||||
user_id = 12345
|
||||
# Мокаем результат запроса - администратор не найден
|
||||
admin_repository._execute_query_with_result.return_value = []
|
||||
|
||||
|
||||
result = await admin_repository.get_admin(user_id)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_admin_without_created_at(self, admin_repository):
|
||||
"""Тест получения информации об администраторе без даты создания"""
|
||||
user_id = 12345
|
||||
# Мокаем результат запроса без created_at
|
||||
admin_repository._execute_query_with_result.return_value = [
|
||||
(12345, "admin")
|
||||
]
|
||||
|
||||
admin_repository._execute_query_with_result.return_value = [(12345, "admin")]
|
||||
|
||||
result = await admin_repository.get_admin(user_id)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result is not None
|
||||
assert result.user_id == 12345
|
||||
assert result.role == "admin"
|
||||
assert result.created_at is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_admin_error_handling(self, admin_repository, sample_admin):
|
||||
"""Тест обработки ошибок при добавлении администратора"""
|
||||
# Мокаем ошибку при выполнении запроса
|
||||
admin_repository._execute_query.side_effect = Exception("Database error")
|
||||
|
||||
|
||||
with pytest.raises(Exception, match="Database error"):
|
||||
await admin_repository.add_admin(sample_admin)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remove_admin_error_handling(self, admin_repository):
|
||||
"""Тест обработки ошибок при удалении администратора"""
|
||||
# Мокаем ошибку при выполнении запроса
|
||||
admin_repository._execute_query.side_effect = Exception("Database error")
|
||||
|
||||
|
||||
with pytest.raises(Exception, match="Database error"):
|
||||
await admin_repository.remove_admin(12345)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_is_admin_error_handling(self, admin_repository):
|
||||
"""Тест обработки ошибок при проверке администратора"""
|
||||
# Мокаем ошибку при выполнении запроса
|
||||
admin_repository._execute_query_with_result.side_effect = Exception("Database error")
|
||||
|
||||
admin_repository._execute_query_with_result.side_effect = Exception(
|
||||
"Database error"
|
||||
)
|
||||
|
||||
with pytest.raises(Exception, match="Database error"):
|
||||
await admin_repository.is_admin(12345)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_admin_error_handling(self, admin_repository):
|
||||
"""Тест обработки ошибок при получении информации об администраторе"""
|
||||
# Мокаем ошибку при выполнении запроса
|
||||
admin_repository._execute_query_with_result.side_effect = Exception("Database error")
|
||||
|
||||
admin_repository._execute_query_with_result.side_effect = Exception(
|
||||
"Database error"
|
||||
)
|
||||
|
||||
with pytest.raises(Exception, match="Database error"):
|
||||
await admin_repository.get_admin(12345)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_tables_error_handling(self, admin_repository):
|
||||
"""Тест обработки ошибок при создании таблиц"""
|
||||
# Мокаем ошибку при выполнении первого запроса
|
||||
admin_repository._execute_query.side_effect = Exception("Database error")
|
||||
|
||||
|
||||
with pytest.raises(Exception, match="Database error"):
|
||||
await admin_repository.create_tables()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_model_compatibility(self, admin_repository):
|
||||
"""Тест совместимости с моделью Admin"""
|
||||
user_id = 12345
|
||||
role = "moderator"
|
||||
|
||||
|
||||
# Создаем администратора с помощью модели
|
||||
admin = Admin(user_id=user_id, role=role)
|
||||
|
||||
|
||||
# Проверяем, что можем передать его в репозиторий
|
||||
await admin_repository.add_admin(admin)
|
||||
|
||||
|
||||
# Проверяем, что вызов был с правильными параметрами
|
||||
call_args = admin_repository._execute_query.call_args
|
||||
assert call_args[0][1] == (user_id, role)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiple_admin_operations(self, admin_repository):
|
||||
"""Тест множественных операций с администраторами"""
|
||||
# Добавляем первого администратора
|
||||
admin1 = Admin(user_id=111, role="admin")
|
||||
await admin_repository.add_admin(admin1)
|
||||
|
||||
|
||||
# Добавляем второго администратора
|
||||
admin2 = Admin(user_id=222, role="moderator")
|
||||
await admin_repository.add_admin(admin2)
|
||||
|
||||
|
||||
# Проверяем, что оба добавлены
|
||||
assert admin_repository._execute_query.call_count == 2
|
||||
|
||||
|
||||
# Проверяем, что первый администратор существует
|
||||
admin_repository._execute_query_with_result.return_value = [(1,)]
|
||||
result1 = await admin_repository.is_admin(111)
|
||||
assert result1 is True
|
||||
|
||||
|
||||
# Проверяем, что второй администратор существует
|
||||
result2 = await admin_repository.is_admin(222)
|
||||
assert result2 is True
|
||||
|
||||
|
||||
# Удаляем первого администратора
|
||||
await admin_repository.remove_admin(111)
|
||||
|
||||
|
||||
# Проверяем, что он больше не существует
|
||||
admin_repository._execute_query_with_result.return_value = []
|
||||
result3 = await admin_repository.is_admin(111)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from database.async_db import AsyncBotDB
|
||||
|
||||
|
||||
class TestAsyncBotDB:
|
||||
"""Тесты для AsyncBotDB"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_factory(self):
|
||||
"""Мок для RepositoryFactory"""
|
||||
@@ -23,94 +24,130 @@ class TestAsyncBotDB:
|
||||
mock_factory.blacklist_history.add_record_on_ban = AsyncMock()
|
||||
mock_factory.blacklist_history.set_unban_date = AsyncMock(return_value=True)
|
||||
return mock_factory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def async_bot_db(self, mock_factory):
|
||||
"""Экземпляр AsyncBotDB для тестов"""
|
||||
with patch('database.async_db.RepositoryFactory') as mock_factory_class:
|
||||
with patch("database.async_db.RepositoryFactory") as mock_factory_class:
|
||||
mock_factory_class.return_value = mock_factory
|
||||
db = AsyncBotDB("test.db")
|
||||
return db
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_audio_moderate_record(self, async_bot_db, mock_factory):
|
||||
"""Тест метода delete_audio_moderate_record"""
|
||||
message_id = 12345
|
||||
|
||||
|
||||
await async_bot_db.delete_audio_moderate_record(message_id)
|
||||
|
||||
|
||||
# Проверяем, что метод вызван в репозитории
|
||||
mock_factory.audio.delete_audio_moderate_record.assert_called_once_with(message_id)
|
||||
|
||||
mock_factory.audio.delete_audio_moderate_record.assert_called_once_with(
|
||||
message_id
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_audio_moderate_record_with_different_message_id(self, async_bot_db, mock_factory):
|
||||
async def test_delete_audio_moderate_record_with_different_message_id(
|
||||
self, async_bot_db, mock_factory
|
||||
):
|
||||
"""Тест метода delete_audio_moderate_record с разными message_id"""
|
||||
test_cases = [123, 456, 789, 99999]
|
||||
|
||||
|
||||
for message_id in test_cases:
|
||||
await async_bot_db.delete_audio_moderate_record(message_id)
|
||||
mock_factory.audio.delete_audio_moderate_record.assert_called_with(message_id)
|
||||
|
||||
mock_factory.audio.delete_audio_moderate_record.assert_called_with(
|
||||
message_id
|
||||
)
|
||||
|
||||
# Проверяем, что метод вызван для каждого message_id
|
||||
assert mock_factory.audio.delete_audio_moderate_record.call_count == len(test_cases)
|
||||
|
||||
assert mock_factory.audio.delete_audio_moderate_record.call_count == len(
|
||||
test_cases
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_audio_moderate_record_exception_handling(self, async_bot_db, mock_factory):
|
||||
async def test_delete_audio_moderate_record_exception_handling(
|
||||
self, async_bot_db, mock_factory
|
||||
):
|
||||
"""Тест обработки исключений в delete_audio_moderate_record"""
|
||||
message_id = 12345
|
||||
mock_factory.audio.delete_audio_moderate_record.side_effect = Exception("Database error")
|
||||
|
||||
mock_factory.audio.delete_audio_moderate_record.side_effect = Exception(
|
||||
"Database error"
|
||||
)
|
||||
|
||||
# Метод должен пробросить исключение
|
||||
with pytest.raises(Exception, match="Database error"):
|
||||
await async_bot_db.delete_audio_moderate_record(message_id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_audio_moderate_record_integration_with_other_methods(self, async_bot_db, mock_factory):
|
||||
async def test_delete_audio_moderate_record_integration_with_other_methods(
|
||||
self, async_bot_db, mock_factory
|
||||
):
|
||||
"""Тест интеграции delete_audio_moderate_record с другими методами"""
|
||||
message_id = 12345
|
||||
user_id = 67890
|
||||
|
||||
|
||||
# Мокаем другие методы
|
||||
mock_factory.audio.get_user_id_by_message_id_for_voice_bot = AsyncMock(return_value=user_id)
|
||||
mock_factory.audio.set_user_id_and_message_id_for_voice_bot = AsyncMock(return_value=True)
|
||||
|
||||
mock_factory.audio.get_user_id_by_message_id_for_voice_bot = AsyncMock(
|
||||
return_value=user_id
|
||||
)
|
||||
mock_factory.audio.set_user_id_and_message_id_for_voice_bot = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
|
||||
# Тестируем последовательность операций
|
||||
await async_bot_db.get_user_id_by_message_id_for_voice_bot(message_id)
|
||||
await async_bot_db.set_user_id_and_message_id_for_voice_bot(message_id, user_id)
|
||||
await async_bot_db.delete_audio_moderate_record(message_id)
|
||||
|
||||
|
||||
# Проверяем, что все методы вызваны
|
||||
mock_factory.audio.get_user_id_by_message_id_for_voice_bot.assert_called_once_with(message_id)
|
||||
mock_factory.audio.set_user_id_and_message_id_for_voice_bot.assert_called_once_with(message_id, user_id)
|
||||
mock_factory.audio.delete_audio_moderate_record.assert_called_once_with(message_id)
|
||||
|
||||
mock_factory.audio.get_user_id_by_message_id_for_voice_bot.assert_called_once_with(
|
||||
message_id
|
||||
)
|
||||
mock_factory.audio.set_user_id_and_message_id_for_voice_bot.assert_called_once_with(
|
||||
message_id, user_id
|
||||
)
|
||||
mock_factory.audio.delete_audio_moderate_record.assert_called_once_with(
|
||||
message_id
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_audio_moderate_record_zero_message_id(self, async_bot_db, mock_factory):
|
||||
async def test_delete_audio_moderate_record_zero_message_id(
|
||||
self, async_bot_db, mock_factory
|
||||
):
|
||||
"""Тест delete_audio_moderate_record с message_id = 0"""
|
||||
message_id = 0
|
||||
|
||||
|
||||
await async_bot_db.delete_audio_moderate_record(message_id)
|
||||
|
||||
mock_factory.audio.delete_audio_moderate_record.assert_called_once_with(message_id)
|
||||
|
||||
|
||||
mock_factory.audio.delete_audio_moderate_record.assert_called_once_with(
|
||||
message_id
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_audio_moderate_record_negative_message_id(self, async_bot_db, mock_factory):
|
||||
async def test_delete_audio_moderate_record_negative_message_id(
|
||||
self, async_bot_db, mock_factory
|
||||
):
|
||||
"""Тест delete_audio_moderate_record с отрицательным message_id"""
|
||||
message_id = -12345
|
||||
|
||||
|
||||
await async_bot_db.delete_audio_moderate_record(message_id)
|
||||
|
||||
mock_factory.audio.delete_audio_moderate_record.assert_called_once_with(message_id)
|
||||
|
||||
|
||||
mock_factory.audio.delete_audio_moderate_record.assert_called_once_with(
|
||||
message_id
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_audio_moderate_record_large_message_id(self, async_bot_db, mock_factory):
|
||||
async def test_delete_audio_moderate_record_large_message_id(
|
||||
self, async_bot_db, mock_factory
|
||||
):
|
||||
"""Тест delete_audio_moderate_record с большим message_id"""
|
||||
message_id = 999999999
|
||||
|
||||
|
||||
await async_bot_db.delete_audio_moderate_record(message_id)
|
||||
|
||||
mock_factory.audio.delete_audio_moderate_record.assert_called_once_with(message_id)
|
||||
|
||||
|
||||
mock_factory.audio.delete_audio_moderate_record.assert_called_once_with(
|
||||
message_id
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_user_blacklist_calls_history(self, async_bot_db, mock_factory):
|
||||
"""Тест что set_user_blacklist вызывает добавление в историю"""
|
||||
@@ -118,21 +155,21 @@ class TestAsyncBotDB:
|
||||
message_for_user = "Нарушение правил"
|
||||
date_to_unban = 1234567890
|
||||
ban_author = 999
|
||||
|
||||
|
||||
await async_bot_db.set_user_blacklist(
|
||||
user_id=user_id,
|
||||
user_name=None,
|
||||
message_for_user=message_for_user,
|
||||
date_to_unban=date_to_unban,
|
||||
ban_author=ban_author
|
||||
ban_author=ban_author,
|
||||
)
|
||||
|
||||
|
||||
# Проверяем, что сначала добавлен в blacklist
|
||||
mock_factory.blacklist.add_user.assert_called_once()
|
||||
|
||||
|
||||
# Проверяем, что затем добавлена запись в историю
|
||||
mock_factory.blacklist_history.add_record_on_ban.assert_called_once()
|
||||
|
||||
|
||||
# Проверяем параметры записи в историю
|
||||
history_call = mock_factory.blacklist_history.add_record_on_ban.call_args[0][0]
|
||||
assert history_call.user_id == user_id
|
||||
@@ -140,77 +177,89 @@ class TestAsyncBotDB:
|
||||
assert history_call.date_ban is not None
|
||||
assert history_call.date_unban is None
|
||||
assert history_call.ban_author == ban_author
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_user_blacklist_history_error_does_not_fail(self, async_bot_db, mock_factory):
|
||||
async def test_set_user_blacklist_history_error_does_not_fail(
|
||||
self, async_bot_db, mock_factory
|
||||
):
|
||||
"""Тест что ошибка записи в историю не ломает процесс бана"""
|
||||
user_id = 12345
|
||||
mock_factory.blacklist_history.add_record_on_ban.side_effect = Exception("History error")
|
||||
|
||||
mock_factory.blacklist_history.add_record_on_ban.side_effect = Exception(
|
||||
"History error"
|
||||
)
|
||||
|
||||
# Бан должен пройти успешно, несмотря на ошибку в истории
|
||||
await async_bot_db.set_user_blacklist(
|
||||
user_id=user_id,
|
||||
message_for_user="Тест",
|
||||
date_to_unban=None,
|
||||
ban_author=None
|
||||
ban_author=None,
|
||||
)
|
||||
|
||||
|
||||
# Проверяем, что пользователь все равно добавлен в blacklist
|
||||
mock_factory.blacklist.add_user.assert_called_once()
|
||||
|
||||
|
||||
# Проверяем, что попытка записи в историю была
|
||||
mock_factory.blacklist_history.add_record_on_ban.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_user_blacklist_calls_history(self, async_bot_db, mock_factory):
|
||||
async def test_delete_user_blacklist_calls_history(
|
||||
self, async_bot_db, mock_factory
|
||||
):
|
||||
"""Тест что delete_user_blacklist вызывает обновление истории"""
|
||||
user_id = 12345
|
||||
|
||||
|
||||
result = await async_bot_db.delete_user_blacklist(user_id)
|
||||
|
||||
|
||||
# Проверяем, что сначала обновлена история
|
||||
mock_factory.blacklist_history.set_unban_date.assert_called_once()
|
||||
history_call = mock_factory.blacklist_history.set_unban_date.call_args
|
||||
assert history_call[0][0] == user_id
|
||||
assert history_call[0][1] is not None # date_unban timestamp
|
||||
|
||||
|
||||
# Проверяем, что затем удален из blacklist
|
||||
mock_factory.blacklist.remove_user.assert_called_once_with(user_id)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_user_blacklist_history_error_does_not_fail(self, async_bot_db, mock_factory):
|
||||
async def test_delete_user_blacklist_history_error_does_not_fail(
|
||||
self, async_bot_db, mock_factory
|
||||
):
|
||||
"""Тест что ошибка обновления истории не ломает процесс разбана"""
|
||||
user_id = 12345
|
||||
mock_factory.blacklist_history.set_unban_date.side_effect = Exception("History error")
|
||||
|
||||
mock_factory.blacklist_history.set_unban_date.side_effect = Exception(
|
||||
"History error"
|
||||
)
|
||||
|
||||
# Разбан должен пройти успешно, несмотря на ошибку в истории
|
||||
result = await async_bot_db.delete_user_blacklist(user_id)
|
||||
|
||||
|
||||
# Проверяем, что попытка обновления истории была
|
||||
mock_factory.blacklist_history.set_unban_date.assert_called_once()
|
||||
|
||||
|
||||
# Проверяем, что пользователь все равно удален из blacklist
|
||||
mock_factory.blacklist.remove_user.assert_called_once_with(user_id)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_user_blacklist_returns_false_on_blacklist_error(self, async_bot_db, mock_factory):
|
||||
async def test_delete_user_blacklist_returns_false_on_blacklist_error(
|
||||
self, async_bot_db, mock_factory
|
||||
):
|
||||
"""Тест что delete_user_blacklist возвращает False при ошибке удаления из blacklist"""
|
||||
user_id = 12345
|
||||
mock_factory.blacklist.remove_user.return_value = False
|
||||
|
||||
|
||||
result = await async_bot_db.delete_user_blacklist(user_id)
|
||||
|
||||
|
||||
# Проверяем, что история обновлена
|
||||
mock_factory.blacklist_history.set_unban_date.assert_called_once()
|
||||
|
||||
|
||||
# Проверяем, что удаление из blacklist было попытка
|
||||
mock_factory.blacklist.remove_user.assert_called_once_with(user_id)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result is False
|
||||
|
||||
@@ -3,8 +3,8 @@ from datetime import datetime
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, mock_open, patch
|
||||
|
||||
import pytest
|
||||
from helper_bot.handlers.voice.exceptions import (DatabaseError,
|
||||
FileOperationError)
|
||||
|
||||
from helper_bot.handlers.voice.exceptions import DatabaseError, FileOperationError
|
||||
from helper_bot.handlers.voice.services import AudioFileService
|
||||
|
||||
|
||||
@@ -17,16 +17,19 @@ def mock_bot_db():
|
||||
mock_db.add_audio_record_simple = AsyncMock()
|
||||
return mock_db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def audio_service(mock_bot_db):
|
||||
"""Экземпляр AudioFileService для тестов"""
|
||||
return AudioFileService(mock_bot_db)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_datetime():
|
||||
"""Тестовая дата"""
|
||||
return datetime(2025, 1, 15, 14, 30, 0)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_bot():
|
||||
"""Мок для бота"""
|
||||
@@ -35,6 +38,7 @@ def mock_bot():
|
||||
bot.download_file = AsyncMock()
|
||||
return bot
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_message():
|
||||
"""Мок для сообщения"""
|
||||
@@ -43,6 +47,7 @@ def mock_message():
|
||||
message.voice.file_id = "test_file_id"
|
||||
return message
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_file_info():
|
||||
"""Мок для информации о файле"""
|
||||
@@ -53,76 +58,92 @@ def mock_file_info():
|
||||
|
||||
class TestGenerateFileName:
|
||||
"""Тесты для метода generate_file_name"""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_file_name_first_record(self, audio_service, mock_bot_db):
|
||||
"""Тест генерации имени файла для первой записи пользователя"""
|
||||
mock_bot_db.get_user_audio_records_count.return_value = 0
|
||||
|
||||
|
||||
result = await audio_service.generate_file_name(12345)
|
||||
|
||||
|
||||
assert result == "message_from_12345_number_1"
|
||||
mock_bot_db.get_user_audio_records_count.assert_called_once_with(user_id=12345)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_file_name_existing_records(self, audio_service, mock_bot_db):
|
||||
async def test_generate_file_name_existing_records(
|
||||
self, audio_service, mock_bot_db
|
||||
):
|
||||
"""Тест генерации имени файла для существующих записей"""
|
||||
mock_bot_db.get_user_audio_records_count.return_value = 3
|
||||
mock_bot_db.get_path_for_audio_record.return_value = "message_from_12345_number_3"
|
||||
|
||||
mock_bot_db.get_path_for_audio_record.return_value = (
|
||||
"message_from_12345_number_3"
|
||||
)
|
||||
|
||||
result = await audio_service.generate_file_name(12345)
|
||||
|
||||
|
||||
assert result == "message_from_12345_number_4"
|
||||
mock_bot_db.get_user_audio_records_count.assert_called_once_with(user_id=12345)
|
||||
mock_bot_db.get_path_for_audio_record.assert_called_once_with(user_id=12345)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_file_name_no_last_record(self, audio_service, mock_bot_db):
|
||||
"""Тест генерации имени файла когда нет последней записи"""
|
||||
mock_bot_db.get_user_audio_records_count.return_value = 2
|
||||
mock_bot_db.get_path_for_audio_record.return_value = None
|
||||
|
||||
|
||||
result = await audio_service.generate_file_name(12345)
|
||||
|
||||
|
||||
assert result == "message_from_12345_number_3"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_file_name_invalid_last_record_format(self, audio_service, mock_bot_db):
|
||||
async def test_generate_file_name_invalid_last_record_format(
|
||||
self, audio_service, mock_bot_db
|
||||
):
|
||||
"""Тест генерации имени файла с некорректным форматом последней записи"""
|
||||
mock_bot_db.get_user_audio_records_count.return_value = 2
|
||||
mock_bot_db.get_path_for_audio_record.return_value = "invalid_format"
|
||||
|
||||
|
||||
result = await audio_service.generate_file_name(12345)
|
||||
|
||||
|
||||
assert result == "message_from_12345_number_3"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_file_name_exception_handling(self, audio_service, mock_bot_db):
|
||||
async def test_generate_file_name_exception_handling(
|
||||
self, audio_service, mock_bot_db
|
||||
):
|
||||
"""Тест обработки исключений при генерации имени файла"""
|
||||
mock_bot_db.get_user_audio_records_count.side_effect = Exception("Database error")
|
||||
|
||||
mock_bot_db.get_user_audio_records_count.side_effect = Exception(
|
||||
"Database error"
|
||||
)
|
||||
|
||||
with pytest.raises(FileOperationError) as exc_info:
|
||||
await audio_service.generate_file_name(12345)
|
||||
|
||||
|
||||
assert "Не удалось сгенерировать имя файла" in str(exc_info.value)
|
||||
|
||||
|
||||
class TestSaveAudioFile:
|
||||
"""Тесты для метода save_audio_file"""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_audio_file_success(self, audio_service, mock_bot_db, sample_datetime):
|
||||
async def test_save_audio_file_success(
|
||||
self, audio_service, mock_bot_db, sample_datetime
|
||||
):
|
||||
"""Тест успешного сохранения аудио файла"""
|
||||
file_name = "test_audio"
|
||||
user_id = 12345
|
||||
file_id = "test_file_id"
|
||||
|
||||
|
||||
# Мокаем verify_file_exists чтобы он возвращал True
|
||||
with patch.object(audio_service, 'verify_file_exists', return_value=True):
|
||||
await audio_service.save_audio_file(file_name, user_id, sample_datetime, file_id)
|
||||
|
||||
mock_bot_db.add_audio_record_simple.assert_called_once_with(file_name, user_id, sample_datetime)
|
||||
|
||||
with patch.object(audio_service, "verify_file_exists", return_value=True):
|
||||
await audio_service.save_audio_file(
|
||||
file_name, user_id, sample_datetime, file_id
|
||||
)
|
||||
|
||||
mock_bot_db.add_audio_record_simple.assert_called_once_with(
|
||||
file_name, user_id, sample_datetime
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_audio_file_with_string_date(self, audio_service, mock_bot_db):
|
||||
"""Тест сохранения аудио файла со строковой датой"""
|
||||
@@ -130,63 +151,84 @@ class TestSaveAudioFile:
|
||||
user_id = 12345
|
||||
date_string = "2025-01-15 14:30:00"
|
||||
file_id = "test_file_id"
|
||||
|
||||
|
||||
# Мокаем verify_file_exists чтобы он возвращал True
|
||||
with patch.object(audio_service, 'verify_file_exists', return_value=True):
|
||||
await audio_service.save_audio_file(file_name, user_id, date_string, file_id)
|
||||
|
||||
mock_bot_db.add_audio_record_simple.assert_called_once_with(file_name, user_id, date_string)
|
||||
|
||||
with patch.object(audio_service, "verify_file_exists", return_value=True):
|
||||
await audio_service.save_audio_file(
|
||||
file_name, user_id, date_string, file_id
|
||||
)
|
||||
|
||||
mock_bot_db.add_audio_record_simple.assert_called_once_with(
|
||||
file_name, user_id, date_string
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_audio_file_exception_handling(self, audio_service, mock_bot_db, sample_datetime):
|
||||
async def test_save_audio_file_exception_handling(
|
||||
self, audio_service, mock_bot_db, sample_datetime
|
||||
):
|
||||
"""Тест обработки исключений при сохранении аудио файла"""
|
||||
mock_bot_db.add_audio_record_simple.side_effect = Exception("Database error")
|
||||
|
||||
|
||||
# Мокаем verify_file_exists чтобы он возвращал True
|
||||
with patch.object(audio_service, 'verify_file_exists', return_value=True):
|
||||
with patch.object(audio_service, "verify_file_exists", return_value=True):
|
||||
with pytest.raises(DatabaseError) as exc_info:
|
||||
await audio_service.save_audio_file("test", 12345, sample_datetime, "file_id")
|
||||
|
||||
await audio_service.save_audio_file(
|
||||
"test", 12345, sample_datetime, "file_id"
|
||||
)
|
||||
|
||||
assert "Не удалось сохранить аудио файл в БД" in str(exc_info.value)
|
||||
|
||||
|
||||
class TestDownloadAndSaveAudio:
|
||||
"""Тесты для метода download_and_save_audio"""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_download_and_save_audio_success(self, audio_service, mock_bot, mock_message, mock_file_info):
|
||||
async def test_download_and_save_audio_success(
|
||||
self, audio_service, mock_bot, mock_message, mock_file_info
|
||||
):
|
||||
"""Тест успешного скачивания и сохранения аудио"""
|
||||
mock_bot.get_file.return_value = mock_file_info
|
||||
|
||||
|
||||
# Мокаем скачанный файл
|
||||
mock_downloaded_file = Mock()
|
||||
mock_downloaded_file.tell.return_value = 0
|
||||
mock_downloaded_file.seek = Mock()
|
||||
mock_downloaded_file.read.return_value = b"audio_data"
|
||||
|
||||
|
||||
# Настраиваем поведение tell() для получения размера файла
|
||||
def mock_tell():
|
||||
return 0 if mock_downloaded_file.seek.call_count == 0 else 1024
|
||||
|
||||
mock_downloaded_file.tell = Mock(side_effect=mock_tell)
|
||||
|
||||
|
||||
mock_bot.download_file.return_value = mock_downloaded_file
|
||||
|
||||
with patch('builtins.open', mock_open()) as mock_file:
|
||||
with patch('os.makedirs'):
|
||||
with patch('os.path.exists', return_value=True):
|
||||
with patch('os.path.getsize', return_value=1024):
|
||||
await audio_service.download_and_save_audio(mock_bot, mock_message, "test_audio")
|
||||
|
||||
mock_bot.get_file.assert_called_once_with(file_id="test_file_id")
|
||||
mock_bot.download_file.assert_called_once_with(file_path="voice/test_file_id.ogg")
|
||||
|
||||
with patch("builtins.open", mock_open()) as mock_file:
|
||||
with patch("os.makedirs"):
|
||||
with patch("os.path.exists", return_value=True):
|
||||
with patch("os.path.getsize", return_value=1024):
|
||||
await audio_service.download_and_save_audio(
|
||||
mock_bot, mock_message, "test_audio"
|
||||
)
|
||||
|
||||
mock_bot.get_file.assert_called_once_with(
|
||||
file_id="test_file_id"
|
||||
)
|
||||
mock_bot.download_file.assert_called_once_with(
|
||||
file_path="voice/test_file_id.ogg"
|
||||
)
|
||||
mock_file.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_download_and_save_audio_no_message(self, audio_service, mock_bot):
|
||||
"""Тест скачивания когда сообщение отсутствует."""
|
||||
with patch('helper_bot.handlers.voice.services.asyncio.sleep', new_callable=AsyncMock):
|
||||
with patch(
|
||||
"helper_bot.handlers.voice.services.asyncio.sleep", new_callable=AsyncMock
|
||||
):
|
||||
with pytest.raises(FileOperationError) as exc_info:
|
||||
await audio_service.download_and_save_audio(mock_bot, None, "test_audio")
|
||||
await audio_service.download_and_save_audio(
|
||||
mock_bot, None, "test_audio"
|
||||
)
|
||||
|
||||
assert "Сообщение или голосовое сообщение не найдено" in str(exc_info.value)
|
||||
|
||||
@@ -196,87 +238,109 @@ class TestDownloadAndSaveAudio:
|
||||
message = Mock()
|
||||
message.voice = None
|
||||
|
||||
with patch('helper_bot.handlers.voice.services.asyncio.sleep', new_callable=AsyncMock):
|
||||
with patch(
|
||||
"helper_bot.handlers.voice.services.asyncio.sleep", new_callable=AsyncMock
|
||||
):
|
||||
with pytest.raises(FileOperationError) as exc_info:
|
||||
await audio_service.download_and_save_audio(mock_bot, message, "test_audio")
|
||||
await audio_service.download_and_save_audio(
|
||||
mock_bot, message, "test_audio"
|
||||
)
|
||||
|
||||
assert "Сообщение или голосовое сообщение не найдено" in str(exc_info.value)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_download_and_save_audio_download_failed(self, audio_service, mock_bot, mock_message, mock_file_info):
|
||||
async def test_download_and_save_audio_download_failed(
|
||||
self, audio_service, mock_bot, mock_message, mock_file_info
|
||||
):
|
||||
"""Тест скачивания когда загрузка не удалась."""
|
||||
mock_bot.get_file.return_value = mock_file_info
|
||||
mock_bot.download_file.return_value = None
|
||||
|
||||
with patch('helper_bot.handlers.voice.services.asyncio.sleep', new_callable=AsyncMock):
|
||||
with patch(
|
||||
"helper_bot.handlers.voice.services.asyncio.sleep", new_callable=AsyncMock
|
||||
):
|
||||
with pytest.raises(FileOperationError) as exc_info:
|
||||
await audio_service.download_and_save_audio(mock_bot, mock_message, "test_audio")
|
||||
await audio_service.download_and_save_audio(
|
||||
mock_bot, mock_message, "test_audio"
|
||||
)
|
||||
|
||||
assert "Не удалось скачать файл" in str(exc_info.value)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_download_and_save_audio_exception_handling(self, audio_service, mock_bot, mock_message):
|
||||
async def test_download_and_save_audio_exception_handling(
|
||||
self, audio_service, mock_bot, mock_message
|
||||
):
|
||||
"""Тест обработки исключений при скачивании."""
|
||||
mock_bot.get_file.side_effect = Exception("Network error")
|
||||
|
||||
with patch('helper_bot.handlers.voice.services.asyncio.sleep', new_callable=AsyncMock):
|
||||
with patch(
|
||||
"helper_bot.handlers.voice.services.asyncio.sleep", new_callable=AsyncMock
|
||||
):
|
||||
with pytest.raises(FileOperationError) as exc_info:
|
||||
await audio_service.download_and_save_audio(mock_bot, mock_message, "test_audio")
|
||||
await audio_service.download_and_save_audio(
|
||||
mock_bot, mock_message, "test_audio"
|
||||
)
|
||||
|
||||
assert "Не удалось скачать и сохранить аудио" in str(exc_info.value)
|
||||
|
||||
|
||||
|
||||
|
||||
class TestAudioFileServiceIntegration:
|
||||
"""Интеграционные тесты для AudioFileService"""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_full_audio_processing_workflow(self, mock_bot_db):
|
||||
"""Тест полного рабочего процесса обработки аудио"""
|
||||
service = AudioFileService(mock_bot_db)
|
||||
|
||||
|
||||
# Настраиваем моки
|
||||
mock_bot_db.get_user_audio_records_count.return_value = 1
|
||||
mock_bot_db.get_path_for_audio_record.return_value = "message_from_12345_number_1"
|
||||
mock_bot_db.get_path_for_audio_record.return_value = (
|
||||
"message_from_12345_number_1"
|
||||
)
|
||||
mock_bot_db.add_audio_record_simple = AsyncMock()
|
||||
|
||||
|
||||
# Тестируем генерацию имени файла
|
||||
file_name = await service.generate_file_name(12345)
|
||||
assert file_name == "message_from_12345_number_2"
|
||||
|
||||
|
||||
# Тестируем сохранение в БД
|
||||
test_date = datetime.now()
|
||||
with patch.object(service, 'verify_file_exists', return_value=True):
|
||||
with patch.object(service, "verify_file_exists", return_value=True):
|
||||
await service.save_audio_file(file_name, 12345, test_date, "test_file_id")
|
||||
|
||||
|
||||
# Проверяем вызовы
|
||||
mock_bot_db.get_user_audio_records_count.assert_called_once_with(user_id=12345)
|
||||
mock_bot_db.get_path_for_audio_record.assert_called_once_with(user_id=12345)
|
||||
mock_bot_db.add_audio_record_simple.assert_called_once_with(file_name, 12345, test_date)
|
||||
|
||||
mock_bot_db.add_audio_record_simple.assert_called_once_with(
|
||||
file_name, 12345, test_date
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_file_name_generation_sequence(self, mock_bot_db):
|
||||
"""Тест последовательности генерации имен файлов"""
|
||||
service = AudioFileService(mock_bot_db)
|
||||
|
||||
|
||||
# Первая запись
|
||||
mock_bot_db.get_user_audio_records_count.return_value = 0
|
||||
file_name_1 = await service.generate_file_name(12345)
|
||||
assert file_name_1 == "message_from_12345_number_1"
|
||||
|
||||
|
||||
# Вторая запись
|
||||
mock_bot_db.get_user_audio_records_count.return_value = 1
|
||||
mock_bot_db.get_path_for_audio_record.return_value = "message_from_12345_number_1"
|
||||
mock_bot_db.get_path_for_audio_record.return_value = (
|
||||
"message_from_12345_number_1"
|
||||
)
|
||||
file_name_2 = await service.generate_file_name(12345)
|
||||
assert file_name_2 == "message_from_12345_number_2"
|
||||
|
||||
|
||||
# Третья запись
|
||||
mock_bot_db.get_user_audio_records_count.return_value = 2
|
||||
mock_bot_db.get_path_for_audio_record.return_value = "message_from_12345_number_2"
|
||||
mock_bot_db.get_path_for_audio_record.return_value = (
|
||||
"message_from_12345_number_2"
|
||||
)
|
||||
file_name_3 = await service.generate_file_name(12345)
|
||||
assert file_name_3 == "message_from_12345_number_3"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__])
|
||||
|
||||
@@ -3,13 +3,14 @@ from datetime import datetime
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from database.models import AudioListenRecord, AudioMessage, AudioModerate
|
||||
from database.repositories.audio_repository import AudioRepository
|
||||
|
||||
|
||||
class TestAudioRepository:
|
||||
"""Тесты для AudioRepository"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db_connection(self):
|
||||
"""Мок для DatabaseConnection"""
|
||||
@@ -18,18 +19,20 @@ class TestAudioRepository:
|
||||
mock_connection._execute_query_with_result = AsyncMock()
|
||||
mock_connection.logger = Mock()
|
||||
return mock_connection
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def audio_repository(self, mock_db_connection):
|
||||
"""Экземпляр AudioRepository для тестов"""
|
||||
# Патчим наследование от DatabaseConnection
|
||||
with patch.object(AudioRepository, '__init__', return_value=None):
|
||||
with patch.object(AudioRepository, "__init__", return_value=None):
|
||||
repo = AudioRepository()
|
||||
repo._execute_query = mock_db_connection._execute_query
|
||||
repo._execute_query_with_result = mock_db_connection._execute_query_with_result
|
||||
repo._execute_query_with_result = (
|
||||
mock_db_connection._execute_query_with_result
|
||||
)
|
||||
repo.logger = mock_db_connection.logger
|
||||
return repo
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_audio_message(self):
|
||||
"""Тестовое аудио сообщение"""
|
||||
@@ -38,45 +41,49 @@ class TestAudioRepository:
|
||||
author_id=12345,
|
||||
date_added="2025-01-15 14:30:00",
|
||||
file_id="test_file_id",
|
||||
listen_count=0
|
||||
listen_count=0,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_datetime(self):
|
||||
"""Тестовая дата"""
|
||||
return datetime(2025, 1, 15, 14, 30, 0)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_timestamp(self):
|
||||
"""Тестовый UNIX timestamp"""
|
||||
return int(time.mktime(datetime(2025, 1, 15, 14, 30, 0).timetuple()))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_enable_foreign_keys(self, audio_repository):
|
||||
"""Тест включения внешних ключей"""
|
||||
await audio_repository.enable_foreign_keys()
|
||||
|
||||
audio_repository._execute_query.assert_called_once_with("PRAGMA foreign_keys = ON;")
|
||||
|
||||
|
||||
audio_repository._execute_query.assert_called_once_with(
|
||||
"PRAGMA foreign_keys = ON;"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_tables(self, audio_repository):
|
||||
"""Тест создания таблиц"""
|
||||
await audio_repository.create_tables()
|
||||
|
||||
|
||||
# Проверяем, что все три таблицы созданы
|
||||
assert audio_repository._execute_query.call_count == 3
|
||||
|
||||
|
||||
# Проверяем вызовы для каждой таблицы
|
||||
calls = audio_repository._execute_query.call_args_list
|
||||
assert any("audio_message_reference" in str(call) for call in calls)
|
||||
assert any("user_audio_listens" in str(call) for call in calls)
|
||||
assert any("audio_moderate" in str(call) for call in calls)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_with_string_date(self, audio_repository, sample_audio_message):
|
||||
async def test_add_audio_record_with_string_date(
|
||||
self, audio_repository, sample_audio_message
|
||||
):
|
||||
"""Тест добавления аудио записи со строковой датой"""
|
||||
await audio_repository.add_audio_record(sample_audio_message)
|
||||
|
||||
|
||||
# Проверяем, что метод вызван с правильными параметрами
|
||||
audio_repository._execute_query.assert_called_once()
|
||||
call_args = audio_repository._execute_query.call_args
|
||||
@@ -88,7 +95,7 @@ class TestAudioRepository:
|
||||
assert call_args[0][1][0] == "test_audio_123.ogg"
|
||||
assert call_args[0][1][1] == 12345
|
||||
assert isinstance(call_args[0][1][2], int) # timestamp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_with_datetime_date(self, audio_repository):
|
||||
"""Тест добавления аудио записи с datetime датой"""
|
||||
@@ -97,15 +104,15 @@ class TestAudioRepository:
|
||||
author_id=67890,
|
||||
date_added=datetime(2025, 1, 20, 10, 15, 0),
|
||||
file_id="test_file_id_2",
|
||||
listen_count=0
|
||||
listen_count=0,
|
||||
)
|
||||
|
||||
|
||||
await audio_repository.add_audio_record(audio_msg)
|
||||
|
||||
|
||||
# Проверяем, что date_added преобразован в timestamp
|
||||
call_args = audio_repository._execute_query.call_args
|
||||
assert isinstance(call_args[0][1][2], int) # timestamp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_with_timestamp_date(self, audio_repository):
|
||||
"""Тест добавления аудио записи с timestamp датой"""
|
||||
@@ -115,268 +122,293 @@ class TestAudioRepository:
|
||||
author_id=11111,
|
||||
date_added=timestamp,
|
||||
file_id="test_file_id_3",
|
||||
listen_count=0
|
||||
listen_count=0,
|
||||
)
|
||||
|
||||
|
||||
await audio_repository.add_audio_record(audio_msg)
|
||||
|
||||
|
||||
# Проверяем, что date_added остался timestamp
|
||||
call_args = audio_repository._execute_query.call_args
|
||||
assert call_args[0][1][2] == timestamp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_simple_with_string_date(self, audio_repository):
|
||||
"""Тест упрощенного добавления аудио записи со строковой датой"""
|
||||
await audio_repository.add_audio_record_simple("test_audio.ogg", 12345, "2025-01-15 14:30:00")
|
||||
|
||||
await audio_repository.add_audio_record_simple(
|
||||
"test_audio.ogg", 12345, "2025-01-15 14:30:00"
|
||||
)
|
||||
|
||||
# Проверяем, что метод вызван
|
||||
audio_repository._execute_query.assert_called_once()
|
||||
call_args = audio_repository._execute_query.call_args
|
||||
assert call_args[0][1][0] == "test_audio.ogg" # file_name
|
||||
assert call_args[0][1][1] == 12345 # user_id
|
||||
assert isinstance(call_args[0][1][2], int) # timestamp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_simple_with_datetime_date(self, audio_repository, sample_datetime):
|
||||
async def test_add_audio_record_simple_with_datetime_date(
|
||||
self, audio_repository, sample_datetime
|
||||
):
|
||||
"""Тест упрощенного добавления аудио записи с datetime датой"""
|
||||
await audio_repository.add_audio_record_simple("test_audio.ogg", 12345, sample_datetime)
|
||||
|
||||
await audio_repository.add_audio_record_simple(
|
||||
"test_audio.ogg", 12345, sample_datetime
|
||||
)
|
||||
|
||||
# Проверяем, что date_added преобразован в timestamp
|
||||
call_args = audio_repository._execute_query.call_args
|
||||
assert isinstance(call_args[0][1][2], int) # timestamp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_last_date_audio(self, audio_repository):
|
||||
"""Тест получения даты последнего аудио"""
|
||||
expected_timestamp = 1642248600 # 2022-01-17 10:30:00
|
||||
audio_repository._execute_query_with_result.return_value = [(expected_timestamp,)]
|
||||
|
||||
audio_repository._execute_query_with_result.return_value = [
|
||||
(expected_timestamp,)
|
||||
]
|
||||
|
||||
result = await audio_repository.get_last_date_audio()
|
||||
|
||||
|
||||
assert result == expected_timestamp
|
||||
audio_repository._execute_query_with_result.assert_called_once_with(
|
||||
"SELECT date_added FROM audio_message_reference ORDER BY date_added DESC LIMIT 1"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_last_date_audio_no_records(self, audio_repository):
|
||||
"""Тест получения даты последнего аудио когда записей нет"""
|
||||
audio_repository._execute_query_with_result.return_value = []
|
||||
|
||||
|
||||
result = await audio_repository.get_last_date_audio()
|
||||
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_audio_records_count(self, audio_repository):
|
||||
"""Тест получения количества аудио записей пользователя"""
|
||||
audio_repository._execute_query_with_result.return_value = [(5,)]
|
||||
|
||||
|
||||
result = await audio_repository.get_user_audio_records_count(12345)
|
||||
|
||||
|
||||
assert result == 5
|
||||
audio_repository._execute_query_with_result.assert_called_once_with(
|
||||
"SELECT COUNT(*) FROM audio_message_reference WHERE author_id = ?", (12345,)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_path_for_audio_record(self, audio_repository):
|
||||
"""Тест получения пути к аудио записи пользователя"""
|
||||
audio_repository._execute_query_with_result.return_value = [("test_audio.ogg",)]
|
||||
|
||||
|
||||
result = await audio_repository.get_path_for_audio_record(12345)
|
||||
|
||||
|
||||
assert result == "test_audio.ogg"
|
||||
audio_repository._execute_query_with_result.assert_called_once_with(
|
||||
"""
|
||||
SELECT file_name FROM audio_message_reference
|
||||
WHERE author_id = ? ORDER BY date_added DESC LIMIT 1
|
||||
""", (12345,)
|
||||
""",
|
||||
(12345,),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_path_for_audio_record_no_records(self, audio_repository):
|
||||
"""Тест получения пути к аудио записи когда записей нет"""
|
||||
audio_repository._execute_query_with_result.return_value = []
|
||||
|
||||
|
||||
result = await audio_repository.get_path_for_audio_record(12345)
|
||||
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_listen_audio(self, audio_repository):
|
||||
"""Тест проверки непрослушанных аудио"""
|
||||
# Мокаем результаты запросов
|
||||
audio_repository._execute_query_with_result.side_effect = [
|
||||
[("audio1.ogg",), ("audio2.ogg",)], # прослушанные
|
||||
[("audio1.ogg",), ("audio2.ogg",), ("audio3.ogg",)] # все аудио
|
||||
[("audio1.ogg",), ("audio2.ogg",), ("audio3.ogg",)], # все аудио
|
||||
]
|
||||
|
||||
|
||||
result = await audio_repository.check_listen_audio(12345)
|
||||
|
||||
|
||||
# Должно вернуться только непрослушанные (audio3.ogg)
|
||||
assert result == ["audio3.ogg"]
|
||||
assert audio_repository._execute_query_with_result.call_count == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mark_listened_audio(self, audio_repository):
|
||||
"""Тест отметки аудио как прослушанного"""
|
||||
await audio_repository.mark_listened_audio("test_audio.ogg", 12345)
|
||||
|
||||
|
||||
audio_repository._execute_query.assert_called_once_with(
|
||||
"INSERT OR IGNORE INTO user_audio_listens (file_name, user_id) VALUES (?, ?)",
|
||||
("test_audio.ogg", 12345)
|
||||
("test_audio.ogg", 12345),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_id_by_file_name(self, audio_repository):
|
||||
"""Тест получения user_id по имени файла"""
|
||||
audio_repository._execute_query_with_result.return_value = [(12345,)]
|
||||
|
||||
|
||||
result = await audio_repository.get_user_id_by_file_name("test_audio.ogg")
|
||||
|
||||
|
||||
assert result == 12345
|
||||
audio_repository._execute_query_with_result.assert_called_once_with(
|
||||
"SELECT author_id FROM audio_message_reference WHERE file_name = ?", ("test_audio.ogg",)
|
||||
"SELECT author_id FROM audio_message_reference WHERE file_name = ?",
|
||||
("test_audio.ogg",),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_id_by_file_name_not_found(self, audio_repository):
|
||||
"""Тест получения user_id по имени файла когда файл не найден"""
|
||||
audio_repository._execute_query_with_result.return_value = []
|
||||
|
||||
|
||||
result = await audio_repository.get_user_id_by_file_name("nonexistent.ogg")
|
||||
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_date_by_file_name(self, audio_repository):
|
||||
"""Тест получения даты по имени файла"""
|
||||
timestamp = 1642404600 # 2022-01-17 10:30:00
|
||||
audio_repository._execute_query_with_result.return_value = [(timestamp,)]
|
||||
|
||||
|
||||
result = await audio_repository.get_date_by_file_name("test_audio.ogg")
|
||||
|
||||
|
||||
# Должна вернуться читаемая дата
|
||||
assert result == "17.01.2022 10:30"
|
||||
audio_repository._execute_query_with_result.assert_called_once_with(
|
||||
"SELECT date_added FROM audio_message_reference WHERE file_name = ?", ("test_audio.ogg",)
|
||||
"SELECT date_added FROM audio_message_reference WHERE file_name = ?",
|
||||
("test_audio.ogg",),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_date_by_file_name_not_found(self, audio_repository):
|
||||
"""Тест получения даты по имени файла когда файл не найден"""
|
||||
audio_repository._execute_query_with_result.return_value = []
|
||||
|
||||
|
||||
result = await audio_repository.get_date_by_file_name("nonexistent.ogg")
|
||||
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_listen_audio(self, audio_repository):
|
||||
"""Тест очистки записей прослушивания пользователя"""
|
||||
await audio_repository.refresh_listen_audio(12345)
|
||||
|
||||
|
||||
audio_repository._execute_query.assert_called_once_with(
|
||||
"DELETE FROM user_audio_listens WHERE user_id = ?", (12345,)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_listen_count_for_user(self, audio_repository):
|
||||
"""Тест удаления данных о прослушанных аудио пользователя"""
|
||||
await audio_repository.delete_listen_count_for_user(12345)
|
||||
|
||||
|
||||
audio_repository._execute_query.assert_called_once_with(
|
||||
"DELETE FROM user_audio_listens WHERE user_id = ?", (12345,)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_user_id_and_message_id_for_voice_bot_success(self, audio_repository):
|
||||
async def test_set_user_id_and_message_id_for_voice_bot_success(
|
||||
self, audio_repository
|
||||
):
|
||||
"""Тест успешной установки связи для voice bot"""
|
||||
result = await audio_repository.set_user_id_and_message_id_for_voice_bot(123, 456)
|
||||
|
||||
result = await audio_repository.set_user_id_and_message_id_for_voice_bot(
|
||||
123, 456
|
||||
)
|
||||
|
||||
assert result is True
|
||||
audio_repository._execute_query.assert_called_once_with(
|
||||
"INSERT OR IGNORE INTO audio_moderate (user_id, message_id) VALUES (?, ?)",
|
||||
(456, 123)
|
||||
(456, 123),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_user_id_and_message_id_for_voice_bot_exception(self, audio_repository):
|
||||
async def test_set_user_id_and_message_id_for_voice_bot_exception(
|
||||
self, audio_repository
|
||||
):
|
||||
"""Тест установки связи для voice bot при ошибке"""
|
||||
audio_repository._execute_query.side_effect = Exception("Database error")
|
||||
|
||||
result = await audio_repository.set_user_id_and_message_id_for_voice_bot(123, 456)
|
||||
|
||||
|
||||
result = await audio_repository.set_user_id_and_message_id_for_voice_bot(
|
||||
123, 456
|
||||
)
|
||||
|
||||
assert result is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_id_by_message_id_for_voice_bot(self, audio_repository):
|
||||
"""Тест получения user_id по message_id для voice bot"""
|
||||
audio_repository._execute_query_with_result.return_value = [(456,)]
|
||||
|
||||
|
||||
result = await audio_repository.get_user_id_by_message_id_for_voice_bot(123)
|
||||
|
||||
|
||||
assert result == 456
|
||||
audio_repository._execute_query_with_result.assert_called_once_with(
|
||||
"SELECT user_id FROM audio_moderate WHERE message_id = ?", (123,)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_id_by_message_id_for_voice_bot_not_found(self, audio_repository):
|
||||
async def test_get_user_id_by_message_id_for_voice_bot_not_found(
|
||||
self, audio_repository
|
||||
):
|
||||
"""Тест получения user_id по message_id когда связь не найдена"""
|
||||
audio_repository._execute_query_with_result.return_value = []
|
||||
|
||||
|
||||
result = await audio_repository.get_user_id_by_message_id_for_voice_bot(123)
|
||||
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_audio_moderate_record(self, audio_repository):
|
||||
"""Тест удаления записи из таблицы audio_moderate"""
|
||||
message_id = 12345
|
||||
|
||||
|
||||
await audio_repository.delete_audio_moderate_record(message_id)
|
||||
|
||||
|
||||
audio_repository._execute_query.assert_called_once_with(
|
||||
"DELETE FROM audio_moderate WHERE message_id = ?", (message_id,)
|
||||
)
|
||||
audio_repository.logger.info.assert_called_once_with(
|
||||
f"Удалена запись из audio_moderate для message_id {message_id}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_logging(self, audio_repository, sample_audio_message):
|
||||
async def test_add_audio_record_logging(
|
||||
self, audio_repository, sample_audio_message
|
||||
):
|
||||
"""Тест логирования при добавлении аудио записи"""
|
||||
await audio_repository.add_audio_record(sample_audio_message)
|
||||
|
||||
|
||||
# Проверяем, что лог записан
|
||||
audio_repository.logger.info.assert_called_once()
|
||||
log_message = audio_repository.logger.info.call_args[0][0]
|
||||
assert "Аудио добавлено" in log_message
|
||||
assert "test_audio_123.ogg" in log_message
|
||||
assert "12345" in log_message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_simple_logging(self, audio_repository):
|
||||
"""Тест логирования при упрощенном добавлении аудио записи"""
|
||||
await audio_repository.add_audio_record_simple("test_audio.ogg", 12345, "2025-01-15 14:30:00")
|
||||
|
||||
await audio_repository.add_audio_record_simple(
|
||||
"test_audio.ogg", 12345, "2025-01-15 14:30:00"
|
||||
)
|
||||
|
||||
# Проверяем, что лог записан
|
||||
audio_repository.logger.info.assert_called_once()
|
||||
log_message = audio_repository.logger.info.call_args[0][0]
|
||||
assert "Аудио добавлено" in log_message
|
||||
assert "test_audio.ogg" in log_message
|
||||
assert "12345" in log_message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_date_by_file_name_logging(self, audio_repository):
|
||||
"""Тест логирования при получении даты по имени файла"""
|
||||
timestamp = 1642404600 # 2022-01-17 10:30:00
|
||||
audio_repository._execute_query_with_result.return_value = [(timestamp,)]
|
||||
|
||||
|
||||
await audio_repository.get_date_by_file_name("test_audio.ogg")
|
||||
|
||||
|
||||
# Проверяем, что лог записан
|
||||
audio_repository.logger.info.assert_called_once()
|
||||
log_message = audio_repository.logger.info.call_args[0][0]
|
||||
@@ -387,20 +419,20 @@ class TestAudioRepository:
|
||||
|
||||
class TestAudioRepositoryIntegration:
|
||||
"""Интеграционные тесты для AudioRepository"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def real_audio_repository(self):
|
||||
"""Реальный экземпляр AudioRepository для интеграционных тестов"""
|
||||
# Здесь можно создать реальное подключение к тестовой БД
|
||||
# Но для простоты используем мок
|
||||
return Mock()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_full_audio_workflow(self, real_audio_repository):
|
||||
"""Тест полного рабочего процесса с аудио"""
|
||||
# Этот тест можно расширить для реальной БД
|
||||
assert True # Placeholder для будущих интеграционных тестов
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_foreign_keys_enabled(self, real_audio_repository):
|
||||
"""Тест что внешние ключи включены"""
|
||||
|
||||
@@ -3,12 +3,13 @@ from datetime import datetime
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from database.repositories.audio_repository import AudioRepository
|
||||
|
||||
|
||||
class TestAudioRepositoryNewSchema:
|
||||
"""Тесты для AudioRepository с новой схемой БД"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db_connection(self):
|
||||
"""Мок для DatabaseConnection"""
|
||||
@@ -17,259 +18,286 @@ class TestAudioRepositoryNewSchema:
|
||||
mock_connection._execute_query_with_result = AsyncMock()
|
||||
mock_connection.logger = Mock()
|
||||
return mock_connection
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def audio_repository(self, mock_db_connection):
|
||||
"""Экземпляр AudioRepository для тестов"""
|
||||
with patch.object(AudioRepository, '__init__', return_value=None):
|
||||
with patch.object(AudioRepository, "__init__", return_value=None):
|
||||
repo = AudioRepository()
|
||||
repo._execute_query = mock_db_connection._execute_query
|
||||
repo._execute_query_with_result = mock_db_connection._execute_query_with_result
|
||||
repo._execute_query_with_result = (
|
||||
mock_db_connection._execute_query_with_result
|
||||
)
|
||||
repo.logger = mock_db_connection.logger
|
||||
return repo
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_tables_new_schema(self, audio_repository):
|
||||
"""Тест создания таблиц с новой схемой БД"""
|
||||
await audio_repository.create_tables()
|
||||
|
||||
|
||||
# Проверяем, что все три таблицы созданы
|
||||
assert audio_repository._execute_query.call_count == 3
|
||||
|
||||
|
||||
# Получаем все вызовы
|
||||
calls = audio_repository._execute_query.call_args_list
|
||||
|
||||
|
||||
# Проверяем таблицу audio_message_reference
|
||||
audio_table_call = next(call for call in calls if "audio_message_reference" in str(call))
|
||||
audio_table_call = next(
|
||||
call for call in calls if "audio_message_reference" in str(call)
|
||||
)
|
||||
assert "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT" in str(audio_table_call)
|
||||
assert "file_name TEXT NOT NULL UNIQUE" in str(audio_table_call)
|
||||
assert "author_id INTEGER NOT NULL" in str(audio_table_call)
|
||||
assert "date_added INTEGER NOT NULL" in str(audio_table_call)
|
||||
assert "FOREIGN KEY (author_id) REFERENCES our_users (user_id) ON DELETE CASCADE" in str(audio_table_call)
|
||||
|
||||
assert (
|
||||
"FOREIGN KEY (author_id) REFERENCES our_users (user_id) ON DELETE CASCADE"
|
||||
in str(audio_table_call)
|
||||
)
|
||||
|
||||
# Проверяем таблицу user_audio_listens
|
||||
listens_table_call = next(call for call in calls if "user_audio_listens" in str(call))
|
||||
listens_table_call = next(
|
||||
call for call in calls if "user_audio_listens" in str(call)
|
||||
)
|
||||
assert "file_name TEXT NOT NULL" in str(listens_table_call)
|
||||
assert "user_id INTEGER NOT NULL" in str(listens_table_call)
|
||||
assert "PRIMARY KEY (file_name, user_id)" in str(listens_table_call)
|
||||
assert "FOREIGN KEY (user_id) REFERENCES our_users (user_id) ON DELETE CASCADE" in str(listens_table_call)
|
||||
|
||||
assert (
|
||||
"FOREIGN KEY (user_id) REFERENCES our_users (user_id) ON DELETE CASCADE"
|
||||
in str(listens_table_call)
|
||||
)
|
||||
|
||||
# Проверяем таблицу audio_moderate
|
||||
moderate_table_call = next(call for call in calls if "audio_moderate" in str(call))
|
||||
moderate_table_call = next(
|
||||
call for call in calls if "audio_moderate" in str(call)
|
||||
)
|
||||
assert "user_id INTEGER NOT NULL" in str(moderate_table_call)
|
||||
assert "message_id INTEGER" in str(moderate_table_call)
|
||||
assert "PRIMARY KEY (user_id, message_id)" in str(moderate_table_call)
|
||||
assert "FOREIGN KEY (user_id) REFERENCES our_users (user_id) ON DELETE CASCADE" in str(moderate_table_call)
|
||||
|
||||
assert (
|
||||
"FOREIGN KEY (user_id) REFERENCES our_users (user_id) ON DELETE CASCADE"
|
||||
in str(moderate_table_call)
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_string_date_conversion(self, audio_repository):
|
||||
"""Тест преобразования строковой даты в UNIX timestamp"""
|
||||
from database.models import AudioMessage
|
||||
|
||||
|
||||
audio_msg = AudioMessage(
|
||||
file_name="test_audio.ogg",
|
||||
author_id=12345,
|
||||
date_added="2025-01-15 14:30:00",
|
||||
file_id="test_file_id",
|
||||
listen_count=0
|
||||
listen_count=0,
|
||||
)
|
||||
|
||||
|
||||
await audio_repository.add_audio_record(audio_msg)
|
||||
|
||||
|
||||
# Проверяем, что метод вызван
|
||||
call_args = audio_repository._execute_query.call_args
|
||||
params = call_args[0][1]
|
||||
|
||||
|
||||
# Проверяем параметры
|
||||
assert params[0] == "test_audio.ogg"
|
||||
assert params[1] == 12345
|
||||
assert isinstance(params[2], int) # timestamp
|
||||
|
||||
|
||||
# Проверяем, что timestamp соответствует дате
|
||||
expected_timestamp = int(datetime(2025, 1, 15, 14, 30, 0).timestamp())
|
||||
assert params[2] == expected_timestamp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_datetime_conversion(self, audio_repository):
|
||||
"""Тест преобразования datetime в UNIX timestamp"""
|
||||
from database.models import AudioMessage
|
||||
|
||||
|
||||
test_datetime = datetime(2025, 1, 20, 10, 15, 30)
|
||||
audio_msg = AudioMessage(
|
||||
file_name="test_audio.ogg",
|
||||
author_id=12345,
|
||||
date_added=test_datetime,
|
||||
file_id="test_file_id",
|
||||
listen_count=0
|
||||
listen_count=0,
|
||||
)
|
||||
|
||||
|
||||
await audio_repository.add_audio_record(audio_msg)
|
||||
|
||||
|
||||
# Проверяем параметры
|
||||
call_args = audio_repository._execute_query.call_args
|
||||
params = call_args[0][1]
|
||||
|
||||
|
||||
expected_timestamp = int(test_datetime.timestamp())
|
||||
assert params[2] == expected_timestamp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_timestamp_no_conversion(self, audio_repository):
|
||||
"""Тест что timestamp остается timestamp без преобразования"""
|
||||
from database.models import AudioMessage
|
||||
|
||||
|
||||
test_timestamp = int(time.time())
|
||||
audio_msg = AudioMessage(
|
||||
file_name="test_audio.ogg",
|
||||
author_id=12345,
|
||||
date_added=test_timestamp,
|
||||
file_id="test_file_id",
|
||||
listen_count=0
|
||||
listen_count=0,
|
||||
)
|
||||
|
||||
|
||||
await audio_repository.add_audio_record(audio_msg)
|
||||
|
||||
|
||||
# Проверяем параметры
|
||||
call_args = audio_repository._execute_query.call_args
|
||||
params = call_args[0][1]
|
||||
|
||||
|
||||
assert params[2] == test_timestamp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_simple_string_date(self, audio_repository):
|
||||
"""Тест упрощенного добавления со строковой датой"""
|
||||
await audio_repository.add_audio_record_simple("test_audio.ogg", 12345, "2025-01-15 14:30:00")
|
||||
|
||||
await audio_repository.add_audio_record_simple(
|
||||
"test_audio.ogg", 12345, "2025-01-15 14:30:00"
|
||||
)
|
||||
|
||||
# Проверяем параметры
|
||||
call_args = audio_repository._execute_query.call_args
|
||||
params = call_args[0][1]
|
||||
|
||||
|
||||
assert params[0] == "test_audio.ogg"
|
||||
assert params[1] == 12345
|
||||
assert isinstance(params[2], int) # timestamp
|
||||
|
||||
|
||||
# Проверяем timestamp
|
||||
expected_timestamp = int(datetime(2025, 1, 15, 14, 30, 0).timestamp())
|
||||
assert params[2] == expected_timestamp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_simple_datetime(self, audio_repository):
|
||||
"""Тест упрощенного добавления с datetime"""
|
||||
test_datetime = datetime(2025, 1, 25, 16, 45, 0)
|
||||
await audio_repository.add_audio_record_simple("test_audio.ogg", 12345, test_datetime)
|
||||
|
||||
await audio_repository.add_audio_record_simple(
|
||||
"test_audio.ogg", 12345, test_datetime
|
||||
)
|
||||
|
||||
# Проверяем параметры
|
||||
call_args = audio_repository._execute_query.call_args
|
||||
params = call_args[0][1]
|
||||
|
||||
|
||||
expected_timestamp = int(test_datetime.timestamp())
|
||||
assert params[2] == expected_timestamp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_date_by_file_name_timestamp_conversion(self, audio_repository):
|
||||
"""Тест преобразования UNIX timestamp в читаемую дату"""
|
||||
test_timestamp = 1642248600 # 2022-01-17 10:30:00
|
||||
audio_repository._execute_query_with_result.return_value = [(test_timestamp,)]
|
||||
|
||||
|
||||
result = await audio_repository.get_date_by_file_name("test_audio.ogg")
|
||||
|
||||
|
||||
# Должна вернуться читаемая дата в формате dd.mm.yyyy HH:MM
|
||||
assert result == "15.01.2022 15:10"
|
||||
assert isinstance(result, str)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_date_by_file_name_different_timestamp(self, audio_repository):
|
||||
"""Тест преобразования другого timestamp в читаемую дату"""
|
||||
test_timestamp = 1705312800 # 2024-01-16 12:00:00
|
||||
audio_repository._execute_query_with_result.return_value = [(test_timestamp,)]
|
||||
|
||||
|
||||
result = await audio_repository.get_date_by_file_name("test_audio.ogg")
|
||||
|
||||
|
||||
assert result == "15.01.2024 13:00"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_date_by_file_name_midnight(self, audio_repository):
|
||||
"""Тест преобразования timestamp для полуночи"""
|
||||
test_timestamp = 1705190400 # 2024-01-15 00:00:00
|
||||
audio_repository._execute_query_with_result.return_value = [(test_timestamp,)]
|
||||
|
||||
|
||||
result = await audio_repository.get_date_by_file_name("test_audio.ogg")
|
||||
|
||||
|
||||
assert result == "14.01.2024 03:00"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_date_by_file_name_year_end(self, audio_repository):
|
||||
"""Тест преобразования timestamp для конца года"""
|
||||
test_timestamp = 1704067200 # 2023-12-31 23:59:59
|
||||
audio_repository._execute_query_with_result.return_value = [(test_timestamp,)]
|
||||
|
||||
|
||||
result = await audio_repository.get_date_by_file_name("test_audio.ogg")
|
||||
|
||||
|
||||
assert result == "01.01.2024 03:00"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_foreign_keys_enabled_called(self, audio_repository):
|
||||
"""Тест что метод enable_foreign_keys вызывается"""
|
||||
await audio_repository.enable_foreign_keys()
|
||||
|
||||
audio_repository._execute_query.assert_called_once_with("PRAGMA foreign_keys = ON;")
|
||||
|
||||
audio_repository._execute_query.assert_called_once_with(
|
||||
"PRAGMA foreign_keys = ON;"
|
||||
)
|
||||
audio_repository.logger.info.assert_not_called() # Этот метод не логирует
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_tables_logging(self, audio_repository):
|
||||
"""Тест логирования при создании таблиц"""
|
||||
await audio_repository.create_tables()
|
||||
|
||||
|
||||
# Проверяем, что лог записан
|
||||
audio_repository.logger.info.assert_called_once_with("Таблицы для аудио созданы")
|
||||
|
||||
audio_repository.logger.info.assert_called_once_with(
|
||||
"Таблицы для аудио созданы"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_logging_format(self, audio_repository):
|
||||
"""Тест формата лога при добавлении аудио записи"""
|
||||
from database.models import AudioMessage
|
||||
|
||||
|
||||
audio_msg = AudioMessage(
|
||||
file_name="test_audio.ogg",
|
||||
author_id=12345,
|
||||
date_added="2025-01-15 14:30:00",
|
||||
file_id="test_file_id",
|
||||
listen_count=0
|
||||
listen_count=0,
|
||||
)
|
||||
|
||||
|
||||
await audio_repository.add_audio_record(audio_msg)
|
||||
|
||||
|
||||
# Проверяем формат лога
|
||||
log_call = audio_repository.logger.info.call_args
|
||||
log_message = log_call[0][0]
|
||||
|
||||
|
||||
assert "Аудио добавлено:" in log_message
|
||||
assert "file_name=test_audio.ogg" in log_message
|
||||
assert "author_id=12345" in log_message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_simple_logging_format(self, audio_repository):
|
||||
"""Тест формата лога при упрощенном добавлении"""
|
||||
await audio_repository.add_audio_record_simple("test_audio.ogg", 12345, "2025-01-15 14:30:00")
|
||||
|
||||
await audio_repository.add_audio_record_simple(
|
||||
"test_audio.ogg", 12345, "2025-01-15 14:30:00"
|
||||
)
|
||||
|
||||
# Проверяем формат лога
|
||||
log_call = audio_repository.logger.info.call_args
|
||||
log_message = log_call[0][0]
|
||||
|
||||
|
||||
assert "Аудио добавлено:" in log_message
|
||||
assert "file_name=test_audio.ogg" in log_message
|
||||
assert "user_id=12345" in log_message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_date_by_file_name_logging_format(self, audio_repository):
|
||||
"""Тест формата лога при получении даты"""
|
||||
test_timestamp = 1642248600 # 2022-01-17 10:30:00
|
||||
audio_repository._execute_query_with_result.return_value = [(test_timestamp,)]
|
||||
|
||||
|
||||
await audio_repository.get_date_by_file_name("test_audio.ogg")
|
||||
|
||||
|
||||
# Проверяем формат лога
|
||||
log_call = audio_repository.logger.info.call_args
|
||||
log_message = log_call[0][0]
|
||||
|
||||
|
||||
assert "Получена дата" in log_message
|
||||
assert "15.01.2022 15:10" in log_message
|
||||
assert "test_audio.ogg" in log_message
|
||||
@@ -277,67 +305,67 @@ class TestAudioRepositoryNewSchema:
|
||||
|
||||
class TestAudioRepositoryEdgeCases:
|
||||
"""Тесты граничных случаев для AudioRepository"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def audio_repository(self):
|
||||
"""Экземпляр AudioRepository для тестов"""
|
||||
with patch.object(AudioRepository, '__init__', return_value=None):
|
||||
with patch.object(AudioRepository, "__init__", return_value=None):
|
||||
repo = AudioRepository()
|
||||
repo._execute_query = AsyncMock()
|
||||
repo._execute_query_with_result = AsyncMock()
|
||||
repo.logger = Mock()
|
||||
return repo
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_empty_string_date(self, audio_repository):
|
||||
"""Тест добавления с пустой строковой датой"""
|
||||
from database.models import AudioMessage
|
||||
|
||||
|
||||
audio_msg = AudioMessage(
|
||||
file_name="test_audio.ogg",
|
||||
author_id=12345,
|
||||
date_added="",
|
||||
file_id="test_file_id",
|
||||
listen_count=0
|
||||
listen_count=0,
|
||||
)
|
||||
|
||||
|
||||
# Должно вызвать ValueError при парсинге пустой строки
|
||||
with pytest.raises(ValueError):
|
||||
await audio_repository.add_audio_record(audio_msg)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_invalid_string_date(self, audio_repository):
|
||||
"""Тест добавления с некорректной строковой датой"""
|
||||
from database.models import AudioMessage
|
||||
|
||||
|
||||
audio_msg = AudioMessage(
|
||||
file_name="test_audio.ogg",
|
||||
author_id=12345,
|
||||
date_added="invalid_date",
|
||||
file_id="test_file_id",
|
||||
listen_count=0
|
||||
listen_count=0,
|
||||
)
|
||||
|
||||
|
||||
# Должно вызвать ValueError при парсинге некорректной даты
|
||||
with pytest.raises(ValueError):
|
||||
await audio_repository.add_audio_record(audio_msg)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_none_date(self, audio_repository):
|
||||
"""Тест добавления с None датой"""
|
||||
from database.models import AudioMessage
|
||||
|
||||
|
||||
audio_msg = AudioMessage(
|
||||
file_name="test_audio.ogg",
|
||||
author_id=12345,
|
||||
date_added=None,
|
||||
file_id="test_file_id",
|
||||
listen_count=0
|
||||
listen_count=0,
|
||||
)
|
||||
|
||||
|
||||
# Метод обрабатывает None как timestamp без преобразования
|
||||
await audio_repository.add_audio_record(audio_msg)
|
||||
|
||||
|
||||
# Проверяем, что метод был вызван с None
|
||||
call_args = audio_repository._execute_query.call_args
|
||||
params = call_args[0][1]
|
||||
@@ -349,49 +377,53 @@ class TestAudioRepositoryEdgeCases:
|
||||
# Должно вызвать ValueError при парсинге пустой строки
|
||||
with pytest.raises(ValueError):
|
||||
await audio_repository.add_audio_record_simple("test_audio.ogg", 12345, "")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_simple_invalid_string_date(self, audio_repository):
|
||||
"""Тест упрощенного добавления с некорректной строковой датой"""
|
||||
# Должно вызвать ValueError при парсинге некорректной даты
|
||||
with pytest.raises(ValueError):
|
||||
await audio_repository.add_audio_record_simple("test_audio.ogg", 12345, "invalid_date")
|
||||
|
||||
await audio_repository.add_audio_record_simple(
|
||||
"test_audio.ogg", 12345, "invalid_date"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_audio_record_simple_none_date(self, audio_repository):
|
||||
"""Тест упрощенного добавления с None датой"""
|
||||
# Метод обрабатывает None как timestamp без преобразования
|
||||
await audio_repository.add_audio_record_simple("test_audio.ogg", 12345, None)
|
||||
|
||||
|
||||
# Проверяем, что метод был вызван с None
|
||||
call_args = audio_repository._execute_query.call_args
|
||||
params = call_args[0][1]
|
||||
assert params[2] is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_date_by_file_name_zero_timestamp(self, audio_repository):
|
||||
"""Тест получения даты для timestamp = 0 (1970-01-01)"""
|
||||
audio_repository._execute_query_with_result.return_value = [(0,)]
|
||||
|
||||
|
||||
result = await audio_repository.get_date_by_file_name("test_audio.ogg")
|
||||
|
||||
|
||||
assert result == "01.01.1970 03:00"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_date_by_file_name_negative_timestamp(self, audio_repository):
|
||||
"""Тест получения даты для отрицательного timestamp"""
|
||||
audio_repository._execute_query_with_result.return_value = [(-3600,)] # 1969-12-31 23:00:00
|
||||
|
||||
audio_repository._execute_query_with_result.return_value = [
|
||||
(-3600,)
|
||||
] # 1969-12-31 23:00:00
|
||||
|
||||
result = await audio_repository.get_date_by_file_name("test_audio.ogg")
|
||||
|
||||
|
||||
assert result == "01.01.1970 02:00"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_date_by_file_name_future_timestamp(self, audio_repository):
|
||||
"""Тест получения даты для будущего timestamp"""
|
||||
future_timestamp = int(datetime(2030, 12, 31, 23, 59, 59).timestamp())
|
||||
audio_repository._execute_query_with_result.return_value = [(future_timestamp,)]
|
||||
|
||||
|
||||
result = await audio_repository.get_date_by_file_name("test_audio.ogg")
|
||||
|
||||
|
||||
assert result == "31.12.2030 23:59"
|
||||
|
||||
@@ -4,33 +4,34 @@ from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from helper_bot.utils.auto_unban_scheduler import AutoUnbanScheduler
|
||||
|
||||
|
||||
class TestAutoUnbanIntegration:
|
||||
"""Интеграционные тесты для автоматического разбана"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_db_path(self):
|
||||
"""Путь к тестовой базе данных"""
|
||||
return 'database/test_auto_unban.db'
|
||||
|
||||
return "database/test_auto_unban.db"
|
||||
|
||||
@pytest.fixture
|
||||
def setup_test_db(self, test_db_path):
|
||||
"""Создает тестовую базу данных с таблицами blacklist, our_users и blacklist_history"""
|
||||
# Удаляем старую тестовую базу если она существует
|
||||
if os.path.exists(test_db_path):
|
||||
os.remove(test_db_path)
|
||||
|
||||
|
||||
# Создаем новую базу данных
|
||||
conn = sqlite3.connect(test_db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
|
||||
# Включаем поддержку внешних ключей
|
||||
cursor.execute("PRAGMA foreign_keys = ON")
|
||||
|
||||
|
||||
# Создаем таблицу our_users (нужна для внешних ключей)
|
||||
cursor.execute('''
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS our_users (
|
||||
user_id INTEGER NOT NULL PRIMARY KEY,
|
||||
first_name TEXT,
|
||||
@@ -44,10 +45,10 @@ class TestAutoUnbanIntegration:
|
||||
date_changed INTEGER NOT NULL,
|
||||
voice_bot_welcome_received BOOLEAN DEFAULT 0
|
||||
)
|
||||
''')
|
||||
|
||||
""")
|
||||
|
||||
# Создаем таблицу blacklist
|
||||
cursor.execute('''
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS blacklist (
|
||||
user_id INTEGER NOT NULL PRIMARY KEY,
|
||||
message_for_user TEXT,
|
||||
@@ -56,10 +57,10 @@ class TestAutoUnbanIntegration:
|
||||
ban_author INTEGER,
|
||||
FOREIGN KEY (user_id) REFERENCES our_users(user_id) ON DELETE CASCADE
|
||||
)
|
||||
''')
|
||||
|
||||
""")
|
||||
|
||||
# Создаем таблицу blacklist_history
|
||||
cursor.execute('''
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS blacklist_history (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
@@ -72,273 +73,419 @@ class TestAutoUnbanIntegration:
|
||||
FOREIGN KEY (user_id) REFERENCES our_users(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (ban_author) REFERENCES our_users(user_id) ON DELETE SET NULL
|
||||
)
|
||||
''')
|
||||
|
||||
""")
|
||||
|
||||
# Создаем индексы для blacklist_history
|
||||
cursor.execute('''
|
||||
cursor.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_blacklist_history_user_id ON blacklist_history(user_id)
|
||||
''')
|
||||
cursor.execute('''
|
||||
""")
|
||||
cursor.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_blacklist_history_date_ban ON blacklist_history(date_ban)
|
||||
''')
|
||||
cursor.execute('''
|
||||
""")
|
||||
cursor.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_blacklist_history_date_unban ON blacklist_history(date_unban)
|
||||
''')
|
||||
|
||||
""")
|
||||
|
||||
# Добавляем тестовых пользователей в our_users
|
||||
current_time = int(datetime.now(timezone(timedelta(hours=3))).timestamp())
|
||||
users_data = [
|
||||
(123, "Test", "Test User 1", "test_user1", 0, "ru", 0, "😊", current_time, current_time, 0),
|
||||
(456, "Test", "Test User 2", "test_user2", 0, "ru", 0, "😊", current_time, current_time, 0),
|
||||
(789, "Test", "Test User 3", "test_user3", 0, "ru", 0, "😊", current_time, current_time, 0),
|
||||
(999, "Test", "Test User 4", "test_user4", 0, "ru", 0, "😊", current_time, current_time, 0),
|
||||
(
|
||||
123,
|
||||
"Test",
|
||||
"Test User 1",
|
||||
"test_user1",
|
||||
0,
|
||||
"ru",
|
||||
0,
|
||||
"😊",
|
||||
current_time,
|
||||
current_time,
|
||||
0,
|
||||
),
|
||||
(
|
||||
456,
|
||||
"Test",
|
||||
"Test User 2",
|
||||
"test_user2",
|
||||
0,
|
||||
"ru",
|
||||
0,
|
||||
"😊",
|
||||
current_time,
|
||||
current_time,
|
||||
0,
|
||||
),
|
||||
(
|
||||
789,
|
||||
"Test",
|
||||
"Test User 3",
|
||||
"test_user3",
|
||||
0,
|
||||
"ru",
|
||||
0,
|
||||
"😊",
|
||||
current_time,
|
||||
current_time,
|
||||
0,
|
||||
),
|
||||
(
|
||||
999,
|
||||
"Test",
|
||||
"Test User 4",
|
||||
"test_user4",
|
||||
0,
|
||||
"ru",
|
||||
0,
|
||||
"😊",
|
||||
current_time,
|
||||
current_time,
|
||||
0,
|
||||
),
|
||||
]
|
||||
cursor.executemany(
|
||||
"""INSERT INTO our_users (user_id, first_name, full_name, username, is_bot,
|
||||
language_code, has_stickers, emoji, date_added, date_changed, voice_bot_welcome_received)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
users_data
|
||||
users_data,
|
||||
)
|
||||
|
||||
|
||||
# Добавляем тестовые данные в blacklist
|
||||
today_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp())
|
||||
tomorrow_timestamp = int((datetime.now(timezone(timedelta(hours=3))) + timedelta(days=1)).timestamp())
|
||||
|
||||
tomorrow_timestamp = int(
|
||||
(datetime.now(timezone(timedelta(hours=3))) + timedelta(days=1)).timestamp()
|
||||
)
|
||||
|
||||
blacklist_data = [
|
||||
(123, "Test ban 1", today_timestamp, current_time, None), # Разблокируется сегодня
|
||||
(456, "Test ban 2", today_timestamp, current_time, None), # Разблокируется сегодня
|
||||
(789, "Test ban 3", tomorrow_timestamp, current_time, None), # Разблокируется завтра
|
||||
(999, "Test ban 4", None, current_time, None), # Навсегда заблокирован
|
||||
(
|
||||
123,
|
||||
"Test ban 1",
|
||||
today_timestamp,
|
||||
current_time,
|
||||
None,
|
||||
), # Разблокируется сегодня
|
||||
(
|
||||
456,
|
||||
"Test ban 2",
|
||||
today_timestamp,
|
||||
current_time,
|
||||
None,
|
||||
), # Разблокируется сегодня
|
||||
(
|
||||
789,
|
||||
"Test ban 3",
|
||||
tomorrow_timestamp,
|
||||
current_time,
|
||||
None,
|
||||
), # Разблокируется завтра
|
||||
(999, "Test ban 4", None, current_time, None), # Навсегда заблокирован
|
||||
]
|
||||
|
||||
|
||||
cursor.executemany(
|
||||
"INSERT INTO blacklist (user_id, message_for_user, date_to_unban, created_at, ban_author) VALUES (?, ?, ?, ?, ?)",
|
||||
blacklist_data
|
||||
blacklist_data,
|
||||
)
|
||||
|
||||
|
||||
# Добавляем тестовые данные в blacklist_history
|
||||
# Для пользователей 123 и 456 (которые будут разблокированы) создаем записи с date_unban = NULL
|
||||
yesterday_timestamp = int((datetime.now(timezone(timedelta(hours=3))) - timedelta(days=1)).timestamp())
|
||||
|
||||
yesterday_timestamp = int(
|
||||
(datetime.now(timezone(timedelta(hours=3))) - timedelta(days=1)).timestamp()
|
||||
)
|
||||
|
||||
history_data = [
|
||||
(123, "Test ban 1", yesterday_timestamp, None, None, yesterday_timestamp, yesterday_timestamp), # Будет разблокирован
|
||||
(456, "Test ban 2", yesterday_timestamp, None, None, yesterday_timestamp, yesterday_timestamp), # Будет разблокирован
|
||||
(789, "Test ban 3", yesterday_timestamp, None, None, yesterday_timestamp, yesterday_timestamp), # Не будет разблокирован сегодня
|
||||
(999, "Test ban 4", yesterday_timestamp, None, None, yesterday_timestamp, yesterday_timestamp), # Навсегда заблокирован
|
||||
(
|
||||
123,
|
||||
"Test ban 1",
|
||||
yesterday_timestamp,
|
||||
None,
|
||||
None,
|
||||
yesterday_timestamp,
|
||||
yesterday_timestamp,
|
||||
), # Будет разблокирован
|
||||
(
|
||||
456,
|
||||
"Test ban 2",
|
||||
yesterday_timestamp,
|
||||
None,
|
||||
None,
|
||||
yesterday_timestamp,
|
||||
yesterday_timestamp,
|
||||
), # Будет разблокирован
|
||||
(
|
||||
789,
|
||||
"Test ban 3",
|
||||
yesterday_timestamp,
|
||||
None,
|
||||
None,
|
||||
yesterday_timestamp,
|
||||
yesterday_timestamp,
|
||||
), # Не будет разблокирован сегодня
|
||||
(
|
||||
999,
|
||||
"Test ban 4",
|
||||
yesterday_timestamp,
|
||||
None,
|
||||
None,
|
||||
yesterday_timestamp,
|
||||
yesterday_timestamp,
|
||||
), # Навсегда заблокирован
|
||||
]
|
||||
|
||||
|
||||
cursor.executemany(
|
||||
"""INSERT INTO blacklist_history
|
||||
(user_id, message_for_user, date_ban, date_unban, ban_author, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)""",
|
||||
history_data
|
||||
history_data,
|
||||
)
|
||||
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
yield test_db_path
|
||||
|
||||
|
||||
# Очистка после тестов
|
||||
if os.path.exists(test_db_path):
|
||||
os.remove(test_db_path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_bdf(self, test_db_path):
|
||||
"""Создает мок фабрики зависимостей с тестовой базой"""
|
||||
mock_factory = Mock()
|
||||
mock_factory.settings = {
|
||||
'Telegram': {
|
||||
'group_for_logs': '-1001234567890',
|
||||
'important_logs': '-1001234567891'
|
||||
"Telegram": {
|
||||
"group_for_logs": "-1001234567890",
|
||||
"important_logs": "-1001234567891",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Создаем реальный экземпляр базы данных с тестовым файлом
|
||||
import os
|
||||
|
||||
from database.async_db import AsyncBotDB
|
||||
|
||||
mock_factory.database = AsyncBotDB(test_db_path)
|
||||
|
||||
|
||||
return mock_factory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_bot(self):
|
||||
"""Создает мок бота"""
|
||||
mock_bot = Mock()
|
||||
mock_bot.send_message = AsyncMock()
|
||||
return mock_bot
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('helper_bot.utils.auto_unban_scheduler.get_global_instance')
|
||||
async def test_auto_unban_with_real_db(self, mock_get_instance, setup_test_db, mock_bdf, mock_bot):
|
||||
@patch("helper_bot.utils.auto_unban_scheduler.get_global_instance")
|
||||
async def test_auto_unban_with_real_db(
|
||||
self, mock_get_instance, setup_test_db, mock_bdf, mock_bot
|
||||
):
|
||||
"""Тест автоматического разбана с реальной базой данных"""
|
||||
# Настройка моков
|
||||
mock_get_instance.return_value = mock_bdf
|
||||
|
||||
|
||||
# Создаем планировщик
|
||||
scheduler = AutoUnbanScheduler()
|
||||
scheduler.bot_db = mock_bdf.database
|
||||
scheduler.set_bot(mock_bot)
|
||||
|
||||
|
||||
# Проверяем начальное состояние базы
|
||||
conn = sqlite3.connect(setup_test_db)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT COUNT(*) FROM blacklist")
|
||||
initial_count = cursor.fetchone()[0]
|
||||
assert initial_count == 4
|
||||
|
||||
|
||||
# Проверяем начальное состояние истории: должно быть 2 записи с date_unban IS NULL для user_id 123 и 456
|
||||
cursor.execute("SELECT COUNT(*) FROM blacklist_history WHERE user_id IN (123, 456) AND date_unban IS NULL")
|
||||
cursor.execute(
|
||||
"SELECT COUNT(*) FROM blacklist_history WHERE user_id IN (123, 456) AND date_unban IS NULL"
|
||||
)
|
||||
initial_open_history = cursor.fetchone()[0]
|
||||
assert initial_open_history == 2
|
||||
|
||||
|
||||
# Запоминаем время до разбана для проверки updated_at
|
||||
before_unban_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp())
|
||||
|
||||
before_unban_timestamp = int(
|
||||
datetime.now(timezone(timedelta(hours=3))).timestamp()
|
||||
)
|
||||
|
||||
# Выполняем автоматический разбан
|
||||
await scheduler.auto_unban_users()
|
||||
|
||||
|
||||
# Запоминаем время после разбана для проверки updated_at
|
||||
after_unban_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp())
|
||||
|
||||
after_unban_timestamp = int(
|
||||
datetime.now(timezone(timedelta(hours=3))).timestamp()
|
||||
)
|
||||
|
||||
# Проверяем, что пользователи с сегодняшней датой разблокированы
|
||||
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,))
|
||||
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
|
||||
|
||||
|
||||
# Проверяем, что пользователи с завтрашней датой остались
|
||||
cursor.execute("SELECT COUNT(*) FROM blacklist WHERE date_to_unban IS NOT NULL AND date_to_unban > ?",
|
||||
(current_timestamp,))
|
||||
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
|
||||
|
||||
|
||||
# Проверяем, что навсегда заблокированные пользователи остались
|
||||
cursor.execute("SELECT COUNT(*) FROM blacklist WHERE date_to_unban IS NULL")
|
||||
permanent_count = cursor.fetchone()[0]
|
||||
assert permanent_count == 1
|
||||
|
||||
|
||||
# Проверяем общее количество записей
|
||||
cursor.execute("SELECT COUNT(*) FROM blacklist")
|
||||
final_count = cursor.fetchone()[0]
|
||||
assert final_count == 2 # Остались только завтрашние и навсегда заблокированные
|
||||
|
||||
|
||||
# Проверяем историю банов: для user_id 123 и 456 должны быть установлены date_unban
|
||||
cursor.execute("SELECT user_id, date_unban, updated_at FROM blacklist_history WHERE user_id IN (123, 456) ORDER BY user_id")
|
||||
cursor.execute(
|
||||
"SELECT user_id, date_unban, updated_at FROM blacklist_history WHERE user_id IN (123, 456) ORDER BY user_id"
|
||||
)
|
||||
history_records = cursor.fetchall()
|
||||
|
||||
|
||||
assert len(history_records) == 2
|
||||
|
||||
|
||||
for user_id, date_unban, updated_at in history_records:
|
||||
# Проверяем, что date_unban установлен (не NULL)
|
||||
assert date_unban is not None, f"date_unban должен быть установлен для user_id={user_id}"
|
||||
assert isinstance(date_unban, int), f"date_unban должен быть integer для user_id={user_id}"
|
||||
|
||||
assert (
|
||||
date_unban is not None
|
||||
), f"date_unban должен быть установлен для user_id={user_id}"
|
||||
assert isinstance(
|
||||
date_unban, int
|
||||
), f"date_unban должен быть integer для user_id={user_id}"
|
||||
|
||||
# Проверяем, что date_unban находится в разумных пределах (между before и after)
|
||||
assert before_unban_timestamp <= date_unban <= after_unban_timestamp, \
|
||||
f"date_unban для user_id={user_id} должен быть между {before_unban_timestamp} и {after_unban_timestamp}, получен {date_unban}"
|
||||
|
||||
assert (
|
||||
before_unban_timestamp <= date_unban <= after_unban_timestamp
|
||||
), f"date_unban для user_id={user_id} должен быть между {before_unban_timestamp} и {after_unban_timestamp}, получен {date_unban}"
|
||||
|
||||
# Проверяем, что updated_at обновлен
|
||||
assert updated_at is not None, f"updated_at должен быть установлен для user_id={user_id}"
|
||||
assert isinstance(updated_at, int), f"updated_at должен быть integer для user_id={user_id}"
|
||||
assert before_unban_timestamp <= updated_at <= after_unban_timestamp, \
|
||||
f"updated_at для user_id={user_id} должен быть между {before_unban_timestamp} и {after_unban_timestamp}, получен {updated_at}"
|
||||
|
||||
assert (
|
||||
updated_at is not None
|
||||
), f"updated_at должен быть установлен для user_id={user_id}"
|
||||
assert isinstance(
|
||||
updated_at, int
|
||||
), f"updated_at должен быть integer для user_id={user_id}"
|
||||
assert (
|
||||
before_unban_timestamp <= updated_at <= after_unban_timestamp
|
||||
), f"updated_at для user_id={user_id} должен быть между {before_unban_timestamp} и {after_unban_timestamp}, получен {updated_at}"
|
||||
|
||||
# Проверяем, что для user_id 789 и 999 записи в истории остались без изменений (date_unban все еще NULL)
|
||||
cursor.execute("SELECT COUNT(*) FROM blacklist_history WHERE user_id IN (789, 999) AND date_unban IS NULL")
|
||||
cursor.execute(
|
||||
"SELECT COUNT(*) FROM blacklist_history WHERE user_id IN (789, 999) AND date_unban IS NULL"
|
||||
)
|
||||
unchanged_history = cursor.fetchone()[0]
|
||||
assert unchanged_history == 2, "Записи для user_id 789 и 999 должны остаться с date_unban = NULL"
|
||||
|
||||
assert (
|
||||
unchanged_history == 2
|
||||
), "Записи для user_id 789 и 999 должны остаться с date_unban = NULL"
|
||||
|
||||
conn.close()
|
||||
|
||||
|
||||
# Проверяем, что отчет был отправлен
|
||||
mock_bot.send_message.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('helper_bot.utils.auto_unban_scheduler.get_global_instance')
|
||||
async def test_auto_unban_no_users_today(self, mock_get_instance, setup_test_db, mock_bdf, mock_bot):
|
||||
@patch("helper_bot.utils.auto_unban_scheduler.get_global_instance")
|
||||
async def test_auto_unban_no_users_today(
|
||||
self, mock_get_instance, setup_test_db, mock_bdf, mock_bot
|
||||
):
|
||||
"""Тест разбана когда нет пользователей для разблокировки сегодня"""
|
||||
# Настройка моков
|
||||
mock_get_instance.return_value = mock_bdf
|
||||
|
||||
|
||||
# Удаляем пользователей с сегодняшней датой
|
||||
conn = sqlite3.connect(setup_test_db)
|
||||
cursor = conn.cursor()
|
||||
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,))
|
||||
|
||||
cursor.execute(
|
||||
"DELETE FROM blacklist WHERE date_to_unban IS NOT NULL AND date_to_unban <= ?",
|
||||
(current_timestamp,),
|
||||
)
|
||||
|
||||
# Проверяем начальное состояние истории: все записи должны иметь date_unban = NULL
|
||||
cursor.execute("SELECT COUNT(*) FROM blacklist_history WHERE date_unban IS NULL")
|
||||
cursor.execute(
|
||||
"SELECT COUNT(*) FROM blacklist_history WHERE date_unban IS NULL"
|
||||
)
|
||||
initial_open_history = cursor.fetchone()[0]
|
||||
assert initial_open_history == 4 # Все 4 записи должны быть открытыми
|
||||
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
# Создаем планировщик
|
||||
scheduler = AutoUnbanScheduler()
|
||||
scheduler.bot_db = mock_bdf.database
|
||||
scheduler.set_bot(mock_bot)
|
||||
|
||||
|
||||
# Выполняем автоматический разбан
|
||||
await scheduler.auto_unban_users()
|
||||
|
||||
|
||||
# Проверяем, что история не изменилась (все записи все еще с date_unban = NULL)
|
||||
conn = sqlite3.connect(setup_test_db)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT COUNT(*) FROM blacklist_history WHERE date_unban IS NULL")
|
||||
cursor.execute(
|
||||
"SELECT COUNT(*) FROM blacklist_history WHERE date_unban IS NULL"
|
||||
)
|
||||
final_open_history = cursor.fetchone()[0]
|
||||
assert final_open_history == 4, "История не должна изменяться, если нет пользователей для разблокировки"
|
||||
assert (
|
||||
final_open_history == 4
|
||||
), "История не должна изменяться, если нет пользователей для разблокировки"
|
||||
conn.close()
|
||||
|
||||
|
||||
# Проверяем, что отчет не был отправлен (нет пользователей для разблокировки)
|
||||
mock_bot.send_message.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('helper_bot.utils.auto_unban_scheduler.get_global_instance')
|
||||
async def test_auto_unban_database_error(self, mock_get_instance, setup_test_db, mock_bdf, mock_bot):
|
||||
@patch("helper_bot.utils.auto_unban_scheduler.get_global_instance")
|
||||
async def test_auto_unban_database_error(
|
||||
self, mock_get_instance, setup_test_db, mock_bdf, mock_bot
|
||||
):
|
||||
"""Тест обработки ошибок базы данных"""
|
||||
# Настройка моков
|
||||
mock_get_instance.return_value = mock_bdf
|
||||
|
||||
|
||||
# Создаем планировщик
|
||||
scheduler = AutoUnbanScheduler()
|
||||
scheduler.bot_db = mock_bdf.database
|
||||
scheduler.set_bot(mock_bot)
|
||||
|
||||
|
||||
# Удаляем таблицу чтобы вызвать ошибку
|
||||
conn = sqlite3.connect(setup_test_db)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DROP TABLE blacklist")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
# Выполняем автоматический разбан
|
||||
await scheduler.auto_unban_users()
|
||||
|
||||
|
||||
# Проверяем, что отчет об ошибке был отправлен
|
||||
mock_bot.send_message.assert_called_once()
|
||||
call_args = mock_bot.send_message.call_args
|
||||
assert call_args[1]['chat_id'] == '-1001234567891' # important_logs
|
||||
assert "Ошибка автоматического разбана" in call_args[1]['text']
|
||||
|
||||
assert call_args[1]["chat_id"] == "-1001234567891" # important_logs
|
||||
assert "Ошибка автоматического разбана" in call_args[1]["text"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('helper_bot.utils.auto_unban_scheduler.get_global_instance')
|
||||
async def test_auto_unban_updates_history(self, mock_get_instance, setup_test_db, mock_bdf, mock_bot):
|
||||
@patch("helper_bot.utils.auto_unban_scheduler.get_global_instance")
|
||||
async def test_auto_unban_updates_history(
|
||||
self, mock_get_instance, setup_test_db, mock_bdf, mock_bot
|
||||
):
|
||||
"""Тест что автоматический разбан обновляет историю банов"""
|
||||
# Настройка моков
|
||||
mock_get_instance.return_value = mock_bdf
|
||||
|
||||
|
||||
# Создаем планировщик
|
||||
scheduler = AutoUnbanScheduler()
|
||||
scheduler.bot_db = mock_bdf.database
|
||||
scheduler.set_bot(mock_bot)
|
||||
|
||||
|
||||
conn = sqlite3.connect(setup_test_db)
|
||||
cursor = conn.cursor()
|
||||
|
||||
|
||||
# Проверяем начальное состояние: для user_id 123 и 456 должны быть записи с date_unban = NULL
|
||||
cursor.execute("""
|
||||
SELECT id, user_id, date_ban, date_unban, updated_at
|
||||
@@ -347,26 +494,32 @@ class TestAutoUnbanIntegration:
|
||||
ORDER BY user_id
|
||||
""")
|
||||
initial_records = cursor.fetchall()
|
||||
assert len(initial_records) == 2, "Должно быть 2 открытые записи для user_id 123 и 456"
|
||||
|
||||
assert (
|
||||
len(initial_records) == 2
|
||||
), "Должно быть 2 открытые записи для user_id 123 и 456"
|
||||
|
||||
# Запоминаем ID записей и их начальные значения updated_at
|
||||
record_ids = {row[0]: (row[1], row[4]) for row in initial_records}
|
||||
|
||||
|
||||
# Запоминаем время до разбана
|
||||
before_unban_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp())
|
||||
|
||||
before_unban_timestamp = int(
|
||||
datetime.now(timezone(timedelta(hours=3))).timestamp()
|
||||
)
|
||||
|
||||
conn.close()
|
||||
|
||||
|
||||
# Выполняем автоматический разбан
|
||||
await scheduler.auto_unban_users()
|
||||
|
||||
|
||||
# Запоминаем время после разбана
|
||||
after_unban_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp())
|
||||
|
||||
after_unban_timestamp = int(
|
||||
datetime.now(timezone(timedelta(hours=3))).timestamp()
|
||||
)
|
||||
|
||||
# Проверяем, что записи обновлены
|
||||
conn = sqlite3.connect(setup_test_db)
|
||||
cursor = conn.cursor()
|
||||
|
||||
|
||||
cursor.execute("""
|
||||
SELECT id, user_id, date_ban, date_unban, updated_at
|
||||
FROM blacklist_history
|
||||
@@ -374,32 +527,45 @@ class TestAutoUnbanIntegration:
|
||||
ORDER BY user_id
|
||||
""")
|
||||
updated_records = cursor.fetchall()
|
||||
|
||||
|
||||
assert len(updated_records) == 2, "Должно быть 2 записи для user_id 123 и 456"
|
||||
|
||||
|
||||
for record_id, user_id, date_ban, date_unban, updated_at in updated_records:
|
||||
# Проверяем, что это одна из наших записей
|
||||
assert record_id in record_ids, f"Запись с id={record_id} должна быть в исходных записях"
|
||||
|
||||
assert (
|
||||
record_id in record_ids
|
||||
), f"Запись с id={record_id} должна быть в исходных записях"
|
||||
|
||||
# Проверяем, что date_unban установлен
|
||||
assert date_unban is not None, f"date_unban должен быть установлен для user_id={user_id}"
|
||||
assert isinstance(date_unban, int), f"date_unban должен быть integer для user_id={user_id}"
|
||||
|
||||
assert (
|
||||
date_unban is not None
|
||||
), f"date_unban должен быть установлен для user_id={user_id}"
|
||||
assert isinstance(
|
||||
date_unban, int
|
||||
), f"date_unban должен быть integer для user_id={user_id}"
|
||||
|
||||
# Проверяем, что date_unban находится в разумных пределах
|
||||
assert before_unban_timestamp <= date_unban <= after_unban_timestamp, \
|
||||
f"date_unban для user_id={user_id} должен быть между {before_unban_timestamp} и {after_unban_timestamp}"
|
||||
|
||||
assert (
|
||||
before_unban_timestamp <= date_unban <= after_unban_timestamp
|
||||
), f"date_unban для user_id={user_id} должен быть между {before_unban_timestamp} и {after_unban_timestamp}"
|
||||
|
||||
# Проверяем, что updated_at обновлен (должен быть больше начального значения)
|
||||
assert updated_at is not None, f"updated_at должен быть установлен для user_id={user_id}"
|
||||
assert isinstance(updated_at, int), f"updated_at должен быть integer для user_id={user_id}"
|
||||
assert before_unban_timestamp <= updated_at <= after_unban_timestamp, \
|
||||
f"updated_at для user_id={user_id} должен быть между {before_unban_timestamp} и {after_unban_timestamp}"
|
||||
|
||||
assert (
|
||||
updated_at is not None
|
||||
), f"updated_at должен быть установлен для user_id={user_id}"
|
||||
assert isinstance(
|
||||
updated_at, int
|
||||
), f"updated_at должен быть integer для user_id={user_id}"
|
||||
assert (
|
||||
before_unban_timestamp <= updated_at <= after_unban_timestamp
|
||||
), f"updated_at для user_id={user_id} должен быть между {before_unban_timestamp} и {after_unban_timestamp}"
|
||||
|
||||
# Проверяем, что updated_at действительно обновлен (больше начального значения)
|
||||
initial_updated_at = record_ids[record_id][1]
|
||||
assert updated_at >= initial_updated_at, \
|
||||
f"updated_at для user_id={user_id} должен быть больше или равен начальному значению"
|
||||
|
||||
assert (
|
||||
updated_at >= initial_updated_at
|
||||
), f"updated_at для user_id={user_id} должен быть больше или равен начальному значению"
|
||||
|
||||
# Проверяем, что обновлена только последняя запись для каждого пользователя
|
||||
# (если бы было несколько записей, обновилась бы только последняя)
|
||||
cursor.execute("""
|
||||
@@ -407,29 +573,35 @@ class TestAutoUnbanIntegration:
|
||||
WHERE user_id IN (123, 456) AND date_unban IS NOT NULL
|
||||
""")
|
||||
closed_records = cursor.fetchone()[0]
|
||||
assert closed_records == 2, "Должно быть закрыто 2 записи (по одной для каждого пользователя)"
|
||||
|
||||
assert (
|
||||
closed_records == 2
|
||||
), "Должно быть закрыто 2 записи (по одной для каждого пользователя)"
|
||||
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM blacklist_history
|
||||
WHERE user_id IN (123, 456) AND date_unban IS NULL
|
||||
""")
|
||||
open_records = cursor.fetchone()[0]
|
||||
assert open_records == 0, "Не должно быть открытых записей для user_id 123 и 456"
|
||||
|
||||
assert (
|
||||
open_records == 0
|
||||
), "Не должно быть открытых записей для user_id 123 и 456"
|
||||
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_date_format_consistency(self, setup_test_db, mock_bdf):
|
||||
"""Тест консистентности формата дат"""
|
||||
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")
|
||||
cursor.execute(
|
||||
"SELECT date_to_unban FROM blacklist WHERE date_to_unban IS NOT NULL LIMIT 1"
|
||||
)
|
||||
result = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
|
||||
if result and result[0]:
|
||||
timestamp = result[0]
|
||||
# Проверяем, что это валидный timestamp (целое число)
|
||||
@@ -442,38 +614,39 @@ class TestAutoUnbanIntegration:
|
||||
|
||||
class TestSchedulerLifecycle:
|
||||
"""Тесты жизненного цикла планировщика"""
|
||||
|
||||
|
||||
def test_scheduler_start_stop(self):
|
||||
"""Тест запуска и остановки планировщика"""
|
||||
scheduler = AutoUnbanScheduler()
|
||||
|
||||
|
||||
# Запускаем планировщик
|
||||
scheduler.start_scheduler()
|
||||
assert scheduler.scheduler.running
|
||||
|
||||
|
||||
# Останавливаем планировщик (должно пройти без ошибок)
|
||||
scheduler.stop_scheduler()
|
||||
# APScheduler может не сразу остановиться, но это нормально
|
||||
|
||||
|
||||
def test_scheduler_job_creation(self):
|
||||
"""Тест создания задачи в планировщике"""
|
||||
scheduler = AutoUnbanScheduler()
|
||||
|
||||
with patch.object(scheduler.scheduler, 'add_job') as mock_add_job:
|
||||
|
||||
with patch.object(scheduler.scheduler, "add_job") as mock_add_job:
|
||||
scheduler.start_scheduler()
|
||||
|
||||
|
||||
# Проверяем, что задача была создана с правильными параметрами
|
||||
mock_add_job.assert_called_once()
|
||||
call_args = mock_add_job.call_args
|
||||
|
||||
|
||||
# Проверяем функцию
|
||||
assert call_args[0][0] == scheduler.auto_unban_users
|
||||
|
||||
|
||||
# Проверяем триггер (должен быть CronTrigger)
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
|
||||
assert isinstance(call_args[0][1], CronTrigger)
|
||||
|
||||
|
||||
# Проверяем ID и имя задачи
|
||||
assert call_args[1]['id'] == 'auto_unban_users'
|
||||
assert call_args[1]['name'] == 'Автоматический разбан пользователей'
|
||||
assert call_args[1]['replace_existing'] is True
|
||||
assert call_args[1]["id"] == "auto_unban_users"
|
||||
assert call_args[1]["name"] == "Автоматический разбан пользователей"
|
||||
assert call_args[1]["replace_existing"] is True
|
||||
|
||||
@@ -3,231 +3,248 @@ from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from helper_bot.utils.auto_unban_scheduler import (AutoUnbanScheduler,
|
||||
get_auto_unban_scheduler)
|
||||
|
||||
from helper_bot.utils.auto_unban_scheduler import (
|
||||
AutoUnbanScheduler,
|
||||
get_auto_unban_scheduler,
|
||||
)
|
||||
|
||||
|
||||
class TestAutoUnbanScheduler:
|
||||
"""Тесты для класса AutoUnbanScheduler"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def scheduler(self):
|
||||
"""Создает экземпляр планировщика для тестов"""
|
||||
return AutoUnbanScheduler()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_bot_db(self):
|
||||
"""Создает мок базы данных"""
|
||||
mock_db = Mock()
|
||||
mock_db.get_users_for_unblock_today = AsyncMock(return_value={
|
||||
123: "test_user1",
|
||||
456: "test_user2"
|
||||
})
|
||||
mock_db.get_users_for_unblock_today = AsyncMock(
|
||||
return_value={123: "test_user1", 456: "test_user2"}
|
||||
)
|
||||
mock_db.delete_user_blacklist = AsyncMock(return_value=True)
|
||||
return mock_db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_bdf(self):
|
||||
"""Создает мок фабрики зависимостей"""
|
||||
mock_factory = Mock()
|
||||
mock_factory.settings = {
|
||||
'Telegram': {
|
||||
'group_for_logs': '-1001234567890',
|
||||
'important_logs': '-1001234567891'
|
||||
"Telegram": {
|
||||
"group_for_logs": "-1001234567890",
|
||||
"important_logs": "-1001234567891",
|
||||
}
|
||||
}
|
||||
return mock_factory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_bot(self):
|
||||
"""Создает мок бота"""
|
||||
mock_bot = Mock()
|
||||
mock_bot.send_message = AsyncMock()
|
||||
return mock_bot
|
||||
|
||||
|
||||
def test_scheduler_initialization(self, scheduler):
|
||||
"""Тест инициализации планировщика"""
|
||||
assert scheduler.bot_db is not None
|
||||
assert scheduler.scheduler is not None
|
||||
assert scheduler.bot is None
|
||||
|
||||
|
||||
def test_set_bot(self, scheduler, mock_bot):
|
||||
"""Тест установки бота"""
|
||||
scheduler.set_bot(mock_bot)
|
||||
assert scheduler.bot == mock_bot
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('helper_bot.utils.auto_unban_scheduler.get_global_instance')
|
||||
async def test_auto_unban_users_success(self, mock_get_instance, scheduler, mock_bot_db, mock_bdf, mock_bot):
|
||||
@patch("helper_bot.utils.auto_unban_scheduler.get_global_instance")
|
||||
async def test_auto_unban_users_success(
|
||||
self, mock_get_instance, scheduler, mock_bot_db, mock_bdf, mock_bot
|
||||
):
|
||||
"""Тест успешного выполнения автоматического разбана"""
|
||||
# Настройка моков
|
||||
mock_get_instance.return_value = mock_bdf
|
||||
scheduler.bot_db = mock_bot_db
|
||||
scheduler.set_bot(mock_bot)
|
||||
|
||||
|
||||
# Выполнение теста
|
||||
await scheduler.auto_unban_users()
|
||||
|
||||
|
||||
# Проверки
|
||||
mock_bot_db.get_users_for_unblock_today.assert_called_once()
|
||||
assert mock_bot_db.delete_user_blacklist.call_count == 2
|
||||
mock_bot.send_message.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('helper_bot.utils.auto_unban_scheduler.get_global_instance')
|
||||
async def test_auto_unban_users_no_users(self, mock_get_instance, scheduler, mock_bot_db, mock_bdf, mock_bot):
|
||||
@patch("helper_bot.utils.auto_unban_scheduler.get_global_instance")
|
||||
async def test_auto_unban_users_no_users(
|
||||
self, mock_get_instance, scheduler, mock_bot_db, mock_bdf, mock_bot
|
||||
):
|
||||
"""Тест разбана когда нет пользователей для разблокировки"""
|
||||
# Настройка моков
|
||||
mock_get_instance.return_value = mock_bdf
|
||||
mock_bot_db.get_users_for_unblock_today = AsyncMock(return_value={})
|
||||
scheduler.bot_db = mock_bot_db
|
||||
scheduler.set_bot(mock_bot)
|
||||
|
||||
|
||||
# Выполнение теста
|
||||
await scheduler.auto_unban_users()
|
||||
|
||||
|
||||
# Проверки
|
||||
mock_bot_db.get_users_for_unblock_today.assert_called_once()
|
||||
mock_bot_db.delete_user_blacklist.assert_not_called()
|
||||
mock_bot.send_message.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('helper_bot.utils.auto_unban_scheduler.get_global_instance')
|
||||
async def test_auto_unban_users_partial_failure(self, mock_get_instance, scheduler, mock_bot_db, mock_bdf, mock_bot):
|
||||
@patch("helper_bot.utils.auto_unban_scheduler.get_global_instance")
|
||||
async def test_auto_unban_users_partial_failure(
|
||||
self, mock_get_instance, scheduler, mock_bot_db, mock_bdf, mock_bot
|
||||
):
|
||||
"""Тест разбана с частичными ошибками"""
|
||||
# Настройка моков
|
||||
mock_get_instance.return_value = mock_bdf
|
||||
mock_bot_db.get_users_for_unblock_today = AsyncMock(return_value={
|
||||
123: "test_user1",
|
||||
456: "test_user2"
|
||||
})
|
||||
mock_bot_db.get_users_for_unblock_today = AsyncMock(
|
||||
return_value={123: "test_user1", 456: "test_user2"}
|
||||
)
|
||||
# Первый вызов успешен, второй - ошибка
|
||||
mock_bot_db.delete_user_blacklist = AsyncMock(side_effect=[True, False])
|
||||
scheduler.bot_db = mock_bot_db
|
||||
scheduler.set_bot(mock_bot)
|
||||
|
||||
|
||||
# Выполнение теста
|
||||
await scheduler.auto_unban_users()
|
||||
|
||||
|
||||
# Проверки
|
||||
assert mock_bot_db.delete_user_blacklist.call_count == 2
|
||||
mock_bot.send_message.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('helper_bot.utils.auto_unban_scheduler.get_global_instance')
|
||||
async def test_auto_unban_users_exception(self, mock_get_instance, scheduler, mock_bot_db, mock_bdf, mock_bot):
|
||||
@patch("helper_bot.utils.auto_unban_scheduler.get_global_instance")
|
||||
async def test_auto_unban_users_exception(
|
||||
self, mock_get_instance, scheduler, mock_bot_db, mock_bdf, mock_bot
|
||||
):
|
||||
"""Тест разбана с исключением"""
|
||||
# Настройка моков
|
||||
mock_get_instance.return_value = mock_bdf
|
||||
mock_bot_db.get_users_for_unblock_today = AsyncMock(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)
|
||||
|
||||
|
||||
# Выполнение теста
|
||||
await scheduler.auto_unban_users()
|
||||
|
||||
|
||||
# Проверки
|
||||
mock_bot.send_message.assert_called_once()
|
||||
# Проверяем, что сообщение об ошибке было отправлено
|
||||
call_args = mock_bot.send_message.call_args
|
||||
assert "Ошибка автоматического разбана" in call_args[1]['text']
|
||||
|
||||
assert "Ошибка автоматического разбана" in call_args[1]["text"]
|
||||
|
||||
def test_generate_report(self, scheduler):
|
||||
"""Тест генерации отчета"""
|
||||
users = {123: "test_user1", 456: "test_user2"}
|
||||
failed_users = ["456 (test_user2)"]
|
||||
|
||||
|
||||
report = scheduler._generate_report(1, 1, failed_users, users)
|
||||
|
||||
|
||||
assert "Отчет об автоматическом разбане" in report
|
||||
assert "Успешно разблокировано: 1" in report
|
||||
assert "Ошибок: 1" in report
|
||||
assert "ID: 123" in report
|
||||
assert "456 (test_user2)" in report
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('helper_bot.utils.auto_unban_scheduler.get_global_instance')
|
||||
@patch("helper_bot.utils.auto_unban_scheduler.get_global_instance")
|
||||
async def test_send_report(self, mock_get_instance, scheduler, mock_bdf, mock_bot):
|
||||
"""Тест отправки отчета"""
|
||||
mock_get_instance.return_value = mock_bdf
|
||||
scheduler.set_bot(mock_bot)
|
||||
|
||||
|
||||
report = "Test report"
|
||||
await scheduler._send_report(report)
|
||||
|
||||
|
||||
# Проверяем, что send_message был вызван
|
||||
mock_bot.send_message.assert_called_once()
|
||||
|
||||
|
||||
# Проверяем аргументы вызова
|
||||
call_args = mock_bot.send_message.call_args
|
||||
assert call_args[1]['text'] == report
|
||||
assert call_args[1]['parse_mode'] == 'HTML'
|
||||
|
||||
assert call_args[1]["text"] == report
|
||||
assert call_args[1]["parse_mode"] == "HTML"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('helper_bot.utils.auto_unban_scheduler.get_global_instance')
|
||||
async def test_send_error_report(self, mock_get_instance, scheduler, mock_bdf, mock_bot):
|
||||
@patch("helper_bot.utils.auto_unban_scheduler.get_global_instance")
|
||||
async def test_send_error_report(
|
||||
self, mock_get_instance, scheduler, mock_bdf, mock_bot
|
||||
):
|
||||
"""Тест отправки отчета об ошибке"""
|
||||
mock_get_instance.return_value = mock_bdf
|
||||
scheduler.set_bot(mock_bot)
|
||||
|
||||
|
||||
error_msg = "Test error"
|
||||
await scheduler._send_error_report(error_msg)
|
||||
|
||||
|
||||
# Проверяем, что send_message был вызван
|
||||
mock_bot.send_message.assert_called_once()
|
||||
|
||||
|
||||
# Проверяем аргументы вызова
|
||||
call_args = mock_bot.send_message.call_args
|
||||
assert "Ошибка автоматического разбана" in call_args[1]['text']
|
||||
assert error_msg in call_args[1]['text']
|
||||
assert call_args[1]['parse_mode'] == 'HTML'
|
||||
|
||||
assert "Ошибка автоматического разбана" in call_args[1]["text"]
|
||||
assert error_msg in call_args[1]["text"]
|
||||
assert call_args[1]["parse_mode"] == "HTML"
|
||||
|
||||
def test_start_scheduler(self, scheduler):
|
||||
"""Тест запуска планировщика"""
|
||||
with patch.object(scheduler.scheduler, 'add_job') as mock_add_job, \
|
||||
patch.object(scheduler.scheduler, 'start') as mock_start:
|
||||
|
||||
with (
|
||||
patch.object(scheduler.scheduler, "add_job") as mock_add_job,
|
||||
patch.object(scheduler.scheduler, "start") as mock_start,
|
||||
):
|
||||
|
||||
scheduler.start_scheduler()
|
||||
|
||||
|
||||
mock_add_job.assert_called_once()
|
||||
mock_start.assert_called_once()
|
||||
|
||||
|
||||
def test_stop_scheduler(self, scheduler):
|
||||
"""Тест остановки планировщика"""
|
||||
# Сначала запускаем планировщик
|
||||
scheduler.start_scheduler()
|
||||
|
||||
|
||||
# Проверяем, что планировщик запущен
|
||||
assert scheduler.scheduler.running
|
||||
|
||||
|
||||
# Теперь останавливаем (должно пройти без ошибок)
|
||||
scheduler.stop_scheduler()
|
||||
|
||||
|
||||
# Проверяем, что метод выполнился без исключений
|
||||
# APScheduler может не сразу остановиться, но это нормально
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('helper_bot.utils.auto_unban_scheduler.get_global_instance')
|
||||
async def test_run_manual_unban(self, mock_get_instance, scheduler, mock_bot_db, mock_bdf, mock_bot):
|
||||
@patch("helper_bot.utils.auto_unban_scheduler.get_global_instance")
|
||||
async def test_run_manual_unban(
|
||||
self, mock_get_instance, scheduler, mock_bot_db, mock_bdf, mock_bot
|
||||
):
|
||||
"""Тест ручного запуска разбана"""
|
||||
mock_get_instance.return_value = mock_bdf
|
||||
mock_bot_db.get_users_for_unblock_today.return_value = {}
|
||||
scheduler.bot_db = mock_bot_db
|
||||
scheduler.set_bot(mock_bot)
|
||||
|
||||
|
||||
await scheduler.run_manual_unban()
|
||||
|
||||
|
||||
mock_bot_db.get_users_for_unblock_today.assert_called_once()
|
||||
|
||||
|
||||
class TestGetAutoUnbanScheduler:
|
||||
"""Тесты для функции get_auto_unban_scheduler"""
|
||||
|
||||
|
||||
def test_get_auto_unban_scheduler(self):
|
||||
"""Тест получения глобального экземпляра планировщика"""
|
||||
scheduler = get_auto_unban_scheduler()
|
||||
assert isinstance(scheduler, AutoUnbanScheduler)
|
||||
|
||||
|
||||
# Проверяем, что возвращается один и тот же экземпляр
|
||||
scheduler2 = get_auto_unban_scheduler()
|
||||
assert scheduler is scheduler2
|
||||
@@ -235,17 +252,17 @@ class TestGetAutoUnbanScheduler:
|
||||
|
||||
class TestDateHandling:
|
||||
"""Тесты для обработки дат"""
|
||||
|
||||
|
||||
def test_moscow_timezone(self):
|
||||
"""Тест работы с московским временем"""
|
||||
scheduler = AutoUnbanScheduler()
|
||||
|
||||
|
||||
# Проверяем, что дата формируется в правильном формате
|
||||
moscow_tz = timezone(timedelta(hours=3))
|
||||
today = datetime.now(moscow_tz).strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
assert len(today) == 10 # YYYY-MM-DD
|
||||
assert today.count('-') == 2
|
||||
assert today.count("-") == 2
|
||||
assert today[:4].isdigit() # Год
|
||||
assert today[5:7].isdigit() # Месяц
|
||||
assert today[8:10].isdigit() # День
|
||||
@@ -254,35 +271,37 @@ class TestDateHandling:
|
||||
@pytest.mark.asyncio
|
||||
class TestAsyncOperations:
|
||||
"""Тесты асинхронных операций"""
|
||||
|
||||
@patch('helper_bot.utils.auto_unban_scheduler.get_global_instance')
|
||||
|
||||
@patch("helper_bot.utils.auto_unban_scheduler.get_global_instance")
|
||||
async def test_async_auto_unban_flow(self, mock_get_instance):
|
||||
"""Тест полного асинхронного потока разбана"""
|
||||
# Создаем моки
|
||||
mock_bdf = Mock()
|
||||
mock_bdf.settings = {
|
||||
'Telegram': {
|
||||
'group_for_logs': '-1001234567890',
|
||||
'important_logs': '-1001234567891'
|
||||
"Telegram": {
|
||||
"group_for_logs": "-1001234567890",
|
||||
"important_logs": "-1001234567891",
|
||||
}
|
||||
}
|
||||
mock_get_instance.return_value = mock_bdf
|
||||
|
||||
|
||||
mock_bot_db = Mock()
|
||||
mock_bot_db.get_users_for_unblock_today = AsyncMock(return_value={123: "test_user"})
|
||||
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()
|
||||
|
||||
|
||||
# Создаем планировщик
|
||||
scheduler = AutoUnbanScheduler()
|
||||
scheduler.bot_db = mock_bot_db
|
||||
scheduler.set_bot(mock_bot)
|
||||
|
||||
|
||||
# Выполняем разбан
|
||||
await scheduler.auto_unban_users()
|
||||
|
||||
|
||||
# Проверяем результаты
|
||||
mock_bot_db.get_users_for_unblock_today.assert_called_once()
|
||||
mock_bot_db.delete_user_blacklist.assert_called_once_with(123)
|
||||
|
||||
@@ -3,14 +3,16 @@ from datetime import datetime
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from database.models import BlacklistHistoryRecord
|
||||
from database.repositories.blacklist_history_repository import \
|
||||
BlacklistHistoryRepository
|
||||
from database.repositories.blacklist_history_repository import (
|
||||
BlacklistHistoryRepository,
|
||||
)
|
||||
|
||||
|
||||
class TestBlacklistHistoryRepository:
|
||||
"""Тесты для BlacklistHistoryRepository"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db_connection(self):
|
||||
"""Мок для DatabaseConnection"""
|
||||
@@ -19,18 +21,20 @@ class TestBlacklistHistoryRepository:
|
||||
mock_connection._execute_query_with_result = AsyncMock()
|
||||
mock_connection.logger = Mock()
|
||||
return mock_connection
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def blacklist_history_repository(self, mock_db_connection):
|
||||
"""Экземпляр BlacklistHistoryRepository для тестов"""
|
||||
# Патчим наследование от DatabaseConnection
|
||||
with patch.object(BlacklistHistoryRepository, '__init__', return_value=None):
|
||||
with patch.object(BlacklistHistoryRepository, "__init__", return_value=None):
|
||||
repo = BlacklistHistoryRepository()
|
||||
repo._execute_query = mock_db_connection._execute_query
|
||||
repo._execute_query_with_result = mock_db_connection._execute_query_with_result
|
||||
repo._execute_query_with_result = (
|
||||
mock_db_connection._execute_query_with_result
|
||||
)
|
||||
repo.logger = mock_db_connection.logger
|
||||
return repo
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_history_record(self):
|
||||
"""Тестовая запись истории бана"""
|
||||
@@ -44,7 +48,7 @@ class TestBlacklistHistoryRepository:
|
||||
created_at=current_time,
|
||||
updated_at=current_time,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_history_record_with_unban(self):
|
||||
"""Тестовая запись истории бана с датой разбана"""
|
||||
@@ -58,56 +62,75 @@ class TestBlacklistHistoryRepository:
|
||||
created_at=current_time - 86400,
|
||||
updated_at=current_time,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_tables(self, blacklist_history_repository):
|
||||
"""Тест создания таблицы истории банов/разбанов"""
|
||||
await blacklist_history_repository.create_tables()
|
||||
|
||||
|
||||
# Проверяем, что метод вызван (4 раза: таблица + 3 индекса)
|
||||
assert blacklist_history_repository._execute_query.call_count == 4
|
||||
calls = blacklist_history_repository._execute_query.call_args_list
|
||||
|
||||
|
||||
# Проверяем, что создается таблица с правильной структурой
|
||||
create_table_call = calls[0]
|
||||
assert "CREATE TABLE IF NOT EXISTS blacklist_history" in create_table_call[0][0]
|
||||
assert "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT" in create_table_call[0][0]
|
||||
assert (
|
||||
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT" in create_table_call[0][0]
|
||||
)
|
||||
assert "user_id INTEGER NOT NULL" in create_table_call[0][0]
|
||||
assert "message_for_user TEXT" in create_table_call[0][0]
|
||||
assert "date_ban INTEGER NOT NULL" in create_table_call[0][0]
|
||||
assert "date_unban INTEGER" in create_table_call[0][0]
|
||||
assert "ban_author INTEGER" in create_table_call[0][0]
|
||||
assert "created_at INTEGER DEFAULT (strftime('%s', 'now'))" in create_table_call[0][0]
|
||||
assert "updated_at INTEGER DEFAULT (strftime('%s', 'now'))" in create_table_call[0][0]
|
||||
assert "FOREIGN KEY (user_id) REFERENCES our_users(user_id) ON DELETE CASCADE" in create_table_call[0][0]
|
||||
assert "FOREIGN KEY (ban_author) REFERENCES our_users(user_id) ON DELETE SET NULL" in create_table_call[0][0]
|
||||
|
||||
assert (
|
||||
"created_at INTEGER DEFAULT (strftime('%s', 'now'))"
|
||||
in create_table_call[0][0]
|
||||
)
|
||||
assert (
|
||||
"updated_at INTEGER DEFAULT (strftime('%s', 'now'))"
|
||||
in create_table_call[0][0]
|
||||
)
|
||||
assert (
|
||||
"FOREIGN KEY (user_id) REFERENCES our_users(user_id) ON DELETE CASCADE"
|
||||
in create_table_call[0][0]
|
||||
)
|
||||
assert (
|
||||
"FOREIGN KEY (ban_author) REFERENCES our_users(user_id) ON DELETE SET NULL"
|
||||
in create_table_call[0][0]
|
||||
)
|
||||
|
||||
# Проверяем создание индексов
|
||||
index_calls = calls[1:4]
|
||||
index_names = [call[0][0] for call in index_calls]
|
||||
assert any("idx_blacklist_history_user_id" in idx for idx in index_names)
|
||||
assert any("idx_blacklist_history_date_ban" in idx for idx in index_names)
|
||||
assert any("idx_blacklist_history_date_unban" in idx for idx in index_names)
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_history_repository.logger.info.assert_called_once_with(
|
||||
"Таблица истории банов/разбанов создана"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_record_on_ban(self, blacklist_history_repository, sample_history_record):
|
||||
async def test_add_record_on_ban(
|
||||
self, blacklist_history_repository, sample_history_record
|
||||
):
|
||||
"""Тест добавления записи о бане в историю"""
|
||||
await blacklist_history_repository.add_record_on_ban(sample_history_record)
|
||||
|
||||
|
||||
# Проверяем, что метод вызван с правильными параметрами
|
||||
blacklist_history_repository._execute_query.assert_called_once()
|
||||
call_args = blacklist_history_repository._execute_query.call_args
|
||||
|
||||
|
||||
# Проверяем SQL запрос
|
||||
sql_query = call_args[0][0].replace('\n', ' ').replace(' ', ' ').strip()
|
||||
sql_query = call_args[0][0].replace("\n", " ").replace(" ", " ").strip()
|
||||
assert "INSERT INTO blacklist_history" in sql_query
|
||||
assert "user_id, message_for_user, date_ban, date_unban, ban_author, created_at, updated_at" in sql_query
|
||||
|
||||
assert (
|
||||
"user_id, message_for_user, date_ban, date_unban, ban_author, created_at, updated_at"
|
||||
in sql_query
|
||||
)
|
||||
|
||||
# Проверяем параметры
|
||||
params = call_args[0][1]
|
||||
assert params[0] == 12345 # user_id
|
||||
@@ -117,13 +140,13 @@ class TestBlacklistHistoryRepository:
|
||||
assert params[4] == 999 # ban_author
|
||||
assert params[5] == sample_history_record.created_at # created_at
|
||||
assert params[6] == sample_history_record.updated_at # updated_at
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_history_repository.logger.info.assert_called_once()
|
||||
log_call = blacklist_history_repository.logger.info.call_args[0][0]
|
||||
assert "Запись о бане добавлена в историю" in log_call
|
||||
assert "user_id=12345" in log_call
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_record_on_ban_with_defaults(self, blacklist_history_repository):
|
||||
"""Тест добавления записи о бане с дефолтными значениями created_at и updated_at"""
|
||||
@@ -136,122 +159,130 @@ class TestBlacklistHistoryRepository:
|
||||
created_at=None, # Будет установлено автоматически
|
||||
updated_at=None, # Будет установлено автоматически
|
||||
)
|
||||
|
||||
|
||||
await blacklist_history_repository.add_record_on_ban(record)
|
||||
|
||||
|
||||
# Проверяем, что метод вызван
|
||||
blacklist_history_repository._execute_query.assert_called_once()
|
||||
call_args = blacklist_history_repository._execute_query.call_args
|
||||
|
||||
|
||||
# Проверяем, что created_at и updated_at установлены (не None)
|
||||
params = call_args[0][1]
|
||||
assert params[5] is not None # created_at
|
||||
assert params[6] is not None # updated_at
|
||||
assert isinstance(params[5], int)
|
||||
assert isinstance(params[6], int)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_unban_date_success(self, blacklist_history_repository):
|
||||
"""Тест успешного обновления даты разбана"""
|
||||
user_id = 12345
|
||||
date_unban = int(time.time())
|
||||
|
||||
|
||||
# Мокируем результат проверки - находим открытую запись
|
||||
blacklist_history_repository._execute_query_with_result.return_value = [(100,)] # id записи
|
||||
|
||||
blacklist_history_repository._execute_query_with_result.return_value = [
|
||||
(100,)
|
||||
] # id записи
|
||||
|
||||
result = await blacklist_history_repository.set_unban_date(user_id, date_unban)
|
||||
|
||||
|
||||
# Проверяем, что сначала проверяется наличие записи
|
||||
assert blacklist_history_repository._execute_query_with_result.call_count == 1
|
||||
check_call = blacklist_history_repository._execute_query_with_result.call_args
|
||||
assert "SELECT id FROM blacklist_history" in check_call[0][0]
|
||||
assert check_call[0][1] == (user_id,)
|
||||
|
||||
|
||||
# Проверяем, что затем обновляется запись
|
||||
assert blacklist_history_repository._execute_query.call_count == 1
|
||||
update_call = blacklist_history_repository._execute_query.call_args
|
||||
update_query = update_call[0][0].replace('\n', ' ').replace(' ', ' ').strip()
|
||||
update_query = (
|
||||
update_call[0][0].replace("\n", " ").replace(" ", " ").strip()
|
||||
)
|
||||
assert "UPDATE blacklist_history" in update_query
|
||||
assert "SET date_unban = ?" in update_query
|
||||
assert "updated_at = ?" in update_query
|
||||
|
||||
|
||||
# Проверяем параметры обновления
|
||||
update_params = update_call[0][1]
|
||||
assert update_params[0] == date_unban
|
||||
assert update_params[1] is not None # updated_at (текущее время)
|
||||
assert isinstance(update_params[1], int)
|
||||
assert update_params[2] == 100 # id записи
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result is True
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_history_repository.logger.info.assert_called_once()
|
||||
log_call = blacklist_history_repository.logger.info.call_args[0][0]
|
||||
assert "Дата разбана обновлена в истории" in log_call
|
||||
assert f"user_id={user_id}" in log_call
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_unban_date_no_open_record(self, blacklist_history_repository):
|
||||
"""Тест обновления даты разбана когда нет открытой записи"""
|
||||
user_id = 12345
|
||||
date_unban = int(time.time())
|
||||
|
||||
|
||||
# Мокируем результат проверки - нет открытых записей
|
||||
blacklist_history_repository._execute_query_with_result.return_value = []
|
||||
|
||||
|
||||
result = await blacklist_history_repository.set_unban_date(user_id, date_unban)
|
||||
|
||||
|
||||
# Проверяем, что проверка была выполнена
|
||||
assert blacklist_history_repository._execute_query_with_result.call_count == 1
|
||||
|
||||
|
||||
# Проверяем, что UPDATE не был вызван (нет записей для обновления)
|
||||
blacklist_history_repository._execute_query.assert_not_called()
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result is False
|
||||
|
||||
|
||||
# Проверяем логирование предупреждения
|
||||
blacklist_history_repository.logger.warning.assert_called_once()
|
||||
log_call = blacklist_history_repository.logger.warning.call_args[0][0]
|
||||
assert "Не найдена открытая запись в истории для обновления" in log_call
|
||||
assert f"user_id={user_id}" in log_call
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_unban_date_exception(self, blacklist_history_repository):
|
||||
"""Тест обработки исключения при обновлении даты разбана"""
|
||||
user_id = 12345
|
||||
date_unban = int(time.time())
|
||||
|
||||
|
||||
# Мокируем исключение при проверке
|
||||
blacklist_history_repository._execute_query_with_result.side_effect = Exception("Database error")
|
||||
|
||||
blacklist_history_repository._execute_query_with_result.side_effect = Exception(
|
||||
"Database error"
|
||||
)
|
||||
|
||||
result = await blacklist_history_repository.set_unban_date(user_id, date_unban)
|
||||
|
||||
|
||||
# Проверяем, что метод вернул False при ошибке
|
||||
assert result is False
|
||||
|
||||
|
||||
# Проверяем логирование ошибки
|
||||
blacklist_history_repository.logger.error.assert_called_once()
|
||||
log_call = blacklist_history_repository.logger.error.call_args[0][0]
|
||||
assert "Ошибка обновления даты разбана в истории" in log_call
|
||||
assert f"user_id={user_id}" in log_call
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_unban_date_update_exception(self, blacklist_history_repository):
|
||||
"""Тест обработки исключения при обновлении записи"""
|
||||
user_id = 12345
|
||||
date_unban = int(time.time())
|
||||
|
||||
|
||||
# Мокируем успешную проверку, но ошибку при обновлении
|
||||
blacklist_history_repository._execute_query_with_result.return_value = [(100,)]
|
||||
blacklist_history_repository._execute_query.side_effect = Exception("Update error")
|
||||
|
||||
blacklist_history_repository._execute_query.side_effect = Exception(
|
||||
"Update error"
|
||||
)
|
||||
|
||||
result = await blacklist_history_repository.set_unban_date(user_id, date_unban)
|
||||
|
||||
|
||||
# Проверяем, что метод вернул False при ошибке
|
||||
assert result is False
|
||||
|
||||
|
||||
# Проверяем логирование ошибки
|
||||
blacklist_history_repository.logger.error.assert_called_once()
|
||||
log_call = blacklist_history_repository.logger.error.call_args[0][0]
|
||||
|
||||
@@ -3,13 +3,14 @@ from datetime import datetime
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from database.models import BlacklistUser
|
||||
from database.repositories.blacklist_repository import BlacklistRepository
|
||||
|
||||
|
||||
class TestBlacklistRepository:
|
||||
"""Тесты для BlacklistRepository"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db_connection(self):
|
||||
"""Мок для DatabaseConnection"""
|
||||
@@ -18,18 +19,20 @@ class TestBlacklistRepository:
|
||||
mock_connection._execute_query_with_result = AsyncMock()
|
||||
mock_connection.logger = Mock()
|
||||
return mock_connection
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def blacklist_repository(self, mock_db_connection):
|
||||
"""Экземпляр BlacklistRepository для тестов"""
|
||||
# Патчим наследование от DatabaseConnection
|
||||
with patch.object(BlacklistRepository, '__init__', return_value=None):
|
||||
with patch.object(BlacklistRepository, "__init__", return_value=None):
|
||||
repo = BlacklistRepository()
|
||||
repo._execute_query = mock_db_connection._execute_query
|
||||
repo._execute_query_with_result = mock_db_connection._execute_query_with_result
|
||||
repo._execute_query_with_result = (
|
||||
mock_db_connection._execute_query_with_result
|
||||
)
|
||||
repo.logger = mock_db_connection.logger
|
||||
return repo
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_blacklist_user(self):
|
||||
"""Тестовый пользователь в черном списке"""
|
||||
@@ -40,7 +43,7 @@ class TestBlacklistRepository:
|
||||
created_at=int(time.time()),
|
||||
ban_author=999,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_blacklist_user_permanent(self):
|
||||
"""Тестовый пользователь с постоянным баном"""
|
||||
@@ -51,144 +54,171 @@ class TestBlacklistRepository:
|
||||
created_at=int(time.time()),
|
||||
ban_author=None,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_tables(self, blacklist_repository):
|
||||
"""Тест создания таблицы черного списка"""
|
||||
await blacklist_repository.create_tables()
|
||||
|
||||
|
||||
# Проверяем, что метод вызван
|
||||
blacklist_repository._execute_query.assert_called()
|
||||
calls = blacklist_repository._execute_query.call_args_list
|
||||
|
||||
|
||||
# Проверяем, что создается таблица с правильной структурой
|
||||
create_table_call = calls[0]
|
||||
assert "CREATE TABLE IF NOT EXISTS blacklist" in create_table_call[0][0]
|
||||
assert "user_id INTEGER NOT NULL PRIMARY KEY" in create_table_call[0][0]
|
||||
assert "message_for_user TEXT" in create_table_call[0][0]
|
||||
assert "date_to_unban INTEGER" in create_table_call[0][0]
|
||||
assert "created_at INTEGER DEFAULT (strftime('%s', 'now'))" in create_table_call[0][0]
|
||||
assert "FOREIGN KEY (user_id) REFERENCES our_users (user_id) ON DELETE CASCADE" in create_table_call[0][0]
|
||||
|
||||
assert (
|
||||
"created_at INTEGER DEFAULT (strftime('%s', 'now'))"
|
||||
in create_table_call[0][0]
|
||||
)
|
||||
assert (
|
||||
"FOREIGN KEY (user_id) REFERENCES our_users (user_id) ON DELETE CASCADE"
|
||||
in create_table_call[0][0]
|
||||
)
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_repository.logger.info.assert_called_once_with("Таблица черного списка создана")
|
||||
|
||||
blacklist_repository.logger.info.assert_called_once_with(
|
||||
"Таблица черного списка создана"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_user(self, blacklist_repository, sample_blacklist_user):
|
||||
"""Тест добавления пользователя в черный список"""
|
||||
await blacklist_repository.add_user(sample_blacklist_user)
|
||||
|
||||
|
||||
# Проверяем, что метод вызван с правильными параметрами
|
||||
blacklist_repository._execute_query.assert_called_once()
|
||||
call_args = blacklist_repository._execute_query.call_args
|
||||
|
||||
|
||||
# Проверяем SQL запрос (учитываем форматирование)
|
||||
sql_query = call_args[0][0].replace('\n', ' ').replace(' ', ' ').replace(' ', ' ').strip()
|
||||
sql_query = (
|
||||
call_args[0][0]
|
||||
.replace("\n", " ")
|
||||
.replace(" ", " ")
|
||||
.replace(" ", " ")
|
||||
.strip()
|
||||
)
|
||||
expected_sql = "INSERT INTO blacklist (user_id, message_for_user, date_to_unban, ban_author) VALUES (?, ?, ?, ?)"
|
||||
assert sql_query == expected_sql
|
||||
|
||||
|
||||
# Проверяем параметры
|
||||
assert call_args[0][1] == (12345, "Нарушение правил", sample_blacklist_user.date_to_unban, 999)
|
||||
|
||||
assert call_args[0][1] == (
|
||||
12345,
|
||||
"Нарушение правил",
|
||||
sample_blacklist_user.date_to_unban,
|
||||
999,
|
||||
)
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_repository.logger.info.assert_called_once_with(
|
||||
"Пользователь добавлен в черный список: user_id=12345"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_user_permanent_ban(self, blacklist_repository, sample_blacklist_user_permanent):
|
||||
async def test_add_user_permanent_ban(
|
||||
self, blacklist_repository, sample_blacklist_user_permanent
|
||||
):
|
||||
"""Тест добавления пользователя с постоянным баном"""
|
||||
await blacklist_repository.add_user(sample_blacklist_user_permanent)
|
||||
|
||||
|
||||
call_args = blacklist_repository._execute_query.call_args
|
||||
assert call_args[0][1] == (67890, "Постоянный бан", None, None)
|
||||
|
||||
|
||||
blacklist_repository.logger.info.assert_called_once_with(
|
||||
"Пользователь добавлен в черный список: user_id=67890"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remove_user_success(self, blacklist_repository):
|
||||
"""Тест успешного удаления пользователя из черного списка"""
|
||||
await blacklist_repository.remove_user(12345)
|
||||
|
||||
|
||||
# Проверяем, что метод вызван с правильными параметрами
|
||||
blacklist_repository._execute_query.assert_called_once()
|
||||
call_args = blacklist_repository._execute_query.call_args
|
||||
|
||||
|
||||
assert call_args[0][0] == "DELETE FROM blacklist WHERE user_id = ?"
|
||||
assert call_args[0][1] == (12345,)
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_repository.logger.info.assert_called_once_with(
|
||||
"Пользователь с идентификатором 12345 успешно удален из черного списка."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remove_user_failure(self, blacklist_repository):
|
||||
"""Тест неудачного удаления пользователя из черного списка"""
|
||||
# Симулируем ошибку при удалении
|
||||
blacklist_repository._execute_query.side_effect = Exception("Database error")
|
||||
|
||||
|
||||
result = await blacklist_repository.remove_user(12345)
|
||||
|
||||
|
||||
# Проверяем, что возвращается False при ошибке
|
||||
assert result is False
|
||||
|
||||
|
||||
# Проверяем логирование ошибки
|
||||
blacklist_repository.logger.error.assert_called_once()
|
||||
error_log = blacklist_repository.logger.error.call_args[0][0]
|
||||
assert "Ошибка удаления пользователя с идентификатором 12345" in error_log
|
||||
assert "Database error" in error_log
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_exists_true(self, blacklist_repository):
|
||||
"""Тест проверки существования пользователя (пользователь существует)"""
|
||||
# Симулируем результат запроса - пользователь найден
|
||||
blacklist_repository._execute_query_with_result.return_value = [(1,)]
|
||||
|
||||
|
||||
result = await blacklist_repository.user_exists(12345)
|
||||
|
||||
|
||||
# Проверяем, что возвращается True
|
||||
assert result is True
|
||||
|
||||
|
||||
# Проверяем, что метод вызван с правильными параметрами
|
||||
blacklist_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = blacklist_repository._execute_query_with_result.call_args
|
||||
|
||||
|
||||
assert call_args[0][0] == "SELECT 1 FROM blacklist WHERE user_id = ?"
|
||||
assert call_args[0][1] == (12345,)
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_repository.logger.info.assert_called_once_with(
|
||||
"Существует ли пользователь: user_id=12345 Итог: [(1,)]"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_exists_false(self, blacklist_repository):
|
||||
"""Тест проверки существования пользователя (пользователь не существует)"""
|
||||
# Симулируем результат запроса - пользователь не найден
|
||||
blacklist_repository._execute_query_with_result.return_value = []
|
||||
|
||||
|
||||
result = await blacklist_repository.user_exists(12345)
|
||||
|
||||
|
||||
# Проверяем, что возвращается False
|
||||
assert result is False
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_repository.logger.info.assert_called_once_with(
|
||||
"Существует ли пользователь: user_id=12345 Итог: []"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_success(self, blacklist_repository):
|
||||
"""Тест успешного получения пользователя по ID"""
|
||||
# Симулируем результат запроса
|
||||
mock_row = (12345, "Нарушение правил", int(time.time()) + 86400, int(time.time()), 111)
|
||||
mock_row = (
|
||||
12345,
|
||||
"Нарушение правил",
|
||||
int(time.time()) + 86400,
|
||||
int(time.time()),
|
||||
111,
|
||||
)
|
||||
blacklist_repository._execute_query_with_result.return_value = [mock_row]
|
||||
|
||||
|
||||
result = await blacklist_repository.get_user(12345)
|
||||
|
||||
|
||||
# Проверяем, что возвращается правильный объект
|
||||
assert result is not None
|
||||
assert result.user_id == 12345
|
||||
@@ -196,37 +226,40 @@ class TestBlacklistRepository:
|
||||
assert result.date_to_unban == mock_row[2]
|
||||
assert result.created_at == mock_row[3]
|
||||
assert result.ban_author == mock_row[4]
|
||||
|
||||
|
||||
# Проверяем, что метод вызван с правильными параметрами
|
||||
blacklist_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = blacklist_repository._execute_query_with_result.call_args
|
||||
|
||||
assert "SELECT user_id, message_for_user, date_to_unban, created_at, ban_author" in call_args[0][0]
|
||||
|
||||
assert (
|
||||
"SELECT user_id, message_for_user, date_to_unban, created_at, ban_author"
|
||||
in call_args[0][0]
|
||||
)
|
||||
assert call_args[0][1] == (12345,)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_not_found(self, blacklist_repository):
|
||||
"""Тест получения пользователя по ID (пользователь не найден)"""
|
||||
# Симулируем результат запроса - пользователь не найден
|
||||
blacklist_repository._execute_query_with_result.return_value = []
|
||||
|
||||
|
||||
result = await blacklist_repository.get_user(12345)
|
||||
|
||||
|
||||
# Проверяем, что возвращается None
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_all_users_with_limits(self, blacklist_repository):
|
||||
"""Тест получения пользователей с лимитами"""
|
||||
# Симулируем результат запроса
|
||||
mock_rows = [
|
||||
(12345, "Нарушение правил", int(time.time()) + 86400, int(time.time())),
|
||||
(67890, "Постоянный бан", None, int(time.time()) - 86400)
|
||||
(67890, "Постоянный бан", None, int(time.time()) - 86400),
|
||||
]
|
||||
blacklist_repository._execute_query_with_result.return_value = mock_rows
|
||||
|
||||
|
||||
result = await blacklist_repository.get_all_users(offset=0, limit=10)
|
||||
|
||||
|
||||
# Проверяем, что возвращается правильный список
|
||||
assert len(result) == 2
|
||||
assert result[0].user_id == 12345
|
||||
@@ -234,188 +267,211 @@ class TestBlacklistRepository:
|
||||
assert result[1].user_id == 67890
|
||||
assert result[1].message_for_user == "Постоянный бан"
|
||||
assert result[1].date_to_unban is None
|
||||
|
||||
|
||||
# Проверяем, что метод вызван с правильными параметрами
|
||||
blacklist_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = blacklist_repository._execute_query_with_result.call_args
|
||||
|
||||
|
||||
# Нормализуем SQL запрос (убираем лишние пробелы и переносы строк)
|
||||
actual_query = ' '.join(call_args[0][0].split())
|
||||
actual_query = " ".join(call_args[0][0].split())
|
||||
expected_query = "SELECT user_id, message_for_user, date_to_unban, created_at, ban_author FROM blacklist LIMIT ?, ?"
|
||||
assert actual_query == expected_query
|
||||
assert call_args[0][1] == (0, 10)
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_repository.logger.info.assert_called_once_with(
|
||||
"Получен список пользователей в черном списке (offset=0, limit=10): 2"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_all_users_no_limit(self, blacklist_repository):
|
||||
"""Тест получения всех пользователей без лимитов"""
|
||||
# Симулируем результат запроса (теперь включает ban_author)
|
||||
mock_rows = [
|
||||
(12345, "Нарушение правил", int(time.time()) + 86400, int(time.time()), 999),
|
||||
(67890, "Постоянный бан", None, int(time.time()) - 86400, None)
|
||||
(
|
||||
12345,
|
||||
"Нарушение правил",
|
||||
int(time.time()) + 86400,
|
||||
int(time.time()),
|
||||
999,
|
||||
),
|
||||
(67890, "Постоянный бан", None, int(time.time()) - 86400, None),
|
||||
]
|
||||
blacklist_repository._execute_query_with_result.return_value = mock_rows
|
||||
|
||||
|
||||
result = await blacklist_repository.get_all_users_no_limit()
|
||||
|
||||
|
||||
# Проверяем, что возвращается правильный список
|
||||
assert len(result) == 2
|
||||
|
||||
|
||||
# Проверяем, что метод вызван без лимитов
|
||||
blacklist_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = blacklist_repository._execute_query_with_result.call_args
|
||||
|
||||
|
||||
# Нормализуем SQL запрос (убираем лишние пробелы и переносы строк)
|
||||
actual_query = ' '.join(call_args[0][0].split())
|
||||
actual_query = " ".join(call_args[0][0].split())
|
||||
expected_query = "SELECT user_id, message_for_user, date_to_unban, created_at, ban_author FROM blacklist"
|
||||
assert actual_query == expected_query
|
||||
# Проверяем, что параметры пустые (без лимитов)
|
||||
assert len(call_args[0]) == 1 # Только SQL запрос, без параметров
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_repository.logger.info.assert_called_once_with(
|
||||
"Получен список всех пользователей в черном списке: 2"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_users_for_unblock_today(self, blacklist_repository):
|
||||
"""Тест получения пользователей для разблокировки сегодня"""
|
||||
current_timestamp = int(time.time())
|
||||
|
||||
|
||||
# Симулируем результат запроса - пользователи с истекшим сроком
|
||||
mock_rows = [(12345,), (67890,)]
|
||||
blacklist_repository._execute_query_with_result.return_value = mock_rows
|
||||
|
||||
result = await blacklist_repository.get_users_for_unblock_today(current_timestamp)
|
||||
|
||||
|
||||
result = await blacklist_repository.get_users_for_unblock_today(
|
||||
current_timestamp
|
||||
)
|
||||
|
||||
# Проверяем, что возвращается правильный словарь
|
||||
assert len(result) == 2
|
||||
assert 12345 in result
|
||||
assert 67890 in result
|
||||
assert result[12345] == 12345
|
||||
assert result[67890] == 67890
|
||||
|
||||
|
||||
# Проверяем, что метод вызван с правильными параметрами
|
||||
blacklist_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = blacklist_repository._execute_query_with_result.call_args
|
||||
|
||||
assert call_args[0][0] == "SELECT user_id FROM blacklist WHERE date_to_unban IS NOT NULL AND date_to_unban <= ?"
|
||||
|
||||
assert (
|
||||
call_args[0][0]
|
||||
== "SELECT user_id FROM blacklist WHERE date_to_unban IS NOT NULL AND date_to_unban <= ?"
|
||||
)
|
||||
assert call_args[0][1] == (current_timestamp,)
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_repository.logger.info.assert_called_once_with(
|
||||
"Получен список пользователей для разблокировки: {12345: 12345, 67890: 67890}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_users_for_unblock_today_empty(self, blacklist_repository):
|
||||
"""Тест получения пользователей для разблокировки (пустой результат)"""
|
||||
current_timestamp = int(time.time())
|
||||
|
||||
|
||||
# Симулируем пустой результат запроса
|
||||
blacklist_repository._execute_query_with_result.return_value = []
|
||||
|
||||
result = await blacklist_repository.get_users_for_unblock_today(current_timestamp)
|
||||
|
||||
|
||||
result = await blacklist_repository.get_users_for_unblock_today(
|
||||
current_timestamp
|
||||
)
|
||||
|
||||
# Проверяем, что возвращается пустой словарь
|
||||
assert result == {}
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_repository.logger.info.assert_called_once_with(
|
||||
"Получен список пользователей для разблокировки: {}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_count(self, blacklist_repository):
|
||||
"""Тест получения количества пользователей в черном списке"""
|
||||
# Симулируем результат запроса
|
||||
blacklist_repository._execute_query_with_result.return_value = [(5,)]
|
||||
|
||||
|
||||
result = await blacklist_repository.get_count()
|
||||
|
||||
|
||||
# Проверяем, что возвращается правильное количество
|
||||
assert result == 5
|
||||
|
||||
|
||||
# Проверяем, что метод вызван с правильными параметрами
|
||||
blacklist_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = blacklist_repository._execute_query_with_result.call_args
|
||||
|
||||
|
||||
assert call_args[0][0] == "SELECT COUNT(*) FROM blacklist"
|
||||
# Проверяем, что параметры пустые
|
||||
assert len(call_args[0]) == 1 # Только SQL запрос, без параметров
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_count_zero(self, blacklist_repository):
|
||||
"""Тест получения количества пользователей (0 пользователей)"""
|
||||
# Симулируем пустой результат запроса
|
||||
blacklist_repository._execute_query_with_result.return_value = []
|
||||
|
||||
|
||||
result = await blacklist_repository.get_count()
|
||||
|
||||
|
||||
# Проверяем, что возвращается 0
|
||||
assert result == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_count_none_result(self, blacklist_repository):
|
||||
"""Тест получения количества пользователей (None результат)"""
|
||||
# Симулируем None результат запроса
|
||||
blacklist_repository._execute_query_with_result.return_value = None
|
||||
|
||||
|
||||
result = await blacklist_repository.get_count()
|
||||
|
||||
|
||||
# Проверяем, что возвращается 0
|
||||
assert result == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_error_handling_in_get_user(self, blacklist_repository):
|
||||
"""Тест обработки ошибок при получении пользователя"""
|
||||
# Симулируем ошибку базы данных
|
||||
blacklist_repository._execute_query_with_result.side_effect = Exception("Database connection failed")
|
||||
|
||||
blacklist_repository._execute_query_with_result.side_effect = Exception(
|
||||
"Database connection failed"
|
||||
)
|
||||
|
||||
# Проверяем, что исключение пробрасывается
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
await blacklist_repository.get_user(12345)
|
||||
|
||||
|
||||
assert "Database connection failed" in str(exc_info.value)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_error_handling_in_get_all_users(self, blacklist_repository):
|
||||
"""Тест обработки ошибок при получении всех пользователей"""
|
||||
# Симулируем ошибку базы данных
|
||||
blacklist_repository._execute_query_with_result.side_effect = Exception("Database connection failed")
|
||||
|
||||
blacklist_repository._execute_query_with_result.side_effect = Exception(
|
||||
"Database connection failed"
|
||||
)
|
||||
|
||||
# Проверяем, что исключение пробрасывается
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
await blacklist_repository.get_all_users()
|
||||
|
||||
|
||||
assert "Database connection failed" in str(exc_info.value)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_error_handling_in_get_count(self, blacklist_repository):
|
||||
"""Тест обработки ошибок при получении количества"""
|
||||
# Симулируем ошибку базы данных
|
||||
blacklist_repository._execute_query_with_result.side_effect = Exception("Database connection failed")
|
||||
|
||||
blacklist_repository._execute_query_with_result.side_effect = Exception(
|
||||
"Database connection failed"
|
||||
)
|
||||
|
||||
# Проверяем, что исключение пробрасывается
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
await blacklist_repository.get_count()
|
||||
|
||||
|
||||
assert "Database connection failed" in str(exc_info.value)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_error_handling_in_get_users_for_unblock_today(self, blacklist_repository):
|
||||
async def test_error_handling_in_get_users_for_unblock_today(
|
||||
self, blacklist_repository
|
||||
):
|
||||
"""Тест обработки ошибок при получении пользователей для разблокировки"""
|
||||
# Симулируем ошибку базы данных
|
||||
blacklist_repository._execute_query_with_result.side_effect = Exception("Database connection failed")
|
||||
|
||||
blacklist_repository._execute_query_with_result.side_effect = Exception(
|
||||
"Database connection failed"
|
||||
)
|
||||
|
||||
# Проверяем, что исключение пробрасывается
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
await blacklist_repository.get_users_for_unblock_today(int(time.time()))
|
||||
|
||||
|
||||
assert "Database connection failed" in str(exc_info.value)
|
||||
|
||||
# TODO: 20-й тест - test_integration_workflow
|
||||
@@ -426,7 +482,7 @@ class TestBlacklistRepository:
|
||||
# 4. Получение общего количества пользователей
|
||||
# 5. Удаление пользователя из черного списка
|
||||
# 6. Проверка, что пользователь больше не существует
|
||||
#
|
||||
#
|
||||
# Проблема: тест падает из-за сложности мокирования возвращаемых значений
|
||||
# при создании объектов BlacklistUser из результатов запросов к БД.
|
||||
# Требует более сложной настройки моков для корректной работы.
|
||||
|
||||
@@ -3,8 +3,11 @@ from datetime import datetime
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from helper_bot.handlers.callback.callback_handlers import (
|
||||
delete_voice_message, save_voice_message)
|
||||
delete_voice_message,
|
||||
save_voice_message,
|
||||
)
|
||||
from helper_bot.handlers.voice.constants import CALLBACK_DELETE, CALLBACK_SAVE
|
||||
|
||||
|
||||
@@ -21,6 +24,7 @@ def mock_call():
|
||||
call.answer = AsyncMock()
|
||||
return call
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_bot_db():
|
||||
"""Мок для базы данных"""
|
||||
@@ -29,20 +33,20 @@ def mock_bot_db():
|
||||
mock_db.delete_audio_moderate_record = AsyncMock()
|
||||
return mock_db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_settings():
|
||||
"""Мок для настроек"""
|
||||
return {
|
||||
'Telegram': {
|
||||
'group_for_posts': 'test_group_id'
|
||||
}
|
||||
}
|
||||
return {"Telegram": {"group_for_posts": "test_group_id"}}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_audio_service():
|
||||
"""Мок для AudioFileService"""
|
||||
mock_service = Mock()
|
||||
mock_service.generate_file_name = AsyncMock(return_value="message_from_67890_number_1")
|
||||
mock_service.generate_file_name = AsyncMock(
|
||||
return_value="message_from_67890_number_1"
|
||||
)
|
||||
mock_service.save_audio_file = AsyncMock()
|
||||
mock_service.download_and_save_audio = AsyncMock()
|
||||
return mock_service
|
||||
@@ -50,143 +54,205 @@ def mock_audio_service():
|
||||
|
||||
class TestSaveVoiceMessage:
|
||||
"""Тесты для функции save_voice_message"""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_voice_message_success(self, mock_call, mock_bot_db, mock_settings, mock_audio_service):
|
||||
async def test_save_voice_message_success(
|
||||
self, mock_call, mock_bot_db, mock_settings, mock_audio_service
|
||||
):
|
||||
"""Тест успешного сохранения голосового сообщения"""
|
||||
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService') as mock_service_class:
|
||||
with patch(
|
||||
"helper_bot.handlers.callback.callback_handlers.AudioFileService"
|
||||
) as mock_service_class:
|
||||
mock_service_class.return_value = mock_audio_service
|
||||
|
||||
await save_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
|
||||
|
||||
|
||||
await save_voice_message(
|
||||
mock_call, bot_db=mock_bot_db, settings=mock_settings
|
||||
)
|
||||
|
||||
# Проверяем, что все методы вызваны
|
||||
mock_bot_db.get_user_id_by_message_id_for_voice_bot.assert_called_once_with(12345)
|
||||
mock_bot_db.get_user_id_by_message_id_for_voice_bot.assert_called_once_with(
|
||||
12345
|
||||
)
|
||||
mock_audio_service.generate_file_name.assert_called_once_with(67890)
|
||||
mock_audio_service.save_audio_file.assert_called_once()
|
||||
mock_audio_service.download_and_save_audio.assert_called_once_with(
|
||||
mock_call.bot, mock_call.message, "message_from_67890_number_1"
|
||||
)
|
||||
|
||||
|
||||
# Проверяем удаление сообщения из чата
|
||||
mock_call.bot.delete_message.assert_called_once_with(
|
||||
chat_id='test_group_id',
|
||||
message_id=12345
|
||||
chat_id="test_group_id", message_id=12345
|
||||
)
|
||||
|
||||
|
||||
# Проверяем удаление записи из audio_moderate
|
||||
mock_bot_db.delete_audio_moderate_record.assert_called_once_with(12345)
|
||||
|
||||
|
||||
# Проверяем ответ пользователю
|
||||
mock_call.answer.assert_called_once_with(text='Сохранено!', cache_time=3)
|
||||
|
||||
mock_call.answer.assert_called_once_with(text="Сохранено!", cache_time=3)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_voice_message_with_correct_parameters(self, mock_call, mock_bot_db, mock_settings, mock_audio_service):
|
||||
async def test_save_voice_message_with_correct_parameters(
|
||||
self, mock_call, mock_bot_db, mock_settings, mock_audio_service
|
||||
):
|
||||
"""Тест сохранения с правильными параметрами"""
|
||||
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService') as mock_service_class:
|
||||
with patch(
|
||||
"helper_bot.handlers.callback.callback_handlers.AudioFileService"
|
||||
) as mock_service_class:
|
||||
mock_service_class.return_value = mock_audio_service
|
||||
|
||||
await save_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
|
||||
|
||||
|
||||
await save_voice_message(
|
||||
mock_call, bot_db=mock_bot_db, settings=mock_settings
|
||||
)
|
||||
|
||||
# Проверяем параметры save_audio_file
|
||||
save_call_args = mock_audio_service.save_audio_file.call_args
|
||||
assert save_call_args[0][0] == "message_from_67890_number_1" # file_name
|
||||
assert save_call_args[0][1] == 67890 # user_id
|
||||
assert isinstance(save_call_args[0][2], datetime) # date_added
|
||||
assert save_call_args[0][3] == "test_file_id_123" # file_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_voice_message_exception_handling(self, mock_call, mock_bot_db, mock_settings):
|
||||
async def test_save_voice_message_exception_handling(
|
||||
self, mock_call, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест обработки исключений при сохранении"""
|
||||
mock_bot_db.get_user_id_by_message_id_for_voice_bot.side_effect = Exception("Database error")
|
||||
|
||||
mock_bot_db.get_user_id_by_message_id_for_voice_bot.side_effect = Exception(
|
||||
"Database error"
|
||||
)
|
||||
|
||||
await save_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
|
||||
|
||||
|
||||
# Проверяем, что при ошибке отправляется соответствующий ответ
|
||||
mock_call.answer.assert_called_once_with(text='Ошибка при сохранении!', cache_time=3)
|
||||
|
||||
mock_call.answer.assert_called_once_with(
|
||||
text="Ошибка при сохранении!", cache_time=3
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_voice_message_audio_service_exception(self, mock_call, mock_bot_db, mock_settings, mock_audio_service):
|
||||
async def test_save_voice_message_audio_service_exception(
|
||||
self, mock_call, mock_bot_db, mock_settings, mock_audio_service
|
||||
):
|
||||
"""Тест обработки исключений в AudioFileService"""
|
||||
mock_audio_service.save_audio_file.side_effect = Exception("Save error")
|
||||
|
||||
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService') as mock_service_class:
|
||||
|
||||
with patch(
|
||||
"helper_bot.handlers.callback.callback_handlers.AudioFileService"
|
||||
) as mock_service_class:
|
||||
mock_service_class.return_value = mock_audio_service
|
||||
|
||||
await save_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
|
||||
|
||||
|
||||
await save_voice_message(
|
||||
mock_call, bot_db=mock_bot_db, settings=mock_settings
|
||||
)
|
||||
|
||||
# Проверяем, что при ошибке отправляется соответствующий ответ
|
||||
mock_call.answer.assert_called_once_with(text='Ошибка при сохранении!', cache_time=3)
|
||||
|
||||
mock_call.answer.assert_called_once_with(
|
||||
text="Ошибка при сохранении!", cache_time=3
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_voice_message_download_exception(self, mock_call, mock_bot_db, mock_settings, mock_audio_service):
|
||||
async def test_save_voice_message_download_exception(
|
||||
self, mock_call, mock_bot_db, mock_settings, mock_audio_service
|
||||
):
|
||||
"""Тест обработки исключений при скачивании файла"""
|
||||
mock_audio_service.download_and_save_audio.side_effect = Exception("Download error")
|
||||
|
||||
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService') as mock_service_class:
|
||||
mock_audio_service.download_and_save_audio.side_effect = Exception(
|
||||
"Download error"
|
||||
)
|
||||
|
||||
with patch(
|
||||
"helper_bot.handlers.callback.callback_handlers.AudioFileService"
|
||||
) as mock_service_class:
|
||||
mock_service_class.return_value = mock_audio_service
|
||||
|
||||
await save_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
|
||||
|
||||
|
||||
await save_voice_message(
|
||||
mock_call, bot_db=mock_bot_db, settings=mock_settings
|
||||
)
|
||||
|
||||
# Проверяем, что при ошибке отправляется соответствующий ответ
|
||||
mock_call.answer.assert_called_once_with(text='Ошибка при сохранении!', cache_time=3)
|
||||
mock_call.answer.assert_called_once_with(
|
||||
text="Ошибка при сохранении!", cache_time=3
|
||||
)
|
||||
|
||||
|
||||
class TestDeleteVoiceMessage:
|
||||
"""Тесты для функции delete_voice_message"""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_voice_message_success(self, mock_call, mock_bot_db, mock_settings):
|
||||
async def test_delete_voice_message_success(
|
||||
self, mock_call, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест успешного удаления голосового сообщения"""
|
||||
await delete_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
|
||||
|
||||
await delete_voice_message(
|
||||
mock_call, bot_db=mock_bot_db, settings=mock_settings
|
||||
)
|
||||
|
||||
# Проверяем удаление сообщения из чата
|
||||
mock_call.bot.delete_message.assert_called_once_with(
|
||||
chat_id='test_group_id',
|
||||
message_id=12345
|
||||
chat_id="test_group_id", message_id=12345
|
||||
)
|
||||
|
||||
|
||||
# Проверяем удаление записи из audio_moderate
|
||||
mock_bot_db.delete_audio_moderate_record.assert_called_once_with(12345)
|
||||
|
||||
|
||||
# Проверяем ответ пользователю
|
||||
mock_call.answer.assert_called_once_with(text='Удалено!', cache_time=3)
|
||||
|
||||
mock_call.answer.assert_called_once_with(text="Удалено!", cache_time=3)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_voice_message_exception_handling(self, mock_call, mock_bot_db, mock_settings):
|
||||
async def test_delete_voice_message_exception_handling(
|
||||
self, mock_call, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест обработки исключений при удалении"""
|
||||
mock_call.bot.delete_message.side_effect = Exception("Delete error")
|
||||
|
||||
await delete_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
|
||||
|
||||
|
||||
await delete_voice_message(
|
||||
mock_call, bot_db=mock_bot_db, settings=mock_settings
|
||||
)
|
||||
|
||||
# Проверяем, что при ошибке отправляется соответствующий ответ
|
||||
mock_call.answer.assert_called_once_with(text='Ошибка при удалении!', cache_time=3)
|
||||
|
||||
mock_call.answer.assert_called_once_with(
|
||||
text="Ошибка при удалении!", cache_time=3
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_voice_message_database_exception(self, mock_call, mock_bot_db, mock_settings):
|
||||
async def test_delete_voice_message_database_exception(
|
||||
self, mock_call, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест обработки исключений в базе данных при удалении"""
|
||||
mock_bot_db.delete_audio_moderate_record.side_effect = Exception("Database error")
|
||||
|
||||
await delete_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
|
||||
|
||||
mock_bot_db.delete_audio_moderate_record.side_effect = Exception(
|
||||
"Database error"
|
||||
)
|
||||
|
||||
await delete_voice_message(
|
||||
mock_call, bot_db=mock_bot_db, settings=mock_settings
|
||||
)
|
||||
|
||||
# Проверяем, что при ошибке отправляется соответствующий ответ
|
||||
mock_call.answer.assert_called_once_with(text='Ошибка при удалении!', cache_time=3)
|
||||
mock_call.answer.assert_called_once_with(
|
||||
text="Ошибка при удалении!", cache_time=3
|
||||
)
|
||||
|
||||
|
||||
class TestCallbackHandlersIntegration:
|
||||
"""Интеграционные тесты для callback handlers"""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_voice_message_full_workflow(self, mock_call, mock_bot_db, mock_settings):
|
||||
async def test_save_voice_message_full_workflow(
|
||||
self, mock_call, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест полного рабочего процесса сохранения"""
|
||||
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService') as mock_service_class:
|
||||
with patch(
|
||||
"helper_bot.handlers.callback.callback_handlers.AudioFileService"
|
||||
) as mock_service_class:
|
||||
mock_service = Mock()
|
||||
mock_service.generate_file_name = AsyncMock(return_value="message_from_67890_number_1")
|
||||
mock_service.generate_file_name = AsyncMock(
|
||||
return_value="message_from_67890_number_1"
|
||||
)
|
||||
mock_service.save_audio_file = AsyncMock()
|
||||
mock_service.download_and_save_audio = AsyncMock()
|
||||
mock_service_class.return_value = mock_service
|
||||
|
||||
await save_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
|
||||
|
||||
|
||||
await save_voice_message(
|
||||
mock_call, bot_db=mock_bot_db, settings=mock_settings
|
||||
)
|
||||
|
||||
# Проверяем последовательность вызовов
|
||||
assert mock_bot_db.get_user_id_by_message_id_for_voice_bot.called
|
||||
assert mock_service.generate_file_name.called
|
||||
@@ -195,46 +261,62 @@ class TestCallbackHandlersIntegration:
|
||||
assert mock_call.bot.delete_message.called
|
||||
assert mock_bot_db.delete_audio_moderate_record.called
|
||||
assert mock_call.answer.called
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_voice_message_full_workflow(self, mock_call, mock_bot_db, mock_settings):
|
||||
async def test_delete_voice_message_full_workflow(
|
||||
self, mock_call, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест полного рабочего процесса удаления"""
|
||||
await delete_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
|
||||
|
||||
await delete_voice_message(
|
||||
mock_call, bot_db=mock_bot_db, settings=mock_settings
|
||||
)
|
||||
|
||||
# Проверяем последовательность вызовов
|
||||
assert mock_call.bot.delete_message.called
|
||||
assert mock_bot_db.delete_audio_moderate_record.called
|
||||
assert mock_call.answer.called
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_audio_moderate_cleanup_consistency(self, mock_call, mock_bot_db, mock_settings):
|
||||
async def test_audio_moderate_cleanup_consistency(
|
||||
self, mock_call, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест консистентности очистки audio_moderate"""
|
||||
# Тестируем, что в обоих случаях (сохранение и удаление)
|
||||
# Тестируем, что в обоих случаях (сохранение и удаление)
|
||||
# вызывается delete_audio_moderate_record
|
||||
|
||||
|
||||
# Создаем отдельные моки для каждого теста
|
||||
mock_bot_db_save = Mock()
|
||||
mock_bot_db_save.get_user_id_by_message_id_for_voice_bot = AsyncMock(return_value=67890)
|
||||
mock_bot_db_save.get_user_id_by_message_id_for_voice_bot = AsyncMock(
|
||||
return_value=67890
|
||||
)
|
||||
mock_bot_db_save.delete_audio_moderate_record = AsyncMock()
|
||||
|
||||
|
||||
mock_bot_db_delete = Mock()
|
||||
mock_bot_db_delete.delete_audio_moderate_record = AsyncMock()
|
||||
|
||||
|
||||
# Тест для сохранения
|
||||
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService') as mock_service_class:
|
||||
with patch(
|
||||
"helper_bot.handlers.callback.callback_handlers.AudioFileService"
|
||||
) as mock_service_class:
|
||||
mock_service = Mock()
|
||||
mock_service.generate_file_name = AsyncMock(return_value="message_from_67890_number_1")
|
||||
mock_service.generate_file_name = AsyncMock(
|
||||
return_value="message_from_67890_number_1"
|
||||
)
|
||||
mock_service.save_audio_file = AsyncMock()
|
||||
mock_service.download_and_save_audio = AsyncMock()
|
||||
mock_service_class.return_value = mock_service
|
||||
|
||||
await save_voice_message(mock_call, bot_db=mock_bot_db_save, settings=mock_settings)
|
||||
|
||||
await save_voice_message(
|
||||
mock_call, bot_db=mock_bot_db_save, settings=mock_settings
|
||||
)
|
||||
save_calls = mock_bot_db_save.delete_audio_moderate_record.call_count
|
||||
|
||||
|
||||
# Тест для удаления
|
||||
await delete_voice_message(mock_call, bot_db=mock_bot_db_delete, settings=mock_settings)
|
||||
await delete_voice_message(
|
||||
mock_call, bot_db=mock_bot_db_delete, settings=mock_settings
|
||||
)
|
||||
delete_calls = mock_bot_db_delete.delete_audio_moderate_record.call_count
|
||||
|
||||
|
||||
# Проверяем, что в обоих случаях вызывается очистка
|
||||
assert save_calls == 1
|
||||
assert delete_calls == 1
|
||||
@@ -242,9 +324,11 @@ class TestCallbackHandlersIntegration:
|
||||
|
||||
class TestCallbackHandlersEdgeCases:
|
||||
"""Тесты граничных случаев для callback handlers"""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_voice_message_no_voice_attribute(self, mock_bot_db, mock_settings):
|
||||
async def test_save_voice_message_no_voice_attribute(
|
||||
self, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест сохранения когда у сообщения нет voice атрибута"""
|
||||
call = Mock()
|
||||
call.message = Mock()
|
||||
@@ -253,26 +337,36 @@ class TestCallbackHandlersEdgeCases:
|
||||
call.bot = Mock()
|
||||
call.bot.delete_message = AsyncMock()
|
||||
call.answer = AsyncMock()
|
||||
|
||||
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService'):
|
||||
|
||||
with patch("helper_bot.handlers.callback.callback_handlers.AudioFileService"):
|
||||
await save_voice_message(call, bot_db=mock_bot_db, settings=mock_settings)
|
||||
|
||||
|
||||
# Должна быть ошибка
|
||||
call.answer.assert_called_once_with(text='Ошибка при сохранении!', cache_time=3)
|
||||
|
||||
call.answer.assert_called_once_with(
|
||||
text="Ошибка при сохранении!", cache_time=3
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_voice_message_user_not_found(self, mock_call, mock_bot_db, mock_settings):
|
||||
async def test_save_voice_message_user_not_found(
|
||||
self, mock_call, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест сохранения когда пользователь не найден"""
|
||||
mock_bot_db.get_user_id_by_message_id_for_voice_bot.return_value = None
|
||||
|
||||
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService'):
|
||||
await save_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
|
||||
|
||||
|
||||
with patch("helper_bot.handlers.callback.callback_handlers.AudioFileService"):
|
||||
await save_voice_message(
|
||||
mock_call, bot_db=mock_bot_db, settings=mock_settings
|
||||
)
|
||||
|
||||
# Должна быть ошибка
|
||||
mock_call.answer.assert_called_once_with(text='Ошибка при сохранении!', cache_time=3)
|
||||
|
||||
mock_call.answer.assert_called_once_with(
|
||||
text="Ошибка при сохранении!", cache_time=3
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_voice_message_with_different_message_id(self, mock_bot_db, mock_settings):
|
||||
async def test_delete_voice_message_with_different_message_id(
|
||||
self, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест удаления с другим message_id"""
|
||||
call = Mock()
|
||||
call.message = Mock()
|
||||
@@ -280,16 +374,15 @@ class TestCallbackHandlersEdgeCases:
|
||||
call.bot = Mock()
|
||||
call.bot.delete_message = AsyncMock()
|
||||
call.answer = AsyncMock()
|
||||
|
||||
|
||||
await delete_voice_message(call, bot_db=mock_bot_db, settings=mock_settings)
|
||||
|
||||
|
||||
# Проверяем, что используется правильный message_id
|
||||
call.bot.delete_message.assert_called_once_with(
|
||||
chat_id='test_group_id',
|
||||
message_id=99999
|
||||
chat_id="test_group_id", message_id=99999
|
||||
)
|
||||
mock_bot_db.delete_audio_moderate_record.assert_called_once_with(99999)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__])
|
||||
|
||||
@@ -8,67 +8,81 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from aiogram import types
|
||||
|
||||
from helper_bot.utils.helper_func import (
|
||||
add_in_db_media, add_in_db_media_mediagroup, download_file,
|
||||
send_media_group_message_to_private_chat)
|
||||
add_in_db_media,
|
||||
add_in_db_media_mediagroup,
|
||||
download_file,
|
||||
send_media_group_message_to_private_chat,
|
||||
)
|
||||
|
||||
|
||||
class TestDownloadFile:
|
||||
"""Тесты для функции download_file"""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_download_file_success_photo(self):
|
||||
"""Тест успешного скачивания фото"""
|
||||
# Создаем временную директорию
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
with patch('helper_bot.utils.helper_func.os.makedirs'), \
|
||||
patch('helper_bot.utils.helper_func.os.path.exists', return_value=True), \
|
||||
patch('helper_bot.utils.helper_func.os.path.getsize', return_value=1024), \
|
||||
patch('helper_bot.utils.helper_func.os.path.basename', return_value='photo.jpg'), \
|
||||
patch('helper_bot.utils.helper_func.os.path.splitext', return_value=('photo', '.jpg')):
|
||||
|
||||
with (
|
||||
patch("helper_bot.utils.helper_func.os.makedirs"),
|
||||
patch("helper_bot.utils.helper_func.os.path.exists", return_value=True),
|
||||
patch(
|
||||
"helper_bot.utils.helper_func.os.path.getsize", return_value=1024
|
||||
),
|
||||
patch(
|
||||
"helper_bot.utils.helper_func.os.path.basename",
|
||||
return_value="photo.jpg",
|
||||
),
|
||||
patch(
|
||||
"helper_bot.utils.helper_func.os.path.splitext",
|
||||
return_value=("photo", ".jpg"),
|
||||
),
|
||||
):
|
||||
|
||||
# Мокаем сообщение и бота
|
||||
mock_message = Mock()
|
||||
mock_message.bot = Mock()
|
||||
mock_file = Mock()
|
||||
mock_file.file_path = 'photos/photo.jpg'
|
||||
mock_file.file_path = "photos/photo.jpg"
|
||||
mock_message.bot.get_file = AsyncMock(return_value=mock_file)
|
||||
mock_message.bot.download_file = AsyncMock()
|
||||
|
||||
|
||||
# Вызываем функцию
|
||||
result = await download_file(mock_message, 'test_file_id', 'photo')
|
||||
|
||||
result = await download_file(mock_message, "test_file_id", "photo")
|
||||
|
||||
# Проверяем результат
|
||||
assert result is not None
|
||||
assert 'files/photos/test_file_id.jpg' in result
|
||||
mock_message.bot.get_file.assert_called_once_with('test_file_id')
|
||||
assert "files/photos/test_file_id.jpg" in result
|
||||
mock_message.bot.get_file.assert_called_once_with("test_file_id")
|
||||
mock_message.bot.download_file.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_download_file_invalid_parameters(self):
|
||||
"""Тест с неверными параметрами"""
|
||||
result = await download_file(None, 'test_file_id', 'photo')
|
||||
result = await download_file(None, "test_file_id", "photo")
|
||||
assert result is None
|
||||
|
||||
|
||||
mock_message = Mock()
|
||||
mock_message.bot = None
|
||||
result = await download_file(mock_message, 'test_file_id', 'photo')
|
||||
result = await download_file(mock_message, "test_file_id", "photo")
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_download_file_error(self):
|
||||
"""Тест обработки ошибки при скачивании"""
|
||||
mock_message = Mock()
|
||||
mock_message.bot = Mock()
|
||||
mock_message.bot.get_file = AsyncMock(side_effect=Exception("Network error"))
|
||||
|
||||
result = await download_file(mock_message, 'test_file_id', 'photo')
|
||||
|
||||
result = await download_file(mock_message, "test_file_id", "photo")
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestAddInDbMedia:
|
||||
"""Тесты для функции add_in_db_media"""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_in_db_media_success_photo(self):
|
||||
"""Тест успешного добавления фото в БД"""
|
||||
@@ -76,65 +90,75 @@ class TestAddInDbMedia:
|
||||
mock_message = Mock()
|
||||
mock_message.message_id = 123
|
||||
mock_message.photo = [Mock()]
|
||||
mock_message.photo[-1].file_id = 'photo_123'
|
||||
mock_message.photo[-1].file_id = "photo_123"
|
||||
mock_message.video = None
|
||||
mock_message.voice = None
|
||||
mock_message.audio = None
|
||||
mock_message.video_note = None
|
||||
|
||||
|
||||
# Мокаем БД
|
||||
mock_db = AsyncMock()
|
||||
mock_db.add_post_content = AsyncMock(return_value=True)
|
||||
|
||||
with patch('helper_bot.utils.helper_func.download_file', return_value='files/photos/photo_123.jpg'):
|
||||
|
||||
with patch(
|
||||
"helper_bot.utils.helper_func.download_file",
|
||||
return_value="files/photos/photo_123.jpg",
|
||||
):
|
||||
result = await add_in_db_media(mock_message, mock_db)
|
||||
|
||||
|
||||
assert result is True
|
||||
mock_db.add_post_content.assert_called_once_with(123, 123, 'files/photos/photo_123.jpg', 'photo')
|
||||
|
||||
mock_db.add_post_content.assert_called_once_with(
|
||||
123, 123, "files/photos/photo_123.jpg", "photo"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_in_db_media_download_fails(self):
|
||||
"""Тест когда скачивание файла не удается"""
|
||||
mock_message = Mock()
|
||||
mock_message.message_id = 123
|
||||
mock_message.photo = [Mock()]
|
||||
mock_message.photo[-1].file_id = 'photo_123'
|
||||
mock_message.photo[-1].file_id = "photo_123"
|
||||
mock_message.video = None
|
||||
mock_message.voice = None
|
||||
mock_message.audio = None
|
||||
mock_message.video_note = None
|
||||
|
||||
|
||||
mock_db = AsyncMock()
|
||||
|
||||
with patch('helper_bot.utils.helper_func.download_file', return_value=None):
|
||||
|
||||
with patch("helper_bot.utils.helper_func.download_file", return_value=None):
|
||||
result = await add_in_db_media(mock_message, mock_db)
|
||||
|
||||
|
||||
assert result is False
|
||||
mock_db.add_post_content.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_in_db_media_db_fails(self):
|
||||
"""Тест когда добавление в БД не удается"""
|
||||
mock_message = Mock()
|
||||
mock_message.message_id = 123
|
||||
mock_message.photo = [Mock()]
|
||||
mock_message.photo[-1].file_id = 'photo_123'
|
||||
mock_message.photo[-1].file_id = "photo_123"
|
||||
mock_message.video = None
|
||||
mock_message.voice = None
|
||||
mock_message.audio = None
|
||||
mock_message.video_note = None
|
||||
|
||||
|
||||
mock_db = AsyncMock()
|
||||
mock_db.add_post_content = AsyncMock(return_value=False)
|
||||
|
||||
with patch('helper_bot.utils.helper_func.download_file', return_value='files/photos/photo_123.jpg'), \
|
||||
patch('helper_bot.utils.helper_func.os.remove'):
|
||||
|
||||
|
||||
with (
|
||||
patch(
|
||||
"helper_bot.utils.helper_func.download_file",
|
||||
return_value="files/photos/photo_123.jpg",
|
||||
),
|
||||
patch("helper_bot.utils.helper_func.os.remove"),
|
||||
):
|
||||
|
||||
result = await add_in_db_media(mock_message, mock_db)
|
||||
|
||||
|
||||
assert result is False
|
||||
mock_db.add_post_content.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_in_db_media_unsupported_content(self):
|
||||
"""Тест с неподдерживаемым типом контента"""
|
||||
@@ -145,18 +169,18 @@ class TestAddInDbMedia:
|
||||
mock_message.voice = None
|
||||
mock_message.audio = None
|
||||
mock_message.video_note = None
|
||||
|
||||
|
||||
mock_db = AsyncMock()
|
||||
|
||||
|
||||
result = await add_in_db_media(mock_message, mock_db)
|
||||
|
||||
|
||||
assert result is False
|
||||
mock_db.add_post_content.assert_not_called()
|
||||
|
||||
|
||||
class TestAddInDbMediaMediagroup:
|
||||
"""Тесты для функции add_in_db_media_mediagroup"""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_in_db_media_mediagroup_success(self):
|
||||
"""Тест успешного добавления медиагруппы в БД"""
|
||||
@@ -164,43 +188,47 @@ class TestAddInDbMediaMediagroup:
|
||||
mock_message1 = Mock()
|
||||
mock_message1.message_id = 1
|
||||
mock_message1.photo = [Mock()]
|
||||
mock_message1.photo[-1].file_id = 'photo_1'
|
||||
mock_message1.photo[-1].file_id = "photo_1"
|
||||
mock_message1.video = None
|
||||
mock_message1.voice = None
|
||||
mock_message1.audio = None
|
||||
mock_message1.video_note = None
|
||||
|
||||
|
||||
mock_message2 = Mock()
|
||||
mock_message2.message_id = 2
|
||||
mock_message2.photo = None
|
||||
mock_message2.video = Mock()
|
||||
mock_message2.video.file_id = 'video_1'
|
||||
mock_message2.video.file_id = "video_1"
|
||||
mock_message2.voice = None
|
||||
mock_message2.audio = None
|
||||
mock_message2.video_note = None
|
||||
|
||||
|
||||
sent_messages = [mock_message1, mock_message2]
|
||||
|
||||
|
||||
# Мокаем БД
|
||||
mock_db = AsyncMock()
|
||||
mock_db.add_post_content = AsyncMock(return_value=True)
|
||||
|
||||
with patch('helper_bot.utils.helper_func.download_file', return_value='files/test.jpg'):
|
||||
result = await add_in_db_media_mediagroup(sent_messages, mock_db, main_post_id=100)
|
||||
|
||||
|
||||
with patch(
|
||||
"helper_bot.utils.helper_func.download_file", return_value="files/test.jpg"
|
||||
):
|
||||
result = await add_in_db_media_mediagroup(
|
||||
sent_messages, mock_db, main_post_id=100
|
||||
)
|
||||
|
||||
assert result is True
|
||||
assert mock_db.add_post_content.call_count == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_in_db_media_mediagroup_empty_list(self):
|
||||
"""Тест с пустым списком сообщений"""
|
||||
mock_db = AsyncMock()
|
||||
|
||||
|
||||
result = await add_in_db_media_mediagroup([], mock_db)
|
||||
|
||||
|
||||
assert result is False
|
||||
mock_db.add_post_content.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_in_db_media_mediagroup_partial_failure(self):
|
||||
"""Тест когда часть сообщений обрабатывается успешно"""
|
||||
@@ -208,12 +236,12 @@ class TestAddInDbMediaMediagroup:
|
||||
mock_message1 = Mock()
|
||||
mock_message1.message_id = 1
|
||||
mock_message1.photo = [Mock()]
|
||||
mock_message1.photo[-1].file_id = 'photo_1'
|
||||
mock_message1.photo[-1].file_id = "photo_1"
|
||||
mock_message1.video = None
|
||||
mock_message1.voice = None
|
||||
mock_message1.audio = None
|
||||
mock_message1.video_note = None
|
||||
|
||||
|
||||
mock_message2 = Mock()
|
||||
mock_message2.message_id = 2
|
||||
mock_message2.photo = None
|
||||
@@ -221,16 +249,18 @@ class TestAddInDbMediaMediagroup:
|
||||
mock_message2.voice = None
|
||||
mock_message2.audio = None
|
||||
mock_message2.video_note = None # Неподдерживаемый тип
|
||||
|
||||
|
||||
sent_messages = [mock_message1, mock_message2]
|
||||
|
||||
|
||||
# Мокаем БД
|
||||
mock_db = AsyncMock()
|
||||
mock_db.add_post_content = AsyncMock(return_value=True)
|
||||
|
||||
with patch('helper_bot.utils.helper_func.download_file', return_value='files/test.jpg'):
|
||||
|
||||
with patch(
|
||||
"helper_bot.utils.helper_func.download_file", return_value="files/test.jpg"
|
||||
):
|
||||
result = await add_in_db_media_mediagroup(sent_messages, mock_db)
|
||||
|
||||
|
||||
# Должен вернуть False, так как есть ошибки (второе сообщение не поддерживается)
|
||||
assert result is False
|
||||
assert mock_db.add_post_content.call_count == 1
|
||||
@@ -238,7 +268,7 @@ class TestAddInDbMediaMediagroup:
|
||||
|
||||
class TestSendMediaGroupMessageToPrivateChat:
|
||||
"""Тесты для функции send_media_group_message_to_private_chat"""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_media_group_message_success(self):
|
||||
"""Тест успешной отправки медиагруппы"""
|
||||
@@ -246,25 +276,29 @@ class TestSendMediaGroupMessageToPrivateChat:
|
||||
mock_message = Mock()
|
||||
mock_message.from_user.id = 123
|
||||
mock_message.bot = Mock()
|
||||
|
||||
|
||||
# Мокаем отправленное сообщение
|
||||
mock_sent_message = Mock()
|
||||
mock_sent_message.message_id = 456
|
||||
mock_sent_message.caption = "Test caption"
|
||||
mock_message.bot.send_media_group = AsyncMock(return_value=[mock_sent_message])
|
||||
|
||||
|
||||
# Мокаем БД
|
||||
mock_db = AsyncMock()
|
||||
|
||||
with patch('helper_bot.utils.helper_func.add_in_db_media_mediagroup', return_value=True):
|
||||
with patch('asyncio.create_task'): # Мокаем create_task, чтобы фоновая задача не выполнялась
|
||||
|
||||
with patch(
|
||||
"helper_bot.utils.helper_func.add_in_db_media_mediagroup", return_value=True
|
||||
):
|
||||
with patch(
|
||||
"asyncio.create_task"
|
||||
): # Мокаем create_task, чтобы фоновая задача не выполнялась
|
||||
result = await send_media_group_message_to_private_chat(
|
||||
100, mock_message, [], mock_db, main_post_id=789
|
||||
)
|
||||
|
||||
|
||||
assert result == [456] # Функция возвращает список message_id
|
||||
mock_message.bot.send_media_group.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_media_group_message_media_processing_fails(self):
|
||||
"""Тест когда обработка медиа не удается"""
|
||||
@@ -272,22 +306,27 @@ class TestSendMediaGroupMessageToPrivateChat:
|
||||
mock_message = Mock()
|
||||
mock_message.from_user.id = 123
|
||||
mock_message.bot = Mock()
|
||||
|
||||
|
||||
# Мокаем отправленное сообщение
|
||||
mock_sent_message = Mock()
|
||||
mock_sent_message.message_id = 456
|
||||
mock_sent_message.caption = "Test caption"
|
||||
mock_message.bot.send_media_group = AsyncMock(return_value=[mock_sent_message])
|
||||
|
||||
|
||||
# Мокаем БД
|
||||
mock_db = AsyncMock()
|
||||
|
||||
with patch('helper_bot.utils.helper_func.add_in_db_media_mediagroup', return_value=False):
|
||||
with patch('asyncio.create_task'): # Мокаем create_task, чтобы фоновая задача не выполнялась
|
||||
|
||||
with patch(
|
||||
"helper_bot.utils.helper_func.add_in_db_media_mediagroup",
|
||||
return_value=False,
|
||||
):
|
||||
with patch(
|
||||
"asyncio.create_task"
|
||||
): # Мокаем create_task, чтобы фоновая задача не выполнялась
|
||||
result = await send_media_group_message_to_private_chat(
|
||||
100, mock_message, [], mock_db, main_post_id=789
|
||||
)
|
||||
|
||||
|
||||
assert result == [456] # Функция возвращает список message_id
|
||||
mock_message.bot.send_media_group.assert_called_once()
|
||||
|
||||
|
||||
@@ -1,470 +1,484 @@
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from aiogram.types import (InlineKeyboardButton, InlineKeyboardMarkup,
|
||||
KeyboardButton, ReplyKeyboardMarkup)
|
||||
from aiogram.types import (
|
||||
InlineKeyboardButton,
|
||||
InlineKeyboardMarkup,
|
||||
KeyboardButton,
|
||||
ReplyKeyboardMarkup,
|
||||
)
|
||||
|
||||
from database.async_db import AsyncBotDB
|
||||
from helper_bot.filters.main import ChatTypeFilter
|
||||
from helper_bot.keyboards.keyboards import (create_keyboard_with_pagination,
|
||||
get_reply_keyboard,
|
||||
get_reply_keyboard_admin,
|
||||
get_reply_keyboard_for_post,
|
||||
get_reply_keyboard_leave_chat)
|
||||
from helper_bot.keyboards.keyboards import (
|
||||
create_keyboard_with_pagination,
|
||||
get_reply_keyboard,
|
||||
get_reply_keyboard_admin,
|
||||
get_reply_keyboard_for_post,
|
||||
get_reply_keyboard_leave_chat,
|
||||
)
|
||||
|
||||
|
||||
class TestKeyboards:
|
||||
"""Тесты для клавиатур"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db(self):
|
||||
"""Создает мок базы данных"""
|
||||
db = Mock(spec=AsyncBotDB)
|
||||
db.get_user_info = Mock(return_value={
|
||||
'stickers': True,
|
||||
'admin': False
|
||||
})
|
||||
db.get_user_info = Mock(return_value={"stickers": True, "admin": False})
|
||||
return db
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_reply_keyboard_basic(self, mock_db):
|
||||
"""Тест базовой клавиатуры"""
|
||||
user_id = 123456
|
||||
|
||||
|
||||
keyboard = await get_reply_keyboard(mock_db, user_id)
|
||||
|
||||
|
||||
# Проверяем, что возвращается клавиатура
|
||||
assert isinstance(keyboard, ReplyKeyboardMarkup)
|
||||
assert keyboard.keyboard is not None
|
||||
assert len(keyboard.keyboard) > 0
|
||||
|
||||
|
||||
# Проверяем, что каждая кнопка в отдельной строке
|
||||
for row in keyboard.keyboard:
|
||||
assert len(row) == 1 # Каждая строка содержит только одну кнопку
|
||||
|
||||
|
||||
# Проверяем наличие основных кнопок
|
||||
all_buttons = []
|
||||
for row in keyboard.keyboard:
|
||||
for button in row:
|
||||
all_buttons.append(button.text)
|
||||
|
||||
|
||||
# Проверяем наличие основных кнопок
|
||||
assert '📢Предложить свой пост' in all_buttons
|
||||
assert '👋🏼Сказать пока!' in all_buttons
|
||||
assert '📩Связаться с админами' in all_buttons
|
||||
|
||||
assert "📢Предложить свой пост" in all_buttons
|
||||
assert "👋🏼Сказать пока!" in all_buttons
|
||||
assert "📩Связаться с админами" in all_buttons
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_reply_keyboard_with_stickers(self, mock_db):
|
||||
"""Тест клавиатуры со стикерами"""
|
||||
user_id = 123456
|
||||
# Мокаем метод get_stickers_info
|
||||
mock_db.get_stickers_info = AsyncMock(return_value=False)
|
||||
|
||||
|
||||
keyboard = await get_reply_keyboard(mock_db, user_id)
|
||||
|
||||
|
||||
all_buttons = []
|
||||
for row in keyboard.keyboard:
|
||||
for button in row:
|
||||
all_buttons.append(button.text)
|
||||
|
||||
|
||||
# Проверяем наличие кнопки стикеров
|
||||
assert '🤪Хочу стикеры' in all_buttons
|
||||
|
||||
assert "🤪Хочу стикеры" in all_buttons
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_reply_keyboard_without_stickers(self, mock_db):
|
||||
"""Тест клавиатуры без стикеров"""
|
||||
user_id = 123456
|
||||
# Мокаем метод get_stickers_info
|
||||
mock_db.get_stickers_info = AsyncMock(return_value=True)
|
||||
|
||||
|
||||
keyboard = await get_reply_keyboard(mock_db, user_id)
|
||||
|
||||
|
||||
all_buttons = []
|
||||
for row in keyboard.keyboard:
|
||||
for button in row:
|
||||
all_buttons.append(button.text)
|
||||
|
||||
|
||||
# Проверяем отсутствие кнопки стикеров
|
||||
assert '🤪Хочу стикеры' not in all_buttons
|
||||
|
||||
assert "🤪Хочу стикеры" not in all_buttons
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_reply_keyboard_admin(self, mock_db):
|
||||
"""Тест клавиатуры для админа"""
|
||||
user_id = 123456
|
||||
# Мокаем метод get_stickers_info
|
||||
mock_db.get_stickers_info = AsyncMock(return_value=False)
|
||||
|
||||
|
||||
keyboard = await get_reply_keyboard(mock_db, user_id)
|
||||
|
||||
|
||||
all_buttons = []
|
||||
for row in keyboard.keyboard:
|
||||
for button in row:
|
||||
all_buttons.append(button.text)
|
||||
|
||||
|
||||
# Проверяем наличие основных кнопок
|
||||
assert '📢Предложить свой пост' in all_buttons
|
||||
assert '👋🏼Сказать пока!' in all_buttons
|
||||
assert '📩Связаться с админами' in all_buttons
|
||||
|
||||
assert "📢Предложить свой пост" in all_buttons
|
||||
assert "👋🏼Сказать пока!" in all_buttons
|
||||
assert "📩Связаться с админами" in all_buttons
|
||||
|
||||
def test_get_reply_keyboard_admin_keyboard(self):
|
||||
"""Тест админской клавиатуры"""
|
||||
keyboard = get_reply_keyboard_admin()
|
||||
|
||||
|
||||
assert isinstance(keyboard, ReplyKeyboardMarkup)
|
||||
assert keyboard.keyboard is not None
|
||||
assert len(keyboard.keyboard) == 2 # Две строки
|
||||
|
||||
|
||||
# Проверяем первую строку (3 кнопки)
|
||||
first_row = keyboard.keyboard[0]
|
||||
assert len(first_row) == 3
|
||||
assert first_row[0].text == "Бан (Список)"
|
||||
assert first_row[1].text == "Бан по нику"
|
||||
assert first_row[2].text == "Бан по ID"
|
||||
|
||||
|
||||
# Проверяем вторую строку (2 кнопки)
|
||||
second_row = keyboard.keyboard[1]
|
||||
assert len(second_row) == 2
|
||||
assert second_row[0].text == "Разбан (список)"
|
||||
assert second_row[1].text == "Вернуться в бота"
|
||||
|
||||
|
||||
def test_get_reply_keyboard_for_post(self):
|
||||
"""Тест клавиатуры для постов"""
|
||||
keyboard = get_reply_keyboard_for_post()
|
||||
|
||||
|
||||
assert isinstance(keyboard, InlineKeyboardMarkup)
|
||||
assert keyboard.inline_keyboard is not None
|
||||
assert len(keyboard.inline_keyboard) > 0
|
||||
|
||||
|
||||
all_buttons = []
|
||||
for row in keyboard.inline_keyboard:
|
||||
for button in row:
|
||||
all_buttons.append(button.text)
|
||||
|
||||
|
||||
# Проверяем наличие кнопок для постов
|
||||
assert 'Опубликовать' in all_buttons
|
||||
assert 'Отклонить' in all_buttons
|
||||
|
||||
assert "Опубликовать" in all_buttons
|
||||
assert "Отклонить" in all_buttons
|
||||
|
||||
def test_get_reply_keyboard_leave_chat(self):
|
||||
"""Тест клавиатуры для выхода из чата"""
|
||||
keyboard = get_reply_keyboard_leave_chat()
|
||||
|
||||
|
||||
assert isinstance(keyboard, ReplyKeyboardMarkup)
|
||||
assert keyboard.keyboard is not None
|
||||
assert len(keyboard.keyboard) > 0
|
||||
|
||||
|
||||
all_buttons = []
|
||||
for row in keyboard.keyboard:
|
||||
for button in row:
|
||||
all_buttons.append(button.text)
|
||||
|
||||
|
||||
# Проверяем наличие кнопки выхода
|
||||
assert 'Выйти из чата' in all_buttons
|
||||
|
||||
assert "Выйти из чата" in all_buttons
|
||||
|
||||
def test_keyboard_resize(self):
|
||||
"""Тест настройки resize клавиатуры"""
|
||||
keyboard = get_reply_keyboard_for_post()
|
||||
|
||||
|
||||
# Проверяем, что клавиатура настроена правильно
|
||||
# InlineKeyboardMarkup не имеет resize_keyboard
|
||||
assert isinstance(keyboard, InlineKeyboardMarkup)
|
||||
|
||||
|
||||
def test_keyboard_one_time(self):
|
||||
"""Тест настройки one_time клавиатуры"""
|
||||
keyboard = get_reply_keyboard_leave_chat()
|
||||
|
||||
|
||||
# Проверяем, что клавиатура настроена правильно
|
||||
assert hasattr(keyboard, 'one_time_keyboard')
|
||||
assert hasattr(keyboard, "one_time_keyboard")
|
||||
assert keyboard.one_time_keyboard is True
|
||||
|
||||
|
||||
class TestChatTypeFilter:
|
||||
"""Тесты для фильтра типа чата"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_message(self):
|
||||
"""Создает мок сообщения"""
|
||||
message = Mock()
|
||||
message.chat = Mock()
|
||||
return message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_type_filter_private(self, mock_message):
|
||||
"""Тест фильтра для приватного чата"""
|
||||
mock_message.chat.type = "private"
|
||||
|
||||
|
||||
filter_private = ChatTypeFilter(chat_type=["private"])
|
||||
filter_group = ChatTypeFilter(chat_type=["group"])
|
||||
filter_supergroup = ChatTypeFilter(chat_type=["supergroup"])
|
||||
|
||||
|
||||
# Проверяем, что фильтр работает правильно
|
||||
assert await filter_private(mock_message) is True
|
||||
assert await filter_group(mock_message) is False
|
||||
assert await filter_supergroup(mock_message) is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_type_filter_group(self, mock_message):
|
||||
"""Тест фильтра для группового чата"""
|
||||
mock_message.chat.type = "group"
|
||||
|
||||
|
||||
filter_private = ChatTypeFilter(chat_type=["private"])
|
||||
filter_group = ChatTypeFilter(chat_type=["group"])
|
||||
filter_supergroup = ChatTypeFilter(chat_type=["supergroup"])
|
||||
|
||||
|
||||
# Проверяем, что фильтр работает правильно
|
||||
assert await filter_private(mock_message) is False
|
||||
assert await filter_group(mock_message) is True
|
||||
assert await filter_supergroup(mock_message) is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_type_filter_supergroup(self, mock_message):
|
||||
"""Тест фильтра для супергруппы"""
|
||||
mock_message.chat.type = "supergroup"
|
||||
|
||||
|
||||
filter_private = ChatTypeFilter(chat_type=["private"])
|
||||
filter_group = ChatTypeFilter(chat_type=["group"])
|
||||
filter_supergroup = ChatTypeFilter(chat_type=["supergroup"])
|
||||
|
||||
|
||||
# Проверяем, что фильтр работает правильно
|
||||
assert await filter_private(mock_message) is False
|
||||
assert await filter_group(mock_message) is False
|
||||
assert await filter_supergroup(mock_message) is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_type_filter_multiple_types(self, mock_message):
|
||||
"""Тест фильтра с несколькими типами чатов"""
|
||||
filter_private_group = ChatTypeFilter(chat_type=["private", "group"])
|
||||
filter_all = ChatTypeFilter(chat_type=["private", "group", "supergroup"])
|
||||
|
||||
|
||||
# Тест для приватного чата
|
||||
mock_message.chat.type = "private"
|
||||
assert await filter_private_group(mock_message) is True
|
||||
assert await filter_all(mock_message) is True
|
||||
|
||||
|
||||
# Тест для группового чата
|
||||
mock_message.chat.type = "group"
|
||||
assert await filter_private_group(mock_message) is True
|
||||
assert await filter_all(mock_message) is True
|
||||
|
||||
|
||||
# Тест для супергруппы
|
||||
mock_message.chat.type = "supergroup"
|
||||
assert await filter_private_group(mock_message) is False
|
||||
assert await filter_all(mock_message) is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_type_filter_channel(self, mock_message):
|
||||
"""Тест фильтра для канала"""
|
||||
mock_message.chat.type = "channel"
|
||||
|
||||
|
||||
filter_channel = ChatTypeFilter(chat_type=["channel"])
|
||||
filter_private = ChatTypeFilter(chat_type=["private"])
|
||||
|
||||
|
||||
assert await filter_channel(mock_message) is True
|
||||
assert await filter_private(mock_message) is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_type_filter_empty_list(self, mock_message):
|
||||
"""Тест фильтра с пустым списком типов"""
|
||||
mock_message.chat.type = "private"
|
||||
|
||||
|
||||
filter_empty = ChatTypeFilter(chat_type=[])
|
||||
|
||||
|
||||
# Фильтр с пустым списком должен возвращать False
|
||||
assert await filter_empty(mock_message) is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_type_filter_invalid_type(self, mock_message):
|
||||
"""Тест фильтра с несуществующим типом чата"""
|
||||
mock_message.chat.type = "invalid_type"
|
||||
|
||||
|
||||
filter_private = ChatTypeFilter(chat_type=["private"])
|
||||
filter_invalid = ChatTypeFilter(chat_type=["invalid_type"])
|
||||
|
||||
|
||||
assert await filter_private(mock_message) is False
|
||||
assert await filter_invalid(mock_message) is True
|
||||
|
||||
|
||||
class TestKeyboardIntegration:
|
||||
"""Интеграционные тесты клавиатур"""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_keyboard_structure_consistency(self):
|
||||
"""Тест консистентности структуры клавиатур"""
|
||||
# Мокаем базу данных
|
||||
mock_db = Mock(spec=AsyncBotDB)
|
||||
mock_db.get_stickers_info = AsyncMock(return_value=False)
|
||||
|
||||
|
||||
# Тестируем все типы клавиатур
|
||||
keyboard1 = await get_reply_keyboard(mock_db, 123456)
|
||||
keyboard2 = get_reply_keyboard_for_post()
|
||||
keyboard3 = get_reply_keyboard_leave_chat()
|
||||
|
||||
|
||||
# Проверяем первую клавиатуру (ReplyKeyboardMarkup)
|
||||
assert isinstance(keyboard1, ReplyKeyboardMarkup)
|
||||
assert hasattr(keyboard1, 'keyboard')
|
||||
assert hasattr(keyboard1, "keyboard")
|
||||
assert isinstance(keyboard1.keyboard, list)
|
||||
|
||||
|
||||
# Проверяем вторую клавиатуру (InlineKeyboardMarkup)
|
||||
assert isinstance(keyboard2, InlineKeyboardMarkup)
|
||||
assert hasattr(keyboard2, 'inline_keyboard')
|
||||
assert hasattr(keyboard2, "inline_keyboard")
|
||||
assert isinstance(keyboard2.inline_keyboard, list)
|
||||
|
||||
|
||||
# Проверяем третью клавиатуру (ReplyKeyboardMarkup)
|
||||
assert isinstance(keyboard3, ReplyKeyboardMarkup)
|
||||
assert hasattr(keyboard3, 'keyboard')
|
||||
assert hasattr(keyboard3, "keyboard")
|
||||
assert isinstance(keyboard3.keyboard, list)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_keyboard_button_texts(self):
|
||||
"""Тест текстов кнопок клавиатур"""
|
||||
# Тестируем основные кнопки
|
||||
db = Mock(spec=AsyncBotDB)
|
||||
db.get_stickers_info = AsyncMock(return_value=False)
|
||||
|
||||
|
||||
main_keyboard = await get_reply_keyboard(db, 123456)
|
||||
post_keyboard = get_reply_keyboard_for_post()
|
||||
leave_keyboard = get_reply_keyboard_leave_chat()
|
||||
|
||||
|
||||
# Собираем все тексты кнопок
|
||||
main_buttons = []
|
||||
for row in main_keyboard.keyboard:
|
||||
for button in row:
|
||||
main_buttons.append(button.text)
|
||||
|
||||
|
||||
post_buttons = []
|
||||
for row in post_keyboard.inline_keyboard:
|
||||
for button in row:
|
||||
post_buttons.append(button.text)
|
||||
|
||||
|
||||
leave_buttons = []
|
||||
for row in leave_keyboard.keyboard:
|
||||
for button in row:
|
||||
leave_buttons.append(button.text)
|
||||
|
||||
|
||||
# Проверяем наличие основных кнопок
|
||||
assert '📢Предложить свой пост' in main_buttons
|
||||
assert '👋🏼Сказать пока!' in main_buttons
|
||||
assert '📩Связаться с админами' in main_buttons
|
||||
assert '🤪Хочу стикеры' in main_buttons
|
||||
|
||||
assert "📢Предложить свой пост" in main_buttons
|
||||
assert "👋🏼Сказать пока!" in main_buttons
|
||||
assert "📩Связаться с админами" in main_buttons
|
||||
assert "🤪Хочу стикеры" in main_buttons
|
||||
|
||||
# Проверяем кнопки для постов
|
||||
assert 'Опубликовать' in post_buttons
|
||||
assert 'Отклонить' in post_buttons
|
||||
|
||||
assert "Опубликовать" in post_buttons
|
||||
assert "Отклонить" in post_buttons
|
||||
|
||||
# Проверяем кнопку выхода
|
||||
assert 'Выйти из чата' in leave_buttons
|
||||
assert "Выйти из чата" in leave_buttons
|
||||
|
||||
|
||||
class TestPagination:
|
||||
"""Тесты для функции create_keyboard_with_pagination"""
|
||||
|
||||
|
||||
def test_pagination_empty_list(self):
|
||||
"""Тест с пустым списком элементов"""
|
||||
keyboard = create_keyboard_with_pagination(1, 0, [], 'test')
|
||||
keyboard = create_keyboard_with_pagination(1, 0, [], "test")
|
||||
assert keyboard is not None
|
||||
# Проверяем, что есть только кнопка "Назад"
|
||||
assert len(keyboard.inline_keyboard) == 1
|
||||
assert keyboard.inline_keyboard[0][0].text == "🏠 Назад"
|
||||
|
||||
|
||||
def test_pagination_single_page(self):
|
||||
"""Тест с одной страницей"""
|
||||
items = [("User1", 1), ("User2", 2), ("User3", 3)]
|
||||
keyboard = create_keyboard_with_pagination(1, 3, items, 'test')
|
||||
|
||||
keyboard = create_keyboard_with_pagination(1, 3, items, "test")
|
||||
|
||||
# Проверяем количество кнопок (3 пользователя + кнопка "Назад")
|
||||
assert len(keyboard.inline_keyboard) == 2 # 1 ряд с пользователями + 1 ряд с "Назад"
|
||||
assert (
|
||||
len(keyboard.inline_keyboard) == 2
|
||||
) # 1 ряд с пользователями + 1 ряд с "Назад"
|
||||
assert len(keyboard.inline_keyboard[0]) == 3 # 3 пользователя в первом ряду
|
||||
assert keyboard.inline_keyboard[1][0].text == "🏠 Назад"
|
||||
|
||||
|
||||
# Проверяем, что нет кнопок навигации
|
||||
assert len(keyboard.inline_keyboard[0]) == 3 # только пользователи
|
||||
|
||||
|
||||
def test_pagination_multiple_pages(self):
|
||||
"""Тест с несколькими страницами"""
|
||||
items = [("User" + str(i), i) for i in range(1, 15)] # 14 пользователей
|
||||
keyboard = create_keyboard_with_pagination(1, 14, items, 'test')
|
||||
|
||||
keyboard = create_keyboard_with_pagination(1, 14, items, "test")
|
||||
|
||||
# На первой странице должно быть 9 пользователей (3 ряда по 3) + кнопка "Следующая" + "Назад"
|
||||
assert len(keyboard.inline_keyboard) == 5 # 3 ряда пользователей + навигация + назад
|
||||
assert (
|
||||
len(keyboard.inline_keyboard) == 5
|
||||
) # 3 ряда пользователей + навигация + назад
|
||||
assert len(keyboard.inline_keyboard[0]) == 3 # первый ряд: 3 пользователя
|
||||
assert len(keyboard.inline_keyboard[1]) == 3 # второй ряд: 3 пользователя
|
||||
assert len(keyboard.inline_keyboard[2]) == 3 # третий ряд: 3 пользователя
|
||||
assert keyboard.inline_keyboard[3][0].text == "➡️ Следующая" # кнопка навигации
|
||||
assert keyboard.inline_keyboard[4][0].text == "🏠 Назад" # кнопка назад
|
||||
|
||||
|
||||
def test_pagination_second_page(self):
|
||||
"""Тест второй страницы"""
|
||||
items = [("User" + str(i), i) for i in range(1, 15)] # 14 пользователей
|
||||
keyboard = create_keyboard_with_pagination(2, 14, items, 'test')
|
||||
|
||||
keyboard = create_keyboard_with_pagination(2, 14, items, "test")
|
||||
|
||||
# На второй странице должно быть 5 пользователей (2 ряда: 3+2) + кнопки "Предыдущая" и "Назад"
|
||||
assert len(keyboard.inline_keyboard) == 4 # 2 ряда пользователей + навигация + назад
|
||||
assert (
|
||||
len(keyboard.inline_keyboard) == 4
|
||||
) # 2 ряда пользователей + навигация + назад
|
||||
assert len(keyboard.inline_keyboard[0]) == 3 # первый ряд: 3 пользователя
|
||||
assert len(keyboard.inline_keyboard[1]) == 2 # второй ряд: 2 пользователя
|
||||
assert keyboard.inline_keyboard[2][0].text == "⬅️ Предыдущая"
|
||||
assert keyboard.inline_keyboard[3][0].text == "🏠 Назад"
|
||||
|
||||
|
||||
def test_pagination_middle_page(self):
|
||||
"""Тест средней страницы"""
|
||||
items = [("User" + str(i), i) for i in range(1, 25)] # 24 пользователя
|
||||
keyboard = create_keyboard_with_pagination(2, 24, items, 'test')
|
||||
|
||||
keyboard = create_keyboard_with_pagination(2, 24, items, "test")
|
||||
|
||||
# На второй странице должно быть 9 пользователей (3 ряда по 3) + кнопки "Предыдущая" и "Следующая"
|
||||
assert len(keyboard.inline_keyboard) == 5 # 3 ряда пользователей + навигация + назад
|
||||
assert (
|
||||
len(keyboard.inline_keyboard) == 5
|
||||
) # 3 ряда пользователей + навигация + назад
|
||||
assert len(keyboard.inline_keyboard[0]) == 3 # первый ряд: 3 пользователя
|
||||
assert len(keyboard.inline_keyboard[1]) == 3 # второй ряд: 3 пользователя
|
||||
assert len(keyboard.inline_keyboard[2]) == 3 # третий ряд: 3 пользователя
|
||||
assert keyboard.inline_keyboard[3][0].text == "⬅️ Предыдущая"
|
||||
assert keyboard.inline_keyboard[3][1].text == "➡️ Следующая"
|
||||
|
||||
|
||||
def test_pagination_invalid_page_number(self):
|
||||
"""Тест с некорректным номером страницы"""
|
||||
items = [("User" + str(i), i) for i in range(1, 10)] # 9 пользователей
|
||||
keyboard = create_keyboard_with_pagination(0, 9, items, 'test') # страница 0
|
||||
|
||||
keyboard = create_keyboard_with_pagination(0, 9, items, "test") # страница 0
|
||||
|
||||
# Должна вернуться первая страница
|
||||
assert len(keyboard.inline_keyboard) == 4 # 3 ряда пользователей + назад
|
||||
assert len(keyboard.inline_keyboard[0]) == 3 # первый ряд: 3 пользователя
|
||||
assert len(keyboard.inline_keyboard[1]) == 3 # второй ряд: 3 пользователя
|
||||
assert len(keyboard.inline_keyboard[2]) == 3 # третий ряд: 3 пользователя
|
||||
|
||||
|
||||
def test_pagination_page_out_of_range(self):
|
||||
"""Тест с номером страницы больше максимального"""
|
||||
items = [("User" + str(i), i) for i in range(1, 10)] # 9 пользователей
|
||||
keyboard = create_keyboard_with_pagination(5, 9, items, 'test') # страница 5 при 1 странице
|
||||
|
||||
keyboard = create_keyboard_with_pagination(
|
||||
5, 9, items, "test"
|
||||
) # страница 5 при 1 странице
|
||||
|
||||
# Должна вернуться первая страница
|
||||
assert len(keyboard.inline_keyboard) == 4 # 3 ряда пользователей + назад
|
||||
assert len(keyboard.inline_keyboard[0]) == 3 # первый ряд: 3 пользователя
|
||||
assert len(keyboard.inline_keyboard[1]) == 3 # второй ряд: 3 пользователя
|
||||
assert len(keyboard.inline_keyboard[2]) == 3 # третий ряд: 3 пользователя
|
||||
|
||||
|
||||
def test_pagination_callback_data_format(self):
|
||||
"""Тест формата callback_data"""
|
||||
items = [("User1", 123), ("User2", 456)]
|
||||
keyboard = create_keyboard_with_pagination(1, 2, items, 'ban')
|
||||
|
||||
keyboard = create_keyboard_with_pagination(1, 2, items, "ban")
|
||||
|
||||
# Проверяем формат callback_data для пользователей
|
||||
assert keyboard.inline_keyboard[0][0].callback_data == "ban_123"
|
||||
assert keyboard.inline_keyboard[0][1].callback_data == "ban_456"
|
||||
|
||||
|
||||
# Проверяем формат callback_data для кнопки "Назад"
|
||||
assert keyboard.inline_keyboard[1][0].callback_data == "return"
|
||||
|
||||
|
||||
def test_pagination_navigation_callback_data(self):
|
||||
"""Тест callback_data для кнопок навигации"""
|
||||
items = [("User" + str(i), i) for i in range(1, 15)] # 14 пользователей
|
||||
keyboard = create_keyboard_with_pagination(2, 14, items, 'test')
|
||||
|
||||
keyboard = create_keyboard_with_pagination(2, 14, items, "test")
|
||||
|
||||
# Проверяем callback_data для кнопки "Предыдущая"
|
||||
assert keyboard.inline_keyboard[2][0].callback_data == "page_1"
|
||||
|
||||
|
||||
# Проверяем callback_data для кнопки "Назад"
|
||||
assert keyboard.inline_keyboard[3][0].callback_data == "return"
|
||||
|
||||
|
||||
def test_pagination_exactly_items_per_page(self):
|
||||
"""Тест когда количество элементов точно равно items_per_page"""
|
||||
items = [("User" + str(i), i) for i in range(1, 10)] # ровно 9 пользователей
|
||||
keyboard = create_keyboard_with_pagination(1, 9, items, 'test')
|
||||
|
||||
keyboard = create_keyboard_with_pagination(1, 9, items, "test")
|
||||
|
||||
# Должна быть только одна страница без кнопок навигации
|
||||
assert len(keyboard.inline_keyboard) == 4 # 3 ряда пользователей + назад
|
||||
assert len(keyboard.inline_keyboard[0]) == 3 # первый ряд: 3 пользователя
|
||||
@@ -473,5 +487,5 @@ class TestPagination:
|
||||
assert keyboard.inline_keyboard[3][0].text == "🏠 Назад"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__, '-v'])
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
|
||||
@@ -3,23 +3,24 @@ from datetime import datetime
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from database.models import UserMessage
|
||||
from database.repositories.message_repository import MessageRepository
|
||||
|
||||
|
||||
class TestMessageRepository:
|
||||
"""Тесты для MessageRepository."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db_path(self):
|
||||
"""Фикстура для пути к тестовой БД."""
|
||||
return ":memory:"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def message_repository(self, mock_db_path):
|
||||
"""Фикстура для MessageRepository."""
|
||||
return MessageRepository(mock_db_path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_message(self):
|
||||
"""Фикстура для тестового сообщения."""
|
||||
@@ -27,9 +28,9 @@ class TestMessageRepository:
|
||||
message_text="Тестовое сообщение",
|
||||
user_id=12345,
|
||||
telegram_message_id=67890,
|
||||
date=int(datetime.now().timestamp())
|
||||
date=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_message_no_date(self):
|
||||
"""Фикстура для тестового сообщения без даты."""
|
||||
@@ -37,134 +38,145 @@ class TestMessageRepository:
|
||||
message_text="Тестовое сообщение без даты",
|
||||
user_id=12345,
|
||||
telegram_message_id=67891,
|
||||
date=None
|
||||
date=None,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_tables(self, message_repository):
|
||||
"""Тест создания таблиц."""
|
||||
# Мокаем _execute_query
|
||||
message_repository._execute_query = AsyncMock()
|
||||
|
||||
|
||||
await message_repository.create_tables()
|
||||
|
||||
|
||||
message_repository._execute_query.assert_called_once()
|
||||
call_args = message_repository._execute_query.call_args[0][0]
|
||||
assert "CREATE TABLE IF NOT EXISTS user_messages" in call_args
|
||||
assert "telegram_message_id INTEGER NOT NULL" in call_args
|
||||
assert "date INTEGER NOT NULL" in call_args
|
||||
assert "FOREIGN KEY (user_id) REFERENCES our_users (user_id) ON DELETE CASCADE" in call_args
|
||||
|
||||
assert (
|
||||
"FOREIGN KEY (user_id) REFERENCES our_users (user_id) ON DELETE CASCADE"
|
||||
in call_args
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_message_with_date(self, message_repository, sample_message):
|
||||
"""Тест добавления сообщения с датой."""
|
||||
# Мокаем _execute_query
|
||||
message_repository._execute_query = AsyncMock()
|
||||
|
||||
|
||||
await message_repository.add_message(sample_message)
|
||||
|
||||
|
||||
message_repository._execute_query.assert_called_once()
|
||||
call_args = message_repository._execute_query.call_args
|
||||
query = call_args[0][0]
|
||||
params = call_args[0][1]
|
||||
|
||||
|
||||
assert "INSERT INTO user_messages" in query
|
||||
assert "VALUES (?, ?, ?, ?)" in query
|
||||
assert params == (
|
||||
sample_message.message_text,
|
||||
sample_message.user_id,
|
||||
sample_message.telegram_message_id,
|
||||
sample_message.date
|
||||
sample_message.date,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_message_without_date(self, message_repository, sample_message_no_date):
|
||||
async def test_add_message_without_date(
|
||||
self, message_repository, sample_message_no_date
|
||||
):
|
||||
"""Тест добавления сообщения без даты (должна генерироваться автоматически)."""
|
||||
# Мокаем _execute_query
|
||||
message_repository._execute_query = AsyncMock()
|
||||
|
||||
|
||||
await message_repository.add_message(sample_message_no_date)
|
||||
|
||||
|
||||
# Проверяем, что дата была установлена
|
||||
assert sample_message_no_date.date is not None
|
||||
assert isinstance(sample_message_no_date.date, int)
|
||||
assert sample_message_no_date.date > 0
|
||||
|
||||
|
||||
message_repository._execute_query.assert_called_once()
|
||||
call_args = message_repository._execute_query.call_args
|
||||
params = call_args[0][1]
|
||||
|
||||
|
||||
assert params[3] == sample_message_no_date.date # date field
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_message_logs_correctly(self, message_repository, sample_message):
|
||||
"""Тест логирования при добавлении сообщения."""
|
||||
# Мокаем _execute_query и logger
|
||||
message_repository._execute_query = AsyncMock()
|
||||
message_repository.logger = MagicMock()
|
||||
|
||||
|
||||
await message_repository.add_message(sample_message)
|
||||
|
||||
|
||||
message_repository.logger.info.assert_called_once()
|
||||
log_message = message_repository.logger.info.call_args[0][0]
|
||||
assert f"telegram_message_id={sample_message.telegram_message_id}" in log_message
|
||||
|
||||
assert (
|
||||
f"telegram_message_id={sample_message.telegram_message_id}" in log_message
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_by_message_id_found(self, message_repository):
|
||||
"""Тест получения пользователя по message_id (пользователь найден)."""
|
||||
message_id = 67890
|
||||
expected_user_id = 12345
|
||||
|
||||
|
||||
# Мокаем _execute_query_with_result
|
||||
message_repository._execute_query_with_result = AsyncMock(
|
||||
return_value=[[expected_user_id]]
|
||||
)
|
||||
|
||||
|
||||
result = await message_repository.get_user_by_message_id(message_id)
|
||||
|
||||
|
||||
assert result == expected_user_id
|
||||
message_repository._execute_query_with_result.assert_called_once_with(
|
||||
"SELECT user_id FROM user_messages WHERE telegram_message_id = ?",
|
||||
(message_id,)
|
||||
(message_id,),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_by_message_id_not_found(self, message_repository):
|
||||
"""Тест получения пользователя по message_id (пользователь не найден)."""
|
||||
message_id = 99999
|
||||
|
||||
|
||||
# Мокаем _execute_query_with_result
|
||||
message_repository._execute_query_with_result = AsyncMock(return_value=[])
|
||||
|
||||
|
||||
result = await message_repository.get_user_by_message_id(message_id)
|
||||
|
||||
|
||||
assert result is None
|
||||
message_repository._execute_query_with_result.assert_called_once_with(
|
||||
"SELECT user_id FROM user_messages WHERE telegram_message_id = ?",
|
||||
(message_id,)
|
||||
(message_id,),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_by_message_id_empty_result(self, message_repository):
|
||||
"""Тест получения пользователя по message_id (пустой результат)."""
|
||||
message_id = 99999
|
||||
|
||||
|
||||
# Мокаем _execute_query_with_result
|
||||
message_repository._execute_query_with_result = AsyncMock(return_value=[[]])
|
||||
|
||||
|
||||
result = await message_repository.get_user_by_message_id(message_id)
|
||||
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_message_handles_exception(self, message_repository, sample_message):
|
||||
async def test_add_message_handles_exception(
|
||||
self, message_repository, sample_message
|
||||
):
|
||||
"""Тест обработки исключений при добавлении сообщения."""
|
||||
# Мокаем _execute_query для вызова исключения
|
||||
message_repository._execute_query = AsyncMock(side_effect=Exception("Database error"))
|
||||
|
||||
message_repository._execute_query = AsyncMock(
|
||||
side_effect=Exception("Database error")
|
||||
)
|
||||
|
||||
with pytest.raises(Exception, match="Database error"):
|
||||
await message_repository.add_message(sample_message)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_by_message_id_handles_exception(self, message_repository):
|
||||
"""Тест обработки исключений при получении пользователя."""
|
||||
@@ -172,10 +184,10 @@ class TestMessageRepository:
|
||||
message_repository._execute_query_with_result = AsyncMock(
|
||||
side_effect=Exception("Database error")
|
||||
)
|
||||
|
||||
|
||||
with pytest.raises(Exception, match="Database error"):
|
||||
await message_repository.get_user_by_message_id(12345)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_message_with_zero_date(self, message_repository):
|
||||
"""Тест добавления сообщения с датой равной 0 (должна генерироваться новая)."""
|
||||
@@ -183,19 +195,19 @@ class TestMessageRepository:
|
||||
message_text="Тестовое сообщение с нулевой датой",
|
||||
user_id=12345,
|
||||
telegram_message_id=67892,
|
||||
date=0
|
||||
date=0,
|
||||
)
|
||||
|
||||
|
||||
# Мокаем _execute_query
|
||||
message_repository._execute_query = AsyncMock()
|
||||
|
||||
|
||||
await message_repository.add_message(message)
|
||||
|
||||
|
||||
# Проверяем, что дата была изменена с 0 (теперь это происходит только если date is None)
|
||||
# В текущей реализации дата 0 считается валидной и не изменяется
|
||||
assert isinstance(message.date, int)
|
||||
assert message.date >= 0
|
||||
|
||||
|
||||
message_repository._execute_query.assert_called_once()
|
||||
params = message_repository._execute_query.call_args[0][1]
|
||||
assert params[3] == message.date # date field
|
||||
|
||||
@@ -4,17 +4,18 @@ import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from database.models import UserMessage
|
||||
from database.repositories.message_repository import MessageRepository
|
||||
|
||||
|
||||
class TestMessageRepositoryIntegration:
|
||||
"""Интеграционные тесты для MessageRepository с реальной БД."""
|
||||
|
||||
|
||||
async def _setup_test_database(self, message_repository):
|
||||
"""Вспомогательная функция для настройки тестовой БД."""
|
||||
# Сначала создаем таблицу our_users для тестов
|
||||
await message_repository._execute_query('''
|
||||
await message_repository._execute_query("""
|
||||
CREATE TABLE IF NOT EXISTS our_users (
|
||||
user_id INTEGER NOT NULL PRIMARY KEY,
|
||||
first_name TEXT,
|
||||
@@ -28,36 +29,42 @@ class TestMessageRepositoryIntegration:
|
||||
date_changed INTEGER NOT NULL,
|
||||
voice_bot_welcome_received BOOLEAN DEFAULT 0
|
||||
)
|
||||
''')
|
||||
|
||||
""")
|
||||
|
||||
# Добавляем тестового пользователя
|
||||
await message_repository._execute_query(
|
||||
"INSERT OR REPLACE INTO our_users (user_id, first_name, full_name, date_added, date_changed) VALUES (?, ?, ?, ?, ?)",
|
||||
(12345, "Test", "Test User", int(datetime.now().timestamp()), int(datetime.now().timestamp()))
|
||||
(
|
||||
12345,
|
||||
"Test",
|
||||
"Test User",
|
||||
int(datetime.now().timestamp()),
|
||||
int(datetime.now().timestamp()),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
# Теперь создаем таблицу user_messages
|
||||
await message_repository.create_tables()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_db_path(self):
|
||||
"""Фикстура для временного пути к БД."""
|
||||
with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
|
||||
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
|
||||
temp_path = f.name
|
||||
|
||||
|
||||
yield temp_path
|
||||
|
||||
|
||||
# Очистка после тестов
|
||||
try:
|
||||
os.unlink(temp_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def message_repository(self, temp_db_path):
|
||||
"""Фикстура для MessageRepository с реальной БД."""
|
||||
return MessageRepository(temp_db_path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_message(self):
|
||||
"""Фикстура для тестового сообщения."""
|
||||
@@ -65,9 +72,9 @@ class TestMessageRepositoryIntegration:
|
||||
message_text="Интеграционное тестовое сообщение",
|
||||
user_id=12345,
|
||||
telegram_message_id=67890,
|
||||
date=int(datetime.now().timestamp())
|
||||
date=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_message_no_date(self):
|
||||
"""Фикстура для тестового сообщения без даты."""
|
||||
@@ -75,137 +82,155 @@ class TestMessageRepositoryIntegration:
|
||||
message_text="Интеграционное тестовое сообщение без даты",
|
||||
user_id=12345,
|
||||
telegram_message_id=67891,
|
||||
date=None
|
||||
date=None,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_tables_integration(self, message_repository):
|
||||
"""Интеграционный тест создания таблиц."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(message_repository)
|
||||
|
||||
|
||||
# Проверяем, что таблица создана, пытаясь добавить сообщение
|
||||
message = UserMessage(
|
||||
message_text="Тест создания таблиц",
|
||||
user_id=12345,
|
||||
telegram_message_id=67890,
|
||||
date=int(datetime.now().timestamp())
|
||||
date=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
# Не должно вызывать ошибку
|
||||
await message_repository.add_message(message)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_and_retrieve_message_integration(self, message_repository, sample_message):
|
||||
async def test_add_and_retrieve_message_integration(
|
||||
self, message_repository, sample_message
|
||||
):
|
||||
"""Интеграционный тест добавления и получения сообщения."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(message_repository)
|
||||
|
||||
|
||||
# Добавляем сообщение
|
||||
await message_repository.add_message(sample_message)
|
||||
|
||||
|
||||
# Получаем пользователя по message_id
|
||||
user_id = await message_repository.get_user_by_message_id(sample_message.telegram_message_id)
|
||||
|
||||
user_id = await message_repository.get_user_by_message_id(
|
||||
sample_message.telegram_message_id
|
||||
)
|
||||
|
||||
# Проверяем результат
|
||||
assert user_id == sample_message.user_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_message_without_date_integration(self, message_repository, sample_message_no_date):
|
||||
async def test_add_message_without_date_integration(
|
||||
self, message_repository, sample_message_no_date
|
||||
):
|
||||
"""Интеграционный тест добавления сообщения без даты."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(message_repository)
|
||||
|
||||
|
||||
# Добавляем сообщение без даты
|
||||
await message_repository.add_message(sample_message_no_date)
|
||||
|
||||
|
||||
# Проверяем, что дата была установлена
|
||||
assert sample_message_no_date.date is not None
|
||||
assert isinstance(sample_message_no_date.date, int)
|
||||
assert sample_message_no_date.date > 0
|
||||
|
||||
|
||||
# Проверяем, что сообщение можно найти
|
||||
user_id = await message_repository.get_user_by_message_id(sample_message_no_date.telegram_message_id)
|
||||
user_id = await message_repository.get_user_by_message_id(
|
||||
sample_message_no_date.telegram_message_id
|
||||
)
|
||||
assert user_id == sample_message_no_date.user_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_by_message_id_not_found_integration(self, message_repository):
|
||||
async def test_get_user_by_message_id_not_found_integration(
|
||||
self, message_repository
|
||||
):
|
||||
"""Интеграционный тест поиска несуществующего сообщения."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(message_repository)
|
||||
|
||||
|
||||
# Ищем несуществующее сообщение
|
||||
user_id = await message_repository.get_user_by_message_id(99999)
|
||||
|
||||
|
||||
# Должно вернуть None
|
||||
assert user_id is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiple_messages_integration(self, message_repository):
|
||||
"""Интеграционный тест работы с несколькими сообщениями."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(message_repository)
|
||||
|
||||
|
||||
# Добавляем несколько сообщений (используем существующий user_id 12345)
|
||||
messages = [
|
||||
UserMessage(
|
||||
message_text=f"Сообщение {i}",
|
||||
user_id=12345, # Используем существующий user_id
|
||||
telegram_message_id=2000 + i,
|
||||
date=int(datetime.now().timestamp()) + i
|
||||
date=int(datetime.now().timestamp()) + i,
|
||||
)
|
||||
for i in range(1, 4)
|
||||
]
|
||||
|
||||
|
||||
for message in messages:
|
||||
await message_repository.add_message(message)
|
||||
|
||||
|
||||
# Проверяем, что все сообщения можно найти
|
||||
for message in messages:
|
||||
user_id = await message_repository.get_user_by_message_id(message.telegram_message_id)
|
||||
user_id = await message_repository.get_user_by_message_id(
|
||||
message.telegram_message_id
|
||||
)
|
||||
assert user_id == message.user_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_message_with_special_characters_integration(self, message_repository):
|
||||
async def test_message_with_special_characters_integration(
|
||||
self, message_repository
|
||||
):
|
||||
"""Интеграционный тест сообщения со специальными символами."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(message_repository)
|
||||
|
||||
|
||||
# Сообщение со специальными символами
|
||||
special_message = UserMessage(
|
||||
message_text="Сообщение с 'кавычками' и \"двойными кавычками\" и эмодзи 😊",
|
||||
user_id=12345,
|
||||
telegram_message_id=67892,
|
||||
date=int(datetime.now().timestamp())
|
||||
date=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
# Добавляем сообщение
|
||||
await message_repository.add_message(special_message)
|
||||
|
||||
|
||||
# Проверяем, что можно найти
|
||||
user_id = await message_repository.get_user_by_message_id(special_message.telegram_message_id)
|
||||
user_id = await message_repository.get_user_by_message_id(
|
||||
special_message.telegram_message_id
|
||||
)
|
||||
assert user_id == special_message.user_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_foreign_key_constraint_integration(self, message_repository):
|
||||
"""Интеграционный тест ограничения внешнего ключа."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(message_repository)
|
||||
|
||||
|
||||
# Пытаемся добавить сообщение с несуществующим user_id
|
||||
invalid_message = UserMessage(
|
||||
message_text="Сообщение с несуществующим пользователем",
|
||||
user_id=99999, # Несуществующий пользователь
|
||||
telegram_message_id=67893,
|
||||
date=int(datetime.now().timestamp())
|
||||
date=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
# В SQLite с включенными внешними ключами это должно вызвать ошибку
|
||||
# Теперь у нас есть таблица our_users, поэтому внешний ключ должен работать
|
||||
try:
|
||||
await message_repository.add_message(invalid_message)
|
||||
# Если не вызвало ошибку, проверяем что сообщение не добавилось
|
||||
user_id = await message_repository.get_user_by_message_id(invalid_message.telegram_message_id)
|
||||
user_id = await message_repository.get_user_by_message_id(
|
||||
invalid_message.telegram_message_id
|
||||
)
|
||||
assert user_id is None
|
||||
except Exception:
|
||||
# Ожидаемое поведение при нарушении внешнего ключа
|
||||
|
||||
@@ -3,23 +3,24 @@ from datetime import datetime
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from database.models import MessageContentLink, PostContent, TelegramPost
|
||||
from database.repositories.post_repository import PostRepository
|
||||
|
||||
|
||||
class TestPostRepository:
|
||||
"""Тесты для PostRepository."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db_path(self):
|
||||
"""Фикстура для пути к тестовой БД."""
|
||||
return ":memory:"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def post_repository(self, mock_db_path):
|
||||
"""Фикстура для PostRepository."""
|
||||
return PostRepository(mock_db_path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_post(self):
|
||||
"""Фикстура для тестового поста."""
|
||||
@@ -28,9 +29,9 @@ class TestPostRepository:
|
||||
text="Тестовый пост",
|
||||
author_id=67890,
|
||||
helper_text_message_id=None,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_post_no_date(self):
|
||||
"""Фикстура для тестового поста без даты."""
|
||||
@@ -42,73 +43,85 @@ class TestPostRepository:
|
||||
created_at=None,
|
||||
status="suggest",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_post_content(self):
|
||||
"""Фикстура для тестового контента поста."""
|
||||
return PostContent(
|
||||
message_id=12345,
|
||||
content_name="/path/to/file.jpg",
|
||||
content_type="photo"
|
||||
message_id=12345, content_name="/path/to/file.jpg", content_type="photo"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_message_link(self):
|
||||
"""Фикстура для тестовой связи сообщения с контентом."""
|
||||
return MessageContentLink(
|
||||
post_id=12345,
|
||||
message_id=67890
|
||||
)
|
||||
|
||||
return MessageContentLink(post_id=12345, message_id=67890)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_tables(self, post_repository):
|
||||
"""Тест создания таблиц."""
|
||||
# Мокаем _execute_query и _execute_query_with_result
|
||||
post_repository._execute_query = AsyncMock()
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=[]) # Для проверки столбца
|
||||
|
||||
post_repository._execute_query_with_result = AsyncMock(
|
||||
return_value=[]
|
||||
) # Для проверки столбца
|
||||
|
||||
await post_repository.create_tables()
|
||||
|
||||
|
||||
# Проверяем, что create_tables вызвался минимум 3 раза (для каждой таблицы)
|
||||
# Может быть больше из-за ALTER TABLE и индексов
|
||||
assert post_repository._execute_query.call_count >= 3
|
||||
|
||||
|
||||
# Проверяем, что все нужные таблицы созданы (порядок может быть разным из-за ALTER TABLE)
|
||||
calls = post_repository._execute_query.call_args_list
|
||||
all_queries = [call[0][0] for call in calls]
|
||||
|
||||
|
||||
# Проверяем создание таблицы постов
|
||||
post_table_queries = [q for q in all_queries if "CREATE TABLE IF NOT EXISTS post_from_telegram_suggest" in q]
|
||||
post_table_queries = [
|
||||
q
|
||||
for q in all_queries
|
||||
if "CREATE TABLE IF NOT EXISTS post_from_telegram_suggest" in q
|
||||
]
|
||||
assert len(post_table_queries) > 0
|
||||
assert "message_id INTEGER NOT NULL PRIMARY KEY" in post_table_queries[0]
|
||||
assert "created_at INTEGER NOT NULL" in post_table_queries[0]
|
||||
assert "status TEXT NOT NULL DEFAULT 'suggest'" in post_table_queries[0]
|
||||
assert "is_anonymous INTEGER" in post_table_queries[0]
|
||||
assert "FOREIGN KEY (author_id) REFERENCES our_users (user_id) ON DELETE CASCADE" in post_table_queries[0]
|
||||
|
||||
assert (
|
||||
"FOREIGN KEY (author_id) REFERENCES our_users (user_id) ON DELETE CASCADE"
|
||||
in post_table_queries[0]
|
||||
)
|
||||
|
||||
# Проверяем создание таблицы контента
|
||||
content_table_queries = [q for q in all_queries if "CREATE TABLE IF NOT EXISTS content_post_from_telegram" in q]
|
||||
content_table_queries = [
|
||||
q
|
||||
for q in all_queries
|
||||
if "CREATE TABLE IF NOT EXISTS content_post_from_telegram" in q
|
||||
]
|
||||
assert len(content_table_queries) > 0
|
||||
assert "PRIMARY KEY (message_id, content_name)" in content_table_queries[0]
|
||||
|
||||
|
||||
# Проверяем создание таблицы связей
|
||||
link_table_queries = [q for q in all_queries if "CREATE TABLE IF NOT EXISTS message_link_to_content" in q]
|
||||
link_table_queries = [
|
||||
q
|
||||
for q in all_queries
|
||||
if "CREATE TABLE IF NOT EXISTS message_link_to_content" in q
|
||||
]
|
||||
assert len(link_table_queries) > 0
|
||||
assert "PRIMARY KEY (post_id, message_id)" in link_table_queries[0]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_post_with_date(self, post_repository, sample_post):
|
||||
"""Тест добавления поста с датой."""
|
||||
# Мокаем _execute_query
|
||||
post_repository._execute_query = AsyncMock()
|
||||
|
||||
|
||||
await post_repository.add_post(sample_post)
|
||||
|
||||
|
||||
post_repository._execute_query.assert_called_once()
|
||||
call_args = post_repository._execute_query.call_args
|
||||
query = call_args[0][0]
|
||||
params = call_args[0][1]
|
||||
|
||||
|
||||
assert "INSERT OR IGNORE INTO post_from_telegram_suggest" in query
|
||||
assert "status" in query
|
||||
assert "is_anonymous" in query
|
||||
@@ -120,63 +133,70 @@ class TestPostRepository:
|
||||
assert params[3] == sample_post.created_at
|
||||
assert params[4] == sample_post.status
|
||||
# is_anonymous преобразуется в int (None -> None, True -> 1, False -> 0)
|
||||
expected_is_anonymous = None if sample_post.is_anonymous is None else (1 if sample_post.is_anonymous else 0)
|
||||
expected_is_anonymous = (
|
||||
None
|
||||
if sample_post.is_anonymous is None
|
||||
else (1 if sample_post.is_anonymous else 0)
|
||||
)
|
||||
assert params[5] == expected_is_anonymous
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_post_without_date(self, post_repository, sample_post_no_date):
|
||||
"""Тест добавления поста без даты (должна генерироваться автоматически)."""
|
||||
# Мокаем _execute_query
|
||||
post_repository._execute_query = AsyncMock()
|
||||
|
||||
|
||||
await post_repository.add_post(sample_post_no_date)
|
||||
|
||||
|
||||
# Проверяем, что дата была установлена
|
||||
assert sample_post_no_date.created_at is not None
|
||||
assert isinstance(sample_post_no_date.created_at, int)
|
||||
assert sample_post_no_date.created_at > 0
|
||||
|
||||
|
||||
post_repository._execute_query.assert_called_once()
|
||||
call_args = post_repository._execute_query.call_args
|
||||
params = call_args[0][1]
|
||||
|
||||
|
||||
assert params[3] == sample_post_no_date.created_at # created_at
|
||||
assert params[4] == sample_post_no_date.status # status (default suggest)
|
||||
# Проверяем is_anonymous (должен быть в параметрах)
|
||||
assert len(params) == 6 # Всего 6 параметров включая is_anonymous
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_post_logs_correctly(self, post_repository, sample_post):
|
||||
"""Тест логирования при добавлении поста."""
|
||||
# Мокаем _execute_query и logger
|
||||
post_repository._execute_query = AsyncMock()
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
await post_repository.add_post(sample_post)
|
||||
|
||||
|
||||
# Проверяем, что логирование вызвано с новым форматом сообщения
|
||||
post_repository.logger.info.assert_called_once()
|
||||
log_call = post_repository.logger.info.call_args[0][0]
|
||||
assert f"message_id={sample_post.message_id}" in log_call
|
||||
assert "Пост добавлен" in log_call or "уже существует" in log_call
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_helper_message(self, post_repository):
|
||||
"""Тест обновления helper сообщения."""
|
||||
# Мокаем _execute_query
|
||||
post_repository._execute_query = AsyncMock()
|
||||
|
||||
|
||||
message_id = 12345
|
||||
helper_message_id = 67890
|
||||
|
||||
|
||||
await post_repository.update_helper_message(message_id, helper_message_id)
|
||||
|
||||
|
||||
post_repository._execute_query.assert_called_once()
|
||||
call_args = post_repository._execute_query.call_args
|
||||
query = call_args[0][0]
|
||||
params = call_args[0][1]
|
||||
|
||||
assert "UPDATE post_from_telegram_suggest SET helper_text_message_id = ? WHERE message_id = ?" in query
|
||||
|
||||
assert (
|
||||
"UPDATE post_from_telegram_suggest SET helper_text_message_id = ? WHERE message_id = ?"
|
||||
in query
|
||||
)
|
||||
assert params == (helper_message_id, message_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -192,7 +212,7 @@ class TestPostRepository:
|
||||
mock_conn.execute = AsyncMock(return_value=mock_cur)
|
||||
post_repository._get_connection.return_value = mock_conn
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
# Создаем таблицы
|
||||
await post_repository.create_tables()
|
||||
post_repository._execute_query.reset_mock()
|
||||
@@ -216,7 +236,10 @@ class TestPostRepository:
|
||||
# Проверяем, что после создания таблиц было вызвано логирование обновления статуса
|
||||
post_repository.logger.info.assert_called()
|
||||
log_calls = [str(call) for call in post_repository.logger.info.call_args_list]
|
||||
assert any("Статус поста message_id=12345 обновлён на approved" in str(call) for call in post_repository.logger.info.call_args_list)
|
||||
assert any(
|
||||
"Статус поста message_id=12345 обновлён на approved" in str(call)
|
||||
for call in post_repository.logger.info.call_args_list
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_status_for_media_group_by_helper_id(self, post_repository):
|
||||
@@ -231,7 +254,7 @@ class TestPostRepository:
|
||||
mock_conn.execute = AsyncMock(return_value=mock_cur)
|
||||
post_repository._get_connection.return_value = mock_conn
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
# Создаем таблицы
|
||||
await post_repository.create_tables()
|
||||
post_repository._execute_query.reset_mock()
|
||||
@@ -257,7 +280,11 @@ class TestPostRepository:
|
||||
assert params == (status, helper_message_id, helper_message_id)
|
||||
# Проверяем, что после создания таблиц было вызвано логирование обновления статуса
|
||||
post_repository.logger.info.assert_called()
|
||||
assert any("Статус медиагруппы helper_message_id=99999 обновлён на declined" in str(call) for call in post_repository.logger.info.call_args_list)
|
||||
assert any(
|
||||
"Статус медиагруппы helper_message_id=99999 обновлён на declined"
|
||||
in str(call)
|
||||
for call in post_repository.logger.info.call_args_list
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_post_content_success(self, post_repository):
|
||||
@@ -265,61 +292,67 @@ class TestPostRepository:
|
||||
# Мокаем _execute_query
|
||||
post_repository._execute_query = AsyncMock()
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
post_id = 12345
|
||||
message_id = 67890
|
||||
content_name = "/path/to/file.jpg"
|
||||
content_type = "photo"
|
||||
|
||||
result = await post_repository.add_post_content(post_id, message_id, content_name, content_type)
|
||||
|
||||
|
||||
result = await post_repository.add_post_content(
|
||||
post_id, message_id, content_name, content_type
|
||||
)
|
||||
|
||||
# Проверяем, что результат True
|
||||
assert result is True
|
||||
|
||||
|
||||
# Проверяем, что _execute_query вызвался 2 раза (для связи и контента)
|
||||
assert post_repository._execute_query.call_count == 2
|
||||
|
||||
|
||||
# Проверяем вызов для связи
|
||||
link_call = post_repository._execute_query.call_args_list[0]
|
||||
link_query = link_call[0][0]
|
||||
link_params = link_call[0][1]
|
||||
assert "INSERT OR IGNORE INTO message_link_to_content" in link_query
|
||||
assert link_params == (post_id, message_id)
|
||||
|
||||
|
||||
# Проверяем вызов для контента
|
||||
content_call = post_repository._execute_query.call_args_list[1]
|
||||
content_query = content_call[0][0]
|
||||
content_params = content_call[0][1]
|
||||
assert "INSERT OR IGNORE INTO content_post_from_telegram" in content_query
|
||||
assert content_params == (message_id, content_name, content_type)
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
post_repository.logger.info.assert_called_once_with(
|
||||
f"Контент поста добавлен: post_id={post_id}, message_id={message_id}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_post_content_exception(self, post_repository):
|
||||
"""Тест обработки исключения при добавлении контента поста."""
|
||||
# Мокаем _execute_query чтобы вызвать исключение
|
||||
post_repository._execute_query = AsyncMock(side_effect=Exception("Database error"))
|
||||
post_repository._execute_query = AsyncMock(
|
||||
side_effect=Exception("Database error")
|
||||
)
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
post_id = 12345
|
||||
message_id = 67890
|
||||
content_name = "/path/to/file.jpg"
|
||||
content_type = "photo"
|
||||
|
||||
result = await post_repository.add_post_content(post_id, message_id, content_name, content_type)
|
||||
|
||||
|
||||
result = await post_repository.add_post_content(
|
||||
post_id, message_id, content_name, content_type
|
||||
)
|
||||
|
||||
# Проверяем, что результат False
|
||||
assert result is False
|
||||
|
||||
|
||||
# Проверяем логирование ошибки
|
||||
post_repository.logger.error.assert_called_once()
|
||||
error_call = post_repository.logger.error.call_args[0][0]
|
||||
assert "Ошибка при добавлении контента поста:" in error_call
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_post_content_by_helper_id(self, post_repository):
|
||||
"""Тест получения контента поста по helper ID."""
|
||||
@@ -327,33 +360,33 @@ class TestPostRepository:
|
||||
mock_result = [
|
||||
("/path/to/photo1.jpg", "photo"),
|
||||
("/path/to/video1.mp4", "video"),
|
||||
("/path/to/photo2.jpg", "photo")
|
||||
("/path/to/photo2.jpg", "photo"),
|
||||
]
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=mock_result)
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
helper_message_id = 67890
|
||||
|
||||
|
||||
result = await post_repository.get_post_content_by_helper_id(helper_message_id)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result == mock_result
|
||||
|
||||
|
||||
# Проверяем вызов _execute_query_with_result
|
||||
post_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = post_repository._execute_query_with_result.call_args
|
||||
query = call_args[0][0]
|
||||
params = call_args[0][1]
|
||||
|
||||
|
||||
assert "SELECT cpft.content_name, cpft.content_type" in query
|
||||
assert "WHERE pft.helper_text_message_id = ?" in query
|
||||
assert params == (helper_message_id,)
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
post_repository.logger.info.assert_called_once_with(
|
||||
f"Получен контент поста: {len(mock_result)} элементов"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_post_text_by_helper_id_found(self, post_repository):
|
||||
"""Тест получения текста поста по helper ID (пост найден)."""
|
||||
@@ -361,28 +394,31 @@ class TestPostRepository:
|
||||
mock_result = [("Тестовый текст поста",)]
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=mock_result)
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
helper_message_id = 67890
|
||||
|
||||
|
||||
result = await post_repository.get_post_text_by_helper_id(helper_message_id)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result == "Тестовый текст поста"
|
||||
|
||||
|
||||
# Проверяем вызов _execute_query_with_result
|
||||
post_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = post_repository._execute_query_with_result.call_args
|
||||
query = call_args[0][0]
|
||||
params = call_args[0][1]
|
||||
|
||||
assert "SELECT text FROM post_from_telegram_suggest WHERE helper_text_message_id = ?" in query
|
||||
|
||||
assert (
|
||||
"SELECT text FROM post_from_telegram_suggest WHERE helper_text_message_id = ?"
|
||||
in query
|
||||
)
|
||||
assert params == (helper_message_id,)
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
post_repository.logger.info.assert_called_once_with(
|
||||
f"Получен текст поста для helper_message_id={helper_message_id}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_post_text_by_helper_id_not_found(self, post_repository):
|
||||
"""Тест получения текста поста по helper ID (пост не найден)."""
|
||||
@@ -390,17 +426,17 @@ class TestPostRepository:
|
||||
mock_result = []
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=mock_result)
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
helper_message_id = 67890
|
||||
|
||||
|
||||
result = await post_repository.get_post_text_by_helper_id(helper_message_id)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result is None
|
||||
|
||||
|
||||
# Проверяем, что logger.info не вызывался
|
||||
post_repository.logger.info.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_post_ids_by_helper_id(self, post_repository):
|
||||
"""Тест получения ID сообщений по helper ID."""
|
||||
@@ -408,29 +444,29 @@ class TestPostRepository:
|
||||
mock_result = [(12345,), (67890,), (11111,)]
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=mock_result)
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
helper_message_id = 67890
|
||||
|
||||
|
||||
result = await post_repository.get_post_ids_by_helper_id(helper_message_id)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result == [12345, 67890, 11111]
|
||||
|
||||
|
||||
# Проверяем вызов _execute_query_with_result
|
||||
post_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = post_repository._execute_query_with_result.call_args
|
||||
query = call_args[0][0]
|
||||
params = call_args[0][1]
|
||||
|
||||
|
||||
assert "SELECT mltc.message_id" in query
|
||||
assert "WHERE pft.helper_text_message_id = ?" in query
|
||||
assert params == (helper_message_id,)
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
post_repository.logger.info.assert_called_once_with(
|
||||
f"Получены ID сообщений: {len(mock_result)} элементов"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_author_id_by_message_id_found(self, post_repository):
|
||||
"""Тест получения ID автора по message ID (автор найден)."""
|
||||
@@ -438,28 +474,31 @@ class TestPostRepository:
|
||||
mock_result = [(67890,)]
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=mock_result)
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
message_id = 12345
|
||||
|
||||
|
||||
result = await post_repository.get_author_id_by_message_id(message_id)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result == 67890
|
||||
|
||||
|
||||
# Проверяем вызов _execute_query_with_result
|
||||
post_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = post_repository._execute_query_with_result.call_args
|
||||
query = call_args[0][0]
|
||||
params = call_args[0][1]
|
||||
|
||||
assert "SELECT author_id FROM post_from_telegram_suggest WHERE message_id = ?" in query
|
||||
|
||||
assert (
|
||||
"SELECT author_id FROM post_from_telegram_suggest WHERE message_id = ?"
|
||||
in query
|
||||
)
|
||||
assert params == (message_id,)
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
post_repository.logger.info.assert_called_once_with(
|
||||
f"Получен author_id: {67890} для message_id={message_id}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_author_id_by_message_id_not_found(self, post_repository):
|
||||
"""Тест получения ID автора по message ID (автор не найден)."""
|
||||
@@ -467,17 +506,17 @@ class TestPostRepository:
|
||||
mock_result = []
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=mock_result)
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
message_id = 12345
|
||||
|
||||
|
||||
result = await post_repository.get_author_id_by_message_id(message_id)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert result is None
|
||||
|
||||
|
||||
# Проверяем, что logger.info не вызывался
|
||||
post_repository.logger.info.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_author_id_by_helper_message_id_found(self, post_repository):
|
||||
"""Тест получения ID автора по helper message ID (автор найден)."""
|
||||
@@ -485,28 +524,33 @@ class TestPostRepository:
|
||||
mock_result = [(67890,)]
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=mock_result)
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
helper_message_id = 12345
|
||||
|
||||
result = await post_repository.get_author_id_by_helper_message_id(helper_message_id)
|
||||
|
||||
|
||||
result = await post_repository.get_author_id_by_helper_message_id(
|
||||
helper_message_id
|
||||
)
|
||||
|
||||
# Проверяем результат
|
||||
assert result == 67890
|
||||
|
||||
|
||||
# Проверяем вызов _execute_query_with_result
|
||||
post_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = post_repository._execute_query_with_result.call_args
|
||||
query = call_args[0][0]
|
||||
params = call_args[0][1]
|
||||
|
||||
assert "SELECT author_id FROM post_from_telegram_suggest WHERE helper_text_message_id = ?" in query
|
||||
|
||||
assert (
|
||||
"SELECT author_id FROM post_from_telegram_suggest WHERE helper_text_message_id = ?"
|
||||
in query
|
||||
)
|
||||
assert params == (helper_message_id,)
|
||||
|
||||
|
||||
# Проверяем логирование
|
||||
post_repository.logger.info.assert_called_once_with(
|
||||
f"Получен author_id: {67890} для helper_message_id={helper_message_id}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_author_id_by_helper_message_id_not_found(self, post_repository):
|
||||
"""Тест получения ID автора по helper message ID (автор не найден)."""
|
||||
@@ -514,117 +558,145 @@ class TestPostRepository:
|
||||
mock_result = []
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=mock_result)
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
helper_message_id = 12345
|
||||
|
||||
result = await post_repository.get_author_id_by_helper_message_id(helper_message_id)
|
||||
|
||||
|
||||
result = await post_repository.get_author_id_by_helper_message_id(
|
||||
helper_message_id
|
||||
)
|
||||
|
||||
# Проверяем результат
|
||||
assert result is None
|
||||
|
||||
|
||||
# Проверяем, что logger.info не вызывался
|
||||
post_repository.logger.info.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_post_text_and_anonymity_by_message_id_found(self, post_repository):
|
||||
async def test_get_post_text_and_anonymity_by_message_id_found(
|
||||
self, post_repository
|
||||
):
|
||||
"""Тест получения текста и is_anonymous по message_id (пост найден)."""
|
||||
# Мокаем _execute_query_with_result
|
||||
mock_result = [("Тестовый текст", 1)] # is_anonymous = 1 (True)
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=mock_result)
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
message_id = 12345
|
||||
|
||||
result = await post_repository.get_post_text_and_anonymity_by_message_id(message_id)
|
||||
|
||||
|
||||
result = await post_repository.get_post_text_and_anonymity_by_message_id(
|
||||
message_id
|
||||
)
|
||||
|
||||
# Проверяем результат
|
||||
text, is_anonymous = result
|
||||
assert text == "Тестовый текст"
|
||||
assert is_anonymous is True
|
||||
|
||||
|
||||
# Проверяем вызов _execute_query_with_result
|
||||
post_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = post_repository._execute_query_with_result.call_args
|
||||
query = call_args[0][0]
|
||||
params = call_args[0][1]
|
||||
|
||||
assert "SELECT text, is_anonymous FROM post_from_telegram_suggest WHERE message_id = ?" in query
|
||||
|
||||
assert (
|
||||
"SELECT text, is_anonymous FROM post_from_telegram_suggest WHERE message_id = ?"
|
||||
in query
|
||||
)
|
||||
assert params == (message_id,)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_post_text_and_anonymity_by_message_id_with_false(self, post_repository):
|
||||
async def test_get_post_text_and_anonymity_by_message_id_with_false(
|
||||
self, post_repository
|
||||
):
|
||||
"""Тест получения текста и is_anonymous по message_id (is_anonymous = False)."""
|
||||
# Мокаем _execute_query_with_result
|
||||
mock_result = [("Тестовый текст", 0)] # is_anonymous = 0 (False)
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=mock_result)
|
||||
|
||||
|
||||
message_id = 12345
|
||||
|
||||
result = await post_repository.get_post_text_and_anonymity_by_message_id(message_id)
|
||||
|
||||
|
||||
result = await post_repository.get_post_text_and_anonymity_by_message_id(
|
||||
message_id
|
||||
)
|
||||
|
||||
# Проверяем результат
|
||||
text, is_anonymous = result
|
||||
assert text == "Тестовый текст"
|
||||
assert is_anonymous is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_post_text_and_anonymity_by_message_id_with_null(self, post_repository):
|
||||
async def test_get_post_text_and_anonymity_by_message_id_with_null(
|
||||
self, post_repository
|
||||
):
|
||||
"""Тест получения текста и is_anonymous по message_id (is_anonymous = NULL)."""
|
||||
# Мокаем _execute_query_with_result
|
||||
mock_result = [("Тестовый текст", None)] # is_anonymous = NULL
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=mock_result)
|
||||
|
||||
|
||||
message_id = 12345
|
||||
|
||||
result = await post_repository.get_post_text_and_anonymity_by_message_id(message_id)
|
||||
|
||||
|
||||
result = await post_repository.get_post_text_and_anonymity_by_message_id(
|
||||
message_id
|
||||
)
|
||||
|
||||
# Проверяем результат
|
||||
text, is_anonymous = result
|
||||
assert text == "Тестовый текст"
|
||||
assert is_anonymous is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_post_text_and_anonymity_by_message_id_not_found(self, post_repository):
|
||||
async def test_get_post_text_and_anonymity_by_message_id_not_found(
|
||||
self, post_repository
|
||||
):
|
||||
"""Тест получения текста и is_anonymous по message_id (пост не найден)."""
|
||||
# Мокаем _execute_query_with_result
|
||||
mock_result = []
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=mock_result)
|
||||
|
||||
|
||||
message_id = 12345
|
||||
|
||||
result = await post_repository.get_post_text_and_anonymity_by_message_id(message_id)
|
||||
|
||||
|
||||
result = await post_repository.get_post_text_and_anonymity_by_message_id(
|
||||
message_id
|
||||
)
|
||||
|
||||
# Проверяем результат
|
||||
text, is_anonymous = result
|
||||
assert text is None
|
||||
assert is_anonymous is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_post_text_and_anonymity_by_helper_id_found(self, post_repository):
|
||||
async def test_get_post_text_and_anonymity_by_helper_id_found(
|
||||
self, post_repository
|
||||
):
|
||||
"""Тест получения текста и is_anonymous по helper_message_id (пост найден)."""
|
||||
# Мокаем _execute_query_with_result
|
||||
mock_result = [("Тестовый текст", 1)] # is_anonymous = 1 (True)
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=mock_result)
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
helper_message_id = 67890
|
||||
|
||||
result = await post_repository.get_post_text_and_anonymity_by_helper_id(helper_message_id)
|
||||
|
||||
|
||||
result = await post_repository.get_post_text_and_anonymity_by_helper_id(
|
||||
helper_message_id
|
||||
)
|
||||
|
||||
# Проверяем результат
|
||||
text, is_anonymous = result
|
||||
assert text == "Тестовый текст"
|
||||
assert is_anonymous is True
|
||||
|
||||
|
||||
# Проверяем вызов _execute_query_with_result
|
||||
post_repository._execute_query_with_result.assert_called_once()
|
||||
call_args = post_repository._execute_query_with_result.call_args
|
||||
query = call_args[0][0]
|
||||
params = call_args[0][1]
|
||||
|
||||
assert "SELECT text, is_anonymous FROM post_from_telegram_suggest WHERE helper_text_message_id = ?" in query
|
||||
|
||||
assert (
|
||||
"SELECT text, is_anonymous FROM post_from_telegram_suggest WHERE helper_text_message_id = ?"
|
||||
in query
|
||||
)
|
||||
assert params == (helper_message_id,)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_post_with_is_anonymous_true(self, post_repository):
|
||||
"""Тест добавления поста с is_anonymous=True."""
|
||||
@@ -633,19 +705,19 @@ class TestPostRepository:
|
||||
text="Тестовый пост анон",
|
||||
author_id=67890,
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
is_anonymous=True
|
||||
is_anonymous=True,
|
||||
)
|
||||
|
||||
|
||||
post_repository._execute_query = AsyncMock()
|
||||
|
||||
|
||||
await post_repository.add_post(post)
|
||||
|
||||
|
||||
call_args = post_repository._execute_query.call_args
|
||||
params = call_args[0][1]
|
||||
|
||||
|
||||
# Проверяем, что is_anonymous преобразован в 1
|
||||
assert params[5] == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_post_with_is_anonymous_false(self, post_repository):
|
||||
"""Тест добавления поста с is_anonymous=False."""
|
||||
@@ -654,19 +726,19 @@ class TestPostRepository:
|
||||
text="Тестовый пост неанон",
|
||||
author_id=67890,
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
is_anonymous=False
|
||||
is_anonymous=False,
|
||||
)
|
||||
|
||||
|
||||
post_repository._execute_query = AsyncMock()
|
||||
|
||||
|
||||
await post_repository.add_post(post)
|
||||
|
||||
|
||||
call_args = post_repository._execute_query.call_args
|
||||
params = call_args[0][1]
|
||||
|
||||
|
||||
# Проверяем, что is_anonymous преобразован в 0
|
||||
assert params[5] == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_post_with_is_anonymous_none(self, post_repository):
|
||||
"""Тест добавления поста с is_anonymous=None."""
|
||||
@@ -675,28 +747,30 @@ class TestPostRepository:
|
||||
text="Тестовый пост",
|
||||
author_id=67890,
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
is_anonymous=None
|
||||
is_anonymous=None,
|
||||
)
|
||||
|
||||
|
||||
post_repository._execute_query = AsyncMock()
|
||||
|
||||
|
||||
await post_repository.add_post(post)
|
||||
|
||||
|
||||
call_args = post_repository._execute_query.call_args
|
||||
params = call_args[0][1]
|
||||
|
||||
|
||||
# Проверяем, что is_anonymous остался None
|
||||
assert params[5] is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_tables_logs_success(self, post_repository):
|
||||
"""Тест логирования успешного создания таблиц."""
|
||||
# Мокаем _execute_query, _execute_query_with_result и logger
|
||||
post_repository._execute_query = AsyncMock()
|
||||
post_repository._execute_query_with_result = AsyncMock(return_value=[]) # Для проверки столбца
|
||||
post_repository._execute_query_with_result = AsyncMock(
|
||||
return_value=[]
|
||||
) # Для проверки столбца
|
||||
post_repository.logger = MagicMock()
|
||||
|
||||
|
||||
await post_repository.create_tables()
|
||||
|
||||
|
||||
# Проверяем, что финальное сообщение о создании таблиц было вызвано
|
||||
post_repository.logger.info.assert_any_call("Таблицы для постов созданы")
|
||||
|
||||
@@ -4,17 +4,18 @@ import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from database.models import MessageContentLink, PostContent, TelegramPost
|
||||
from database.repositories.post_repository import PostRepository
|
||||
|
||||
|
||||
class TestPostRepositoryIntegration:
|
||||
"""Интеграционные тесты для PostRepository с реальной БД."""
|
||||
|
||||
|
||||
async def _setup_test_database(self, post_repository):
|
||||
"""Вспомогательная функция для настройки тестовой БД."""
|
||||
# Сначала создаем таблицу our_users для тестов
|
||||
await post_repository._execute_query('''
|
||||
await post_repository._execute_query("""
|
||||
CREATE TABLE IF NOT EXISTS our_users (
|
||||
user_id INTEGER NOT NULL PRIMARY KEY,
|
||||
first_name TEXT,
|
||||
@@ -28,40 +29,52 @@ class TestPostRepositoryIntegration:
|
||||
date_changed INTEGER NOT NULL,
|
||||
voice_bot_welcome_received BOOLEAN DEFAULT 0
|
||||
)
|
||||
''')
|
||||
|
||||
""")
|
||||
|
||||
# Добавляем тестовых пользователей
|
||||
await post_repository._execute_query(
|
||||
"INSERT OR REPLACE INTO our_users (user_id, first_name, full_name, date_added, date_changed) VALUES (?, ?, ?, ?, ?)",
|
||||
(67890, "Test", "Test User", int(datetime.now().timestamp()), int(datetime.now().timestamp()))
|
||||
(
|
||||
67890,
|
||||
"Test",
|
||||
"Test User",
|
||||
int(datetime.now().timestamp()),
|
||||
int(datetime.now().timestamp()),
|
||||
),
|
||||
)
|
||||
await post_repository._execute_query(
|
||||
"INSERT OR REPLACE INTO our_users (user_id, first_name, full_name, date_added, date_changed) VALUES (?, ?, ?, ?, ?)",
|
||||
(11111, "Test2", "Test User 2", int(datetime.now().timestamp()), int(datetime.now().timestamp()))
|
||||
(
|
||||
11111,
|
||||
"Test2",
|
||||
"Test User 2",
|
||||
int(datetime.now().timestamp()),
|
||||
int(datetime.now().timestamp()),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
# Теперь создаем таблицы для постов
|
||||
await post_repository.create_tables()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_db_path(self):
|
||||
"""Фикстура для временного файла БД."""
|
||||
with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as tmp_file:
|
||||
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp_file:
|
||||
db_path = tmp_file.name
|
||||
|
||||
|
||||
yield db_path
|
||||
|
||||
|
||||
# Очищаем временный файл после тестов
|
||||
try:
|
||||
os.unlink(db_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def post_repository(self, temp_db_path):
|
||||
"""Фикстура для PostRepository с реальной БД."""
|
||||
return PostRepository(temp_db_path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_post(self):
|
||||
"""Фикстура для тестового поста."""
|
||||
@@ -70,9 +83,9 @@ class TestPostRepositoryIntegration:
|
||||
text="Тестовый пост для интеграционных тестов",
|
||||
author_id=67890,
|
||||
helper_text_message_id=None,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_post_2(self):
|
||||
"""Фикстура для второго тестового поста."""
|
||||
@@ -81,9 +94,9 @@ class TestPostRepositoryIntegration:
|
||||
text="Второй тестовый пост",
|
||||
author_id=67890,
|
||||
helper_text_message_id=None,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_post_with_helper(self):
|
||||
"""Фикстура для тестового поста с helper сообщением."""
|
||||
@@ -92,418 +105,485 @@ class TestPostRepositoryIntegration:
|
||||
text="Пост с helper сообщением",
|
||||
author_id=67890,
|
||||
helper_text_message_id=None, # Будет установлен позже
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_tables_integration(self, post_repository):
|
||||
"""Интеграционный тест создания таблиц."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
# Проверяем, что таблицы созданы (попробуем вставить тестовые данные)
|
||||
test_post = TelegramPost(
|
||||
message_id=99999,
|
||||
text="Тест создания таблиц",
|
||||
author_id=67890, # Используем существующего пользователя
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
|
||||
|
||||
# Если таблицы созданы, то insert должен пройти успешно
|
||||
await post_repository.add_post(test_post)
|
||||
|
||||
|
||||
# Проверяем, что пост действительно добавлен
|
||||
author_id = await post_repository.get_author_id_by_message_id(99999)
|
||||
assert author_id == 67890
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_post_integration(self, post_repository, sample_post):
|
||||
"""Интеграционный тест добавления поста."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
# Добавляем пост
|
||||
await post_repository.add_post(sample_post)
|
||||
|
||||
|
||||
# Проверяем, что пост добавлен
|
||||
author_id = await post_repository.get_author_id_by_message_id(sample_post.message_id)
|
||||
author_id = await post_repository.get_author_id_by_message_id(
|
||||
sample_post.message_id
|
||||
)
|
||||
assert author_id == sample_post.author_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_post_without_date_integration(self, post_repository):
|
||||
"""Интеграционный тест добавления поста без даты."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
post_without_date = TelegramPost(
|
||||
message_id=12348,
|
||||
text="Пост без даты",
|
||||
author_id=67890,
|
||||
helper_text_message_id=None,
|
||||
created_at=None
|
||||
created_at=None,
|
||||
)
|
||||
|
||||
|
||||
# Добавляем пост
|
||||
await post_repository.add_post(post_without_date)
|
||||
|
||||
|
||||
# Проверяем, что дата была установлена автоматически
|
||||
assert post_without_date.created_at is not None
|
||||
assert isinstance(post_without_date.created_at, int)
|
||||
assert post_without_date.created_at > 0
|
||||
|
||||
|
||||
# Проверяем, что пост добавлен
|
||||
author_id = await post_repository.get_author_id_by_message_id(post_without_date.message_id)
|
||||
author_id = await post_repository.get_author_id_by_message_id(
|
||||
post_without_date.message_id
|
||||
)
|
||||
assert author_id == post_without_date.author_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_helper_message_integration(self, post_repository, sample_post):
|
||||
async def test_update_helper_message_integration(
|
||||
self, post_repository, sample_post
|
||||
):
|
||||
"""Интеграционный тест обновления helper сообщения."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
# Добавляем пост
|
||||
await post_repository.add_post(sample_post)
|
||||
|
||||
|
||||
# Обновляем helper сообщение
|
||||
helper_message_id = 88888
|
||||
await post_repository.update_helper_message(sample_post.message_id, helper_message_id)
|
||||
|
||||
await post_repository.update_helper_message(
|
||||
sample_post.message_id, helper_message_id
|
||||
)
|
||||
|
||||
# Проверяем, что helper сообщение обновлено
|
||||
# Для этого нужно получить пост и проверить helper_text_message_id
|
||||
# Но у нас нет метода для получения поста по ID, поэтому проверяем косвенно
|
||||
# через get_author_id_by_helper_message_id
|
||||
author_id = await post_repository.get_author_id_by_helper_message_id(helper_message_id)
|
||||
author_id = await post_repository.get_author_id_by_helper_message_id(
|
||||
helper_message_id
|
||||
)
|
||||
assert author_id == sample_post.author_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_post_content_integration(self, post_repository, sample_post):
|
||||
"""Интеграционный тест добавления контента поста."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
# Добавляем пост
|
||||
await post_repository.add_post(sample_post)
|
||||
|
||||
|
||||
# Добавляем контент
|
||||
message_id = 11111
|
||||
content_name = "/path/to/test/photo.jpg"
|
||||
content_type = "photo"
|
||||
|
||||
|
||||
# Сначала нужно добавить сообщение с этим message_id в post_from_telegram_suggest
|
||||
# или использовать существующий message_id
|
||||
content_post = TelegramPost(
|
||||
message_id=message_id,
|
||||
text="Сообщение с контентом",
|
||||
author_id=11111, # Используем существующего пользователя
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
await post_repository.add_post(content_post)
|
||||
|
||||
|
||||
result = await post_repository.add_post_content(
|
||||
sample_post.message_id, message_id, content_name, content_type
|
||||
)
|
||||
|
||||
|
||||
# Проверяем, что контент добавлен успешно
|
||||
assert result is True
|
||||
|
||||
|
||||
# Проверяем, что контент действительно добавлен
|
||||
post_content = await post_repository.get_post_content_by_helper_id(sample_post.message_id)
|
||||
post_content = await post_repository.get_post_content_by_helper_id(
|
||||
sample_post.message_id
|
||||
)
|
||||
# Поскольку у нас нет helper_message_id, контент не будет найден
|
||||
# Это нормальное поведение для данного теста
|
||||
assert isinstance(post_content, list)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_post_content_with_helper_message_integration(self, post_repository, sample_post_with_helper):
|
||||
async def test_add_post_content_with_helper_message_integration(
|
||||
self, post_repository, sample_post_with_helper
|
||||
):
|
||||
"""Интеграционный тест добавления контента поста с helper сообщением."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
# Добавляем пост
|
||||
await post_repository.add_post(sample_post_with_helper)
|
||||
|
||||
|
||||
# Создаем helper сообщение
|
||||
helper_message_id = 99999
|
||||
helper_post = TelegramPost(
|
||||
message_id=helper_message_id,
|
||||
text="Helper сообщение",
|
||||
author_id=67890,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
await post_repository.add_post(helper_post)
|
||||
|
||||
|
||||
# Обновляем пост, чтобы он ссылался на helper сообщение
|
||||
await post_repository.update_helper_message(sample_post_with_helper.message_id, helper_message_id)
|
||||
|
||||
await post_repository.update_helper_message(
|
||||
sample_post_with_helper.message_id, helper_message_id
|
||||
)
|
||||
|
||||
# Добавляем контент
|
||||
message_id = 22222
|
||||
content_name = "/path/to/test/video.mp4"
|
||||
content_type = "video"
|
||||
|
||||
|
||||
# Сначала нужно добавить сообщение с этим message_id в post_from_telegram_suggest
|
||||
content_post = TelegramPost(
|
||||
message_id=message_id,
|
||||
text="Сообщение с видео контентом",
|
||||
author_id=11111, # Используем существующего пользователя
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
await post_repository.add_post(content_post)
|
||||
|
||||
|
||||
result = await post_repository.add_post_content(
|
||||
sample_post_with_helper.message_id, message_id, content_name, content_type
|
||||
)
|
||||
|
||||
|
||||
# Проверяем, что контент добавлен успешно
|
||||
assert result is True
|
||||
|
||||
|
||||
# Проверяем, что контент действительно добавлен
|
||||
post_content = await post_repository.get_post_content_by_helper_id(helper_message_id)
|
||||
post_content = await post_repository.get_post_content_by_helper_id(
|
||||
helper_message_id
|
||||
)
|
||||
assert len(post_content) == 1
|
||||
assert post_content[0][0] == content_name
|
||||
assert post_content[0][1] == content_type
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_post_text_by_helper_id_integration(self, post_repository, sample_post_with_helper):
|
||||
async def test_get_post_text_by_helper_id_integration(
|
||||
self, post_repository, sample_post_with_helper
|
||||
):
|
||||
"""Интеграционный тест получения текста поста по helper ID."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
# Добавляем пост
|
||||
await post_repository.add_post(sample_post_with_helper)
|
||||
|
||||
|
||||
# Создаем helper сообщение
|
||||
helper_message_id = 99999
|
||||
helper_post = TelegramPost(
|
||||
message_id=helper_message_id,
|
||||
text="Helper сообщение",
|
||||
author_id=67890,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
await post_repository.add_post(helper_post)
|
||||
|
||||
|
||||
# Обновляем пост, чтобы он ссылался на helper сообщение
|
||||
await post_repository.update_helper_message(sample_post_with_helper.message_id, helper_message_id)
|
||||
|
||||
await post_repository.update_helper_message(
|
||||
sample_post_with_helper.message_id, helper_message_id
|
||||
)
|
||||
|
||||
# Получаем текст поста
|
||||
post_text = await post_repository.get_post_text_by_helper_id(helper_message_id)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert post_text == sample_post_with_helper.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_post_text_by_helper_id_not_found_integration(self, post_repository):
|
||||
async def test_get_post_text_by_helper_id_not_found_integration(
|
||||
self, post_repository
|
||||
):
|
||||
"""Интеграционный тест получения текста поста по несуществующему helper ID."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
# Пытаемся получить текст поста по несуществующему helper ID
|
||||
post_text = await post_repository.get_post_text_by_helper_id(99999)
|
||||
|
||||
|
||||
# Проверяем, что результат None
|
||||
assert post_text is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_post_ids_by_helper_id_integration(self, post_repository, sample_post_with_helper):
|
||||
async def test_get_post_ids_by_helper_id_integration(
|
||||
self, post_repository, sample_post_with_helper
|
||||
):
|
||||
"""Интеграционный тест получения ID сообщений по helper ID."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
# Добавляем пост
|
||||
await post_repository.add_post(sample_post_with_helper)
|
||||
|
||||
|
||||
# Создаем helper сообщение
|
||||
helper_message_id = 99999
|
||||
helper_post = TelegramPost(
|
||||
message_id=helper_message_id,
|
||||
text="Helper сообщение",
|
||||
author_id=67890,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
await post_repository.add_post(helper_post)
|
||||
|
||||
|
||||
# Обновляем пост, чтобы он ссылался на helper сообщение
|
||||
await post_repository.update_helper_message(sample_post_with_helper.message_id, helper_message_id)
|
||||
|
||||
await post_repository.update_helper_message(
|
||||
sample_post_with_helper.message_id, helper_message_id
|
||||
)
|
||||
|
||||
# Добавляем несколько сообщений с контентом
|
||||
message_ids = [33333, 44444, 55555]
|
||||
content_names = ["/path/to/photo1.jpg", "/path/to/photo2.jpg", "/path/to/video.mp4"]
|
||||
content_names = [
|
||||
"/path/to/photo1.jpg",
|
||||
"/path/to/photo2.jpg",
|
||||
"/path/to/video.mp4",
|
||||
]
|
||||
content_types = ["photo", "photo", "video"]
|
||||
|
||||
for i, (msg_id, content_name, content_type) in enumerate(zip(message_ids, content_names, content_types)):
|
||||
|
||||
for i, (msg_id, content_name, content_type) in enumerate(
|
||||
zip(message_ids, content_names, content_types)
|
||||
):
|
||||
# Сначала нужно добавить сообщение с этим message_id в post_from_telegram_suggest
|
||||
content_post = TelegramPost(
|
||||
message_id=msg_id,
|
||||
text=f"Сообщение с контентом {i+1}",
|
||||
author_id=11111, # Используем существующего пользователя
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
await post_repository.add_post(content_post)
|
||||
|
||||
|
||||
result = await post_repository.add_post_content(
|
||||
sample_post_with_helper.message_id, msg_id, content_name, content_type
|
||||
)
|
||||
assert result is True
|
||||
|
||||
|
||||
# Получаем ID сообщений
|
||||
post_ids = await post_repository.get_post_ids_by_helper_id(helper_message_id)
|
||||
|
||||
|
||||
# Проверяем результат
|
||||
assert len(post_ids) == 3
|
||||
for msg_id in message_ids:
|
||||
assert msg_id in post_ids
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_author_id_by_message_id_integration(self, post_repository, sample_post):
|
||||
async def test_get_author_id_by_message_id_integration(
|
||||
self, post_repository, sample_post
|
||||
):
|
||||
"""Интеграционный тест получения ID автора по message ID."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
# Добавляем пост
|
||||
await post_repository.add_post(sample_post)
|
||||
|
||||
|
||||
# Получаем ID автора
|
||||
author_id = await post_repository.get_author_id_by_message_id(sample_post.message_id)
|
||||
|
||||
author_id = await post_repository.get_author_id_by_message_id(
|
||||
sample_post.message_id
|
||||
)
|
||||
|
||||
# Проверяем результат
|
||||
assert author_id == sample_post.author_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_author_id_by_message_id_not_found_integration(self, post_repository):
|
||||
async def test_get_author_id_by_message_id_not_found_integration(
|
||||
self, post_repository
|
||||
):
|
||||
"""Интеграционный тест получения ID автора по несуществующему message ID."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
# Пытаемся получить ID автора по несуществующему message ID
|
||||
author_id = await post_repository.get_author_id_by_message_id(99999)
|
||||
|
||||
|
||||
# Проверяем, что результат None
|
||||
assert author_id is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_author_id_by_helper_message_id_integration(self, post_repository, sample_post_with_helper):
|
||||
async def test_get_author_id_by_helper_message_id_integration(
|
||||
self, post_repository, sample_post_with_helper
|
||||
):
|
||||
"""Интеграционный тест получения ID автора по helper message ID."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
# Добавляем пост
|
||||
await post_repository.add_post(sample_post_with_helper)
|
||||
|
||||
|
||||
# Создаем helper сообщение
|
||||
helper_message_id = 99999
|
||||
helper_post = TelegramPost(
|
||||
message_id=helper_message_id,
|
||||
text="Helper сообщение",
|
||||
author_id=67890,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
await post_repository.add_post(helper_post)
|
||||
|
||||
|
||||
# Обновляем пост, чтобы он ссылался на helper сообщение
|
||||
await post_repository.update_helper_message(sample_post_with_helper.message_id, helper_message_id)
|
||||
|
||||
await post_repository.update_helper_message(
|
||||
sample_post_with_helper.message_id, helper_message_id
|
||||
)
|
||||
|
||||
# Получаем ID автора
|
||||
author_id = await post_repository.get_author_id_by_helper_message_id(helper_message_id)
|
||||
|
||||
author_id = await post_repository.get_author_id_by_helper_message_id(
|
||||
helper_message_id
|
||||
)
|
||||
|
||||
# Проверяем результат
|
||||
assert author_id == sample_post_with_helper.author_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_author_id_by_helper_message_id_not_found_integration(self, post_repository):
|
||||
async def test_get_author_id_by_helper_message_id_not_found_integration(
|
||||
self, post_repository
|
||||
):
|
||||
"""Интеграционный тест получения ID автора по несуществующему helper message ID."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
# Пытаемся получить ID автора по несуществующему helper message ID
|
||||
author_id = await post_repository.get_author_id_by_helper_message_id(99999)
|
||||
|
||||
|
||||
# Проверяем, что результат None
|
||||
assert author_id is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiple_posts_integration(self, post_repository, sample_post, sample_post_2):
|
||||
async def test_multiple_posts_integration(
|
||||
self, post_repository, sample_post, sample_post_2
|
||||
):
|
||||
"""Интеграционный тест работы с несколькими постами."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
# Добавляем несколько постов
|
||||
await post_repository.add_post(sample_post)
|
||||
await post_repository.add_post(sample_post_2)
|
||||
|
||||
|
||||
# Проверяем, что оба поста добавлены
|
||||
author_id_1 = await post_repository.get_author_id_by_message_id(sample_post.message_id)
|
||||
author_id_2 = await post_repository.get_author_id_by_message_id(sample_post_2.message_id)
|
||||
|
||||
author_id_1 = await post_repository.get_author_id_by_message_id(
|
||||
sample_post.message_id
|
||||
)
|
||||
author_id_2 = await post_repository.get_author_id_by_message_id(
|
||||
sample_post_2.message_id
|
||||
)
|
||||
|
||||
assert author_id_1 == sample_post.author_id
|
||||
assert author_id_2 == sample_post_2.author_id
|
||||
|
||||
|
||||
# Проверяем, что посты имеют разные ID
|
||||
assert sample_post.message_id != sample_post_2.message_id
|
||||
assert sample_post.text != sample_post_2.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_post_content_relationships_integration(self, post_repository, sample_post_with_helper):
|
||||
async def test_post_content_relationships_integration(
|
||||
self, post_repository, sample_post_with_helper
|
||||
):
|
||||
"""Интеграционный тест связей между постами и контентом."""
|
||||
# Настраиваем тестовую БД
|
||||
await self._setup_test_database(post_repository)
|
||||
|
||||
|
||||
# Добавляем пост
|
||||
await post_repository.add_post(sample_post_with_helper)
|
||||
|
||||
|
||||
# Создаем helper сообщение
|
||||
helper_message_id = 99999
|
||||
helper_post = TelegramPost(
|
||||
message_id=helper_message_id,
|
||||
text="Helper сообщение",
|
||||
author_id=67890,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
await post_repository.add_post(helper_post)
|
||||
|
||||
|
||||
# Обновляем пост, чтобы он ссылался на helper сообщение
|
||||
await post_repository.update_helper_message(sample_post_with_helper.message_id, helper_message_id)
|
||||
|
||||
await post_repository.update_helper_message(
|
||||
sample_post_with_helper.message_id, helper_message_id
|
||||
)
|
||||
|
||||
# Добавляем контент разных типов
|
||||
content_data = [
|
||||
(11111, "/path/to/photo1.jpg", "photo"),
|
||||
(22222, "/path/to/video1.mp4", "video"),
|
||||
(33333, "/path/to/audio1.mp3", "audio"),
|
||||
(44444, "/path/to/photo2.jpg", "photo")
|
||||
(44444, "/path/to/photo2.jpg", "photo"),
|
||||
]
|
||||
|
||||
|
||||
for message_id, content_name, content_type in content_data:
|
||||
# Сначала нужно добавить сообщение с этим message_id в post_from_telegram_suggest
|
||||
content_post = TelegramPost(
|
||||
message_id=message_id,
|
||||
text=f"Сообщение с контентом {content_type}",
|
||||
author_id=11111, # Используем существующего пользователя
|
||||
created_at=int(datetime.now().timestamp())
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
)
|
||||
await post_repository.add_post(content_post)
|
||||
|
||||
|
||||
result = await post_repository.add_post_content(
|
||||
sample_post_with_helper.message_id, message_id, content_name, content_type
|
||||
sample_post_with_helper.message_id,
|
||||
message_id,
|
||||
content_name,
|
||||
content_type,
|
||||
)
|
||||
assert result is True
|
||||
|
||||
|
||||
# Проверяем, что весь контент добавлен
|
||||
post_content = await post_repository.get_post_content_by_helper_id(helper_message_id)
|
||||
post_content = await post_repository.get_post_content_by_helper_id(
|
||||
helper_message_id
|
||||
)
|
||||
assert len(post_content) == 4
|
||||
|
||||
|
||||
# Проверяем, что ID сообщений получены правильно
|
||||
post_ids = await post_repository.get_post_ids_by_helper_id(helper_message_id)
|
||||
assert len(post_ids) == 4
|
||||
|
||||
|
||||
# Проверяем, что все ожидаемые ID присутствуют
|
||||
expected_message_ids = [11111, 22222, 33333, 44444]
|
||||
for expected_id in expected_message_ids:
|
||||
assert expected_id in post_ids
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_status_by_message_id_integration(self, post_repository, sample_post):
|
||||
async def test_update_status_by_message_id_integration(
|
||||
self, post_repository, sample_post
|
||||
):
|
||||
"""Интеграционный тест обновления статуса одиночного поста."""
|
||||
await self._setup_test_database(post_repository)
|
||||
await post_repository.add_post(sample_post)
|
||||
|
||||
await post_repository.update_status_by_message_id(sample_post.message_id, "approved")
|
||||
await post_repository.update_status_by_message_id(
|
||||
sample_post.message_id, "approved"
|
||||
)
|
||||
|
||||
rows = await post_repository._execute_query_with_result(
|
||||
"SELECT status FROM post_from_telegram_suggest WHERE message_id = ?",
|
||||
|
||||
@@ -5,13 +5,14 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from aiogram import types
|
||||
|
||||
from database.models import TelegramPost, User
|
||||
from helper_bot.handlers.private.services import BotSettings, PostService
|
||||
|
||||
|
||||
class TestPostService:
|
||||
"""Test class for PostService"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db(self):
|
||||
"""Mock database"""
|
||||
@@ -21,7 +22,7 @@ class TestPostService:
|
||||
db.get_user_by_id = AsyncMock()
|
||||
db.add_message_link = AsyncMock()
|
||||
return db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_settings(self):
|
||||
"""Mock bot settings"""
|
||||
@@ -33,14 +34,14 @@ class TestPostService:
|
||||
important_logs="test_important",
|
||||
preview_link="test_link",
|
||||
logs="test_logs_setting",
|
||||
test="test_test_setting"
|
||||
test="test_test_setting",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def post_service(self, mock_db, mock_settings):
|
||||
"""Create PostService instance"""
|
||||
return PostService(mock_db, mock_settings)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_message(self):
|
||||
"""Mock Telegram message"""
|
||||
@@ -57,243 +58,451 @@ class TestPostService:
|
||||
message.chat = Mock()
|
||||
message.chat.id = 12345
|
||||
return message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_text_post_saves_raw_text(self, post_service, mock_message, mock_db):
|
||||
async def test_handle_text_post_saves_raw_text(
|
||||
self, post_service, mock_message, mock_db
|
||||
):
|
||||
"""Test that handle_text_post saves raw text to database"""
|
||||
mock_sent_message = Mock()
|
||||
mock_sent_message.message_id = 200
|
||||
|
||||
with patch('helper_bot.handlers.private.services.get_text_message', return_value="Formatted text"):
|
||||
with patch('helper_bot.handlers.private.services.send_text_message', return_value=mock_sent_message):
|
||||
with patch('helper_bot.handlers.private.services.get_first_name', return_value="Test"):
|
||||
with patch('helper_bot.handlers.private.services.get_reply_keyboard_for_post', return_value=None):
|
||||
with patch('helper_bot.handlers.private.services.determine_anonymity', return_value=False):
|
||||
|
||||
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_text_message",
|
||||
return_value="Formatted text",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.send_text_message",
|
||||
return_value=mock_sent_message,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_first_name",
|
||||
return_value="Test",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_reply_keyboard_for_post",
|
||||
return_value=None,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.determine_anonymity",
|
||||
return_value=False,
|
||||
):
|
||||
|
||||
await post_service.handle_text_post(mock_message, "Test")
|
||||
|
||||
|
||||
# Check that add_post was called
|
||||
mock_db.add_post.assert_called_once()
|
||||
call_args = mock_db.add_post.call_args[0][0]
|
||||
|
||||
|
||||
# Check that raw text is saved
|
||||
assert isinstance(call_args, TelegramPost)
|
||||
assert call_args.text == "Тестовый пост" # Raw text
|
||||
assert call_args.message_id == 200
|
||||
assert call_args.author_id == 12345
|
||||
assert call_args.is_anonymous is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_text_post_determines_anonymity(self, post_service, mock_message, mock_db):
|
||||
async def test_handle_text_post_determines_anonymity(
|
||||
self, post_service, mock_message, mock_db
|
||||
):
|
||||
"""Test that handle_text_post determines anonymity correctly"""
|
||||
mock_message.text = "Тестовый пост анон"
|
||||
mock_sent_message = Mock()
|
||||
mock_sent_message.message_id = 200
|
||||
|
||||
with patch('helper_bot.handlers.private.services.get_text_message', return_value="Formatted text"):
|
||||
with patch('helper_bot.handlers.private.services.send_text_message', return_value=mock_sent_message):
|
||||
with patch('helper_bot.handlers.private.services.get_first_name', return_value="Test"):
|
||||
with patch('helper_bot.handlers.private.services.get_reply_keyboard_for_post', return_value=None):
|
||||
with patch('helper_bot.handlers.private.services.determine_anonymity', return_value=True):
|
||||
|
||||
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_text_message",
|
||||
return_value="Formatted text",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.send_text_message",
|
||||
return_value=mock_sent_message,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_first_name",
|
||||
return_value="Test",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_reply_keyboard_for_post",
|
||||
return_value=None,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.determine_anonymity",
|
||||
return_value=True,
|
||||
):
|
||||
|
||||
await post_service.handle_text_post(mock_message, "Test")
|
||||
|
||||
|
||||
call_args = mock_db.add_post.call_args[0][0]
|
||||
assert call_args.is_anonymous is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_photo_post_saves_raw_caption(self, post_service, mock_message, mock_db):
|
||||
async def test_handle_photo_post_saves_raw_caption(
|
||||
self, post_service, mock_message, mock_db
|
||||
):
|
||||
"""Test that handle_photo_post saves raw caption to database"""
|
||||
mock_message.caption = "Тестовая подпись"
|
||||
mock_message.photo = [Mock()]
|
||||
mock_message.photo[-1].file_id = "photo_123"
|
||||
|
||||
|
||||
sent_message = Mock()
|
||||
sent_message.message_id = 201
|
||||
sent_message.caption = "Formatted caption"
|
||||
|
||||
with patch('helper_bot.handlers.private.services.get_text_message', return_value="Formatted caption"):
|
||||
with patch('helper_bot.handlers.private.services.send_photo_message', return_value=sent_message):
|
||||
with patch('helper_bot.handlers.private.services.get_first_name', return_value="Test"):
|
||||
with patch('helper_bot.handlers.private.services.get_reply_keyboard_for_post', return_value=None):
|
||||
with patch('helper_bot.handlers.private.services.determine_anonymity', return_value=False):
|
||||
with patch('helper_bot.handlers.private.services.add_in_db_media', return_value=True):
|
||||
|
||||
await post_service.handle_photo_post(mock_message, "Test")
|
||||
|
||||
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_text_message",
|
||||
return_value="Formatted caption",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.send_photo_message",
|
||||
return_value=sent_message,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_first_name",
|
||||
return_value="Test",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_reply_keyboard_for_post",
|
||||
return_value=None,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.determine_anonymity",
|
||||
return_value=False,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.add_in_db_media",
|
||||
return_value=True,
|
||||
):
|
||||
|
||||
await post_service.handle_photo_post(
|
||||
mock_message, "Test"
|
||||
)
|
||||
|
||||
mock_db.add_post.assert_called_once()
|
||||
call_args = mock_db.add_post.call_args[0][0]
|
||||
|
||||
|
||||
# Check that raw caption is saved
|
||||
assert call_args.text == "Тестовая подпись" # Raw caption
|
||||
assert (
|
||||
call_args.text == "Тестовая подпись"
|
||||
) # Raw caption
|
||||
assert call_args.message_id == 201
|
||||
assert call_args.is_anonymous is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_photo_post_without_caption(self, post_service, mock_message, mock_db):
|
||||
async def test_handle_photo_post_without_caption(
|
||||
self, post_service, mock_message, mock_db
|
||||
):
|
||||
"""Test that handle_photo_post handles missing caption"""
|
||||
mock_message.caption = None
|
||||
mock_message.photo = [Mock()]
|
||||
mock_message.photo[-1].file_id = "photo_123"
|
||||
|
||||
|
||||
sent_message = Mock()
|
||||
sent_message.message_id = 202
|
||||
|
||||
with patch('helper_bot.handlers.private.services.get_text_message', return_value=""):
|
||||
with patch('helper_bot.handlers.private.services.send_photo_message', return_value=sent_message):
|
||||
with patch('helper_bot.handlers.private.services.get_first_name', return_value="Test"):
|
||||
with patch('helper_bot.handlers.private.services.get_reply_keyboard_for_post', return_value=None):
|
||||
with patch('helper_bot.handlers.private.services.determine_anonymity', return_value=False):
|
||||
with patch('helper_bot.handlers.private.services.add_in_db_media', return_value=True):
|
||||
|
||||
await post_service.handle_photo_post(mock_message, "Test")
|
||||
|
||||
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_text_message", return_value=""
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.send_photo_message",
|
||||
return_value=sent_message,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_first_name",
|
||||
return_value="Test",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_reply_keyboard_for_post",
|
||||
return_value=None,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.determine_anonymity",
|
||||
return_value=False,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.add_in_db_media",
|
||||
return_value=True,
|
||||
):
|
||||
|
||||
await post_service.handle_photo_post(
|
||||
mock_message, "Test"
|
||||
)
|
||||
|
||||
call_args = mock_db.add_post.call_args[0][0]
|
||||
assert call_args.text == "" # Empty string for missing caption
|
||||
assert (
|
||||
call_args.text == ""
|
||||
) # Empty string for missing caption
|
||||
assert call_args.is_anonymous is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_video_post_saves_raw_caption(self, post_service, mock_message, mock_db):
|
||||
async def test_handle_video_post_saves_raw_caption(
|
||||
self, post_service, mock_message, mock_db
|
||||
):
|
||||
"""Test that handle_video_post saves raw caption to database"""
|
||||
mock_message.caption = "Видео подпись"
|
||||
mock_message.video = Mock()
|
||||
mock_message.video.file_id = "video_123"
|
||||
|
||||
|
||||
sent_message = Mock()
|
||||
sent_message.message_id = 203
|
||||
|
||||
with patch('helper_bot.handlers.private.services.get_text_message', return_value="Formatted"):
|
||||
with patch('helper_bot.handlers.private.services.send_video_message', return_value=sent_message):
|
||||
with patch('helper_bot.handlers.private.services.get_first_name', return_value="Test"):
|
||||
with patch('helper_bot.handlers.private.services.get_reply_keyboard_for_post', return_value=None):
|
||||
with patch('helper_bot.handlers.private.services.determine_anonymity', return_value=True):
|
||||
with patch('helper_bot.handlers.private.services.add_in_db_media', return_value=True):
|
||||
|
||||
await post_service.handle_video_post(mock_message, "Test")
|
||||
|
||||
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_text_message",
|
||||
return_value="Formatted",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.send_video_message",
|
||||
return_value=sent_message,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_first_name",
|
||||
return_value="Test",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_reply_keyboard_for_post",
|
||||
return_value=None,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.determine_anonymity",
|
||||
return_value=True,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.add_in_db_media",
|
||||
return_value=True,
|
||||
):
|
||||
|
||||
await post_service.handle_video_post(
|
||||
mock_message, "Test"
|
||||
)
|
||||
|
||||
call_args = mock_db.add_post.call_args[0][0]
|
||||
assert call_args.text == "Видео подпись" # Raw caption
|
||||
assert call_args.is_anonymous is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_audio_post_saves_raw_caption(self, post_service, mock_message, mock_db):
|
||||
async def test_handle_audio_post_saves_raw_caption(
|
||||
self, post_service, mock_message, mock_db
|
||||
):
|
||||
"""Test that handle_audio_post saves raw caption to database"""
|
||||
mock_message.caption = "Аудио подпись"
|
||||
mock_message.audio = Mock()
|
||||
mock_message.audio.file_id = "audio_123"
|
||||
|
||||
|
||||
sent_message = Mock()
|
||||
sent_message.message_id = 204
|
||||
|
||||
with patch('helper_bot.handlers.private.services.get_text_message', return_value="Formatted"):
|
||||
with patch('helper_bot.handlers.private.services.send_audio_message', return_value=sent_message):
|
||||
with patch('helper_bot.handlers.private.services.get_first_name', return_value="Test"):
|
||||
with patch('helper_bot.handlers.private.services.get_reply_keyboard_for_post', return_value=None):
|
||||
with patch('helper_bot.handlers.private.services.determine_anonymity', return_value=False):
|
||||
with patch('helper_bot.handlers.private.services.add_in_db_media', return_value=True):
|
||||
|
||||
await post_service.handle_audio_post(mock_message, "Test")
|
||||
|
||||
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_text_message",
|
||||
return_value="Formatted",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.send_audio_message",
|
||||
return_value=sent_message,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_first_name",
|
||||
return_value="Test",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_reply_keyboard_for_post",
|
||||
return_value=None,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.determine_anonymity",
|
||||
return_value=False,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.add_in_db_media",
|
||||
return_value=True,
|
||||
):
|
||||
|
||||
await post_service.handle_audio_post(
|
||||
mock_message, "Test"
|
||||
)
|
||||
|
||||
call_args = mock_db.add_post.call_args[0][0]
|
||||
assert call_args.text == "Аудио подпись" # Raw caption
|
||||
assert call_args.is_anonymous is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_video_note_post_saves_empty_string(self, post_service, mock_message, mock_db):
|
||||
async def test_handle_video_note_post_saves_empty_string(
|
||||
self, post_service, mock_message, mock_db
|
||||
):
|
||||
"""Test that handle_video_note_post saves empty string"""
|
||||
mock_message.video_note = Mock()
|
||||
mock_message.video_note.file_id = "video_note_123"
|
||||
|
||||
|
||||
sent_message = Mock()
|
||||
sent_message.message_id = 205
|
||||
|
||||
with patch('helper_bot.handlers.private.services.send_video_note_message', return_value=sent_message):
|
||||
with patch('helper_bot.handlers.private.services.get_reply_keyboard_for_post', return_value=None):
|
||||
with patch('helper_bot.handlers.private.services.determine_anonymity', return_value=False):
|
||||
with patch('helper_bot.handlers.private.services.add_in_db_media', return_value=True):
|
||||
|
||||
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.send_video_note_message",
|
||||
return_value=sent_message,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_reply_keyboard_for_post",
|
||||
return_value=None,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.determine_anonymity",
|
||||
return_value=False,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.add_in_db_media",
|
||||
return_value=True,
|
||||
):
|
||||
|
||||
await post_service.handle_video_note_post(mock_message)
|
||||
|
||||
|
||||
call_args = mock_db.add_post.call_args[0][0]
|
||||
assert call_args.text == "" # Empty string
|
||||
assert call_args.is_anonymous is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_voice_post_saves_empty_string(self, post_service, mock_message, mock_db):
|
||||
async def test_handle_voice_post_saves_empty_string(
|
||||
self, post_service, mock_message, mock_db
|
||||
):
|
||||
"""Test that handle_voice_post saves empty string"""
|
||||
mock_message.voice = Mock()
|
||||
mock_message.voice.file_id = "voice_123"
|
||||
|
||||
|
||||
sent_message = Mock()
|
||||
sent_message.message_id = 206
|
||||
|
||||
with patch('helper_bot.handlers.private.services.send_voice_message', return_value=sent_message):
|
||||
with patch('helper_bot.handlers.private.services.get_reply_keyboard_for_post', return_value=None):
|
||||
with patch('helper_bot.handlers.private.services.determine_anonymity', return_value=False):
|
||||
with patch('helper_bot.handlers.private.services.add_in_db_media', return_value=True):
|
||||
|
||||
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.send_voice_message",
|
||||
return_value=sent_message,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_reply_keyboard_for_post",
|
||||
return_value=None,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.determine_anonymity",
|
||||
return_value=False,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.add_in_db_media",
|
||||
return_value=True,
|
||||
):
|
||||
|
||||
await post_service.handle_voice_post(mock_message)
|
||||
|
||||
|
||||
call_args = mock_db.add_post.call_args[0][0]
|
||||
assert call_args.text == "" # Empty string
|
||||
assert call_args.is_anonymous is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_media_group_post_saves_raw_caption(self, post_service, mock_message, mock_db):
|
||||
async def test_handle_media_group_post_saves_raw_caption(
|
||||
self, post_service, mock_message, mock_db
|
||||
):
|
||||
"""Test that handle_media_group_post saves raw caption to database"""
|
||||
mock_message.message_id = 300
|
||||
mock_message.media_group_id = 1
|
||||
|
||||
|
||||
album = [Mock()]
|
||||
album[0].caption = "Медиагруппа подпись"
|
||||
|
||||
|
||||
mock_helper_message = Mock()
|
||||
mock_helper_message.message_id = 302
|
||||
|
||||
with patch('helper_bot.handlers.private.services.get_text_message', return_value="Formatted"):
|
||||
with patch('helper_bot.handlers.private.services.prepare_media_group_from_middlewares', return_value=[]):
|
||||
with patch('helper_bot.handlers.private.services.send_media_group_message_to_private_chat', return_value=[301]):
|
||||
with patch('helper_bot.handlers.private.services.get_first_name', return_value="Test"):
|
||||
with patch('helper_bot.handlers.private.services.get_reply_keyboard_for_post', return_value=None):
|
||||
with patch('helper_bot.handlers.private.services.send_text_message', return_value=mock_helper_message):
|
||||
with patch('helper_bot.handlers.private.services.determine_anonymity', return_value=True):
|
||||
with patch('asyncio.sleep', new_callable=AsyncMock):
|
||||
|
||||
await post_service.handle_media_group_post(mock_message, album, "Test")
|
||||
|
||||
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_text_message",
|
||||
return_value="Formatted",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.prepare_media_group_from_middlewares",
|
||||
return_value=[],
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.send_media_group_message_to_private_chat",
|
||||
return_value=[301],
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_first_name",
|
||||
return_value="Test",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_reply_keyboard_for_post",
|
||||
return_value=None,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.send_text_message",
|
||||
return_value=mock_helper_message,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.determine_anonymity",
|
||||
return_value=True,
|
||||
):
|
||||
with patch("asyncio.sleep", new_callable=AsyncMock):
|
||||
|
||||
await post_service.handle_media_group_post(
|
||||
mock_message, album, "Test"
|
||||
)
|
||||
|
||||
# Check main post
|
||||
calls = mock_db.add_post.call_args_list
|
||||
main_post = calls[0][0][0]
|
||||
|
||||
assert main_post.text == "Медиагруппа подпись" # Raw caption
|
||||
assert main_post.message_id == 301 # Последний message_id из списка
|
||||
|
||||
assert (
|
||||
main_post.text == "Медиагруппа подпись"
|
||||
) # Raw caption
|
||||
assert (
|
||||
main_post.message_id == 301
|
||||
) # Последний message_id из списка
|
||||
assert main_post.is_anonymous is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_media_group_post_without_caption(self, post_service, mock_message, mock_db):
|
||||
async def test_handle_media_group_post_without_caption(
|
||||
self, post_service, mock_message, mock_db
|
||||
):
|
||||
"""Test that handle_media_group_post handles missing caption"""
|
||||
mock_message.message_id = 301
|
||||
mock_message.media_group_id = 1
|
||||
|
||||
|
||||
album = [Mock()]
|
||||
album[0].caption = None
|
||||
|
||||
|
||||
mock_helper_message = Mock()
|
||||
mock_helper_message.message_id = 303
|
||||
|
||||
with patch('helper_bot.handlers.private.services.get_text_message', return_value=" "):
|
||||
with patch('helper_bot.handlers.private.services.prepare_media_group_from_middlewares', return_value=[]):
|
||||
with patch('helper_bot.handlers.private.services.send_media_group_message_to_private_chat', return_value=[302]):
|
||||
with patch('helper_bot.handlers.private.services.get_first_name', return_value="Test"):
|
||||
with patch('helper_bot.handlers.private.services.get_reply_keyboard_for_post', return_value=None):
|
||||
with patch('helper_bot.handlers.private.services.send_text_message', return_value=mock_helper_message):
|
||||
with patch('helper_bot.handlers.private.services.determine_anonymity', return_value=False):
|
||||
with patch('asyncio.sleep', new_callable=AsyncMock):
|
||||
|
||||
await post_service.handle_media_group_post(mock_message, album, "Test")
|
||||
|
||||
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_text_message", return_value=" "
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.prepare_media_group_from_middlewares",
|
||||
return_value=[],
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.send_media_group_message_to_private_chat",
|
||||
return_value=[302],
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_first_name",
|
||||
return_value="Test",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.get_reply_keyboard_for_post",
|
||||
return_value=None,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.send_text_message",
|
||||
return_value=mock_helper_message,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.private.services.determine_anonymity",
|
||||
return_value=False,
|
||||
):
|
||||
with patch("asyncio.sleep", new_callable=AsyncMock):
|
||||
|
||||
await post_service.handle_media_group_post(
|
||||
mock_message, album, "Test"
|
||||
)
|
||||
|
||||
calls = mock_db.add_post.call_args_list
|
||||
main_post = calls[0][0][0]
|
||||
|
||||
assert main_post.text == "" # Empty string for missing caption
|
||||
|
||||
assert (
|
||||
main_post.text == ""
|
||||
) # Empty string for missing caption
|
||||
assert main_post.is_anonymous is False
|
||||
|
||||
@@ -1,25 +1,32 @@
|
||||
"""
|
||||
Тесты для rate limiter
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from helper_bot.config.rate_limit_config import (RateLimitSettings,
|
||||
get_rate_limit_config)
|
||||
from helper_bot.utils.rate_limit_monitor import (RateLimitMonitor,
|
||||
RateLimitStats,
|
||||
record_rate_limit_request)
|
||||
from helper_bot.utils.rate_limiter import (ChatRateLimiter, GlobalRateLimiter,
|
||||
RateLimitConfig, RetryHandler,
|
||||
TelegramRateLimiter,
|
||||
send_with_rate_limit)
|
||||
|
||||
from helper_bot.config.rate_limit_config import RateLimitSettings, get_rate_limit_config
|
||||
from helper_bot.utils.rate_limit_monitor import (
|
||||
RateLimitMonitor,
|
||||
RateLimitStats,
|
||||
record_rate_limit_request,
|
||||
)
|
||||
from helper_bot.utils.rate_limiter import (
|
||||
ChatRateLimiter,
|
||||
GlobalRateLimiter,
|
||||
RateLimitConfig,
|
||||
RetryHandler,
|
||||
TelegramRateLimiter,
|
||||
send_with_rate_limit,
|
||||
)
|
||||
|
||||
|
||||
class TestRateLimitConfig:
|
||||
"""Тесты для RateLimitConfig"""
|
||||
|
||||
|
||||
def test_default_config(self):
|
||||
"""Тест создания конфигурации по умолчанию"""
|
||||
config = RateLimitConfig()
|
||||
@@ -31,37 +38,41 @@ class TestRateLimitConfig:
|
||||
|
||||
class TestChatRateLimiter:
|
||||
"""Тесты для ChatRateLimiter"""
|
||||
|
||||
|
||||
def test_initialization(self):
|
||||
"""Тест инициализации"""
|
||||
config = RateLimitConfig(messages_per_second=1.0, burst_limit=2)
|
||||
limiter = ChatRateLimiter(config)
|
||||
|
||||
|
||||
assert limiter.config == config
|
||||
assert limiter.last_send_time == 0.0
|
||||
assert limiter.burst_count == 0
|
||||
assert limiter.retry_delay == 1.0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wait_if_needed_no_wait(self):
|
||||
"""Тест что не ждет если не нужно"""
|
||||
config = RateLimitConfig(messages_per_second=10.0, burst_limit=10)
|
||||
limiter = ChatRateLimiter(config)
|
||||
|
||||
|
||||
start_time = time.time()
|
||||
await limiter.wait_if_needed()
|
||||
end_time = time.time()
|
||||
|
||||
|
||||
# Должно пройти очень быстро
|
||||
assert end_time - start_time < 0.1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wait_if_needed_with_wait(self):
|
||||
"""Тест что ждет если нужно (sleep патчится, проверяем вызов с нужной длительностью)."""
|
||||
config = RateLimitConfig(messages_per_second=0.5, burst_limit=10) # 1 сообщение в 2 секунды
|
||||
config = RateLimitConfig(
|
||||
messages_per_second=0.5, burst_limit=10
|
||||
) # 1 сообщение в 2 секунды
|
||||
limiter = ChatRateLimiter(config)
|
||||
|
||||
with patch('helper_bot.utils.rate_limiter.asyncio.sleep', new_callable=AsyncMock) as mock_sleep:
|
||||
with patch(
|
||||
"helper_bot.utils.rate_limiter.asyncio.sleep", new_callable=AsyncMock
|
||||
) as mock_sleep:
|
||||
await limiter.wait_if_needed()
|
||||
mock_sleep.assert_not_called()
|
||||
|
||||
@@ -77,7 +88,9 @@ class TestChatRateLimiter:
|
||||
config = RateLimitConfig(messages_per_second=10.0, burst_limit=2)
|
||||
limiter = ChatRateLimiter(config)
|
||||
|
||||
with patch('helper_bot.utils.rate_limiter.asyncio.sleep', new_callable=AsyncMock) as mock_sleep:
|
||||
with patch(
|
||||
"helper_bot.utils.rate_limiter.asyncio.sleep", new_callable=AsyncMock
|
||||
) as mock_sleep:
|
||||
await limiter.wait_if_needed()
|
||||
await limiter.wait_if_needed()
|
||||
mock_sleep.reset_mock()
|
||||
@@ -87,30 +100,32 @@ class TestChatRateLimiter:
|
||||
assert mock_sleep.call_count >= 1
|
||||
args = [c[0][0] for c in mock_sleep.call_args_list]
|
||||
burst_waits = [a for a in args if 0.8 <= a <= 1.2]
|
||||
assert len(burst_waits) >= 1, f"Ожидался вызов sleep(~1.0) по burst, получены: {args}"
|
||||
assert (
|
||||
len(burst_waits) >= 1
|
||||
), f"Ожидался вызов sleep(~1.0) по burst, получены: {args}"
|
||||
|
||||
|
||||
class TestGlobalRateLimiter:
|
||||
"""Тесты для GlobalRateLimiter"""
|
||||
|
||||
|
||||
def test_initialization(self):
|
||||
"""Тест инициализации"""
|
||||
config = RateLimitConfig()
|
||||
limiter = GlobalRateLimiter(config)
|
||||
|
||||
|
||||
assert limiter.config == config
|
||||
assert limiter.chat_limiters == {}
|
||||
assert limiter.global_last_send == 0.0
|
||||
|
||||
|
||||
def test_get_chat_limiter(self):
|
||||
"""Тест получения limiter для чата"""
|
||||
config = RateLimitConfig()
|
||||
limiter = GlobalRateLimiter(config)
|
||||
|
||||
|
||||
chat_limiter = limiter.get_chat_limiter(123)
|
||||
assert isinstance(chat_limiter, ChatRateLimiter)
|
||||
assert limiter.chat_limiters[123] == chat_limiter
|
||||
|
||||
|
||||
# Повторный вызов должен вернуть тот же объект
|
||||
same_limiter = limiter.get_chat_limiter(123)
|
||||
assert same_limiter is chat_limiter
|
||||
@@ -118,26 +133,26 @@ class TestGlobalRateLimiter:
|
||||
|
||||
class TestRetryHandler:
|
||||
"""Тесты для RetryHandler"""
|
||||
|
||||
|
||||
def test_initialization(self):
|
||||
"""Тест инициализации"""
|
||||
config = RateLimitConfig()
|
||||
handler = RetryHandler(config)
|
||||
assert handler.config == config
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execute_with_retry_success(self):
|
||||
"""Тест успешного выполнения без retry"""
|
||||
config = RateLimitConfig()
|
||||
handler = RetryHandler(config)
|
||||
|
||||
|
||||
mock_func = AsyncMock(return_value="success")
|
||||
|
||||
|
||||
result = await handler.execute_with_retry(mock_func, 123)
|
||||
|
||||
|
||||
assert result == "success"
|
||||
mock_func.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execute_with_retry_retry_after(self):
|
||||
"""Тест retry после RetryAfter ошибки (sleep патчится, проверяем вызов)."""
|
||||
@@ -148,14 +163,15 @@ class TestRetryHandler:
|
||||
|
||||
mock_func = AsyncMock()
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
retry_after_error = TelegramRetryAfter(
|
||||
method=MagicMock(),
|
||||
message="Flood control exceeded",
|
||||
retry_after=1
|
||||
method=MagicMock(), message="Flood control exceeded", retry_after=1
|
||||
)
|
||||
mock_func.side_effect = [retry_after_error, "success"]
|
||||
|
||||
with patch('helper_bot.utils.rate_limiter.asyncio.sleep', new_callable=AsyncMock) as mock_sleep:
|
||||
with patch(
|
||||
"helper_bot.utils.rate_limiter.asyncio.sleep", new_callable=AsyncMock
|
||||
) as mock_sleep:
|
||||
result = await handler.execute_with_retry(mock_func, 123, max_retries=1)
|
||||
|
||||
assert result == "success"
|
||||
@@ -166,60 +182,60 @@ class TestRetryHandler:
|
||||
|
||||
class TestTelegramRateLimiter:
|
||||
"""Тесты для TelegramRateLimiter"""
|
||||
|
||||
|
||||
def test_initialization(self):
|
||||
"""Тест инициализации"""
|
||||
config = RateLimitConfig()
|
||||
limiter = TelegramRateLimiter(config)
|
||||
|
||||
|
||||
assert limiter.config == config
|
||||
assert isinstance(limiter.global_limiter, GlobalRateLimiter)
|
||||
assert isinstance(limiter.retry_handler, RetryHandler)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_with_rate_limit(self):
|
||||
"""Тест отправки с rate limiting"""
|
||||
config = RateLimitConfig(messages_per_second=10.0, burst_limit=10)
|
||||
limiter = TelegramRateLimiter(config)
|
||||
|
||||
|
||||
mock_send_func = AsyncMock(return_value="sent")
|
||||
|
||||
|
||||
result = await limiter.send_with_rate_limit(mock_send_func, 123)
|
||||
|
||||
|
||||
assert result == "sent"
|
||||
mock_send_func.assert_called_once()
|
||||
|
||||
|
||||
class TestRateLimitMonitor:
|
||||
"""Тесты для RateLimitMonitor"""
|
||||
|
||||
|
||||
def test_initialization(self):
|
||||
"""Тест инициализации"""
|
||||
monitor = RateLimitMonitor()
|
||||
|
||||
|
||||
assert monitor.stats == {}
|
||||
assert isinstance(monitor.global_stats, RateLimitStats)
|
||||
assert monitor.max_history_size == 1000
|
||||
|
||||
|
||||
def test_record_request_success(self):
|
||||
"""Тест записи успешного запроса"""
|
||||
monitor = RateLimitMonitor()
|
||||
|
||||
|
||||
monitor.record_request(123, True, 0.5)
|
||||
|
||||
|
||||
assert 123 in monitor.stats
|
||||
chat_stats = monitor.stats[123]
|
||||
assert chat_stats.total_requests == 1
|
||||
assert chat_stats.successful_requests == 1
|
||||
assert chat_stats.failed_requests == 0
|
||||
assert chat_stats.total_wait_time == 0.5
|
||||
|
||||
|
||||
def test_record_request_failure(self):
|
||||
"""Тест записи неудачного запроса"""
|
||||
monitor = RateLimitMonitor()
|
||||
|
||||
|
||||
monitor.record_request(123, False, 1.0, "RetryAfter")
|
||||
|
||||
|
||||
assert 123 in monitor.stats
|
||||
chat_stats = monitor.stats[123]
|
||||
assert chat_stats.total_requests == 1
|
||||
@@ -227,58 +243,58 @@ class TestRateLimitMonitor:
|
||||
assert chat_stats.failed_requests == 1
|
||||
assert chat_stats.retry_after_errors == 1
|
||||
assert chat_stats.total_wait_time == 1.0
|
||||
|
||||
|
||||
def test_get_chat_stats(self):
|
||||
"""Тест получения статистики чата"""
|
||||
monitor = RateLimitMonitor()
|
||||
|
||||
|
||||
# Статистика для несуществующего чата
|
||||
assert monitor.get_chat_stats(999) is None
|
||||
|
||||
|
||||
# Записываем запрос
|
||||
monitor.record_request(123, True, 0.5)
|
||||
|
||||
|
||||
# Получаем статистику
|
||||
stats = monitor.get_chat_stats(123)
|
||||
assert stats is not None
|
||||
assert stats.chat_id == 123
|
||||
assert stats.total_requests == 1
|
||||
|
||||
|
||||
def test_success_rate_calculation(self):
|
||||
"""Тест расчета процента успеха"""
|
||||
monitor = RateLimitMonitor()
|
||||
|
||||
|
||||
# 3 успешных, 1 неудачный
|
||||
monitor.record_request(123, True, 0.1)
|
||||
monitor.record_request(123, True, 0.2)
|
||||
monitor.record_request(123, True, 0.3)
|
||||
monitor.record_request(123, False, 0.4, "RetryAfter")
|
||||
|
||||
|
||||
stats = monitor.get_chat_stats(123)
|
||||
assert stats.success_rate == 0.75 # 3/4
|
||||
assert stats.error_rate == 0.25 # 1/4
|
||||
assert stats.error_rate == 0.25 # 1/4
|
||||
|
||||
|
||||
class TestRateLimitConfig:
|
||||
"""Тесты для конфигурации rate limiting"""
|
||||
|
||||
|
||||
def test_get_rate_limit_config(self):
|
||||
"""Тест получения конфигурации"""
|
||||
# Тест production конфигурации
|
||||
prod_config = get_rate_limit_config("production")
|
||||
assert prod_config.messages_per_second == 0.5
|
||||
assert prod_config.burst_limit == 2
|
||||
|
||||
|
||||
# Тест development конфигурации
|
||||
dev_config = get_rate_limit_config("development")
|
||||
assert dev_config.messages_per_second == 1.0
|
||||
assert dev_config.burst_limit == 3
|
||||
|
||||
|
||||
# Тест strict конфигурации
|
||||
strict_config = get_rate_limit_config("strict")
|
||||
assert strict_config.messages_per_second == 0.3
|
||||
assert strict_config.burst_limit == 1
|
||||
|
||||
|
||||
# Тест неизвестной конфигурации (должна вернуть production)
|
||||
unknown_config = get_rate_limit_config("unknown")
|
||||
assert unknown_config.messages_per_second == 0.5
|
||||
@@ -288,9 +304,9 @@ class TestRateLimitConfig:
|
||||
async def test_send_with_rate_limit_integration():
|
||||
"""Интеграционный тест для send_with_rate_limit"""
|
||||
mock_send_func = AsyncMock(return_value="message_sent")
|
||||
|
||||
|
||||
result = await send_with_rate_limit(mock_send_func, 123)
|
||||
|
||||
|
||||
assert result == "message_sent"
|
||||
mock_send_func.assert_called_once()
|
||||
|
||||
|
||||
@@ -3,43 +3,46 @@ from unittest.mock import AsyncMock, Mock, patch
|
||||
import pytest
|
||||
from aiogram import types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from helper_bot.handlers.admin.exceptions import (InvalidInputError,
|
||||
UserAlreadyBannedError,
|
||||
UserNotFoundError)
|
||||
|
||||
from helper_bot.handlers.admin.exceptions import (
|
||||
InvalidInputError,
|
||||
UserAlreadyBannedError,
|
||||
UserNotFoundError,
|
||||
)
|
||||
from helper_bot.handlers.admin.services import AdminService, BannedUser, User
|
||||
|
||||
|
||||
class TestAdminService:
|
||||
"""Тесты для AdminService"""
|
||||
|
||||
|
||||
def setup_method(self):
|
||||
"""Настройка перед каждым тестом"""
|
||||
self.mock_db = Mock()
|
||||
self.admin_service = AdminService(self.mock_db)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_last_users_success(self):
|
||||
"""Тест успешного получения списка последних пользователей"""
|
||||
# Arrange
|
||||
# Формат данных: кортежи (full_name, user_id) как возвращает БД
|
||||
mock_users_data = [
|
||||
('User One', 1), # (full_name, user_id)
|
||||
('User Two', 2) # (full_name, user_id)
|
||||
("User One", 1), # (full_name, user_id)
|
||||
("User Two", 2), # (full_name, user_id)
|
||||
]
|
||||
self.mock_db.get_last_users = AsyncMock(return_value=mock_users_data)
|
||||
|
||||
|
||||
# Act
|
||||
result = await self.admin_service.get_last_users()
|
||||
|
||||
|
||||
# Assert
|
||||
assert len(result) == 2
|
||||
assert result[0].user_id == 1
|
||||
assert result[0].username == 'Неизвестно' # username не возвращается из БД
|
||||
assert result[0].full_name == 'User One'
|
||||
assert result[0].username == "Неизвестно" # username не возвращается из БД
|
||||
assert result[0].full_name == "User One"
|
||||
assert result[1].user_id == 2
|
||||
assert result[1].username == 'Неизвестно' # username не возвращается из БД
|
||||
assert result[1].full_name == 'User Two'
|
||||
|
||||
assert result[1].username == "Неизвестно" # username не возвращается из БД
|
||||
assert result[1].full_name == "User Two"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_by_username_success(self):
|
||||
"""Тест успешного получения пользователя по username"""
|
||||
@@ -49,95 +52,102 @@ class TestAdminService:
|
||||
full_name = "Test User"
|
||||
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 = await self.admin_service.get_user_by_username(username)
|
||||
|
||||
|
||||
# Assert
|
||||
assert result is not None
|
||||
assert result.user_id == user_id
|
||||
assert result.username == username
|
||||
assert result.full_name == full_name
|
||||
|
||||
|
||||
@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 = AsyncMock(return_value=None)
|
||||
|
||||
|
||||
# Act
|
||||
result = await self.admin_service.get_user_by_username(username)
|
||||
|
||||
|
||||
# Assert
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_by_id_success(self):
|
||||
"""Тест успешного получения пользователя по ID"""
|
||||
# Arrange
|
||||
user_id = 123
|
||||
from database.models import User as DBUser
|
||||
|
||||
user_info = DBUser(
|
||||
user_id=user_id,
|
||||
first_name="Test",
|
||||
full_name="Test User",
|
||||
username="test_user"
|
||||
full_name="Test User",
|
||||
username="test_user",
|
||||
)
|
||||
self.mock_db.get_user_by_id = AsyncMock(return_value=user_info)
|
||||
|
||||
|
||||
# Act
|
||||
result = await self.admin_service.get_user_by_id(user_id)
|
||||
|
||||
|
||||
# Assert
|
||||
assert result is not None
|
||||
assert result.user_id == user_id
|
||||
assert result.username == 'test_user'
|
||||
assert result.full_name == 'Test User'
|
||||
|
||||
assert result.username == "test_user"
|
||||
assert result.full_name == "Test User"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_by_id_not_found(self):
|
||||
"""Тест получения пользователя по несуществующему ID"""
|
||||
# Arrange
|
||||
user_id = 999
|
||||
self.mock_db.get_user_by_id = AsyncMock(return_value=None)
|
||||
|
||||
|
||||
# Act
|
||||
result = await self.admin_service.get_user_by_id(user_id)
|
||||
|
||||
|
||||
# Assert
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_user_input_success(self):
|
||||
"""Тест успешной валидации ID пользователя"""
|
||||
# Act
|
||||
result = await self.admin_service.validate_user_input("123")
|
||||
|
||||
|
||||
# Assert
|
||||
assert result == 123
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_user_input_invalid_number(self):
|
||||
"""Тест валидации некорректного ID"""
|
||||
# Act & Assert
|
||||
with pytest.raises(InvalidInputError, match="ID пользователя должен быть числом"):
|
||||
with pytest.raises(
|
||||
InvalidInputError, match="ID пользователя должен быть числом"
|
||||
):
|
||||
await self.admin_service.validate_user_input("abc")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_user_input_negative_number(self):
|
||||
"""Тест валидации отрицательного ID"""
|
||||
# Act & Assert
|
||||
with pytest.raises(InvalidInputError, match="ID пользователя должен быть положительным числом"):
|
||||
with pytest.raises(
|
||||
InvalidInputError, match="ID пользователя должен быть положительным числом"
|
||||
):
|
||||
await self.admin_service.validate_user_input("-1")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_user_input_zero(self):
|
||||
"""Тест валидации нулевого ID"""
|
||||
# Act & Assert
|
||||
with pytest.raises(InvalidInputError, match="ID пользователя должен быть положительным числом"):
|
||||
with pytest.raises(
|
||||
InvalidInputError, match="ID пользователя должен быть положительным числом"
|
||||
):
|
||||
await self.admin_service.validate_user_input("0")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ban_user_success(self):
|
||||
"""Тест успешной блокировки пользователя"""
|
||||
@@ -146,17 +156,19 @@ class TestAdminService:
|
||||
username = "test_user"
|
||||
reason = "Test ban"
|
||||
ban_days = 7
|
||||
|
||||
|
||||
self.mock_db.check_user_in_blacklist = AsyncMock(return_value=False)
|
||||
self.mock_db.set_user_blacklist = AsyncMock(return_value=None)
|
||||
|
||||
|
||||
# Act
|
||||
await self.admin_service.ban_user(user_id, username, reason, ban_days, ban_author_id=999)
|
||||
|
||||
await self.admin_service.ban_user(
|
||||
user_id, username, reason, ban_days, ban_author_id=999
|
||||
)
|
||||
|
||||
# Assert
|
||||
self.mock_db.check_user_in_blacklist.assert_called_once_with(user_id)
|
||||
self.mock_db.set_user_blacklist.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ban_user_already_banned(self):
|
||||
"""Тест попытки заблокировать уже заблокированного пользователя"""
|
||||
@@ -165,13 +177,17 @@ class TestAdminService:
|
||||
username = "test_user"
|
||||
reason = "Test ban"
|
||||
ban_days = 7
|
||||
|
||||
|
||||
self.mock_db.check_user_in_blacklist = AsyncMock(return_value=True)
|
||||
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(UserAlreadyBannedError, match=f"Пользователь {user_id} уже заблокирован"):
|
||||
await self.admin_service.ban_user(user_id, username, reason, ban_days, ban_author_id=999)
|
||||
|
||||
with pytest.raises(
|
||||
UserAlreadyBannedError, match=f"Пользователь {user_id} уже заблокирован"
|
||||
):
|
||||
await self.admin_service.ban_user(
|
||||
user_id, username, reason, ban_days, ban_author_id=999
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ban_user_permanent(self):
|
||||
"""Тест постоянной блокировки пользователя"""
|
||||
@@ -180,38 +196,42 @@ class TestAdminService:
|
||||
username = "test_user"
|
||||
reason = "Permanent ban"
|
||||
ban_days = None
|
||||
|
||||
|
||||
self.mock_db.check_user_in_blacklist = AsyncMock(return_value=False)
|
||||
self.mock_db.set_user_blacklist = AsyncMock(return_value=None)
|
||||
|
||||
|
||||
# Act
|
||||
await self.admin_service.ban_user(user_id, username, reason, ban_days, ban_author_id=999)
|
||||
|
||||
await self.admin_service.ban_user(
|
||||
user_id, username, reason, ban_days, ban_author_id=999
|
||||
)
|
||||
|
||||
# Assert
|
||||
self.mock_db.set_user_blacklist.assert_called_once_with(user_id, None, reason, None, ban_author=999)
|
||||
|
||||
self.mock_db.set_user_blacklist.assert_called_once_with(
|
||||
user_id, None, reason, None, ban_author=999
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unban_user_success(self):
|
||||
"""Тест успешной разблокировки пользователя"""
|
||||
# Arrange
|
||||
user_id = 123
|
||||
self.mock_db.delete_user_blacklist = AsyncMock(return_value=None)
|
||||
|
||||
|
||||
# Act
|
||||
await self.admin_service.unban_user(user_id)
|
||||
|
||||
|
||||
# Assert
|
||||
self.mock_db.delete_user_blacklist.assert_called_once_with(user_id)
|
||||
|
||||
|
||||
class TestUser:
|
||||
"""Тесты для модели User"""
|
||||
|
||||
|
||||
def test_user_creation(self):
|
||||
"""Тест создания объекта User"""
|
||||
# Act
|
||||
user = User(user_id=123, username="test_user", full_name="Test User")
|
||||
|
||||
|
||||
# Assert
|
||||
assert user.user_id == 123
|
||||
assert user.username == "test_user"
|
||||
@@ -220,17 +240,17 @@ class TestUser:
|
||||
|
||||
class TestBannedUser:
|
||||
"""Тесты для модели BannedUser"""
|
||||
|
||||
|
||||
def test_banned_user_creation(self):
|
||||
"""Тест создания объекта BannedUser"""
|
||||
# Act
|
||||
banned_user = BannedUser(
|
||||
user_id=123,
|
||||
username="test_user",
|
||||
reason="Test ban",
|
||||
unban_date="2025-01-01"
|
||||
user_id=123,
|
||||
username="test_user",
|
||||
reason="Test ban",
|
||||
unban_date="2025-01-01",
|
||||
)
|
||||
|
||||
|
||||
# Assert
|
||||
assert banned_user.user_id == 123
|
||||
assert banned_user.username == "test_user"
|
||||
|
||||
@@ -5,29 +5,34 @@ from unittest.mock import AsyncMock, MagicMock, Mock
|
||||
import pytest
|
||||
from aiogram import types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
|
||||
from helper_bot.handlers.group.constants import ERROR_MESSAGES, FSM_STATES
|
||||
from helper_bot.handlers.group.exceptions import (NoReplyToMessageError,
|
||||
UserNotFoundError)
|
||||
from helper_bot.handlers.group.group_handlers import (GroupHandlers,
|
||||
create_group_handlers)
|
||||
from helper_bot.handlers.group.exceptions import (
|
||||
NoReplyToMessageError,
|
||||
UserNotFoundError,
|
||||
)
|
||||
from helper_bot.handlers.group.group_handlers import (
|
||||
GroupHandlers,
|
||||
create_group_handlers,
|
||||
)
|
||||
from helper_bot.handlers.group.services import AdminReplyService
|
||||
|
||||
|
||||
class TestGroupHandlers:
|
||||
"""Test class for GroupHandlers"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db(self):
|
||||
"""Mock database"""
|
||||
db = Mock()
|
||||
db.get_user_by_message_id = Mock()
|
||||
return db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_keyboard_markup(self):
|
||||
"""Mock keyboard markup"""
|
||||
return Mock()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_message(self):
|
||||
"""Mock Telegram message"""
|
||||
@@ -44,7 +49,7 @@ class TestGroupHandlers:
|
||||
message.bot = Mock()
|
||||
message.bot.send_message = AsyncMock()
|
||||
return message
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_reply_message(self, mock_message):
|
||||
"""Mock reply message"""
|
||||
@@ -52,21 +57,21 @@ class TestGroupHandlers:
|
||||
reply_message.message_id = 222
|
||||
mock_message.reply_to_message = reply_message
|
||||
return mock_message
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_state(self):
|
||||
"""Mock FSM state"""
|
||||
state = Mock(spec=FSMContext)
|
||||
state.set_state = AsyncMock()
|
||||
return state
|
||||
|
||||
|
||||
def test_create_group_handlers(self, mock_db, mock_keyboard_markup):
|
||||
"""Test creating group handlers instance"""
|
||||
handlers = create_group_handlers(mock_db, mock_keyboard_markup)
|
||||
assert isinstance(handlers, GroupHandlers)
|
||||
assert handlers.db == mock_db
|
||||
assert handlers.keyboard_markup == mock_keyboard_markup
|
||||
|
||||
|
||||
def test_group_handlers_initialization(self, mock_db, mock_keyboard_markup):
|
||||
"""Test GroupHandlers initialization"""
|
||||
handlers = GroupHandlers(mock_db, mock_keyboard_markup)
|
||||
@@ -74,109 +79,121 @@ class TestGroupHandlers:
|
||||
assert handlers.keyboard_markup == mock_keyboard_markup
|
||||
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):
|
||||
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 = AsyncMock(return_value=99999)
|
||||
|
||||
|
||||
handlers = create_group_handlers(mock_db, mock_keyboard_markup)
|
||||
|
||||
|
||||
# Mock the send_reply_to_user method
|
||||
handlers.admin_reply_service.send_reply_to_user = AsyncMock()
|
||||
|
||||
|
||||
await handlers.handle_message(mock_reply_message, mock_state)
|
||||
|
||||
|
||||
# Verify database call
|
||||
mock_db.get_user_by_message_id.assert_called_once_with(222)
|
||||
|
||||
|
||||
# Verify service call
|
||||
handlers.admin_reply_service.send_reply_to_user.assert_called_once_with(
|
||||
99999, mock_reply_message, "test reply message", mock_keyboard_markup
|
||||
)
|
||||
|
||||
|
||||
# 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):
|
||||
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)
|
||||
|
||||
|
||||
# Mock the send_reply_to_user method to prevent it from being called
|
||||
handlers.admin_reply_service.send_reply_to_user = AsyncMock()
|
||||
|
||||
|
||||
# Ensure reply_to_message is None
|
||||
mock_message.reply_to_message = None
|
||||
|
||||
|
||||
await handlers.handle_message(mock_message, mock_state)
|
||||
|
||||
|
||||
# Verify error message was sent
|
||||
mock_message.answer.assert_called_once_with(ERROR_MESSAGES["NO_REPLY_TO_MESSAGE"])
|
||||
|
||||
mock_message.answer.assert_called_once_with(
|
||||
ERROR_MESSAGES["NO_REPLY_TO_MESSAGE"]
|
||||
)
|
||||
|
||||
# Verify no database calls
|
||||
mock_db.get_user_by_message_id.assert_not_called()
|
||||
|
||||
|
||||
# Verify send_reply_to_user was not called
|
||||
handlers.admin_reply_service.send_reply_to_user.assert_not_called()
|
||||
|
||||
|
||||
# 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):
|
||||
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 = AsyncMock(return_value=None)
|
||||
|
||||
|
||||
handlers = create_group_handlers(mock_db, mock_keyboard_markup)
|
||||
|
||||
|
||||
await handlers.handle_message(mock_reply_message, mock_state)
|
||||
|
||||
|
||||
# Verify error message was sent
|
||||
mock_reply_message.answer.assert_called_once_with(ERROR_MESSAGES["USER_NOT_FOUND"])
|
||||
|
||||
mock_reply_message.answer.assert_called_once_with(
|
||||
ERROR_MESSAGES["USER_NOT_FOUND"]
|
||||
)
|
||||
|
||||
# Verify database call
|
||||
mock_db.get_user_by_message_id.assert_called_once_with(222)
|
||||
|
||||
|
||||
# Verify state was not set
|
||||
mock_state.set_state.assert_not_called()
|
||||
|
||||
|
||||
class TestAdminReplyService:
|
||||
"""Test class for AdminReplyService"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db(self):
|
||||
"""Mock database"""
|
||||
db = Mock()
|
||||
db.get_user_by_message_id = Mock()
|
||||
return db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def service(self, mock_db):
|
||||
"""Create service instance"""
|
||||
return AdminReplyService(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 = AsyncMock(return_value=12345)
|
||||
|
||||
|
||||
result = await service.get_user_id_for_reply(111)
|
||||
|
||||
|
||||
assert result == 12345
|
||||
mock_db.get_user_by_message_id.assert_called_once_with(111)
|
||||
|
||||
|
||||
@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 = AsyncMock(return_value=None)
|
||||
|
||||
with pytest.raises(UserNotFoundError, match="User not found for message_id: 111"):
|
||||
|
||||
with pytest.raises(
|
||||
UserNotFoundError, match="User not found for message_id: 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"""
|
||||
@@ -184,12 +201,14 @@ class TestAdminReplyService:
|
||||
message.reply_to_message = Mock()
|
||||
message.reply_to_message.message_id = 222
|
||||
markup = Mock()
|
||||
|
||||
|
||||
# Mock the send_text_message function
|
||||
with pytest.MonkeyPatch().context() as m:
|
||||
mock_send_text = AsyncMock()
|
||||
m.setattr('helper_bot.handlers.group.services.send_text_message', mock_send_text)
|
||||
|
||||
m.setattr(
|
||||
"helper_bot.handlers.group.services.send_text_message", mock_send_text
|
||||
)
|
||||
|
||||
await service.send_reply_to_user(12345, message, "test reply", markup)
|
||||
|
||||
|
||||
mock_send_text.assert_called_once_with(12345, message, "test reply", markup)
|
||||
|
||||
@@ -5,15 +5,18 @@ from unittest.mock import AsyncMock, MagicMock, Mock
|
||||
import pytest
|
||||
from aiogram import types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
|
||||
from helper_bot.handlers.private.constants import BUTTON_TEXTS, FSM_STATES
|
||||
from helper_bot.handlers.private.private_handlers import (
|
||||
PrivateHandlers, create_private_handlers)
|
||||
PrivateHandlers,
|
||||
create_private_handlers,
|
||||
)
|
||||
from helper_bot.handlers.private.services import BotSettings
|
||||
|
||||
|
||||
class TestPrivateHandlers:
|
||||
"""Test class for PrivateHandlers"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db(self):
|
||||
"""Mock database"""
|
||||
@@ -26,7 +29,7 @@ class TestPrivateHandlers:
|
||||
db.add_message = AsyncMock()
|
||||
db.update_helper_message = AsyncMock()
|
||||
return db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_settings(self):
|
||||
"""Mock bot settings"""
|
||||
@@ -38,9 +41,9 @@ class TestPrivateHandlers:
|
||||
important_logs="test_important",
|
||||
preview_link="test_link",
|
||||
logs="test_logs_setting",
|
||||
test="test_test_setting"
|
||||
test="test_test_setting",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_message(self):
|
||||
"""Mock Telegram message"""
|
||||
@@ -53,21 +56,21 @@ class TestPrivateHandlers:
|
||||
from_user.is_bot = False
|
||||
from_user.language_code = "ru"
|
||||
message.from_user = from_user
|
||||
|
||||
|
||||
message.text = "test message"
|
||||
|
||||
|
||||
# Создаем мок для chat
|
||||
chat = Mock()
|
||||
chat.id = 12345
|
||||
message.chat = chat
|
||||
|
||||
|
||||
message.bot = Mock()
|
||||
message.bot.send_message = AsyncMock()
|
||||
message.forward = AsyncMock()
|
||||
message.answer = AsyncMock()
|
||||
message.answer_sticker = AsyncMock()
|
||||
return message
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_state(self):
|
||||
"""Mock FSM state"""
|
||||
@@ -75,14 +78,14 @@ class TestPrivateHandlers:
|
||||
state.set_state = AsyncMock()
|
||||
state.get_state = AsyncMock(return_value=FSM_STATES["START"])
|
||||
return state
|
||||
|
||||
|
||||
def test_create_private_handlers(self, mock_db, mock_settings):
|
||||
"""Test creating private handlers instance"""
|
||||
handlers = create_private_handlers(mock_db, mock_settings)
|
||||
assert isinstance(handlers, PrivateHandlers)
|
||||
assert handlers.db == mock_db
|
||||
assert handlers.settings == mock_settings
|
||||
|
||||
|
||||
def test_private_handlers_initialization(self, mock_db, mock_settings):
|
||||
"""Test PrivateHandlers initialization"""
|
||||
handlers = PrivateHandlers(mock_db, mock_settings)
|
||||
@@ -92,44 +95,62 @@ class TestPrivateHandlers:
|
||||
assert handlers.post_service is not None
|
||||
assert handlers.sticker_service is not None
|
||||
assert handlers.router is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_emoji_message(self, mock_db, mock_settings, mock_message, mock_state):
|
||||
async def test_handle_emoji_message(
|
||||
self, mock_db, mock_settings, mock_message, mock_state
|
||||
):
|
||||
"""Test emoji message handler"""
|
||||
handlers = create_private_handlers(mock_db, mock_settings)
|
||||
|
||||
|
||||
# Mock the check_user_emoji function
|
||||
with pytest.MonkeyPatch().context() as m:
|
||||
mock_check_emoji = AsyncMock(return_value="😊")
|
||||
m.setattr('helper_bot.handlers.private.private_handlers.check_user_emoji', mock_check_emoji)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
# Verify state was set
|
||||
mock_state.set_state.assert_called_once_with(FSM_STATES["START"])
|
||||
|
||||
|
||||
# Verify message was logged
|
||||
mock_message.forward.assert_called_once_with(chat_id=mock_settings.group_for_logs)
|
||||
|
||||
mock_message.forward.assert_called_once_with(
|
||||
chat_id=mock_settings.group_for_logs
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_start_message(self, mock_db, mock_settings, mock_message, mock_state):
|
||||
async def test_handle_start_message(
|
||||
self, mock_db, mock_settings, mock_message, mock_state
|
||||
):
|
||||
"""Test start message handler"""
|
||||
handlers = create_private_handlers(mock_db, mock_settings)
|
||||
|
||||
|
||||
# Mock the get_first_name and messages functions
|
||||
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_first_name",
|
||||
lambda x: "Test",
|
||||
)
|
||||
m.setattr(
|
||||
"helper_bot.handlers.private.private_handlers.messages.get_message",
|
||||
lambda x, y: "Hello Test!",
|
||||
)
|
||||
mock_keyboard = AsyncMock(return_value=Mock())
|
||||
m.setattr('helper_bot.handlers.private.private_handlers.get_reply_keyboard', mock_keyboard)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
# Verify state was set
|
||||
mock_state.set_state.assert_called_once_with(FSM_STATES["START"])
|
||||
|
||||
|
||||
# Verify user was ensured to exist
|
||||
mock_db.add_user.assert_called_once()
|
||||
mock_db.update_user_date.assert_called_once()
|
||||
@@ -137,7 +158,7 @@ class TestPrivateHandlers:
|
||||
|
||||
class TestBotSettings:
|
||||
"""Test class for BotSettings dataclass"""
|
||||
|
||||
|
||||
def test_bot_settings_creation(self):
|
||||
"""Test creating BotSettings instance"""
|
||||
settings = BotSettings(
|
||||
@@ -148,9 +169,9 @@ class TestBotSettings:
|
||||
important_logs="important",
|
||||
preview_link="link",
|
||||
logs="logs_setting",
|
||||
test="test_setting"
|
||||
test="test_setting",
|
||||
)
|
||||
|
||||
|
||||
assert settings.group_for_posts == "posts"
|
||||
assert settings.group_for_message == "message"
|
||||
assert settings.main_public == "public"
|
||||
@@ -163,14 +184,14 @@ class TestBotSettings:
|
||||
|
||||
class TestConstants:
|
||||
"""Test class for constants"""
|
||||
|
||||
|
||||
def test_fsm_states(self):
|
||||
"""Test FSM states constants"""
|
||||
assert FSM_STATES["START"] == "START"
|
||||
assert FSM_STATES["SUGGEST"] == "SUGGEST"
|
||||
assert FSM_STATES["PRE_CHAT"] == "PRE_CHAT"
|
||||
assert FSM_STATES["CHAT"] == "CHAT"
|
||||
|
||||
|
||||
def test_button_texts(self):
|
||||
"""Test button text constants"""
|
||||
assert BUTTON_TEXTS["SUGGEST_POST"] == "📢Предложить свой пост"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,126 +3,133 @@ from pathlib import Path
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from helper_bot.handlers.voice.exceptions import (AudioProcessingError,
|
||||
VoiceMessageError)
|
||||
|
||||
from helper_bot.handlers.voice.exceptions import AudioProcessingError, VoiceMessageError
|
||||
from helper_bot.handlers.voice.services import VoiceBotService
|
||||
from helper_bot.handlers.voice.utils import (get_last_message_text,
|
||||
get_user_emoji_safe,
|
||||
validate_voice_message)
|
||||
from helper_bot.handlers.voice.utils import (
|
||||
get_last_message_text,
|
||||
get_user_emoji_safe,
|
||||
validate_voice_message,
|
||||
)
|
||||
|
||||
|
||||
class TestVoiceBotService:
|
||||
"""Тесты для VoiceBotService"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_bot_db(self):
|
||||
"""Мок для базы данных"""
|
||||
mock_db = Mock()
|
||||
mock_db.settings = {
|
||||
'Settings': {'logs': True},
|
||||
'Telegram': {'important_logs': 'test_chat_id'}
|
||||
"Settings": {"logs": True},
|
||||
"Telegram": {"important_logs": "test_chat_id"},
|
||||
}
|
||||
return mock_db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_settings(self):
|
||||
"""Мок для настроек"""
|
||||
return {
|
||||
'Settings': {'logs': True},
|
||||
'Telegram': {'preview_link': True}
|
||||
}
|
||||
|
||||
return {"Settings": {"logs": True}, "Telegram": {"preview_link": True}}
|
||||
|
||||
@pytest.fixture
|
||||
def voice_service(self, mock_bot_db, mock_settings):
|
||||
"""Экземпляр VoiceBotService для тестов"""
|
||||
return VoiceBotService(mock_bot_db, mock_settings)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_welcome_sticker_success(self, voice_service, mock_settings):
|
||||
"""Тест успешного получения стикера"""
|
||||
with patch('pathlib.Path.rglob') as mock_rglob:
|
||||
mock_rglob.return_value = ['/path/to/sticker1.tgs', '/path/to/sticker2.tgs']
|
||||
|
||||
with patch("pathlib.Path.rglob") as mock_rglob:
|
||||
mock_rglob.return_value = ["/path/to/sticker1.tgs", "/path/to/sticker2.tgs"]
|
||||
|
||||
sticker = await voice_service.get_welcome_sticker()
|
||||
|
||||
|
||||
assert sticker is not None
|
||||
mock_rglob.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_welcome_sticker_no_stickers(self, voice_service, mock_settings):
|
||||
"""Тест получения стикера когда их нет"""
|
||||
with patch('pathlib.Path.rglob') as mock_rglob:
|
||||
with patch("pathlib.Path.rglob") as mock_rglob:
|
||||
mock_rglob.return_value = []
|
||||
|
||||
|
||||
sticker = await voice_service.get_welcome_sticker()
|
||||
|
||||
|
||||
assert sticker is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_random_audio_success(self, voice_service, mock_bot_db):
|
||||
"""Тест успешного получения случайного аудио"""
|
||||
mock_bot_db.check_listen_audio = AsyncMock(return_value=['audio1', 'audio2'])
|
||||
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='😊')
|
||||
|
||||
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 = await voice_service.get_random_audio(456)
|
||||
|
||||
|
||||
assert result is not None
|
||||
assert len(result) == 3
|
||||
assert result[0] in ['audio1', 'audio2']
|
||||
assert result[1] == '2025-01-01 12:00:00'
|
||||
assert result[2] == '😊'
|
||||
|
||||
assert result[0] in ["audio1", "audio2"]
|
||||
assert result[1] == "2025-01-01 12:00:00"
|
||||
assert result[2] == "😊"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_random_audio_no_audio(self, voice_service, mock_bot_db):
|
||||
"""Тест получения аудио когда их нет"""
|
||||
mock_bot_db.check_listen_audio = AsyncMock(return_value=[])
|
||||
|
||||
|
||||
result = await voice_service.get_random_audio(456)
|
||||
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mark_audio_as_listened_success(self, voice_service, mock_bot_db):
|
||||
"""Тест успешной пометки аудио как прослушанного"""
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clear_user_listenings_success(self, voice_service, mock_bot_db):
|
||||
"""Тест успешной очистки прослушиваний"""
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_remaining_audio_count_success(self, voice_service, mock_bot_db):
|
||||
"""Тест получения количества оставшихся аудио"""
|
||||
mock_bot_db.check_listen_audio = AsyncMock(return_value=['audio1', 'audio2', 'audio3'])
|
||||
|
||||
mock_bot_db.check_listen_audio = AsyncMock(
|
||||
return_value=["audio1", "audio2", "audio3"]
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_remaining_audio_count_zero(self, voice_service, mock_bot_db):
|
||||
"""Тест получения количества оставшихся аудио когда их нет"""
|
||||
mock_bot_db.check_listen_audio = AsyncMock(return_value=[])
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_welcome_messages_success(self, voice_service, mock_bot_db, mock_settings):
|
||||
async def test_send_welcome_messages_success(
|
||||
self, voice_service, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест успешной отправки приветственных сообщений."""
|
||||
mock_message = Mock()
|
||||
mock_message.from_user.id = 123
|
||||
@@ -130,58 +137,77 @@ class TestVoiceBotService:
|
||||
mock_message.answer.return_value = Mock()
|
||||
mock_message.answer_sticker = AsyncMock()
|
||||
|
||||
with patch.object(voice_service, 'get_welcome_sticker', new_callable=AsyncMock, return_value='test_sticker.tgs'):
|
||||
with patch('helper_bot.handlers.voice.services.asyncio.sleep', new_callable=AsyncMock):
|
||||
await voice_service.send_welcome_messages(mock_message, '😊')
|
||||
with patch.object(
|
||||
voice_service,
|
||||
"get_welcome_sticker",
|
||||
new_callable=AsyncMock,
|
||||
return_value="test_sticker.tgs",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.voice.services.asyncio.sleep",
|
||||
new_callable=AsyncMock,
|
||||
):
|
||||
await voice_service.send_welcome_messages(mock_message, "😊")
|
||||
|
||||
assert mock_message.answer.call_count >= 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_welcome_messages_no_sticker(self, voice_service, mock_bot_db, mock_settings):
|
||||
async def test_send_welcome_messages_no_sticker(
|
||||
self, voice_service, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест отправки приветственных сообщений без стикера."""
|
||||
mock_message = Mock()
|
||||
mock_message.from_user.id = 123
|
||||
mock_message.answer = AsyncMock()
|
||||
mock_message.answer.return_value = Mock()
|
||||
|
||||
with patch.object(voice_service, 'get_welcome_sticker', new_callable=AsyncMock, return_value=None):
|
||||
with patch('helper_bot.handlers.voice.services.asyncio.sleep', new_callable=AsyncMock):
|
||||
await voice_service.send_welcome_messages(mock_message, '😊')
|
||||
with patch.object(
|
||||
voice_service,
|
||||
"get_welcome_sticker",
|
||||
new_callable=AsyncMock,
|
||||
return_value=None,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.voice.services.asyncio.sleep",
|
||||
new_callable=AsyncMock,
|
||||
):
|
||||
await voice_service.send_welcome_messages(mock_message, "😊")
|
||||
|
||||
assert mock_message.answer.call_count >= 1
|
||||
|
||||
|
||||
class TestVoiceHandlers:
|
||||
"""Тесты для VoiceHandlers"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db(self):
|
||||
"""Мок для базы данных"""
|
||||
return Mock()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_settings(self):
|
||||
"""Мок для настроек"""
|
||||
return {
|
||||
'Telegram': {
|
||||
'group_for_logs': 'test_logs_chat',
|
||||
'group_for_posts': 'test_posts_chat',
|
||||
'preview_link': True
|
||||
"Telegram": {
|
||||
"group_for_logs": "test_logs_chat",
|
||||
"group_for_posts": "test_posts_chat",
|
||||
"preview_link": True,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def voice_handlers(self, mock_db, mock_settings):
|
||||
"""Экземпляр VoiceHandlers для тестов"""
|
||||
from helper_bot.handlers.voice.voice_handler import VoiceHandlers
|
||||
|
||||
return VoiceHandlers(mock_db, mock_settings)
|
||||
|
||||
|
||||
def test_voice_handlers_initialization(self, voice_handlers):
|
||||
"""Тест инициализации VoiceHandlers"""
|
||||
assert voice_handlers.db is not None
|
||||
assert voice_handlers.settings is not None
|
||||
assert voice_handlers.router is not None
|
||||
|
||||
|
||||
def test_setup_handlers(self, voice_handlers):
|
||||
"""Тест настройки обработчиков"""
|
||||
# Проверяем, что роутер содержит обработчики
|
||||
@@ -190,84 +216,92 @@ class TestVoiceHandlers:
|
||||
|
||||
class TestUtils:
|
||||
"""Тесты для утилит"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_bot_db(self):
|
||||
"""Мок для базы данных"""
|
||||
return Mock()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_last_message_text(self, mock_bot_db):
|
||||
"""Тест получения последнего сообщения"""
|
||||
# Возвращаем UNIX timestamp
|
||||
mock_bot_db.last_date_audio = AsyncMock(return_value=1641034800) # 2022-01-01 12:00:00
|
||||
|
||||
mock_bot_db.last_date_audio = AsyncMock(
|
||||
return_value=1641034800
|
||||
) # 2022-01-01 12:00:00
|
||||
|
||||
result = await get_last_message_text(mock_bot_db)
|
||||
|
||||
|
||||
assert result is not None
|
||||
assert "минут" in result or "часа" in result or "дня" 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()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_voice_message_valid(self):
|
||||
"""Тест валидации голосового сообщения"""
|
||||
mock_message = Mock()
|
||||
mock_message.content_type = 'voice'
|
||||
mock_message.content_type = "voice"
|
||||
mock_message.voice = Mock()
|
||||
|
||||
|
||||
result = await validate_voice_message(mock_message)
|
||||
|
||||
|
||||
assert result is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_voice_message_invalid(self):
|
||||
"""Тест валидации невалидного сообщения"""
|
||||
mock_message = Mock()
|
||||
mock_message.voice = None
|
||||
|
||||
|
||||
result = await validate_voice_message(mock_message)
|
||||
|
||||
|
||||
assert result is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_emoji_safe(self, mock_bot_db):
|
||||
"""Тест безопасного получения эмодзи пользователя"""
|
||||
mock_bot_db.get_user_emoji = AsyncMock(return_value="😊")
|
||||
|
||||
|
||||
result = await get_user_emoji_safe(mock_bot_db, 123)
|
||||
|
||||
|
||||
assert result == "😊"
|
||||
mock_bot_db.get_user_emoji.assert_called_once_with(123)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_emoji_safe_none(self, mock_bot_db):
|
||||
"""Тест безопасного получения эмодзи когда его нет"""
|
||||
mock_bot_db.get_user_emoji = AsyncMock(return_value=None)
|
||||
|
||||
|
||||
result = await get_user_emoji_safe(mock_bot_db, 123)
|
||||
|
||||
|
||||
assert result == "😊"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_emoji_safe_error(self, mock_bot_db):
|
||||
"""Тест безопасного получения эмодзи при ошибке"""
|
||||
mock_bot_db.get_user_emoji = AsyncMock(return_value="Ошибка")
|
||||
|
||||
|
||||
result = await get_user_emoji_safe(mock_bot_db, 123)
|
||||
|
||||
|
||||
assert result == "Ошибка"
|
||||
|
||||
|
||||
class TestExceptions:
|
||||
"""Тесты для исключений"""
|
||||
|
||||
|
||||
def test_voice_message_error(self):
|
||||
"""Тест VoiceMessageError"""
|
||||
try:
|
||||
raise VoiceMessageError("Тестовая ошибка")
|
||||
except VoiceMessageError as e:
|
||||
assert str(e) == "Тестовая ошибка"
|
||||
|
||||
|
||||
def test_audio_processing_error(self):
|
||||
"""Тест AudioProcessingError"""
|
||||
try:
|
||||
@@ -276,5 +310,5 @@ class TestExceptions:
|
||||
assert str(e) == "Ошибка обработки"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__])
|
||||
|
||||
@@ -1,47 +1,55 @@
|
||||
import pytest
|
||||
from helper_bot.handlers.voice.constants import (BTN_LISTEN, BTN_SPEAK,
|
||||
BUTTON_COMMAND_MAPPING,
|
||||
CALLBACK_COMMAND_MAPPING,
|
||||
CALLBACK_DELETE,
|
||||
CALLBACK_SAVE, CMD_EMOJI,
|
||||
CMD_HELP, CMD_REFRESH,
|
||||
CMD_RESTART, CMD_START,
|
||||
COMMAND_MAPPING,
|
||||
STATE_STANDUP_WRITE,
|
||||
STATE_START, VOICE_BOT_NAME)
|
||||
|
||||
from helper_bot.handlers.voice.constants import (
|
||||
BTN_LISTEN,
|
||||
BTN_SPEAK,
|
||||
BUTTON_COMMAND_MAPPING,
|
||||
CALLBACK_COMMAND_MAPPING,
|
||||
CALLBACK_DELETE,
|
||||
CALLBACK_SAVE,
|
||||
CMD_EMOJI,
|
||||
CMD_HELP,
|
||||
CMD_REFRESH,
|
||||
CMD_RESTART,
|
||||
CMD_START,
|
||||
COMMAND_MAPPING,
|
||||
STATE_STANDUP_WRITE,
|
||||
STATE_START,
|
||||
VOICE_BOT_NAME,
|
||||
)
|
||||
|
||||
|
||||
class TestVoiceConstants:
|
||||
"""Тесты для констант voice модуля"""
|
||||
|
||||
|
||||
def test_button_command_mapping_structure(self):
|
||||
"""Тест структуры BUTTON_COMMAND_MAPPING"""
|
||||
assert isinstance(BUTTON_COMMAND_MAPPING, dict)
|
||||
assert len(BUTTON_COMMAND_MAPPING) > 0
|
||||
|
||||
|
||||
# Проверяем, что все значения являются строками
|
||||
for key, value in BUTTON_COMMAND_MAPPING.items():
|
||||
assert isinstance(key, str)
|
||||
assert isinstance(value, str)
|
||||
|
||||
|
||||
def test_button_command_mapping_specific_values(self):
|
||||
"""Тест конкретных значений в BUTTON_COMMAND_MAPPING"""
|
||||
assert "🎤Высказаться" in BUTTON_COMMAND_MAPPING
|
||||
assert "🎧Послушать" in BUTTON_COMMAND_MAPPING
|
||||
|
||||
|
||||
assert BUTTON_COMMAND_MAPPING["🎤Высказаться"] == "voice_speak"
|
||||
assert BUTTON_COMMAND_MAPPING["🎧Послушать"] == "voice_listen"
|
||||
|
||||
|
||||
def test_command_mapping_structure(self):
|
||||
"""Тест структуры COMMAND_MAPPING"""
|
||||
assert isinstance(COMMAND_MAPPING, dict)
|
||||
assert len(COMMAND_MAPPING) > 0
|
||||
|
||||
|
||||
# Проверяем, что все значения являются строками
|
||||
for key, value in COMMAND_MAPPING.items():
|
||||
assert isinstance(key, str)
|
||||
assert isinstance(value, str)
|
||||
|
||||
|
||||
def test_command_mapping_specific_values(self):
|
||||
"""Тест конкретных значений в COMMAND_MAPPING"""
|
||||
assert "start" in COMMAND_MAPPING
|
||||
@@ -49,51 +57,51 @@ class TestVoiceConstants:
|
||||
assert "restart" in COMMAND_MAPPING
|
||||
assert "emoji" in COMMAND_MAPPING
|
||||
assert "refresh" in COMMAND_MAPPING
|
||||
|
||||
|
||||
assert COMMAND_MAPPING["start"] == "voice_start"
|
||||
assert COMMAND_MAPPING["help"] == "voice_help"
|
||||
assert COMMAND_MAPPING["restart"] == "voice_restart"
|
||||
assert COMMAND_MAPPING["emoji"] == "voice_emoji"
|
||||
assert COMMAND_MAPPING["refresh"] == "voice_refresh"
|
||||
|
||||
|
||||
def test_callback_command_mapping_structure(self):
|
||||
"""Тест структуры CALLBACK_COMMAND_MAPPING"""
|
||||
assert isinstance(CALLBACK_COMMAND_MAPPING, dict)
|
||||
assert len(CALLBACK_COMMAND_MAPPING) > 0
|
||||
|
||||
|
||||
# Проверяем, что все значения являются строками
|
||||
for key, value in CALLBACK_COMMAND_MAPPING.items():
|
||||
assert isinstance(key, str)
|
||||
assert isinstance(value, str)
|
||||
|
||||
|
||||
def test_callback_command_mapping_specific_values(self):
|
||||
"""Тест конкретных значений в CALLBACK_COMMAND_MAPPING"""
|
||||
assert "save" in CALLBACK_COMMAND_MAPPING
|
||||
assert "delete" in CALLBACK_COMMAND_MAPPING
|
||||
|
||||
|
||||
assert CALLBACK_COMMAND_MAPPING["save"] == "voice_save"
|
||||
assert CALLBACK_COMMAND_MAPPING["delete"] == "voice_delete"
|
||||
|
||||
|
||||
def test_voice_bot_name(self):
|
||||
"""Тест VOICE_BOT_NAME"""
|
||||
assert isinstance(VOICE_BOT_NAME, str)
|
||||
assert len(VOICE_BOT_NAME) > 0
|
||||
assert "voice" in VOICE_BOT_NAME.lower()
|
||||
|
||||
|
||||
def test_state_constants(self):
|
||||
"""Тест констант состояний"""
|
||||
assert isinstance(STATE_START, str)
|
||||
assert isinstance(STATE_STANDUP_WRITE, str)
|
||||
assert len(STATE_START) > 0
|
||||
assert len(STATE_STANDUP_WRITE) > 0
|
||||
|
||||
|
||||
def test_button_constants(self):
|
||||
"""Тест констант кнопок"""
|
||||
assert isinstance(BTN_SPEAK, str)
|
||||
assert isinstance(BTN_LISTEN, str)
|
||||
assert len(BTN_SPEAK) > 0
|
||||
assert len(BTN_LISTEN) > 0
|
||||
|
||||
|
||||
def test_command_constants(self):
|
||||
"""Тест констант команд"""
|
||||
assert isinstance(CMD_START, str)
|
||||
@@ -101,62 +109,62 @@ class TestVoiceConstants:
|
||||
assert isinstance(CMD_RESTART, str)
|
||||
assert isinstance(CMD_EMOJI, str)
|
||||
assert isinstance(CMD_REFRESH, str)
|
||||
|
||||
|
||||
assert CMD_START == "start"
|
||||
assert CMD_HELP == "help"
|
||||
assert CMD_RESTART == "restart"
|
||||
assert CMD_EMOJI == "emoji"
|
||||
assert CMD_REFRESH == "refresh"
|
||||
|
||||
|
||||
def test_callback_constants(self):
|
||||
"""Тест констант callback"""
|
||||
assert isinstance(CALLBACK_SAVE, str)
|
||||
assert isinstance(CALLBACK_DELETE, str)
|
||||
|
||||
|
||||
assert CALLBACK_SAVE == "save"
|
||||
assert CALLBACK_DELETE == "delete"
|
||||
|
||||
|
||||
def test_mapping_consistency(self):
|
||||
"""Тест согласованности маппингов"""
|
||||
# Проверяем, что все ключи в маппингах соответствуют константам
|
||||
assert "🎤Высказаться" in BUTTON_COMMAND_MAPPING
|
||||
assert "🎧Послушать" in BUTTON_COMMAND_MAPPING
|
||||
|
||||
|
||||
assert "start" in COMMAND_MAPPING
|
||||
assert "help" in COMMAND_MAPPING
|
||||
assert "restart" in COMMAND_MAPPING
|
||||
assert "emoji" in COMMAND_MAPPING
|
||||
assert "refresh" in COMMAND_MAPPING
|
||||
|
||||
|
||||
assert "save" in CALLBACK_COMMAND_MAPPING
|
||||
assert "delete" in CALLBACK_COMMAND_MAPPING
|
||||
|
||||
|
||||
def test_mapping_values_format(self):
|
||||
"""Тест формата значений в маппингах"""
|
||||
# Проверяем, что все значения начинаются с 'voice_'
|
||||
for value in BUTTON_COMMAND_MAPPING.values():
|
||||
assert value.startswith("voice_")
|
||||
|
||||
|
||||
for value in COMMAND_MAPPING.values():
|
||||
assert value.startswith("voice_")
|
||||
|
||||
|
||||
for value in CALLBACK_COMMAND_MAPPING.values():
|
||||
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())
|
||||
|
||||
|
||||
# Проверяем, что нет дублирующихся значений в каждом маппинге
|
||||
assert len(button_values) == len(set(button_values))
|
||||
assert len(command_values) == len(set(command_values))
|
||||
assert len(callback_values) == len(set(callback_values))
|
||||
|
||||
|
||||
# Примечание: Дублирование между маппингами допустимо (например, voice_emoji)
|
||||
# так как одно действие может быть вызвано и командой, и кнопкой
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__])
|
||||
|
||||
@@ -1,209 +1,212 @@
|
||||
import pytest
|
||||
from helper_bot.handlers.voice.exceptions import (AudioProcessingError,
|
||||
VoiceBotError,
|
||||
VoiceMessageError)
|
||||
|
||||
from helper_bot.handlers.voice.exceptions import (
|
||||
AudioProcessingError,
|
||||
VoiceBotError,
|
||||
VoiceMessageError,
|
||||
)
|
||||
|
||||
|
||||
class TestVoiceExceptions:
|
||||
"""Тесты для исключений voice модуля"""
|
||||
|
||||
|
||||
def test_voice_message_error_inheritance(self):
|
||||
"""Тест наследования VoiceMessageError"""
|
||||
assert issubclass(VoiceMessageError, Exception)
|
||||
|
||||
|
||||
def test_voice_message_error_message(self):
|
||||
"""Тест сообщения VoiceMessageError"""
|
||||
error_message = "Тестовая ошибка голосового сообщения"
|
||||
error = VoiceMessageError(error_message)
|
||||
|
||||
|
||||
assert str(error) == error_message
|
||||
assert error.args == (error_message,)
|
||||
|
||||
|
||||
def test_voice_message_error_empty_message(self):
|
||||
"""Тест VoiceMessageError с пустым сообщением"""
|
||||
error = VoiceMessageError("")
|
||||
|
||||
|
||||
assert str(error) == ""
|
||||
assert error.args == ("",)
|
||||
|
||||
|
||||
def test_voice_message_error_none_message(self):
|
||||
"""Тест VoiceMessageError с None сообщением"""
|
||||
error = VoiceMessageError(None)
|
||||
|
||||
|
||||
assert str(error) == "None"
|
||||
assert error.args == (None,)
|
||||
|
||||
|
||||
def test_voice_message_error_multiple_args(self):
|
||||
"""Тест VoiceMessageError с несколькими аргументами"""
|
||||
error = VoiceMessageError("Ошибка", "Дополнительная информация")
|
||||
|
||||
|
||||
assert str(error) == "('Ошибка', 'Дополнительная информация')"
|
||||
assert error.args == ("Ошибка", "Дополнительная информация")
|
||||
|
||||
|
||||
def test_audio_processing_error_inheritance(self):
|
||||
"""Тест наследования AudioProcessingError"""
|
||||
assert issubclass(AudioProcessingError, Exception)
|
||||
|
||||
|
||||
def test_audio_processing_error_message(self):
|
||||
"""Тест сообщения AudioProcessingError"""
|
||||
error_message = "Ошибка обработки аудио файла"
|
||||
error = AudioProcessingError(error_message)
|
||||
|
||||
|
||||
assert str(error) == error_message
|
||||
assert error.args == (error_message,)
|
||||
|
||||
|
||||
def test_audio_processing_error_empty_message(self):
|
||||
"""Тест AudioProcessingError с пустым сообщением"""
|
||||
error = AudioProcessingError("")
|
||||
|
||||
|
||||
assert str(error) == ""
|
||||
assert error.args == ("",)
|
||||
|
||||
|
||||
def test_audio_processing_error_none_message(self):
|
||||
"""Тест AudioProcessingError с None сообщением"""
|
||||
error = AudioProcessingError(None)
|
||||
|
||||
|
||||
assert str(error) == "None"
|
||||
assert error.args == (None,)
|
||||
|
||||
|
||||
def test_voice_bot_error_inheritance(self):
|
||||
"""Тест наследования VoiceBotError"""
|
||||
assert issubclass(VoiceBotError, Exception)
|
||||
|
||||
|
||||
def test_voice_bot_error_message(self):
|
||||
"""Тест сообщения VoiceBotError"""
|
||||
error_message = "Общая ошибка voice бота"
|
||||
error = VoiceBotError(error_message)
|
||||
|
||||
|
||||
assert str(error) == error_message
|
||||
assert error.args == (error_message,)
|
||||
|
||||
|
||||
def test_exception_hierarchy(self):
|
||||
"""Тест иерархии исключений"""
|
||||
# Проверяем, что все исключения наследуются от Exception
|
||||
assert issubclass(VoiceMessageError, Exception)
|
||||
assert issubclass(AudioProcessingError, Exception)
|
||||
assert issubclass(VoiceBotError, Exception)
|
||||
|
||||
|
||||
# Проверяем, что VoiceMessageError и AudioProcessingError наследуются от VoiceBotError
|
||||
assert issubclass(VoiceMessageError, VoiceBotError)
|
||||
assert issubclass(AudioProcessingError, VoiceBotError)
|
||||
|
||||
|
||||
# Проверяем, что VoiceBotError не наследуется от других исключений
|
||||
assert not issubclass(VoiceBotError, VoiceMessageError)
|
||||
assert not issubclass(VoiceBotError, AudioProcessingError)
|
||||
|
||||
|
||||
# Проверяем, что VoiceMessageError и AudioProcessingError не наследуются друг от друга
|
||||
assert not issubclass(VoiceMessageError, AudioProcessingError)
|
||||
assert not issubclass(AudioProcessingError, VoiceMessageError)
|
||||
|
||||
|
||||
def test_exception_creation_without_args(self):
|
||||
"""Тест создания исключений без аргументов"""
|
||||
# Должно работать без аргументов
|
||||
voice_error = VoiceMessageError()
|
||||
audio_error = AudioProcessingError()
|
||||
bot_error = VoiceBotError()
|
||||
|
||||
|
||||
assert str(voice_error) == ""
|
||||
assert str(audio_error) == ""
|
||||
assert str(bot_error) == ""
|
||||
|
||||
|
||||
def test_exception_creation_with_int(self):
|
||||
"""Тест создания исключений с числовыми аргументами"""
|
||||
voice_error = VoiceMessageError(123)
|
||||
audio_error = AudioProcessingError(456)
|
||||
bot_error = VoiceBotError(789)
|
||||
|
||||
|
||||
assert str(voice_error) == "123"
|
||||
assert str(audio_error) == "456"
|
||||
assert str(bot_error) == "789"
|
||||
|
||||
|
||||
def test_exception_creation_with_list(self):
|
||||
"""Тест создания исключений со списками"""
|
||||
error_list = ["Ошибка 1", "Ошибка 2"]
|
||||
voice_error = VoiceMessageError(error_list)
|
||||
audio_error = AudioProcessingError(error_list)
|
||||
bot_error = VoiceBotError(error_list)
|
||||
|
||||
|
||||
assert str(voice_error) == str(error_list)
|
||||
assert str(audio_error) == str(error_list)
|
||||
assert str(bot_error) == str(error_list)
|
||||
|
||||
|
||||
def test_exception_creation_with_dict(self):
|
||||
"""Тест создания исключений со словарями"""
|
||||
error_dict = {"code": 500, "message": "Internal error"}
|
||||
voice_error = VoiceMessageError(error_dict)
|
||||
audio_error = AudioProcessingError(error_dict)
|
||||
bot_error = VoiceBotError(error_dict)
|
||||
|
||||
|
||||
assert str(voice_error) == str(error_dict)
|
||||
assert str(audio_error) == str(error_dict)
|
||||
assert str(bot_error) == str(error_dict)
|
||||
|
||||
|
||||
def test_exception_attributes(self):
|
||||
"""Тест атрибутов исключений"""
|
||||
error_message = "Тестовая ошибка"
|
||||
voice_error = VoiceMessageError(error_message)
|
||||
audio_error = AudioProcessingError(error_message)
|
||||
bot_error = VoiceBotError(error_message)
|
||||
|
||||
|
||||
# Проверяем, что исключения имеют атрибут args
|
||||
assert hasattr(voice_error, 'args')
|
||||
assert hasattr(audio_error, 'args')
|
||||
assert hasattr(bot_error, 'args')
|
||||
|
||||
assert hasattr(voice_error, "args")
|
||||
assert hasattr(audio_error, "args")
|
||||
assert hasattr(bot_error, "args")
|
||||
|
||||
# Проверяем, что args содержит переданное сообщение
|
||||
assert voice_error.args == (error_message,)
|
||||
assert audio_error.args == (error_message,)
|
||||
assert bot_error.args == (error_message,)
|
||||
|
||||
|
||||
def test_exception_string_representation(self):
|
||||
"""Тест строкового представления исключений"""
|
||||
error_message = "Тестовая ошибка"
|
||||
voice_error = VoiceMessageError(error_message)
|
||||
audio_error = AudioProcessingError(error_message)
|
||||
bot_error = VoiceBotError(error_message)
|
||||
|
||||
|
||||
# Проверяем, что str() возвращает сообщение
|
||||
assert str(voice_error) == error_message
|
||||
assert str(audio_error) == error_message
|
||||
assert str(bot_error) == error_message
|
||||
|
||||
|
||||
# Проверяем, что repr() содержит имя класса
|
||||
assert "VoiceMessageError" in repr(voice_error)
|
||||
assert "AudioProcessingError" in repr(audio_error)
|
||||
assert "VoiceBotError" in repr(bot_error)
|
||||
|
||||
|
||||
def test_exception_equality(self):
|
||||
"""Тест равенства исключений"""
|
||||
error1 = VoiceMessageError("Ошибка")
|
||||
error2 = VoiceMessageError("Ошибка")
|
||||
error3 = VoiceMessageError("Другая ошибка")
|
||||
|
||||
|
||||
# Исключения с одинаковыми сообщениями не равны (разные объекты)
|
||||
assert error1 != error2
|
||||
assert error1 != error3
|
||||
|
||||
|
||||
# Но их строковые представления равны
|
||||
assert str(error1) == str(error2)
|
||||
assert str(error1) != str(error3)
|
||||
|
||||
|
||||
def test_exception_inheritance_chain(self):
|
||||
"""Тест цепочки наследования исключений"""
|
||||
# Проверяем, что все исключения являются экземплярами Exception
|
||||
voice_error = VoiceMessageError("Ошибка")
|
||||
audio_error = AudioProcessingError("Ошибка")
|
||||
bot_error = VoiceBotError("Ошибка")
|
||||
|
||||
|
||||
assert isinstance(voice_error, Exception)
|
||||
assert isinstance(audio_error, Exception)
|
||||
assert isinstance(bot_error, Exception)
|
||||
|
||||
|
||||
# Проверяем, что исключения являются экземплярами своих классов
|
||||
assert isinstance(voice_error, VoiceMessageError)
|
||||
assert isinstance(audio_error, AudioProcessingError)
|
||||
assert isinstance(bot_error, VoiceBotError)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__])
|
||||
|
||||
@@ -3,38 +3,38 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
import pytest
|
||||
from aiogram import types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from helper_bot.handlers.voice.constants import (STATE_STANDUP_WRITE,
|
||||
STATE_START)
|
||||
|
||||
from helper_bot.handlers.voice.constants import STATE_STANDUP_WRITE, STATE_START
|
||||
from helper_bot.handlers.voice.voice_handler import VoiceHandlers
|
||||
|
||||
|
||||
class TestVoiceHandler:
|
||||
"""Тесты для VoiceHandler"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db(self):
|
||||
"""Мок для базы данных"""
|
||||
mock_db = Mock()
|
||||
mock_db.settings = {
|
||||
'Telegram': {
|
||||
'group_for_logs': 'test_logs_chat',
|
||||
'group_for_posts': 'test_posts_chat',
|
||||
'preview_link': True
|
||||
"Telegram": {
|
||||
"group_for_logs": "test_logs_chat",
|
||||
"group_for_posts": "test_posts_chat",
|
||||
"preview_link": True,
|
||||
}
|
||||
}
|
||||
return mock_db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_settings(self):
|
||||
"""Мок для настроек"""
|
||||
return {
|
||||
'Telegram': {
|
||||
'group_for_logs': 'test_logs_chat',
|
||||
'group_for_posts': 'test_posts_chat',
|
||||
'preview_link': True
|
||||
"Telegram": {
|
||||
"group_for_logs": "test_logs_chat",
|
||||
"group_for_posts": "test_posts_chat",
|
||||
"preview_link": True,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_message(self):
|
||||
"""Мок для сообщения"""
|
||||
@@ -48,76 +48,103 @@ class TestVoiceHandler:
|
||||
message.answer = AsyncMock()
|
||||
message.forward = AsyncMock()
|
||||
return message
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_state(self):
|
||||
"""Мок для состояния FSM"""
|
||||
state = Mock(spec=FSMContext)
|
||||
state.set_state = AsyncMock()
|
||||
return state
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def voice_handler(self, mock_db, mock_settings):
|
||||
"""Экземпляр VoiceHandler для тестов"""
|
||||
return VoiceHandlers(mock_db, mock_settings)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_voice_bot_button_handler_welcome_received(self, voice_handler, mock_message, mock_state, mock_db, mock_settings):
|
||||
async def test_voice_bot_button_handler_welcome_received(
|
||||
self, voice_handler, mock_message, mock_state, mock_db, mock_settings
|
||||
):
|
||||
"""Тест обработчика кнопки когда приветствие уже получено"""
|
||||
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:
|
||||
with patch('helper_bot.handlers.voice.voice_handler.update_user_info') as mock_update_user:
|
||||
|
||||
with patch.object(voice_handler, "restart_function") as mock_restart:
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
async def test_voice_bot_button_handler_welcome_not_received(
|
||||
self, voice_handler, mock_message, mock_state, mock_db, mock_settings
|
||||
):
|
||||
"""Тест обработчика кнопки когда приветствие не получено"""
|
||||
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)
|
||||
|
||||
|
||||
with patch.object(voice_handler, "start") as mock_start:
|
||||
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_start.assert_called_once_with(mock_message, mock_state, mock_db, mock_settings)
|
||||
|
||||
mock_start.assert_called_once_with(
|
||||
mock_message, mock_state, mock_db, mock_settings
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_voice_bot_button_handler_exception(self, voice_handler, mock_message, mock_state, mock_db, mock_settings):
|
||||
async def test_voice_bot_button_handler_exception(
|
||||
self, voice_handler, mock_message, mock_state, mock_db, mock_settings
|
||||
):
|
||||
"""Тест обработчика кнопки при исключении"""
|
||||
mock_db.check_voice_bot_welcome_received.side_effect = Exception("Test error")
|
||||
|
||||
with patch.object(voice_handler, 'start') as mock_start:
|
||||
await voice_handler.voice_bot_button_handler(mock_message, mock_state, mock_db, mock_settings)
|
||||
|
||||
mock_start.assert_called_once_with(mock_message, mock_state, mock_db, mock_settings)
|
||||
|
||||
|
||||
with patch.object(voice_handler, "start") as mock_start:
|
||||
await voice_handler.voice_bot_button_handler(
|
||||
mock_message, mock_state, mock_db, mock_settings
|
||||
)
|
||||
|
||||
mock_start.assert_called_once_with(
|
||||
mock_message, mock_state, mock_db, mock_settings
|
||||
)
|
||||
|
||||
# Упрощенные тесты для основных функций
|
||||
@pytest.mark.asyncio
|
||||
async def test_standup_write(self, voice_handler, mock_message, mock_state, mock_db, mock_settings):
|
||||
async def test_standup_write(
|
||||
self, voice_handler, mock_message, mock_state, mock_db, mock_settings
|
||||
):
|
||||
"""Тест функции standup_write"""
|
||||
with patch('helper_bot.handlers.voice.voice_handler.messages.get_message') as mock_get_message:
|
||||
with patch(
|
||||
"helper_bot.handlers.voice.voice_handler.messages.get_message"
|
||||
) as mock_get_message:
|
||||
mock_get_message.return_value = "Record voice message"
|
||||
|
||||
await voice_handler.standup_write(mock_message, mock_state, mock_db, mock_settings)
|
||||
|
||||
|
||||
await voice_handler.standup_write(
|
||||
mock_message, mock_state, mock_db, mock_settings
|
||||
)
|
||||
|
||||
mock_state.set_state.assert_called_once_with(STATE_STANDUP_WRITE)
|
||||
mock_message.answer.assert_called_once_with(
|
||||
text="Record voice message",
|
||||
reply_markup=types.ReplyKeyboardRemove()
|
||||
text="Record voice message", reply_markup=types.ReplyKeyboardRemove()
|
||||
)
|
||||
|
||||
|
||||
def test_setup_handlers(self, voice_handler):
|
||||
"""Тест настройки обработчиков"""
|
||||
# Проверяем, что роутер содержит обработчики
|
||||
assert len(voice_handler.router.message.handlers) > 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__])
|
||||
|
||||
@@ -3,164 +3,176 @@ from pathlib import Path
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from helper_bot.handlers.voice.exceptions import (AudioProcessingError,
|
||||
VoiceMessageError)
|
||||
|
||||
from helper_bot.handlers.voice.exceptions import AudioProcessingError, VoiceMessageError
|
||||
from helper_bot.handlers.voice.services import VoiceBotService
|
||||
|
||||
|
||||
class TestVoiceBotService:
|
||||
"""Тесты для VoiceBotService"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_bot_db(self):
|
||||
"""Мок для базы данных"""
|
||||
mock_db = Mock()
|
||||
mock_db.settings = {
|
||||
'Settings': {'logs': True},
|
||||
'Telegram': {'important_logs': 'test_chat_id'}
|
||||
"Settings": {"logs": True},
|
||||
"Telegram": {"important_logs": "test_chat_id"},
|
||||
}
|
||||
return mock_db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_settings(self):
|
||||
"""Мок для настроек"""
|
||||
return {
|
||||
'Settings': {'logs': True},
|
||||
'Telegram': {'preview_link': True}
|
||||
}
|
||||
|
||||
return {"Settings": {"logs": True}, "Telegram": {"preview_link": True}}
|
||||
|
||||
@pytest.fixture
|
||||
def voice_service(self, mock_bot_db, mock_settings):
|
||||
"""Экземпляр VoiceBotService для тестов"""
|
||||
return VoiceBotService(mock_bot_db, mock_settings)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_welcome_sticker_success(self, voice_service, mock_settings):
|
||||
"""Тест успешного получения стикера"""
|
||||
with patch('pathlib.Path.rglob') as mock_rglob:
|
||||
mock_rglob.return_value = ['/path/to/sticker1.tgs', '/path/to/sticker2.tgs']
|
||||
|
||||
with patch("pathlib.Path.rglob") as mock_rglob:
|
||||
mock_rglob.return_value = ["/path/to/sticker1.tgs", "/path/to/sticker2.tgs"]
|
||||
|
||||
sticker = await voice_service.get_welcome_sticker()
|
||||
|
||||
|
||||
assert sticker is not None
|
||||
mock_rglob.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_welcome_sticker_no_stickers(self, voice_service, mock_settings):
|
||||
"""Тест получения стикера когда их нет"""
|
||||
with patch('pathlib.Path.rglob') as mock_rglob:
|
||||
with patch("pathlib.Path.rglob") as mock_rglob:
|
||||
mock_rglob.return_value = []
|
||||
|
||||
|
||||
sticker = await voice_service.get_welcome_sticker()
|
||||
|
||||
|
||||
assert sticker is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_welcome_sticker_only_webp_files(self, voice_service, mock_settings):
|
||||
async def test_get_welcome_sticker_only_webp_files(
|
||||
self, voice_service, mock_settings
|
||||
):
|
||||
"""Тест получения стикера когда есть только webp файлы"""
|
||||
with patch('pathlib.Path.rglob') as mock_rglob:
|
||||
mock_rglob.return_value = ['/path/to/sticker1.webp', '/path/to/sticker2.webp']
|
||||
|
||||
with patch("pathlib.Path.rglob") as mock_rglob:
|
||||
mock_rglob.return_value = [
|
||||
"/path/to/sticker1.webp",
|
||||
"/path/to/sticker2.webp",
|
||||
]
|
||||
|
||||
sticker = await voice_service.get_welcome_sticker()
|
||||
|
||||
|
||||
# Проверяем, что стикер не None (метод ищет файлы по паттерну Hello_*)
|
||||
assert sticker is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_welcome_sticker_mixed_files(self, voice_service, mock_settings):
|
||||
"""Тест получения стикера когда есть смешанные файлы"""
|
||||
with patch('pathlib.Path.rglob') as mock_rglob:
|
||||
with patch("pathlib.Path.rglob") as mock_rglob:
|
||||
mock_rglob.return_value = [
|
||||
'/path/to/sticker1.webp',
|
||||
'/path/to/sticker2.tgs',
|
||||
'/path/to/sticker3.webp'
|
||||
"/path/to/sticker1.webp",
|
||||
"/path/to/sticker2.tgs",
|
||||
"/path/to/sticker3.webp",
|
||||
]
|
||||
|
||||
|
||||
sticker = await voice_service.get_welcome_sticker()
|
||||
|
||||
|
||||
assert sticker is not None
|
||||
# Проверяем, что стикер не None (метод возвращает FSInputFile объект)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_random_audio_success(self, voice_service, mock_bot_db):
|
||||
"""Тест успешного получения случайного аудио"""
|
||||
mock_bot_db.check_listen_audio = AsyncMock(return_value=['audio1', 'audio2'])
|
||||
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='😊')
|
||||
|
||||
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 = await voice_service.get_random_audio(456)
|
||||
|
||||
|
||||
assert result is not None
|
||||
assert len(result) == 3
|
||||
# Проверяем, что результат содержит ожидаемые данные, но не проверяем точное значение audio
|
||||
assert result[0] in ['audio1', 'audio2']
|
||||
assert result[1] == '2025-01-01 12:00:00'
|
||||
assert result[2] == '😊'
|
||||
|
||||
assert result[0] in ["audio1", "audio2"]
|
||||
assert result[1] == "2025-01-01 12:00:00"
|
||||
assert result[2] == "😊"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_random_audio_no_audio(self, voice_service, mock_bot_db):
|
||||
"""Тест получения аудио когда их нет"""
|
||||
mock_bot_db.check_listen_audio = AsyncMock(return_value=[])
|
||||
|
||||
|
||||
result = await voice_service.get_random_audio(456)
|
||||
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_random_audio_single_audio(self, voice_service, mock_bot_db):
|
||||
"""Тест получения аудио когда есть только одно"""
|
||||
mock_bot_db.check_listen_audio = AsyncMock(return_value=['audio1'])
|
||||
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='😊')
|
||||
|
||||
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 = await voice_service.get_random_audio(456)
|
||||
|
||||
|
||||
assert result is not None
|
||||
assert len(result) == 3
|
||||
assert result[0] == 'audio1'
|
||||
|
||||
assert result[0] == "audio1"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mark_audio_as_listened_success(self, voice_service, mock_bot_db):
|
||||
"""Тест успешной пометки аудио как прослушанного"""
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clear_user_listenings_success(self, voice_service, mock_bot_db):
|
||||
"""Тест успешной очистки прослушиваний"""
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_remaining_audio_count_success(self, voice_service, mock_bot_db):
|
||||
"""Тест получения количества оставшихся аудио"""
|
||||
mock_bot_db.check_listen_audio = AsyncMock(return_value=['audio1', 'audio2', 'audio3'])
|
||||
|
||||
mock_bot_db.check_listen_audio = AsyncMock(
|
||||
return_value=["audio1", "audio2", "audio3"]
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_remaining_audio_count_zero(self, voice_service, mock_bot_db):
|
||||
"""Тест получения количества оставшихся аудио когда их нет"""
|
||||
mock_bot_db.check_listen_audio = AsyncMock(return_value=[])
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_welcome_messages_success(self, voice_service, mock_bot_db, mock_settings):
|
||||
async def test_send_welcome_messages_success(
|
||||
self, voice_service, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест успешной отправки приветственных сообщений."""
|
||||
mock_message = Mock()
|
||||
mock_message.from_user.id = 123
|
||||
@@ -168,28 +180,48 @@ class TestVoiceBotService:
|
||||
mock_message.answer.return_value = Mock()
|
||||
mock_message.answer_sticker = AsyncMock()
|
||||
|
||||
with patch.object(voice_service, 'get_welcome_sticker', new_callable=AsyncMock, return_value='test_sticker.tgs'):
|
||||
with patch('helper_bot.handlers.voice.services.asyncio.sleep', new_callable=AsyncMock):
|
||||
await voice_service.send_welcome_messages(mock_message, '😊')
|
||||
with patch.object(
|
||||
voice_service,
|
||||
"get_welcome_sticker",
|
||||
new_callable=AsyncMock,
|
||||
return_value="test_sticker.tgs",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.voice.services.asyncio.sleep",
|
||||
new_callable=AsyncMock,
|
||||
):
|
||||
await voice_service.send_welcome_messages(mock_message, "😊")
|
||||
|
||||
assert mock_message.answer.call_count >= 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_welcome_messages_no_sticker(self, voice_service, mock_bot_db, mock_settings):
|
||||
async def test_send_welcome_messages_no_sticker(
|
||||
self, voice_service, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест отправки приветственных сообщений без стикера."""
|
||||
mock_message = Mock()
|
||||
mock_message.from_user.id = 123
|
||||
mock_message.answer = AsyncMock()
|
||||
mock_message.answer.return_value = Mock()
|
||||
|
||||
with patch.object(voice_service, 'get_welcome_sticker', new_callable=AsyncMock, return_value=None):
|
||||
with patch('helper_bot.handlers.voice.services.asyncio.sleep', new_callable=AsyncMock):
|
||||
await voice_service.send_welcome_messages(mock_message, '😊')
|
||||
with patch.object(
|
||||
voice_service,
|
||||
"get_welcome_sticker",
|
||||
new_callable=AsyncMock,
|
||||
return_value=None,
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.voice.services.asyncio.sleep",
|
||||
new_callable=AsyncMock,
|
||||
):
|
||||
await voice_service.send_welcome_messages(mock_message, "😊")
|
||||
|
||||
assert mock_message.answer.call_count >= 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_welcome_messages_with_sticker(self, voice_service, mock_bot_db, mock_settings):
|
||||
async def test_send_welcome_messages_with_sticker(
|
||||
self, voice_service, mock_bot_db, mock_settings
|
||||
):
|
||||
"""Тест отправки приветственных сообщений со стикером."""
|
||||
mock_message = Mock()
|
||||
mock_message.from_user.id = 123
|
||||
@@ -197,41 +229,51 @@ class TestVoiceBotService:
|
||||
mock_message.answer.return_value = Mock()
|
||||
mock_message.answer_sticker = AsyncMock()
|
||||
|
||||
with patch.object(voice_service, 'get_welcome_sticker', new_callable=AsyncMock, return_value='test_sticker.tgs'):
|
||||
with patch('helper_bot.handlers.voice.services.asyncio.sleep', new_callable=AsyncMock):
|
||||
await voice_service.send_welcome_messages(mock_message, '😊')
|
||||
with patch.object(
|
||||
voice_service,
|
||||
"get_welcome_sticker",
|
||||
new_callable=AsyncMock,
|
||||
return_value="test_sticker.tgs",
|
||||
):
|
||||
with patch(
|
||||
"helper_bot.handlers.voice.services.asyncio.sleep",
|
||||
new_callable=AsyncMock,
|
||||
):
|
||||
await voice_service.send_welcome_messages(mock_message, "😊")
|
||||
|
||||
assert mock_message.answer.call_count >= 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_welcome_sticker_with_tgs_files(self, voice_service, mock_settings):
|
||||
async def test_get_welcome_sticker_with_tgs_files(
|
||||
self, voice_service, mock_settings
|
||||
):
|
||||
"""Тест получения стикера когда есть .tgs файлы"""
|
||||
with patch('pathlib.Path.rglob') as mock_rglob:
|
||||
mock_rglob.return_value = ['/path/to/sticker1.tgs', '/path/to/sticker2.tgs']
|
||||
|
||||
with patch("pathlib.Path.rglob") as mock_rglob:
|
||||
mock_rglob.return_value = ["/path/to/sticker1.tgs", "/path/to/sticker2.tgs"]
|
||||
|
||||
sticker = await voice_service.get_welcome_sticker()
|
||||
|
||||
|
||||
assert sticker is not None
|
||||
# Проверяем, что стикер не None (метод возвращает FSInputFile объект)
|
||||
|
||||
|
||||
def test_service_initialization(self, mock_bot_db, mock_settings):
|
||||
"""Тест инициализации сервиса"""
|
||||
service = VoiceBotService(mock_bot_db, mock_settings)
|
||||
|
||||
|
||||
assert service.bot_db == mock_bot_db
|
||||
assert service.settings == mock_settings
|
||||
|
||||
|
||||
def test_service_attributes(self, voice_service):
|
||||
"""Тест атрибутов сервиса"""
|
||||
assert hasattr(voice_service, 'bot_db')
|
||||
assert hasattr(voice_service, 'settings')
|
||||
assert hasattr(voice_service, 'get_welcome_sticker')
|
||||
assert hasattr(voice_service, 'get_random_audio')
|
||||
assert hasattr(voice_service, 'mark_audio_as_listened')
|
||||
assert hasattr(voice_service, 'clear_user_listenings')
|
||||
assert hasattr(voice_service, 'get_remaining_audio_count')
|
||||
assert hasattr(voice_service, 'send_welcome_messages')
|
||||
assert hasattr(voice_service, "bot_db")
|
||||
assert hasattr(voice_service, "settings")
|
||||
assert hasattr(voice_service, "get_welcome_sticker")
|
||||
assert hasattr(voice_service, "get_random_audio")
|
||||
assert hasattr(voice_service, "mark_audio_as_listened")
|
||||
assert hasattr(voice_service, "clear_user_listenings")
|
||||
assert hasattr(voice_service, "get_remaining_audio_count")
|
||||
assert hasattr(voice_service, "send_welcome_messages")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__])
|
||||
|
||||
@@ -3,26 +3,26 @@ from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from aiogram import types
|
||||
from helper_bot.handlers.voice.utils import (format_time_ago,
|
||||
get_last_message_text,
|
||||
get_user_emoji_safe, plural_time,
|
||||
validate_voice_message)
|
||||
|
||||
from helper_bot.handlers.voice.utils import (
|
||||
format_time_ago,
|
||||
get_last_message_text,
|
||||
get_user_emoji_safe,
|
||||
plural_time,
|
||||
validate_voice_message,
|
||||
)
|
||||
|
||||
|
||||
class TestVoiceUtils:
|
||||
"""Тесты для утилит voice модуля"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_bot_db(self):
|
||||
"""Мок для базы данных"""
|
||||
mock_db = Mock()
|
||||
mock_db.settings = {
|
||||
'Telegram': {
|
||||
'group_for_logs': 'test_logs_chat'
|
||||
}
|
||||
}
|
||||
mock_db.settings = {"Telegram": {"group_for_logs": "test_logs_chat"}}
|
||||
return mock_db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_message(self):
|
||||
"""Мок для сообщения"""
|
||||
@@ -35,134 +35,149 @@ class TestVoiceUtils:
|
||||
message.from_user.language_code = "ru"
|
||||
message.chat.id = 456
|
||||
return message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_last_message_text(self, mock_bot_db):
|
||||
"""Тест получения последнего сообщения"""
|
||||
# Возвращаем UNIX timestamp
|
||||
from unittest.mock import AsyncMock
|
||||
mock_bot_db.last_date_audio = AsyncMock(return_value=1641034800) # 2022-01-01 12:00:00
|
||||
|
||||
|
||||
mock_bot_db.last_date_audio = AsyncMock(
|
||||
return_value=1641034800
|
||||
) # 2022-01-01 12:00:00
|
||||
|
||||
result = await get_last_message_text(mock_bot_db)
|
||||
|
||||
|
||||
assert result is not None
|
||||
assert "минут" in result or "часа" in result or "дня" 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()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_voice_message_valid(self):
|
||||
"""Тест валидации голосового сообщения"""
|
||||
mock_message = Mock()
|
||||
mock_message.content_type = 'voice'
|
||||
mock_message.content_type = "voice"
|
||||
mock_message.voice = Mock()
|
||||
|
||||
|
||||
result = await validate_voice_message(mock_message)
|
||||
|
||||
|
||||
assert result is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_voice_message_invalid(self):
|
||||
"""Тест валидации невалидного сообщения"""
|
||||
mock_message = Mock()
|
||||
mock_message.voice = None
|
||||
|
||||
|
||||
result = await validate_voice_message(mock_message)
|
||||
|
||||
|
||||
assert result is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_emoji_safe_with_emoji(self, mock_bot_db):
|
||||
"""Тест безопасного получения эмодзи пользователя когда эмодзи есть"""
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
mock_bot_db.get_user_emoji = AsyncMock(return_value="😊")
|
||||
|
||||
|
||||
result = await get_user_emoji_safe(mock_bot_db, 123)
|
||||
|
||||
|
||||
assert result == "😊"
|
||||
mock_bot_db.get_user_emoji.assert_called_once_with(123)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_emoji_safe_without_emoji(self, mock_bot_db):
|
||||
"""Тест безопасного получения эмодзи пользователя когда эмодзи нет"""
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
mock_bot_db.get_user_emoji = AsyncMock(return_value=None)
|
||||
|
||||
|
||||
result = await get_user_emoji_safe(mock_bot_db, 123)
|
||||
|
||||
|
||||
assert result == "😊"
|
||||
mock_bot_db.get_user_emoji.assert_called_once_with(123)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_emoji_safe_with_empty_emoji(self, mock_bot_db):
|
||||
"""Тест безопасного получения эмодзи пользователя с пустым эмодзи"""
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
mock_bot_db.get_user_emoji = AsyncMock(return_value="")
|
||||
|
||||
|
||||
result = await get_user_emoji_safe(mock_bot_db, 123)
|
||||
|
||||
|
||||
assert result == "😊"
|
||||
mock_bot_db.get_user_emoji.assert_called_once_with(123)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_emoji_safe_with_error(self, mock_bot_db):
|
||||
"""Тест безопасного получения эмодзи пользователя при ошибке"""
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
mock_bot_db.get_user_emoji = AsyncMock(return_value="Ошибка")
|
||||
|
||||
|
||||
result = await get_user_emoji_safe(mock_bot_db, 123)
|
||||
|
||||
|
||||
assert result == "Ошибка"
|
||||
mock_bot_db.get_user_emoji.assert_called_once_with(123)
|
||||
|
||||
|
||||
def test_format_time_ago_minutes(self):
|
||||
"""Тест форматирования времени в минутах"""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Создаем дату 30 минут назад
|
||||
test_date = (datetime.now() - timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
test_date = (datetime.now() - timedelta(minutes=30)).strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
|
||||
result = format_time_ago(test_date)
|
||||
|
||||
|
||||
assert result is not None
|
||||
assert "минут" in result
|
||||
assert "30" in result or "29" in result or "31" in result
|
||||
|
||||
|
||||
def test_format_time_ago_hours(self):
|
||||
"""Тест форматирования времени в часах"""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Создаем дату 2 часа назад
|
||||
test_date = (datetime.now() - timedelta(hours=2)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
result = format_time_ago(test_date)
|
||||
|
||||
|
||||
assert result is not None
|
||||
assert "часа" in result or "часов" in result
|
||||
|
||||
|
||||
def test_format_time_ago_days(self):
|
||||
"""Тест форматирования времени в днях"""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Создаем дату 3 дня назад
|
||||
test_date = (datetime.now() - timedelta(days=3)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
result = format_time_ago(test_date)
|
||||
|
||||
|
||||
assert result is not None
|
||||
assert "дня" in result or "дней" in result
|
||||
|
||||
|
||||
def test_format_time_ago_none(self):
|
||||
"""Тест форматирования времени с None"""
|
||||
result = format_time_ago(None)
|
||||
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_format_time_ago_invalid_format(self):
|
||||
"""Тест форматирования времени с неверным форматом"""
|
||||
result = format_time_ago("invalid_date_format")
|
||||
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_plural_time_minutes(self):
|
||||
"""Тест множественного числа для минут"""
|
||||
assert "1 минуту" in plural_time(1, 1)
|
||||
@@ -170,7 +185,7 @@ class TestVoiceUtils:
|
||||
assert "5 минут" in plural_time(1, 5)
|
||||
assert "11 минут" in plural_time(1, 11)
|
||||
assert "21 минуту" in plural_time(1, 21)
|
||||
|
||||
|
||||
def test_plural_time_hours(self):
|
||||
"""Тест множественного числа для часов"""
|
||||
assert "1 час" in plural_time(2, 1)
|
||||
@@ -178,7 +193,7 @@ class TestVoiceUtils:
|
||||
assert "5 часов" in plural_time(2, 5)
|
||||
assert "11 часов" in plural_time(2, 11)
|
||||
assert "21 час" in plural_time(2, 21)
|
||||
|
||||
|
||||
def test_plural_time_days(self):
|
||||
"""Тест множественного числа для дней"""
|
||||
assert "1 день" in plural_time(3, 1)
|
||||
@@ -186,25 +201,25 @@ class TestVoiceUtils:
|
||||
assert "5 дней" in plural_time(3, 5)
|
||||
assert "11 дней" in plural_time(3, 11)
|
||||
assert "21 день" in plural_time(3, 21)
|
||||
|
||||
|
||||
def test_plural_time_invalid_type(self):
|
||||
"""Тест множественного числа с неверным типом"""
|
||||
result = plural_time(4, 5)
|
||||
|
||||
|
||||
assert result == "5"
|
||||
|
||||
|
||||
def test_plural_time_edge_cases(self):
|
||||
"""Тест граничных случаев для множественного числа"""
|
||||
# Тест для 0
|
||||
assert "0 минут" in plural_time(1, 0)
|
||||
assert "0 часов" in plural_time(2, 0)
|
||||
assert "0 дней" in plural_time(3, 0)
|
||||
|
||||
|
||||
# Тест для больших чисел
|
||||
assert "100 минут" in plural_time(1, 100)
|
||||
assert "100 часов" in plural_time(2, 100)
|
||||
assert "100 дней" in plural_time(3, 100)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__])
|
||||
|
||||
Reference in New Issue
Block a user