Bot API 5.7 and some new features (#834)

* Update API, added some new features

* Fixed unknown chat_action value

* Separate events from dispatcher messages

* Disabled cache for I18n LazyProxy

* Rework events isolation

* Added chat member status changed filter, update Bot API 5.7, other small changes

* Improve exceptions in chat member status filter

* Fixed tests, covered flags and events isolation modules

* Try to fix flake8 unused type ignore

* Fixed linter error

* Cover chat member updated filter

* Cover chat action sender

* Added docs for chat action util

* Try to fix tests for python <= 3.9

* Fixed headers

* Added docs for flags functionality

* Added docs for chat_member_updated filter

* Added change notes

* Update dependencies and fix mypy checks

* Bump version
This commit is contained in:
Alex Root Junior 2022-02-19 01:45:59 +02:00 committed by GitHub
parent ac7f2dc408
commit 7776cf9cf6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
77 changed files with 2485 additions and 502 deletions

View file

@ -4,9 +4,13 @@ import pytest
from _pytest.config import UsageError
from aioredis.connection import parse_url as parse_redis_url
from aiogram import Bot
from aiogram.dispatcher.fsm.storage.memory import MemoryStorage
from aiogram.dispatcher.fsm.storage.redis import RedisStorage
from aiogram import Bot, Dispatcher
from aiogram.dispatcher.fsm.storage.memory import (
DisabledEventIsolation,
MemoryStorage,
SimpleEventIsolation,
)
from aiogram.dispatcher.fsm.storage.redis import RedisEventIsolation, RedisStorage
from tests.mocked_bot import MockedBot
DATA_DIR = Path(__file__).parent / "data"
@ -67,6 +71,42 @@ async def memory_storage():
await storage.close()
@pytest.fixture()
@pytest.mark.redis
async def redis_isolation(redis_server):
if not redis_server:
pytest.skip("Redis is not available here")
isolation = RedisEventIsolation.from_url(redis_server)
try:
await isolation.redis.info()
except ConnectionError as e:
pytest.skip(str(e))
try:
yield isolation
finally:
conn = await isolation.redis
await conn.flushdb()
await isolation.close()
@pytest.fixture()
async def lock_isolation():
isolation = SimpleEventIsolation()
try:
yield isolation
finally:
await isolation.close()
@pytest.fixture()
async def disabled_isolation():
isolation = DisabledEventIsolation()
try:
yield isolation
finally:
await isolation.close()
@pytest.fixture()
def bot():
bot = MockedBot()
@ -75,3 +115,13 @@ def bot():
yield bot
finally:
Bot.reset_current(token)
@pytest.fixture()
async def dispatcher():
dp = Dispatcher()
await dp.emit_startup()
try:
yield dp
finally:
await dp.emit_shutdown()

View file

@ -76,20 +76,15 @@ class TestDispatcher:
assert dp.update.handlers[0].callback == dp._listen_update
assert dp.update.outer_middlewares
def test_parent_router(self):
dp = Dispatcher()
def test_parent_router(self, dispatcher: Dispatcher):
with pytest.raises(RuntimeError):
dp.parent_router = Router()
assert dp.parent_router is None
dp._parent_router = Router()
assert dp.parent_router is None
dispatcher.parent_router = Router()
assert dispatcher.parent_router is None
dispatcher._parent_router = Router()
assert dispatcher.parent_router is None
@pytest.mark.parametrize("isolate_events", (True, False))
async def test_feed_update(self, isolate_events):
dp = Dispatcher(isolate_events=isolate_events)
bot = Bot("42:TEST")
@dp.message()
async def test_feed_update(self, dispatcher: Dispatcher, bot: MockedBot):
@dispatcher.message()
async def my_handler(message: Message, **kwargs):
assert "bot" in kwargs
assert isinstance(kwargs["bot"], Bot)
@ -97,7 +92,7 @@ class TestDispatcher:
return message.text
results_count = 0
result = await dp.feed_update(
result = await dispatcher.feed_update(
bot=bot,
update=Update(
update_id=42,

View file

@ -0,0 +1,345 @@
from datetime import datetime
import pytest
from aiogram.dispatcher.filters.chat_member_updated import (
ADMINISTRATOR,
IS_MEMBER,
JOIN_TRANSITION,
LEAVE_TRANSITION,
ChatMemberUpdatedFilter,
_MemberStatusGroupMarker,
_MemberStatusMarker,
_MemberStatusTransition,
)
from aiogram.types import Chat, ChatMember, ChatMemberUpdated, User
class TestMemberStatusMarker:
def test_str(self):
marker = _MemberStatusMarker("test")
assert str(marker) == "TEST"
assert str(+marker) == "+TEST"
assert str(-marker) == "-TEST"
def test_pos(self):
marker = _MemberStatusMarker("test")
assert marker.is_member is None
positive_marker = +marker
assert positive_marker is not marker
assert marker.is_member is None
assert positive_marker.is_member is True
def test_neg(self):
marker = _MemberStatusMarker("test")
assert marker.is_member is None
negative_marker = -marker
assert negative_marker is not marker
assert marker.is_member is None
assert negative_marker.is_member is False
def test_or(self):
marker1 = _MemberStatusMarker("test1")
marker2 = _MemberStatusMarker("test2")
combination = marker1 | marker2
assert isinstance(combination, _MemberStatusGroupMarker)
assert marker1 in combination.statuses
assert marker2 in combination.statuses
combination2 = marker1 | marker1
assert isinstance(combination2, _MemberStatusGroupMarker)
assert len(combination2.statuses) == 1
marker3 = _MemberStatusMarker("test3")
combination3 = marker3 | combination
assert isinstance(combination3, _MemberStatusGroupMarker)
assert marker3 in combination3.statuses
assert len(combination3.statuses) == 3
assert combination3 is not combination
with pytest.raises(TypeError):
marker1 | 42
def test_rshift(self):
marker1 = _MemberStatusMarker("test1")
marker2 = _MemberStatusMarker("test2")
marker3 = _MemberStatusMarker("test3")
transition = marker1 >> marker2
assert isinstance(transition, _MemberStatusTransition)
assert marker1 in transition.old.statuses
assert marker2 in transition.new.statuses
transition2 = marker1 >> (marker2 | marker3)
assert isinstance(transition2, _MemberStatusTransition)
with pytest.raises(TypeError):
marker1 >> 42
def test_lshift(self):
marker1 = _MemberStatusMarker("test1")
marker2 = _MemberStatusMarker("test2")
marker3 = _MemberStatusMarker("test3")
transition = marker1 << marker2
assert isinstance(transition, _MemberStatusTransition)
assert marker2 in transition.old.statuses
assert marker1 in transition.new.statuses
transition2 = marker1 << (marker2 | marker3)
assert isinstance(transition2, _MemberStatusTransition)
with pytest.raises(TypeError):
marker1 << 42
def test_hash(self):
marker1 = _MemberStatusMarker("test1")
marker1_1 = _MemberStatusMarker("test1")
marker2 = _MemberStatusMarker("test2")
assert hash(marker1) != hash(marker2)
assert hash(marker1) == hash(marker1_1)
assert hash(marker1) != hash(-marker1)
@pytest.mark.parametrize(
"name,is_member,member,result",
[
["test", None, ChatMember(status="member"), False],
["test", None, ChatMember(status="test"), True],
["test", True, ChatMember(status="test"), False],
["test", True, ChatMember(status="test", is_member=True), True],
["test", True, ChatMember(status="test", is_member=False), False],
],
)
def test_check(self, name, is_member, member, result):
marker = _MemberStatusMarker(name, is_member=is_member)
assert marker.check(member=member) == result
class TestMemberStatusGroupMarker:
def test_init_unique(self):
marker1 = _MemberStatusMarker("test1")
marker2 = _MemberStatusMarker("test2")
marker3 = _MemberStatusMarker("test3")
group = _MemberStatusGroupMarker(marker1, marker1, marker2, marker3)
assert len(group.statuses) == 3
def test_init_empty(self):
with pytest.raises(ValueError):
_MemberStatusGroupMarker()
def test_or(self):
marker1 = _MemberStatusMarker("test1")
marker2 = _MemberStatusMarker("test2")
marker3 = _MemberStatusMarker("test3")
marker4 = _MemberStatusMarker("test4")
group1 = _MemberStatusGroupMarker(marker1, marker2)
group2 = _MemberStatusGroupMarker(marker3, marker4)
group3 = group1 | marker3
assert isinstance(group3, _MemberStatusGroupMarker)
assert len(group3.statuses) == 3
group4 = group1 | group2
assert isinstance(group4, _MemberStatusGroupMarker)
assert len(group4.statuses) == 4
with pytest.raises(TypeError):
group4 | 42
def test_rshift(self):
marker1 = _MemberStatusMarker("test1")
marker2 = _MemberStatusMarker("test2")
marker3 = _MemberStatusMarker("test3")
group1 = _MemberStatusGroupMarker(marker1, marker2)
group2 = _MemberStatusGroupMarker(marker1, marker3)
transition1 = group1 >> marker1
assert isinstance(transition1, _MemberStatusTransition)
assert transition1.old is group1
assert marker1 in transition1.new.statuses
transition2 = group1 >> group2
assert isinstance(transition2, _MemberStatusTransition)
with pytest.raises(TypeError):
group1 >> 42
def test_lshift(self):
marker1 = _MemberStatusMarker("test1")
marker2 = _MemberStatusMarker("test2")
marker3 = _MemberStatusMarker("test3")
group1 = _MemberStatusGroupMarker(marker1, marker2)
group2 = _MemberStatusGroupMarker(marker1, marker3)
transition1 = group1 << marker1
assert isinstance(transition1, _MemberStatusTransition)
assert transition1.new is group1
assert marker1 in transition1.old.statuses
transition2 = group1 << group2
assert isinstance(transition2, _MemberStatusTransition)
with pytest.raises(TypeError):
group1 << 42
def test_str(self):
marker1 = _MemberStatusMarker("test1")
marker1_1 = +marker1
marker2 = _MemberStatusMarker("test2")
group1 = marker1 | marker1
assert str(group1) == "TEST1"
group2 = marker1 | marker2
assert str(group2) == "(TEST1 | TEST2)"
group3 = marker1 | marker1_1
assert str(group3) == "(+TEST1 | TEST1)"
@pytest.mark.parametrize(
"status,result",
[
["test", False],
["test1", True],
["test2", True],
],
)
def test_check(self, status, result):
marker1 = _MemberStatusMarker("test1")
marker2 = _MemberStatusMarker("test2")
group = marker1 | marker2
assert group.check(member=ChatMember(status=status)) is result
class TestMemberStatusTransition:
def test_invert(self):
marker1 = _MemberStatusMarker("test1")
marker2 = _MemberStatusMarker("test2")
transition1 = marker1 >> marker2
transition2 = ~transition1
assert transition1 is not transition2
assert transition1.old == transition2.new
assert transition1.new == transition2.old
assert str(transition1) == "TEST1 >> TEST2"
assert str(transition2) == "TEST2 >> TEST1"
@pytest.mark.parametrize(
"transition,old,new,result",
[
[JOIN_TRANSITION, ChatMember(status="left"), ChatMember(status="member"), True],
[
JOIN_TRANSITION,
ChatMember(status="restricted", is_member=True),
ChatMember(status="member"),
False,
],
[
JOIN_TRANSITION,
ChatMember(status="restricted", is_member=False),
ChatMember(status="member"),
True,
],
[
JOIN_TRANSITION,
ChatMember(status="member"),
ChatMember(status="restricted", is_member=False),
False,
],
[
LEAVE_TRANSITION,
ChatMember(status="member"),
ChatMember(status="restricted", is_member=False),
True,
],
],
)
def test_check(self, transition, old, new, result):
assert transition.check(old=old, new=new) == result
class TestChatMemberUpdatedStatusFilter:
@pytest.mark.asyncio
@pytest.mark.parametrize(
"transition,old,new,result",
[
[JOIN_TRANSITION, ChatMember(status="left"), ChatMember(status="member"), True],
[
JOIN_TRANSITION,
ChatMember(status="restricted", is_member=True),
ChatMember(status="member"),
False,
],
[
JOIN_TRANSITION,
ChatMember(status="restricted", is_member=False),
ChatMember(status="member"),
True,
],
[
JOIN_TRANSITION,
ChatMember(status="member"),
ChatMember(status="restricted", is_member=False),
False,
],
[
LEAVE_TRANSITION,
ChatMember(status="member"),
ChatMember(status="restricted", is_member=False),
True,
],
[
ADMINISTRATOR,
ChatMember(status="member"),
ChatMember(status="administrator"),
True,
],
[
IS_MEMBER,
ChatMember(status="restricted", is_member=False),
ChatMember(status="member"),
True,
],
],
)
async def test_call(self, transition, old, new, result):
updated_filter = ChatMemberUpdatedFilter(member_status_changed=transition)
user = User(id=42, first_name="Test", is_bot=False)
update = {
"user": user,
"until_date": datetime.now(),
"is_anonymous": False,
"can_be_edited": True,
"can_manage_chat": True,
"can_delete_messages": True,
"can_manage_voice_chats": True,
"can_restrict_members": True,
"can_promote_members": True,
"can_change_info": True,
"can_invite_users": True,
"can_post_messages": True,
"can_edit_messages": True,
"can_pin_messages": True,
"can_send_messages": True,
"can_send_media_messages": True,
"can_send_polls": True,
"can_send_other_messages": True,
"can_add_web_page_previews": True,
}
event = ChatMemberUpdated(
chat=Chat(id=42, type="test"),
from_user=user,
old_chat_member=old.copy(update=update),
new_chat_member=new.copy(update=update),
date=datetime.now(),
)
assert await updated_filter(event) is result

View file

@ -0,0 +1,66 @@
import pytest
from aiogram.dispatcher.flags.flag import Flag, FlagDecorator, FlagGenerator
@pytest.fixture(name="flag")
def flag_fixture() -> Flag:
return Flag("test", True)
@pytest.fixture(name="flag_decorator")
def flag_decorator_fixture(flag: Flag) -> FlagDecorator:
return FlagDecorator(flag)
@pytest.fixture(name="flag_generator")
def flag_flag_generator() -> FlagGenerator:
return FlagGenerator()
class TestFlagDecorator:
def test_with_value(self, flag_decorator: FlagDecorator):
new_decorator = flag_decorator._with_value(True)
assert new_decorator is not flag_decorator
assert new_decorator.flag is not flag_decorator.flag
assert new_decorator.flag
def test_call_invalid(self, flag_decorator: FlagDecorator):
with pytest.raises(ValueError):
flag_decorator(True, test=True)
def test_call_with_function(self, flag_decorator: FlagDecorator):
def func():
pass
decorated = flag_decorator(func)
assert decorated is func
assert hasattr(decorated, "aiogram_flag")
def test_call_with_arg(self, flag_decorator: FlagDecorator):
new_decorator = flag_decorator("hello")
assert new_decorator is not flag_decorator
assert new_decorator.flag.value == "hello"
def test_call_with_kwargs(self, flag_decorator: FlagDecorator):
new_decorator = flag_decorator(test=True)
assert new_decorator is not flag_decorator
assert isinstance(new_decorator.flag.value, dict)
assert "test" in new_decorator.flag.value
class TestFlagGenerator:
def test_getattr(self):
generator = FlagGenerator()
assert isinstance(generator.foo, FlagDecorator)
assert isinstance(generator.bar, FlagDecorator)
assert generator.foo is not generator.foo
assert generator.foo is not generator.bar
def test_failed_getattr(self):
generator = FlagGenerator()
with pytest.raises(AttributeError):
generator._something

View file

@ -0,0 +1,64 @@
from unittest.mock import patch
import pytest
from aiogram import F
from aiogram.dispatcher.event.handler import HandlerObject
from aiogram.dispatcher.flags.getter import (
check_flags,
extract_flags,
extract_flags_from_object,
get_flag,
)
class TestGetters:
def test_extract_flags_from_object(self):
def func():
pass
assert extract_flags_from_object(func) == {}
func.aiogram_flag = {"test": True}
assert extract_flags_from_object(func) == func.aiogram_flag
@pytest.mark.parametrize(
"obj,result",
[
[None, {}],
[{}, {}],
[{"handler": None}, {}],
[{"handler": HandlerObject(lambda: True, flags={"test": True})}, {"test": True}],
],
)
def test_extract_flags(self, obj, result):
assert extract_flags(obj) == result
@pytest.mark.parametrize(
"obj,name,default,result",
[
[None, "test", None, None],
[None, "test", 42, 42],
[{}, "test", None, None],
[{}, "test", 42, 42],
[{"handler": None}, "test", None, None],
[{"handler": None}, "test", 42, 42],
[{"handler": HandlerObject(lambda: True, flags={"test": True})}, "test", None, True],
[{"handler": HandlerObject(lambda: True, flags={"test": True})}, "test2", None, None],
[{"handler": HandlerObject(lambda: True, flags={"test": True})}, "test2", 42, 42],
],
)
def test_get_flag(self, obj, name, default, result):
assert get_flag(obj, name, default=default) == result
@pytest.mark.parametrize(
"flags,magic,result",
[
[{}, F.test, None],
[{"test": True}, F.test, True],
[{"test": True}, F.spam, None],
],
)
def test_check_flag(self, flags, magic, result):
with patch("aiogram.dispatcher.flags.getter.extract_flags", return_value=flags):
assert check_flags(object(), magic) == result

View file

@ -0,0 +1,30 @@
import pytest
from aiogram.dispatcher.fsm.storage.base import BaseEventIsolation, StorageKey
from tests.mocked_bot import MockedBot
pytestmark = pytest.mark.asyncio
@pytest.fixture(name="storage_key")
def create_storate_key(bot: MockedBot):
return StorageKey(chat_id=-42, user_id=42, bot_id=bot.id)
@pytest.mark.parametrize(
"isolation",
[
pytest.lazy_fixture("redis_isolation"),
pytest.lazy_fixture("lock_isolation"),
pytest.lazy_fixture("disabled_isolation"),
],
)
class TestIsolations:
async def test_lock(
self,
bot: MockedBot,
isolation: BaseEventIsolation,
storage_key: StorageKey,
):
async with isolation.lock(bot=bot, key=storage_key):
assert True, "You are kidding me?"

View file

@ -1,9 +1,11 @@
from typing import Literal
import pytest
from aiogram.dispatcher.fsm.storage.base import DEFAULT_DESTINY, StorageKey
from aiogram.dispatcher.fsm.storage.redis import DefaultKeyBuilder
from aiogram.dispatcher.fsm.storage.redis import (
DefaultKeyBuilder,
RedisEventIsolation,
RedisStorage,
)
pytestmark = pytest.mark.asyncio
@ -45,3 +47,11 @@ class TestRedisDefaultKeyBuilder:
)
with pytest.raises(ValueError):
key_builder.build(key, FIELD)
def test_create_isolation(self):
fake_redis = object()
storage = RedisStorage(redis=fake_redis)
isolation = storage.create_isolation()
assert isinstance(isolation, RedisEventIsolation)
assert isolation.redis is fake_redis
assert isolation.key_builder is storage.key_builder

View file

@ -16,11 +16,6 @@ def create_storate_key(bot: MockedBot):
[pytest.lazy_fixture("redis_storage"), pytest.lazy_fixture("memory_storage")],
)
class TestStorages:
async def test_lock(self, bot: MockedBot, storage: BaseStorage, storage_key: StorageKey):
# TODO: ?!?
async with storage.lock(bot=bot, key=storage_key):
assert True, "You are kidding me?"
async def test_set_state(self, bot: MockedBot, storage: BaseStorage, storage_key: StorageKey):
assert await storage.get_state(bot=bot, key=storage_key) is None

View file

@ -1,4 +1,6 @@
import asyncio
import time
from asyncio import Event
from dataclasses import dataclass
from typing import Any, Dict
@ -19,6 +21,12 @@ from aiogram.methods import GetMe, Request
from aiogram.types import Message, User
from tests.mocked_bot import MockedBot
try:
from asynctest import CoroutineMock, patch
except ImportError:
from unittest.mock import AsyncMock as CoroutineMock # type: ignore
from unittest.mock import patch
class TestAiohttpServer:
def test_setup_application(self):
@ -74,8 +82,11 @@ class TestSimpleRequestHandler:
app = Application()
dp = Dispatcher()
handler_event = Event()
@dp.message(F.text == "test")
def handle_message(msg: Message):
handler_event.set()
return msg.answer("PASS")
handler = SimpleRequestHandler(
@ -97,8 +108,15 @@ class TestSimpleRequestHandler:
assert not result
handler.handle_in_background = True
resp = await self.make_reqest(client=client)
assert resp.status == 200
with patch(
"aiogram.dispatcher.dispatcher.Dispatcher.silent_call_request",
new_callable=CoroutineMock,
) as mocked_silent_call_request:
handler_event.clear()
resp = await self.make_reqest(client=client)
assert resp.status == 200
await asyncio.wait_for(handler_event.wait(), timeout=1)
mocked_silent_call_request.assert_awaited()
result = await resp.json()
assert not result

View file

@ -0,0 +1,129 @@
import asyncio
import time
from datetime import datetime
import pytest
from aiogram import Bot, flags
from aiogram.dispatcher.event.handler import HandlerObject
from aiogram.types import Chat, Message, User
from aiogram.utils.chat_action import ChatActionMiddleware, ChatActionSender
from tests.mocked_bot import MockedBot
try:
from asynctest import CoroutineMock, patch
except ImportError:
from unittest.mock import AsyncMock as CoroutineMock # type: ignore
from unittest.mock import patch
pytestmarm = pytest.mark.asyncio
class TestChatActionSender:
async def test_wait(self, bot: Bot, loop: asyncio.BaseEventLoop):
sender = ChatActionSender.typing(bot=bot, chat_id=42)
loop.call_soon(sender._close_event.set)
start = time.monotonic()
await sender._wait(1)
assert time.monotonic() - start < 1
@pytest.mark.parametrize(
"action",
[
"typing",
"upload_photo",
"record_video",
"upload_video",
"record_voice",
"upload_voice",
"upload_document",
"choose_sticker",
"find_location",
"record_video_note",
"upload_video_note",
],
)
@pytest.mark.parametrize("pass_bot", [True, False])
async def test_factory(self, action: str, bot: MockedBot, pass_bot: bool):
sender_factory = getattr(ChatActionSender, action)
sender = sender_factory(chat_id=42, bot=bot if pass_bot else None)
assert isinstance(sender, ChatActionSender)
assert sender.action == action
assert sender.chat_id == 42
assert sender.bot is bot
async def test_worker(self, bot: Bot):
with patch(
"aiogram.client.bot.Bot.send_chat_action",
new_callable=CoroutineMock,
) as mocked_send_chat_action:
async with ChatActionSender.typing(
bot=bot, chat_id=42, interval=0.01, initial_sleep=0
):
await asyncio.sleep(0.1)
assert mocked_send_chat_action.await_count > 1
mocked_send_chat_action.assert_awaited_with(action="typing", chat_id=42)
async def test_contextmanager(self, bot: MockedBot):
sender: ChatActionSender = ChatActionSender.typing(bot=bot, chat_id=42)
assert not sender.running
await sender._stop() # nothing
async with sender:
assert sender.running
assert not sender._close_event.is_set()
with pytest.raises(RuntimeError):
await sender._run()
assert not sender.running
class TestChatActionMiddleware:
@pytest.mark.parametrize(
"value",
[
None,
"sticker",
{"action": "upload_photo"},
{"interval": 1, "initial_sleep": 0.5},
],
)
async def test_call_default(self, value, bot: Bot):
async def handler(event, data):
return "OK"
if value is None:
handler1 = flags.chat_action(handler)
else:
handler1 = flags.chat_action(value)(handler)
middleware = ChatActionMiddleware()
with patch(
"aiogram.utils.chat_action.ChatActionSender._run",
new_callable=CoroutineMock,
) as mocked_run, patch(
"aiogram.utils.chat_action.ChatActionSender._stop",
new_callable=CoroutineMock,
) as mocked_stop:
data = {"handler": HandlerObject(callback=handler1), "bot": bot}
message = Message(
chat=Chat(id=42, type="private", title="Test"),
from_user=User(id=42, is_bot=False, first_name="Test"),
date=datetime.now(),
message_id=42,
)
result = await middleware(handler=handler1, event=None, data=data)
assert result == "OK"
mocked_run.assert_not_awaited()
mocked_stop.assert_not_awaited()
result = await middleware(
handler=handler1,
event=message,
data=data,
)
assert result == "OK"
mocked_run.assert_awaited()
mocked_stop.assert_awaited()

View file

@ -114,6 +114,24 @@ class TestSimpleI18nMiddleware:
assert middleware not in dp.update.outer_middlewares
assert middleware in dp.message.outer_middlewares
async def test_get_unknown_locale(self, i18n: I18n):
dp = Dispatcher()
middleware = SimpleI18nMiddleware(i18n=i18n)
middleware.setup(router=dp)
locale = await middleware.get_locale(
None,
{
"event_from_user": User(
id=42,
is_bot=False,
first_name="Test",
language_code="unknown",
)
},
)
assert locale == i18n.default_locale
@pytest.mark.asyncio
class TestConstI18nMiddleware: