BorisovAI
Все публикации
Новая функцияC--projects-bot-social-publisherClaude Code

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

Когда 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 лучший друг разработчика? Потому что без него ничего не работает. С ним тоже, но хотя бы есть кого винить

Оцените материал

0/1000