mirror of
https://github.com/aiogram/aiogram.git
synced 2025-12-12 10:11:52 +00:00
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:
parent
ac7f2dc408
commit
7776cf9cf6
77 changed files with 2485 additions and 502 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
345
tests/test_dispatcher/test_filters/test_chat_member_updated.py
Normal file
345
tests/test_dispatcher/test_filters/test_chat_member_updated.py
Normal 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
|
||||
0
tests/test_dispatcher/test_flags/__init__.py
Normal file
0
tests/test_dispatcher/test_flags/__init__.py
Normal file
66
tests/test_dispatcher/test_flags/test_decorator.py
Normal file
66
tests/test_dispatcher/test_flags/test_decorator.py
Normal 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
|
||||
64
tests/test_dispatcher/test_flags/test_getter.py
Normal file
64
tests/test_dispatcher/test_flags/test_getter.py
Normal 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
|
||||
30
tests/test_dispatcher/test_fsm/storage/test_isolation.py
Normal file
30
tests/test_dispatcher/test_fsm/storage/test_isolation.py
Normal 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?"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
129
tests/test_utils/test_chat_action.py
Normal file
129
tests/test_utils/test_chat_action.py
Normal 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()
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue