""" Тесты для bot.py — проверка обработки ошибок запуска бота. Покрывают пункт 1.2 из PLAN_OF_WORKS.md: - Graceful shutdown через signal handlers - Логирование и обработка исключений (LoginFailure, HTTPException) - async with bot паттерн вместо bot.run() """ import sys from pathlib import Path from unittest.mock import MagicMock, patch # Добавляем корень проекта в путь импорта ROOT_DIR = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT_DIR)) class TestBotInit: """Тесты для инициализации бота.""" def test_bot_created_with_default_prefix(self): """Проверка, что бот создан с правильным префиксом команд.""" import bot runner = bot.BotRunner() try: assert runner.bot.command_prefix == "!", ( f"Команда должна быть с префиксом '!', а не '{runner.bot.command_prefix}'" ) finally: runner.stop_event.set() class TestBotErrorHandling: """Тесты для проверки обработки ошибок запуска бота.""" def test_bot_handles_login_failure(self): """BotRunner.run() обрабатывает discord.LoginFailure.""" import bot import discord runner = bot.BotRunner() with patch.object(runner.bot, "start", side_effect=discord.LoginFailure("bad token")): with patch.object(runner.bot, "__aenter__", return_value=runner.bot): with patch.object(runner.bot, "__aexit__", return_value=None): with patch("sys.exit") as mock_exit: runner.run("fake_token") mock_exit.assert_called_once_with(1) def test_bot_handles_http_exception(self): """BotRunner.run() обрабатывает discord.HTTPException.""" import bot import discord runner = bot.BotRunner() mock_response = MagicMock(status=502) with patch.object(runner.bot, "start", side_effect=discord.HTTPException(mock_response, "Bad Gateway")): with patch.object(runner.bot, "__aenter__", return_value=runner.bot): with patch.object(runner.bot, "__aexit__", return_value=None): with patch("sys.exit") as mock_exit: runner.run("fake_token") mock_exit.assert_called_once_with(1) def test_signal_handlers_installed(self): """BotRunner.run() устанавливает обработчики SIGINT и SIGTERM.""" import asyncio import bot import signal runner = bot.BotRunner() def fake_run(coro): """Поддельный asyncio.run, который утилизирует корутину.""" try: coro.close() except RuntimeError: pass # корутина уже закрыта with patch("signal.signal") as mock_signal: with patch("asyncio.run", side_effect=fake_run): runner.run("fake_token") # signal.signal вызван для SIGINT и SIGTERM call_args = [call[0][0] for call in mock_signal.call_args_list] assert signal.SIGINT in call_args assert signal.SIGTERM in call_args def test_code_uses_async_bot_pattern(self): """Проверка, что bot.py использует async with / asyncio.run.""" with open(ROOT_DIR / "bot.py", encoding="utf-8") as f: content = f.read() assert "async with self.bot" in content, "Должен быть паттерн 'async with self.bot'" assert "asyncio.run(main())" in content, "Должен быть вызов asyncio.run()" assert "bot.run(token)" not in content, "Не должно быть bot.run(token) — это антипаттерн"