feat: добавлена система миграций БД и CI/CD пайплайны

- Создана система отслеживания миграций (MigrationRepository, таблица migrations)
- Добавлен скрипт apply_migrations.py для автоматического применения миграций
- Созданы CI/CD пайплайны (.github/workflows/ci.yml, deploy.yml)
- Обновлена документация по миграциям в database-patterns.md
- Миграции применяются автоматически при деплое в продакшн
This commit is contained in:
2026-01-25 23:17:09 +03:00
parent 07e72c4d14
commit e2b1353408
109 changed files with 1342 additions and 1441 deletions

View File

@@ -1,11 +1,11 @@
import pytest
import asyncio
import os
import sys
from unittest.mock import Mock, AsyncMock, patch
from aiogram.types import Message, User, Chat
from aiogram.fsm.context import FSMContext
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
# Импортируем моки в самом начале

View File

@@ -1,9 +1,10 @@
import pytest
import tempfile
import os
import tempfile
from datetime import datetime
from database.repositories.message_repository import MessageRepository
import pytest
from database.models import UserMessage
from database.repositories.message_repository import MessageRepository
@pytest.fixture(scope="session")

View File

@@ -1,11 +1,12 @@
import pytest
import asyncio
import os
import tempfile
from datetime import datetime
from unittest.mock import Mock, AsyncMock
from unittest.mock import AsyncMock, Mock
import pytest
from database.models import MessageContentLink, PostContent, TelegramPost
from database.repositories.post_repository import PostRepository
from database.models import TelegramPost, PostContent, MessageContentLink
@pytest.fixture(scope="session")

View File

@@ -1,10 +1,11 @@
"""
Моки для тестового окружения
"""
import sys
import os
import sys
from unittest.mock import Mock, patch
# Патчим загрузку настроек до импорта модулей
def setup_test_mocks():
"""Настройка моков для тестов"""

View File

@@ -1,10 +1,10 @@
import pytest
from unittest.mock import Mock, AsyncMock, patch, MagicMock
from datetime import datetime
import time
from datetime import datetime
from unittest.mock import AsyncMock, MagicMock, Mock, patch
from database.repositories.admin_repository import AdminRepository
import pytest
from database.models import Admin
from database.repositories.admin_repository import AdminRepository
class TestAdminRepository:

View File

@@ -1,5 +1,6 @@
from unittest.mock import AsyncMock, Mock, patch
import pytest
from unittest.mock import Mock, AsyncMock, patch
from database.async_db import AsyncBotDB

View File

@@ -1,10 +1,11 @@
import pytest
from unittest.mock import Mock, AsyncMock, patch, MagicMock, mock_open
from datetime import datetime
import time
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.services import AudioFileService
from helper_bot.handlers.voice.exceptions import FileOperationError, DatabaseError
@pytest.fixture

View File

@@ -1,10 +1,10 @@
import pytest
from unittest.mock import Mock, AsyncMock, patch, MagicMock
from datetime import datetime
import time
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
from database.models import AudioMessage, AudioListenRecord, AudioModerate
class TestAudioRepository:

View File

@@ -1,8 +1,8 @@
import pytest
from unittest.mock import Mock, AsyncMock, patch, MagicMock
from datetime import datetime
import time
from datetime import datetime
from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest
from database.repositories.audio_repository import AudioRepository

View File

@@ -1,9 +1,9 @@
import pytest
import sqlite3
import os
from datetime import datetime, timezone, timedelta
from unittest.mock import Mock, patch, AsyncMock
import sqlite3
from datetime import datetime, timedelta, timezone
from unittest.mock import AsyncMock, Mock, patch
import pytest
from helper_bot.utils.auto_unban_scheduler import AutoUnbanScheduler
@@ -155,8 +155,9 @@ class TestAutoUnbanIntegration:
}
# Создаем реальный экземпляр базы данных с тестовым файлом
from database.async_db import AsyncBotDB
import os
from database.async_db import AsyncBotDB
mock_factory.database = AsyncBotDB(test_db_path)
return mock_factory

View File

@@ -1,9 +1,10 @@
import pytest
import asyncio
from datetime import datetime, timezone, timedelta
from unittest.mock import Mock, patch, AsyncMock
from datetime import datetime, timedelta, timezone
from unittest.mock import AsyncMock, Mock, patch
from helper_bot.utils.auto_unban_scheduler import AutoUnbanScheduler, get_auto_unban_scheduler
import pytest
from helper_bot.utils.auto_unban_scheduler import (AutoUnbanScheduler,
get_auto_unban_scheduler)
class TestAutoUnbanScheduler:

View File

@@ -1,10 +1,11 @@
import pytest
from unittest.mock import Mock, AsyncMock, patch
from datetime import datetime
import time
from datetime import datetime
from unittest.mock import AsyncMock, Mock, patch
from database.repositories.blacklist_history_repository import BlacklistHistoryRepository
import pytest
from database.models import BlacklistHistoryRecord
from database.repositories.blacklist_history_repository import \
BlacklistHistoryRepository
class TestBlacklistHistoryRepository:

View File

@@ -1,10 +1,10 @@
import pytest
from unittest.mock import Mock, AsyncMock, patch, MagicMock
from datetime import datetime
import time
from datetime import datetime
from unittest.mock import AsyncMock, MagicMock, Mock, patch
from database.repositories.blacklist_repository import BlacklistRepository
import pytest
from database.models import BlacklistUser
from database.repositories.blacklist_repository import BlacklistRepository
class TestBlacklistRepository:

View File

@@ -1,13 +1,11 @@
import pytest
from unittest.mock import Mock, AsyncMock, patch, MagicMock
from datetime import datetime
import time
from datetime import datetime
from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest
from helper_bot.handlers.callback.callback_handlers import (
save_voice_message,
delete_voice_message
)
from helper_bot.handlers.voice.constants import CALLBACK_SAVE, CALLBACK_DELETE
delete_voice_message, save_voice_message)
from helper_bot.handlers.voice.constants import CALLBACK_DELETE, CALLBACK_SAVE
@pytest.fixture

View File

@@ -2,18 +2,15 @@
Тесты для улучшенных методов обработки медиа
"""
import pytest
import os
import tempfile
from unittest.mock import Mock, AsyncMock, patch, MagicMock
from aiogram import types
from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest
from aiogram import types
from helper_bot.utils.helper_func import (
download_file,
add_in_db_media,
add_in_db_media_mediagroup,
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:

View File

@@ -1,16 +1,15 @@
import pytest
from unittest.mock import Mock, patch, AsyncMock
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton
from unittest.mock import AsyncMock, Mock, patch
from helper_bot.keyboards.keyboards import (
get_reply_keyboard,
get_reply_keyboard_admin,
get_reply_keyboard_for_post,
get_reply_keyboard_leave_chat,
create_keyboard_with_pagination
)
from helper_bot.filters.main import ChatTypeFilter
import pytest
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)
class TestKeyboards:

View File

@@ -1,9 +1,10 @@
import pytest
import asyncio
from datetime import datetime
from unittest.mock import AsyncMock, MagicMock
from database.repositories.message_repository import MessageRepository
import pytest
from database.models import UserMessage
from database.repositories.message_repository import MessageRepository
class TestMessageRepository:

View File

@@ -1,10 +1,11 @@
import pytest
import asyncio
import tempfile
import os
import tempfile
from datetime import datetime
from database.repositories.message_repository import MessageRepository
import pytest
from database.models import UserMessage
from database.repositories.message_repository import MessageRepository
class TestMessageRepositoryIntegration:

View File

@@ -1,9 +1,10 @@
import pytest
import asyncio
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
from database.models import TelegramPost, PostContent, MessageContentLink
class TestPostRepository:

View File

@@ -1,10 +1,11 @@
import pytest
import asyncio
import os
import tempfile
from datetime import datetime
import pytest
from database.models import MessageContentLink, PostContent, TelegramPost
from database.repositories.post_repository import PostRepository
from database.models import TelegramPost, PostContent, MessageContentLink
class TestPostRepositoryIntegration:

View File

@@ -1,12 +1,12 @@
"""Tests for PostService"""
import pytest
from unittest.mock import Mock, AsyncMock, MagicMock, patch
from datetime import datetime
from aiogram import types
from unittest.mock import AsyncMock, MagicMock, Mock, patch
from helper_bot.handlers.private.services import PostService, BotSettings
import pytest
from aiogram import types
from database.models import TelegramPost, User
from helper_bot.handlers.private.services import BotSettings, PostService
class TestPostService:

View File

@@ -3,19 +3,18 @@
"""
import asyncio
import time
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from helper_bot.utils.rate_limiter import (
RateLimitConfig,
ChatRateLimiter,
GlobalRateLimiter,
RetryHandler,
TelegramRateLimiter,
send_with_rate_limit
)
from helper_bot.utils.rate_limit_monitor import RateLimitMonitor, RateLimitStats, record_rate_limit_request
from helper_bot.config.rate_limit_config import RateLimitSettings, get_rate_limit_config
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)
class TestRateLimitConfig:

View File

@@ -1,14 +1,12 @@
from unittest.mock import AsyncMock, Mock, patch
import pytest
from unittest.mock import Mock, AsyncMock, patch
from aiogram import types
from aiogram.fsm.context import FSMContext
from helper_bot.handlers.admin.services import AdminService, User, BannedUser
from helper_bot.handlers.admin.exceptions import (
UserNotFoundError,
UserAlreadyBannedError,
InvalidInputError
)
from helper_bot.handlers.admin.exceptions import (InvalidInputError,
UserAlreadyBannedError,
UserNotFoundError)
from helper_bot.handlers.admin.services import AdminService, BannedUser, User
class TestAdminService:

View File

@@ -1,16 +1,16 @@
"""Tests for refactored group handlers"""
from unittest.mock import AsyncMock, MagicMock, Mock
import pytest
from unittest.mock import Mock, AsyncMock, MagicMock
from aiogram import types
from aiogram.fsm.context import FSMContext
from helper_bot.handlers.group.group_handlers import (
create_group_handlers, GroupHandlers
)
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.services import AdminReplyService
from helper_bot.handlers.group.exceptions import NoReplyToMessageError, UserNotFoundError
from helper_bot.handlers.group.constants import FSM_STATES, ERROR_MESSAGES
class TestGroupHandlers:

View File

@@ -1,15 +1,14 @@
"""Tests for refactored private handlers"""
from unittest.mock import AsyncMock, MagicMock, Mock
import pytest
from unittest.mock import Mock, AsyncMock, MagicMock
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 (
create_private_handlers, PrivateHandlers
)
PrivateHandlers, create_private_handlers)
from helper_bot.handlers.private.services import BotSettings
from helper_bot.handlers.private.constants import FSM_STATES, BUTTON_TEXTS
class TestPrivateHandlers:

View File

