All checks were successful
CI pipeline / Test & Code Quality (push) Successful in 34s
173 lines
6.3 KiB
Python
173 lines
6.3 KiB
Python
"""
|
||
Тесты для декораторов group и private handlers (error_handler).
|
||
"""
|
||
|
||
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
||
import pytest
|
||
from aiogram import types
|
||
|
||
from helper_bot.handlers.group.decorators import error_handler as group_error_handler
|
||
from helper_bot.handlers.private.decorators import (
|
||
error_handler as private_error_handler,
|
||
)
|
||
|
||
|
||
class FakeMessage:
|
||
"""Класс-маркер, чтобы мок проходил isinstance(..., types.Message) в декораторе."""
|
||
|
||
pass
|
||
|
||
|
||
@pytest.mark.unit
|
||
@pytest.mark.asyncio
|
||
class TestGroupErrorHandler:
|
||
"""Тесты для error_handler из group/decorators."""
|
||
|
||
async def test_success_returns_result(self):
|
||
"""При успешном выполнении возвращается результат функции."""
|
||
|
||
@group_error_handler
|
||
async def sample_handler():
|
||
return "ok"
|
||
|
||
result = await sample_handler()
|
||
assert result == "ok"
|
||
|
||
async def test_exception_is_reraised(self):
|
||
"""При исключении оно пробрасывается дальше."""
|
||
|
||
@group_error_handler
|
||
async def failing_handler():
|
||
raise ValueError("test error")
|
||
|
||
with pytest.raises(ValueError, match="test error"):
|
||
await failing_handler()
|
||
|
||
@patch("helper_bot.handlers.group.decorators.logger")
|
||
async def test_exception_is_logged(self, mock_logger):
|
||
"""При исключении вызывается logger.error."""
|
||
|
||
@group_error_handler
|
||
async def failing_handler():
|
||
raise RuntimeError("logged error")
|
||
|
||
with pytest.raises(RuntimeError):
|
||
await failing_handler()
|
||
mock_logger.error.assert_called_once()
|
||
assert "logged error" in mock_logger.error.call_args[0][0]
|
||
assert "failing_handler" in mock_logger.error.call_args[0][0]
|
||
|
||
@patch("helper_bot.handlers.group.decorators.types")
|
||
@patch("helper_bot.utils.base_dependency_factory.get_global_instance")
|
||
@patch("helper_bot.handlers.group.decorators.logger")
|
||
async def test_exception_sends_to_important_logs_when_message_has_bot(
|
||
self, mock_logger, mock_get_global, mock_types
|
||
):
|
||
"""При исключении и наличии message с bot отправляется сообщение в important_logs."""
|
||
mock_types.Message = FakeMessage
|
||
message = MagicMock()
|
||
message.__class__ = FakeMessage
|
||
message.bot = MagicMock()
|
||
message.bot.send_message = AsyncMock()
|
||
mock_bdf = MagicMock()
|
||
mock_bdf.settings = {"Telegram": {"important_logs": "-100123"}}
|
||
mock_get_global.return_value = mock_bdf
|
||
|
||
@group_error_handler
|
||
async def failing_handler(msg):
|
||
assert msg is message
|
||
raise ValueError("error for logs")
|
||
|
||
with pytest.raises(ValueError):
|
||
await failing_handler(message)
|
||
|
||
mock_get_global.assert_called_once()
|
||
message.bot.send_message.assert_called_once()
|
||
call_kwargs = message.bot.send_message.call_args[1]
|
||
assert call_kwargs["chat_id"] == "-100123"
|
||
call_text = call_kwargs["text"]
|
||
assert "error for logs" in call_text
|
||
assert "failing_handler" in call_text
|
||
assert "Traceback" in call_text
|
||
|
||
|
||
@pytest.mark.unit
|
||
@pytest.mark.asyncio
|
||
class TestPrivateErrorHandler:
|
||
"""Тесты для error_handler из private/decorators."""
|
||
|
||
async def test_success_returns_result(self):
|
||
"""При успешном выполнении возвращается результат функции."""
|
||
|
||
@private_error_handler
|
||
async def sample_handler():
|
||
return 42
|
||
|
||
result = await sample_handler()
|
||
assert result == 42
|
||
|
||
async def test_exception_is_reraised(self):
|
||
"""При исключении оно пробрасывается дальше."""
|
||
|
||
@private_error_handler
|
||
async def failing_handler():
|
||
raise TypeError("private error")
|
||
|
||
with pytest.raises(TypeError, match="private error"):
|
||
await failing_handler()
|
||
|
||
@patch("helper_bot.handlers.private.decorators.logger")
|
||
async def test_exception_is_logged(self, mock_logger):
|
||
"""При исключении вызывается logger.error."""
|
||
|
||
@private_error_handler
|
||
async def failing_handler():
|
||
raise KeyError("key missing")
|
||
|
||
with pytest.raises(KeyError):
|
||
await failing_handler()
|
||
mock_logger.error.assert_called_once()
|
||
assert "key missing" in mock_logger.error.call_args[0][0]
|
||
|
||
@patch("helper_bot.handlers.private.decorators.types")
|
||
@patch("helper_bot.utils.base_dependency_factory.get_global_instance")
|
||
async def test_exception_sends_to_important_logs_when_message_has_bot(
|
||
self, mock_get_global, mock_types
|
||
):
|
||
"""При исключении и наличии message с bot отправляется сообщение в important_logs."""
|
||
mock_types.Message = FakeMessage
|
||
message = MagicMock()
|
||
message.__class__ = FakeMessage
|
||
message.bot = MagicMock()
|
||
message.bot.send_message = AsyncMock()
|
||
mock_bdf = MagicMock()
|
||
mock_bdf.settings = {"Telegram": {"important_logs": "-100456"}}
|
||
mock_get_global.return_value = mock_bdf
|
||
|
||
@private_error_handler
|
||
async def failing_handler(msg):
|
||
raise RuntimeError("private runtime")
|
||
|
||
with pytest.raises(RuntimeError):
|
||
await failing_handler(message)
|
||
|
||
mock_get_global.assert_called_once()
|
||
message.bot.send_message.assert_called_once()
|
||
call_kwargs = message.bot.send_message.call_args[1]
|
||
assert call_kwargs["chat_id"] == "-100456"
|
||
call_text = call_kwargs["text"]
|
||
assert "private runtime" in call_text
|
||
assert "failing_handler" in call_text
|
||
|
||
async def test_no_message_in_args_no_send(self):
|
||
"""Если в args нет Message, send_message не вызывается (только логирование)."""
|
||
|
||
@private_error_handler
|
||
async def failing_handler():
|
||
raise ValueError("no message")
|
||
|
||
with pytest.raises(ValueError):
|
||
await failing_handler()
|
||
# get_global_instance не должен вызываться, т.к. message не найден в args
|