Когда unit-тесты зелёные, а бот падает в продакшене

Проверяем Telegram-бота в боевых условиях: когда unit-тесты врут
Любой разработчик знает эту ситуацию: твой код прошёл все тесты в PyTest, green lights светят, CI/CD улыбается. Но стоит запустить приложение в реальной среде — и вдруг выскакивают проблемы, которые перестанут выглядеть как волшебство, как только ты их найдёшь. Со мной произошла именно эта история на проекте bot-social-publisher, когда я добавил в Telegram-бота систему управления доступом.
Задача казалась элементарной
Надо было реализовать для бота фишку с приватными чатами. Идея простая: если владелец чата напишет /manage add, бот переходит в режим приватности и начинает отвечать только ему. Команда /manage remove открывает доступ всем обратно. Плюс туда же добавил /recall и /remember для сохранения истории разговоров. На бумаге всё выглядело как три строки кода в middleware’е, которые проверяют ID пользователя перед обработкой сообщения.
Я написал unit-тесты, всё прошло. Но реальный Telegram — совсем другой зверь.
Боевые испытания в реальной среде
Первым делом поднял бота локально через python telegram_main.py и начал его “пилить” из реального Telegram аккаунта. Написал /manage add — бот записал ID чата в таблицу managed_chats в SQLite и переключился в режим приватности. Проверил middleware permission_check.py — всё срабатывает корректно, обработка заблокирована для чужих. Хорошо.
Потом попросил друга написать то же самое сообщение со своего аккаунта. Ожидал — ничего не случится. И действительно, бот промолчал. Отлично, система работает как надо. Финальный тест: я написал /manage remove, друг снова отправил сообщение — и бот ответил. Приватность отключена, доступ восстановлен.
Казалось бы, победа. Но потом обнаружилась подвох.
Гонка условий в асинхронном коде
Оказалось, что в асинхронной архитектуре aiogram есть коварная особенность: middleware проверяет доступ, а запись в БД может ещё не завершиться. Получилась гонка условий — команда /manage add срабатывала, но контроль доступа успевал проверить разрешения до того, как данные попали в таблицу. Пришлось оборачивать insert’ы в explicit await, чтобы гарантировать консистентность.
Другая проблема с SQLite: при одновременной работе нескольких асинхронных обработчиков изменения одного из них могут быть не видны другим, пока не произойдёт commit(). Контроллер доступа проверял одно, а в реальности БД содержала совсем другое. Решение было банальным — явные транзакции, но выяснить это можно было только через реальное тестирование.
Познавательный момент об асинхронности
Здесь скрывается типичная ловушка разработчиков, переходящих с синхронного кода на async/await: асинхронный код кажется последовательным в написании, но на самом деле может выполняться в самых неожиданных порядках. Когда ты пишешь await db.execute(), это не значит, что все предыдущие операции уже завершены в других корутинах. Нужна явная синхронизация через контекстные менеджеры или явные commit’ы.
Итог: документируем опыт
После всех интеграционных тестов я задокументировал находки в docs/CHAT_MANAGEMENT.md, добавил примеры использования в README.md и описал полную архитектуру ChatManager’а. Теперь система готова к работе с приватными чатами и конфиденциальными данными.
Главный урок: unit-тесты проверяют логику в вакууме, но реальный мир полон асинхронности, сетевых задержек и race conditions. Никакой PyTest не найдёт то, что видно только в продакшене. Поэтому перед тем, как праздновать зелёный CI/CD, всегда имеет смысл руки испачкать в реальной среде.
😄 Что говорит разработчик после запуска асинхронного кода? «У меня было семь ошибок, теперь их четырнадцать, но они более интересные».
Метаданные
- Session ID:
- grouped_C--projects-bot-social-publisher_20260209_1217
- Branch:
- main
- Dev Joke
- Почему Kotlin лучший друг разработчика? Потому что без него ничего не работает. С ним тоже, но хотя бы есть кого винить
Часть потока:
Разработка: bot-social-publisher