@@ -1,39 +1,24 @@
import pytest
from unittest.mock import Mock, patch, AsyncMock
from datetime import datetime
import os
from datetime import datetime
from unittest.mock import AsyncMock, Mock, patch
from helper_bot.utils.helper_func import (
get_first_name,
get_text_message,
determine_anonymity,
check_username_and_full_name,
safe_html_escape,
download_file,
prepare_media_group_from_middlewares,
add_in_db_media_mediagroup,
add_in_db_media,
send_media_group_message_to_private_chat,
send_media_group_to_channel,
send_text_message,
send_photo_message,
send_video_message,
send_video_note_message,
send_audio_message,
send_voice_message,
check_access,
add_days_to_date,
get_banned_users_list,
get_banned_users_buttons,
delete_user_blacklist,
update_user_info,
check_user_emoji,
get_random_emoji
)
from helper_bot.utils.messages import get_message
from helper_bot.utils.base_dependency_factory import BaseDependencyFactory, get_global_instance
import helper_bot.utils.messages as messages # Import for patching constants
import pytest
from database.async_db import AsyncBotDB
import helper_bot.utils.messages as messages # Import for patching constants
from helper_bot.utils.base_dependency_factory import (BaseDependencyFactory,
get_global_instance)
from helper_bot.utils.helper_func import (
add_days_to_date, add_in_db_media, add_in_db_media_mediagroup,
check_access, check_user_emoji, check_username_and_full_name,
delete_user_blacklist, determine_anonymity, download_file,
get_banned_users_buttons, get_banned_users_list, get_first_name,
get_random_emoji, get_text_message, prepare_media_group_from_middlewares,
safe_html_escape, send_audio_message,
send_media_group_message_to_private_chat, send_media_group_to_channel,
send_photo_message, send_text_message, send_video_message,
send_video_note_message, send_voice_message, update_user_info)
from helper_bot.utils.messages import get_message
class TestHelperFunctions:
"""Тесты для вспомогательных функций"""

View File

@@ -1,11 +1,14 @@
import pytest
from unittest.mock import Mock, AsyncMock, patch
from datetime import datetime
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.services import VoiceBotService
from helper_bot.handlers.voice.exceptions import VoiceMessageError, AudioProcessingError
from helper_bot.handlers.voice.utils import get_last_message_text, validate_voice_message, get_user_emoji_safe
from helper_bot.handlers.voice.utils import (get_last_message_text,
get_user_emoji_safe,
validate_voice_message)
class TestVoiceBotService:

View File

@@ -1,21 +1,14 @@
import pytest
from helper_bot.handlers.voice.constants import (
BUTTON_COMMAND_MAPPING,
COMMAND_MAPPING,
CALLBACK_COMMAND_MAPPING,
VOICE_BOT_NAME,
STATE_START,
STATE_STANDUP_WRITE,
BTN_SPEAK,
BTN_LISTEN,
CMD_START,
CMD_HELP,
CMD_RESTART,
CMD_EMOJI,
CMD_REFRESH,
CALLBACK_SAVE,
CALLBACK_DELETE
)
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:

View File

@@ -1,9 +1,7 @@
import pytest
from helper_bot.handlers.voice.exceptions import (
VoiceMessageError,
AudioProcessingError,
VoiceBotError
)
from helper_bot.handlers.voice.exceptions import (AudioProcessingError,
VoiceBotError,
VoiceMessageError)
class TestVoiceExceptions:

View File

@@ -1,10 +1,11 @@
from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest
from unittest.mock import Mock, AsyncMock, patch, MagicMock
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.voice_handler import VoiceHandlers
from helper_bot.handlers.voice.constants import STATE_START, STATE_STANDUP_WRITE
class TestVoiceHandler:

View File

@@ -1,10 +1,11 @@
import pytest
from unittest.mock import Mock, AsyncMock, patch, MagicMock
from pathlib import Path
from datetime import datetime
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.services import VoiceBotService
from helper_bot.handlers.voice.exceptions import VoiceMessageError, AudioProcessingError
class TestVoiceBotService:

View File

@@ -1,15 +1,12 @@
import pytest
from unittest.mock import Mock, patch
from datetime import datetime, timedelta
from aiogram import types
from unittest.mock import Mock, patch
from helper_bot.handlers.voice.utils import (
get_last_message_text,
validate_voice_message,
get_user_emoji_safe,
format_time_ago,
plural_time
)
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)
class TestVoiceUtils:
@@ -120,7 +117,7 @@ class TestVoiceUtils:
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")
@@ -133,7 +130,7 @@ class TestVoiceUtils:
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")
@@ -145,7 +142,7 @@ class TestVoiceUtils:
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")