mirror of
https://github.com/aiogram/aiogram.git
synced 2025-12-12 02:03:04 +00:00
Added detection of API Errors and fixed coverage
This commit is contained in:
parent
4f2cc75951
commit
c3844bb18f
17 changed files with 179 additions and 216 deletions
|
|
@ -1,64 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import re
|
|
||||||
from typing import TYPE_CHECKING, List, Type
|
|
||||||
|
|
||||||
from aiogram.methods import Response, TelegramMethod
|
|
||||||
from aiogram.types import TelegramObject
|
|
||||||
from aiogram.utils.exceptions.base import TelegramAPIError
|
|
||||||
from aiogram.utils.exceptions.exceptions import (
|
|
||||||
CantParseEntitiesStartTag,
|
|
||||||
CantParseEntitiesUnclosed,
|
|
||||||
CantParseEntitiesUnmatchedTags,
|
|
||||||
CantParseEntitiesUnsupportedTag,
|
|
||||||
DetailedTelegramAPIError,
|
|
||||||
)
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from aiogram.client.bot import Bot
|
|
||||||
from aiogram.client.session.base import NextRequestMiddlewareType
|
|
||||||
|
|
||||||
|
|
||||||
class RequestErrorMiddleware:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self._registry: List[Type[DetailedTelegramAPIError]] = [
|
|
||||||
CantParseEntitiesStartTag,
|
|
||||||
CantParseEntitiesUnmatchedTags,
|
|
||||||
CantParseEntitiesUnclosed,
|
|
||||||
CantParseEntitiesUnsupportedTag,
|
|
||||||
]
|
|
||||||
|
|
||||||
def mount(self, error: Type[DetailedTelegramAPIError]) -> Type[DetailedTelegramAPIError]:
|
|
||||||
if error in self:
|
|
||||||
raise ValueError(f"{error!r} is already registered")
|
|
||||||
if not hasattr(error, "patterns"):
|
|
||||||
raise ValueError(f"{error!r} has no attribute 'patterns'")
|
|
||||||
self._registry.append(error)
|
|
||||||
return error
|
|
||||||
|
|
||||||
def detect_error(self, err: TelegramAPIError) -> TelegramAPIError:
|
|
||||||
message = err.message
|
|
||||||
for variant in self._registry:
|
|
||||||
for pattern in variant.patterns:
|
|
||||||
if match := re.match(pattern, message):
|
|
||||||
return variant(
|
|
||||||
method=err.method,
|
|
||||||
message=err.message,
|
|
||||||
match=match,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
|
|
||||||
def __contains__(self, item: Type[DetailedTelegramAPIError]) -> bool:
|
|
||||||
return item in self._registry
|
|
||||||
|
|
||||||
async def __call__(
|
|
||||||
self,
|
|
||||||
bot: Bot,
|
|
||||||
method: TelegramMethod[TelegramObject],
|
|
||||||
make_request: NextRequestMiddlewareType,
|
|
||||||
) -> Response[TelegramObject]:
|
|
||||||
try:
|
|
||||||
return await make_request(bot, method)
|
|
||||||
except TelegramAPIError as e:
|
|
||||||
detected_err = self.detect_error(err=e)
|
|
||||||
raise detected_err from e
|
|
||||||
|
|
@ -4,6 +4,7 @@ import abc
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from http import HTTPStatus
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
|
|
@ -25,8 +26,13 @@ from aiogram.utils.helper import Default
|
||||||
from ...methods import Response, TelegramMethod
|
from ...methods import Response, TelegramMethod
|
||||||
from ...methods.base import TelegramType
|
from ...methods.base import TelegramType
|
||||||
from ...types import UNSET, TelegramObject
|
from ...types import UNSET, TelegramObject
|
||||||
|
from ...utils.exceptions.bad_request import BadRequest
|
||||||
|
from ...utils.exceptions.conflict import ConflictError
|
||||||
|
from ...utils.exceptions.network import EntityTooLarge
|
||||||
|
from ...utils.exceptions.not_found import NotFound
|
||||||
|
from ...utils.exceptions.server import RestartingTelegram, ServerError
|
||||||
from ...utils.exceptions.special import MigrateToChat, RetryAfter
|
from ...utils.exceptions.special import MigrateToChat, RetryAfter
|
||||||
from ..errors_middleware import RequestErrorMiddleware
|
from ...utils.exceptions.unauthorized import UnauthorizedError
|
||||||
from ..telegram import PRODUCTION, TelegramAPIServer
|
from ..telegram import PRODUCTION, TelegramAPIServer
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
|
|
@ -55,12 +61,8 @@ class BaseSession(abc.ABC):
|
||||||
timeout: Default[float] = Default(fget=lambda self: float(self.__class__.default_timeout))
|
timeout: Default[float] = Default(fget=lambda self: float(self.__class__.default_timeout))
|
||||||
"""Session scope request timeout"""
|
"""Session scope request timeout"""
|
||||||
|
|
||||||
errors_middleware: ClassVar[RequestErrorMiddleware] = RequestErrorMiddleware()
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.middlewares: List[RequestMiddlewareType[TelegramObject]] = [
|
self.middlewares: List[RequestMiddlewareType[TelegramObject]] = []
|
||||||
self.errors_middleware,
|
|
||||||
]
|
|
||||||
|
|
||||||
def check_response(
|
def check_response(
|
||||||
self, method: TelegramMethod[TelegramType], status_code: int, content: str
|
self, method: TelegramMethod[TelegramType], status_code: int, content: str
|
||||||
|
|
@ -70,10 +72,11 @@ class BaseSession(abc.ABC):
|
||||||
"""
|
"""
|
||||||
json_data = self.json_loads(content)
|
json_data = self.json_loads(content)
|
||||||
response = method.build_response(json_data)
|
response = method.build_response(json_data)
|
||||||
if response.ok:
|
if HTTPStatus.OK <= status_code <= HTTPStatus.IM_USED and response.ok:
|
||||||
return response
|
return response
|
||||||
|
|
||||||
description = cast(str, response.description)
|
description = cast(str, response.description)
|
||||||
|
|
||||||
if parameters := response.parameters:
|
if parameters := response.parameters:
|
||||||
if parameters.retry_after:
|
if parameters.retry_after:
|
||||||
raise RetryAfter(
|
raise RetryAfter(
|
||||||
|
|
@ -85,6 +88,21 @@ class BaseSession(abc.ABC):
|
||||||
message=description,
|
message=description,
|
||||||
migrate_to_chat_id=parameters.migrate_to_chat_id,
|
migrate_to_chat_id=parameters.migrate_to_chat_id,
|
||||||
)
|
)
|
||||||
|
if status_code == HTTPStatus.BAD_REQUEST:
|
||||||
|
raise BadRequest(method=method, message=description)
|
||||||
|
if status_code == HTTPStatus.NOT_FOUND:
|
||||||
|
raise NotFound(method=method, message=description)
|
||||||
|
if status_code == HTTPStatus.CONFLICT:
|
||||||
|
raise ConflictError(method=method, message=description)
|
||||||
|
if status_code in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN):
|
||||||
|
raise UnauthorizedError(method=method, message=description)
|
||||||
|
if status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE:
|
||||||
|
raise EntityTooLarge(method=method, message=description)
|
||||||
|
if status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
|
||||||
|
if "restart" in description:
|
||||||
|
raise RestartingTelegram(method=method, message=description)
|
||||||
|
raise ServerError(method=method, message=description)
|
||||||
|
|
||||||
raise TelegramAPIError(
|
raise TelegramAPIError(
|
||||||
method=method,
|
method=method,
|
||||||
message=description,
|
message=description,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from aiogram.utils.exceptions.base import DetailedTelegramAPIError
|
from aiogram.utils.exceptions.base import TelegramAPIError
|
||||||
|
|
||||||
|
|
||||||
class BadRequest(DetailedTelegramAPIError):
|
class BadRequest(TelegramAPIError):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import ClassVar, List, Match, Optional, TypeVar
|
from typing import Optional, TypeVar
|
||||||
|
|
||||||
from aiogram.methods import TelegramMethod
|
from aiogram.methods import TelegramMethod
|
||||||
from aiogram.methods.base import TelegramType
|
from aiogram.methods.base import TelegramType
|
||||||
|
|
@ -25,16 +25,3 @@ class TelegramAPIError(Exception):
|
||||||
if self.url:
|
if self.url:
|
||||||
message.append(f"(background on this error at: {self.url})")
|
message.append(f"(background on this error at: {self.url})")
|
||||||
return "\n".join(message)
|
return "\n".join(message)
|
||||||
|
|
||||||
|
|
||||||
class DetailedTelegramAPIError(TelegramAPIError):
|
|
||||||
patterns: ClassVar[List[str]]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
method: TelegramMethod[TelegramType],
|
|
||||||
message: str,
|
|
||||||
match: Match[str],
|
|
||||||
) -> None:
|
|
||||||
super().__init__(method=method, message=message)
|
|
||||||
self.match: Match[str] = match
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
from aiogram.utils.exceptions.base import TelegramAPIError
|
||||||
|
|
||||||
|
|
||||||
|
class ConflictError(TelegramAPIError):
|
||||||
|
pass
|
||||||
|
|
@ -1,93 +1,5 @@
|
||||||
from textwrap import indent
|
from aiogram.utils.exceptions.base import TelegramAPIError
|
||||||
from typing import Match
|
|
||||||
|
|
||||||
from aiogram.methods.base import TelegramMethod, TelegramType
|
|
||||||
from aiogram.utils.exceptions.base import DetailedTelegramAPIError
|
|
||||||
from aiogram.utils.exceptions.util import mark_line
|
|
||||||
|
|
||||||
|
|
||||||
class BadRequest(DetailedTelegramAPIError):
|
class BadRequest(TelegramAPIError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CantParseEntities(BadRequest):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CantParseEntitiesStartTag(CantParseEntities):
|
|
||||||
patterns = [
|
|
||||||
"Bad Request: can't parse entities: Can't find end tag corresponding to start tag (?P<tag>.+)"
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
method: TelegramMethod[TelegramType],
|
|
||||||
message: str,
|
|
||||||
match: Match[str],
|
|
||||||
) -> None:
|
|
||||||
super().__init__(method=method, message=message, match=match)
|
|
||||||
self.tag: str = match.group("tag")
|
|
||||||
|
|
||||||
|
|
||||||
class CantParseEntitiesUnmatchedTags(CantParseEntities):
|
|
||||||
patterns = [
|
|
||||||
r'Bad Request: can\'t parse entities: Unmatched end tag at byte offset (?P<offset>\d), expected "</(?P<expected>\w+)>", found "</(?P<found>\w+)>"'
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
method: TelegramMethod[TelegramType],
|
|
||||||
message: str,
|
|
||||||
match: Match[str],
|
|
||||||
) -> None:
|
|
||||||
super().__init__(method=method, message=message, match=match)
|
|
||||||
self.offset: int = int(match.group("offset"))
|
|
||||||
self.expected: str = match.group("expected")
|
|
||||||
self.found: str = match.group("found")
|
|
||||||
|
|
||||||
|
|
||||||
class CantParseEntitiesUnclosed(CantParseEntities):
|
|
||||||
patterns = [
|
|
||||||
"Bad Request: can't parse entities: Unclosed start tag at byte offset (?P<offset>.+)"
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
method: TelegramMethod[TelegramType],
|
|
||||||
message: str,
|
|
||||||
match: Match[str],
|
|
||||||
) -> None:
|
|
||||||
super().__init__(method=method, message=message, match=match)
|
|
||||||
self.offset: int = int(match.group("offset"))
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
message = [self.message]
|
|
||||||
text = getattr(self.method, "text", None) or getattr(self.method, "caption", None)
|
|
||||||
if text:
|
|
||||||
message.extend(["Example:", indent(mark_line(text, self.offset), prefix=" ")])
|
|
||||||
return "\n".join(message)
|
|
||||||
|
|
||||||
|
|
||||||
class CantParseEntitiesUnsupportedTag(CantParseEntities):
|
|
||||||
patterns = [
|
|
||||||
r'Bad Request: can\'t parse entities: Unsupported start tag "(?P<tag>.+)" at byte offset (?P<offset>\d+)'
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
method: TelegramMethod[TelegramType],
|
|
||||||
message: str,
|
|
||||||
match: Match[str],
|
|
||||||
) -> None:
|
|
||||||
super().__init__(method=method, message=message, match=match)
|
|
||||||
self.offset = int(match.group("offset"))
|
|
||||||
self.tag = match.group("tag")
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
message = [self.message]
|
|
||||||
text = getattr(self.method, "text", None) or getattr(self.method, "caption", None)
|
|
||||||
if text:
|
|
||||||
message.extend(
|
|
||||||
["Example:", indent(mark_line(text, self.offset, len(self.tag)), prefix=" ")]
|
|
||||||
)
|
|
||||||
return "\n".join(message)
|
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,7 @@ from aiogram.utils.exceptions.base import TelegramAPIError
|
||||||
|
|
||||||
class NetworkError(TelegramAPIError):
|
class NetworkError(TelegramAPIError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EntityTooLarge(NetworkError):
|
||||||
|
url = "https://core.telegram.org/bots/api#sending-files"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from aiogram.utils.exceptions.base import DetailedTelegramAPIError
|
from aiogram.utils.exceptions.base import TelegramAPIError
|
||||||
|
|
||||||
|
|
||||||
class NotFound(DetailedTelegramAPIError):
|
class NotFound(TelegramAPIError):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,7 @@ from aiogram.utils.exceptions.base import TelegramAPIError
|
||||||
|
|
||||||
class ServerError(TelegramAPIError):
|
class ServerError(TelegramAPIError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RestartingTelegram(ServerError):
|
||||||
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from aiogram.methods import TelegramMethod
|
from aiogram.methods import TelegramMethod
|
||||||
from aiogram.methods.base import TelegramType
|
from aiogram.methods.base import TelegramType
|
||||||
from aiogram.utils.exceptions.base import TelegramAPIError
|
from aiogram.utils.exceptions.base import TelegramAPIError
|
||||||
|
|
@ -37,7 +35,7 @@ class MigrateToChat(TelegramAPIError):
|
||||||
super().__init__(method=method, message=message)
|
super().__init__(method=method, message=message)
|
||||||
self.migrate_to_chat_id = migrate_to_chat_id
|
self.migrate_to_chat_id = migrate_to_chat_id
|
||||||
|
|
||||||
def render_message(self) -> Optional[str]:
|
def render_description(self) -> str:
|
||||||
description = (
|
description = (
|
||||||
f"The group has been migrated to a supergroup with id {self.migrate_to_chat_id}"
|
f"The group has been migrated to a supergroup with id {self.migrate_to_chat_id}"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
from aiogram.utils.exceptions.base import TelegramAPIError
|
||||||
|
|
||||||
|
|
||||||
|
class UnauthorizedError(TelegramAPIError):
|
||||||
|
pass
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
def mark_line(text: str, offset: int, length: int = 1) -> str:
|
|
||||||
try:
|
|
||||||
if offset > 0 and (new_line_pos := text[:offset].rindex("\n")):
|
|
||||||
text = "..." + text[:new_line_pos]
|
|
||||||
offset -= new_line_pos - 3
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if offset > 10:
|
|
||||||
text = "..." + text[offset - 10 :]
|
|
||||||
offset = 13
|
|
||||||
|
|
||||||
mark = " " * offset
|
|
||||||
mark += "^" * length
|
|
||||||
try:
|
|
||||||
if new_line_pos := text[len(mark) :].index("\n"):
|
|
||||||
text = text[:new_line_pos].rstrip() + "..."
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return text + "\n" + mark
|
|
||||||
|
|
@ -53,7 +53,7 @@ class MockedBot(Bot):
|
||||||
ok: bool,
|
ok: bool,
|
||||||
result: TelegramType = None,
|
result: TelegramType = None,
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
error_code: Optional[int] = None,
|
error_code: int = 200,
|
||||||
migrate_to_chat_id: Optional[int] = None,
|
migrate_to_chat_id: Optional[int] = None,
|
||||||
retry_after: Optional[int] = None,
|
retry_after: Optional[int] = None,
|
||||||
) -> Response[TelegramType]:
|
) -> Response[TelegramType]:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
import asyncio
|
||||||
from typing import AsyncContextManager, AsyncGenerator
|
from typing import AsyncContextManager, AsyncGenerator
|
||||||
|
|
||||||
import aiohttp_socks
|
import aiohttp_socks
|
||||||
import pytest
|
import pytest
|
||||||
|
from aiohttp import ClientError
|
||||||
from aresponses import ResponsesMockServer
|
from aresponses import ResponsesMockServer
|
||||||
|
|
||||||
from aiogram import Bot
|
from aiogram import Bot
|
||||||
|
|
@ -9,6 +11,7 @@ from aiogram.client.session import aiohttp
|
||||||
from aiogram.client.session.aiohttp import AiohttpSession
|
from aiogram.client.session.aiohttp import AiohttpSession
|
||||||
from aiogram.methods import Request, TelegramMethod
|
from aiogram.methods import Request, TelegramMethod
|
||||||
from aiogram.types import UNSET, InputFile
|
from aiogram.types import UNSET, InputFile
|
||||||
|
from aiogram.utils.exceptions.network import NetworkError
|
||||||
from tests.mocked_bot import MockedBot
|
from tests.mocked_bot import MockedBot
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -177,6 +180,22 @@ class TestAiohttpSession:
|
||||||
assert isinstance(result, int)
|
assert isinstance(result, int)
|
||||||
assert result == 42
|
assert result == 42
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("error", [ClientError("mocked"), asyncio.TimeoutError()])
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_make_request_network_error(self, error):
|
||||||
|
bot = Bot("42:TEST")
|
||||||
|
|
||||||
|
async def side_effect(*args, **kwargs):
|
||||||
|
raise error
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"aiohttp.client.ClientSession._request",
|
||||||
|
new_callable=CoroutineMock,
|
||||||
|
side_effect=side_effect,
|
||||||
|
):
|
||||||
|
with pytest.raises(NetworkError):
|
||||||
|
await bot.get_me()
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_stream_content(self, aresponses: ResponsesMockServer):
|
async def test_stream_content(self, aresponses: ResponsesMockServer):
|
||||||
aresponses.add(
|
aresponses.add(
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,20 @@ from typing import AsyncContextManager, AsyncGenerator, Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from aiogram import Bot
|
||||||
from aiogram.client.session.base import BaseSession, TelegramType
|
from aiogram.client.session.base import BaseSession, TelegramType
|
||||||
from aiogram.client.telegram import PRODUCTION, TelegramAPIServer
|
from aiogram.client.telegram import PRODUCTION, TelegramAPIServer
|
||||||
from aiogram.methods import DeleteMessage, GetMe, TelegramMethod
|
from aiogram.methods import DeleteMessage, GetMe, TelegramMethod
|
||||||
from aiogram.types import UNSET
|
from aiogram.types import UNSET, User
|
||||||
|
from aiogram.utils.exceptions.bad_request import BadRequest
|
||||||
|
from aiogram.utils.exceptions.base import TelegramAPIError
|
||||||
|
from aiogram.utils.exceptions.conflict import ConflictError
|
||||||
|
from aiogram.utils.exceptions.network import EntityTooLarge
|
||||||
|
from aiogram.utils.exceptions.not_found import NotFound
|
||||||
|
from aiogram.utils.exceptions.server import RestartingTelegram, ServerError
|
||||||
|
from aiogram.utils.exceptions.special import MigrateToChat, RetryAfter
|
||||||
|
from aiogram.utils.exceptions.unauthorized import UnauthorizedError
|
||||||
|
from tests.mocked_bot import MockedBot
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from asynctest import CoroutineMock, patch
|
from asynctest import CoroutineMock, patch
|
||||||
|
|
@ -137,20 +147,53 @@ class TestBaseSession:
|
||||||
|
|
||||||
assert session.clean_json(42) == 42
|
assert session.clean_json(42) == 42
|
||||||
|
|
||||||
def check_response(self):
|
@pytest.mark.parametrize(
|
||||||
|
"status_code,content,error",
|
||||||
|
[
|
||||||
|
[200, '{"ok":true,"result":true}', None],
|
||||||
|
[400, '{"ok":false,"description":"test"}', BadRequest],
|
||||||
|
[
|
||||||
|
400,
|
||||||
|
'{"ok":false,"description":"test", "parameters": {"retry_after": 1}}',
|
||||||
|
RetryAfter,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
400,
|
||||||
|
'{"ok":false,"description":"test", "parameters": {"migrate_to_chat_id": -42}}',
|
||||||
|
MigrateToChat,
|
||||||
|
],
|
||||||
|
[404, '{"ok":false,"description":"test"}', NotFound],
|
||||||
|
[401, '{"ok":false,"description":"test"}', UnauthorizedError],
|
||||||
|
[403, '{"ok":false,"description":"test"}', UnauthorizedError],
|
||||||
|
[409, '{"ok":false,"description":"test"}', ConflictError],
|
||||||
|
[413, '{"ok":false,"description":"test"}', EntityTooLarge],
|
||||||
|
[500, '{"ok":false,"description":"restarting"}', RestartingTelegram],
|
||||||
|
[500, '{"ok":false,"description":"test"}', ServerError],
|
||||||
|
[502, '{"ok":false,"description":"test"}', ServerError],
|
||||||
|
[499, '{"ok":false,"description":"test"}', TelegramAPIError],
|
||||||
|
[499, '{"ok":false,"description":"test"}', TelegramAPIError],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_check_response(self, status_code, content, error):
|
||||||
session = CustomSession()
|
session = CustomSession()
|
||||||
|
method = DeleteMessage(chat_id=42, message_id=42)
|
||||||
session.check_response(
|
if error is None:
|
||||||
method=DeleteMessage(chat_id=42, message_id=42),
|
|
||||||
status_code=200,
|
|
||||||
content='{"ok":true,"result":true}',
|
|
||||||
)
|
|
||||||
with pytest.raises(Exception):
|
|
||||||
session.check_response(
|
session.check_response(
|
||||||
method=DeleteMessage(chat_id=42, message_id=42),
|
method=method,
|
||||||
status_code=400,
|
status_code=status_code,
|
||||||
content='{"ok":false,"description":"test"}',
|
content=content,
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
with pytest.raises(error) as exc_info:
|
||||||
|
session.check_response(
|
||||||
|
method=method,
|
||||||
|
status_code=status_code,
|
||||||
|
content=content,
|
||||||
|
)
|
||||||
|
error: TelegramAPIError = exc_info.value
|
||||||
|
string = str(error)
|
||||||
|
if error.url:
|
||||||
|
assert error.url in string
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_make_request(self):
|
async def test_make_request(self):
|
||||||
|
|
@ -181,3 +224,36 @@ class TestBaseSession:
|
||||||
async with session as ctx:
|
async with session as ctx:
|
||||||
assert session == ctx
|
assert session == ctx
|
||||||
mocked_close.assert_awaited_once()
|
mocked_close.assert_awaited_once()
|
||||||
|
|
||||||
|
def test_add_middleware(self):
|
||||||
|
async def my_middleware(bot, method, make_request):
|
||||||
|
return await make_request(bot, method)
|
||||||
|
|
||||||
|
session = CustomSession()
|
||||||
|
assert not session.middlewares
|
||||||
|
|
||||||
|
session.middleware(my_middleware)
|
||||||
|
assert my_middleware in session.middlewares
|
||||||
|
assert len(session.middlewares) == 1
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_use_middleware(self, bot: MockedBot):
|
||||||
|
flag_before = False
|
||||||
|
flag_after = False
|
||||||
|
|
||||||
|
@bot.session.middleware
|
||||||
|
async def my_middleware(b, method, make_request):
|
||||||
|
nonlocal flag_before, flag_after
|
||||||
|
flag_before = True
|
||||||
|
try:
|
||||||
|
assert isinstance(b, Bot)
|
||||||
|
assert isinstance(method, TelegramMethod)
|
||||||
|
|
||||||
|
return await make_request(bot, method)
|
||||||
|
finally:
|
||||||
|
flag_after = True
|
||||||
|
|
||||||
|
bot.add_result_for(GetMe, ok=True, result=User(id=42, is_bot=True, first_name="Test"))
|
||||||
|
assert await bot.get_me()
|
||||||
|
assert flag_before
|
||||||
|
assert flag_after
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,10 @@ async def invalid_message_handler(message: Message):
|
||||||
raise Exception(42)
|
raise Exception(42)
|
||||||
|
|
||||||
|
|
||||||
|
async def anext(ait):
|
||||||
|
return await ait.__anext__()
|
||||||
|
|
||||||
|
|
||||||
RAW_UPDATE = {
|
RAW_UPDATE = {
|
||||||
"update_id": 42,
|
"update_id": 42,
|
||||||
"message": {
|
"message": {
|
||||||
|
|
@ -147,6 +151,21 @@ class TestDispatcher:
|
||||||
break
|
break
|
||||||
assert index == 42
|
assert index == 42
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_listen_update_with_error(self, bot: MockedBot):
|
||||||
|
dispatcher = Dispatcher()
|
||||||
|
listen = dispatcher._listen_updates(bot=bot)
|
||||||
|
bot.add_result_for(
|
||||||
|
GetUpdates, ok=True, result=[Update(update_id=update_id) for update_id in range(42)]
|
||||||
|
)
|
||||||
|
bot.add_result_for(GetUpdates, ok=False, error_code=500, description="restarting")
|
||||||
|
with patch(
|
||||||
|
"aiogram.utils.backoff.Backoff.asleep",
|
||||||
|
new_callable=CoroutineMock,
|
||||||
|
) as mocked_asleep:
|
||||||
|
assert isinstance(await anext(listen), Update)
|
||||||
|
assert mocked_asleep.awaited
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_silent_call_request(self, bot: MockedBot, caplog):
|
async def test_silent_call_request(self, bot: MockedBot, caplog):
|
||||||
dispatcher = Dispatcher()
|
dispatcher = Dispatcher()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aiogram.dispatcher.event.bases import SkipHandler, skip, UNHANDLED
|
from aiogram.dispatcher.event.bases import UNHANDLED, SkipHandler, skip
|
||||||
from aiogram.dispatcher.router import Router
|
from aiogram.dispatcher.router import Router
|
||||||
from aiogram.utils.warnings import CodeHasNoEffect
|
from aiogram.utils.warnings import CodeHasNoEffect
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue