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,204 @@
import pytest
import asyncio
from datetime import datetime
from unittest.mock import AsyncMock, MagicMock
from database.repositories.message_repository import MessageRepository
from database.models import UserMessage
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):
"""Фикстура для тестового сообщения."""
return UserMessage(
message_text="Тестовое сообщение",
user_id=12345,
telegram_message_id=67890,
date=int(datetime.now().timestamp())
)
@pytest.fixture
def sample_message_no_date(self):
"""Фикстура для тестового сообщения без даты."""
return UserMessage(
message_text="Тестовое сообщение без даты",
user_id=12345,
telegram_message_id=67891,
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
@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
)
@pytest.mark.asyncio
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
@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,)
)
@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,)
)
@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):
"""Тест обработки исключений при добавлении сообщения."""
# Мокаем _execute_query для вызова исключения
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):
"""Тест обработки исключений при получении пользователя."""
# Мокаем _execute_query_with_result для вызова исключения
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 (должна генерироваться новая)."""
message = UserMessage(
message_text="Тестовое сообщение с нулевой датой",
user_id=12345,
telegram_message_id=67892,
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
if __name__ == "__main__":
pytest.main([__file__])