Enhance bot functionality and refactor database interactions

- Added `ca-certificates` installation to Dockerfile for improved network security.
- Updated health check command in Dockerfile to include better timeout handling.
- Refactored `run_helper.py` to implement proper signal handling and logging during shutdown.
- Transitioned database operations to an asynchronous model in `async_db.py`, improving performance and responsiveness.
- Updated database schema to support new foreign key relationships and optimized indexing for better query performance.
- Enhanced various bot handlers to utilize async database methods, improving overall efficiency and user experience.
- Removed obsolete database and fix scripts to streamline the project structure.
This commit is contained in:
2025-09-02 18:22:02 +03:00
parent 013892dcb7
commit 1c6a37bc12
59 changed files with 5682 additions and 4204 deletions

View File

@@ -0,0 +1,497 @@
import pytest
import asyncio
import os
import tempfile
from datetime import datetime
from database.repositories.post_repository import PostRepository
from database.models import TelegramPost, PostContent, MessageContentLink
class TestPostRepositoryIntegration:
"""Интеграционные тесты для PostRepository с реальной БД."""
async def _setup_test_database(self, post_repository):
"""Вспомогательная функция для настройки тестовой БД."""
# Сначала создаем таблицу our_users для тестов
await post_repository._execute_query('''
CREATE TABLE IF NOT EXISTS our_users (
user_id INTEGER NOT NULL PRIMARY KEY,
first_name TEXT,
full_name TEXT,
username TEXT,
is_bot BOOLEAN DEFAULT 0,
language_code TEXT,
has_stickers BOOLEAN DEFAULT 0 NOT NULL,
emoji TEXT,
date_added INTEGER NOT NULL,
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()))
)
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()))
)
# Теперь создаем таблицы для постов
await post_repository.create_tables()
@pytest.fixture
def temp_db_path(self):
"""Фикстура для временного файла БД."""
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):
"""Фикстура для тестового поста."""
return TelegramPost(
message_id=12345,
text="Тестовый пост для интеграционных тестов",
author_id=67890,
helper_text_message_id=None,
created_at=int(datetime.now().timestamp())
)
@pytest.fixture
def sample_post_2(self):
"""Фикстура для второго тестового поста."""
return TelegramPost(
message_id=12346,
text="Второй тестовый пост",
author_id=67890,
helper_text_message_id=None,
created_at=int(datetime.now().timestamp())
)
@pytest.fixture
def sample_post_with_helper(self):
"""Фикстура для тестового поста с helper сообщением."""
return TelegramPost(
message_id=12347,
text="Пост с helper сообщением",
author_id=67890,
helper_text_message_id=None, # Будет установлен позже
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())
)
# Если таблицы созданы, то 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)
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
)
# Добавляем пост
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)
assert author_id == post_without_date.author_id
@pytest.mark.asyncio
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)
# Проверяем, что 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)
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())
)
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)
# Поскольку у нас нет 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):
"""Интеграционный тест добавления контента поста с 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())
)
await post_repository.add_post(helper_post)
# Обновляем пост, чтобы он ссылался на helper сообщение
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())
)
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)
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):
"""Интеграционный тест получения текста поста по 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())
)
await post_repository.add_post(helper_post)
# Обновляем пост, чтобы он ссылался на helper сообщение
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):
"""Интеграционный тест получения текста поста по несуществующему 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):
"""Интеграционный тест получения 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())
)
await post_repository.add_post(helper_post)
# Обновляем пост, чтобы он ссылался на helper сообщение
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_types = ["photo", "photo", "video"]
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())
)
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):
"""Интеграционный тест получения 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)
# Проверяем результат
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):
"""Интеграционный тест получения 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):
"""Интеграционный тест получения 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())
)
await post_repository.add_post(helper_post)
# Обновляем пост, чтобы он ссылался на helper сообщение
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)
# Проверяем результат
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):
"""Интеграционный тест получения 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):
"""Интеграционный тест работы с несколькими постами."""
# Настраиваем тестовую БД
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)
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):
"""Интеграционный тест связей между постами и контентом."""
# Настраиваем тестовую БД
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())
)
await post_repository.add_post(helper_post)
# Обновляем пост, чтобы он ссылался на helper сообщение
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")
]
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())
)
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)
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