discordBot/tests/test_bot.py

92 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Тесты для 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
import discord
# Добавляем корень проекта в путь импорта
ROOT_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT_DIR))
class TestBotInit:
"""Тесты для инициализации бота."""
def test_bot_created_with_default_prefix(self) -> None:
"""Проверка, что бот создан с правильным префиксом команд."""
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) -> None:
"""BotRunner.run() обрабатывает discord.LoginFailure."""
import bot
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) -> None:
"""BotRunner.run() обрабатывает discord.HTTPException."""
import bot
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_shutdown_uses_on_shutdown_listener(self) -> None:
"""BotRunner.run() регистрирует on_shutdown вместо signal handlers.
Signal handlers с asyncio.new_event_loop() создают race condition
с основным loop. Вместо них используется:
- discord.py on_shutdown событие для остановки планировщика
- async with self.bot (context manager) для graceful shutdown
"""
import bot
runner = bot.BotRunner()
# Проверяем, что _on_shutdown и _on_shutdown_async методы существуют
assert hasattr(runner, "_on_shutdown"), "Метод _on_shutdown должен существовать"
assert hasattr(runner, "_on_shutdown_async"), "Метод _on_shutdown_async должен существовать"
# Проверяем, что signal модуль НЕ импортирован в bot.py
with open(ROOT_DIR / "bot.py", encoding="utf-8") as f:
content = f.read()
assert "signal.signal" not in content, "Не должно быть signal.signal — используется on_shutdown"
def test_code_uses_async_bot_pattern(self) -> None:
"""Проверка, что 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) — это антипаттерн"