mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Fix tests, improved docs
This commit is contained in:
parent
e5644248f9
commit
c8ad7552a2
27 changed files with 296 additions and 206 deletions
|
|
@ -12,3 +12,4 @@ python:
|
|||
path: .
|
||||
extra_requirements:
|
||||
- docs
|
||||
- redis
|
||||
|
|
|
|||
1
CHANGES/890.feature.rst
Normal file
1
CHANGES/890.feature.rst
Normal file
|
|
@ -0,0 +1 @@
|
|||
Added full support of `Telegram Bot API 6.0 <https://core.telegram.org/bots/api-changelog#april-16-2022>`_
|
||||
|
|
@ -130,10 +130,6 @@ from .video_chat_scheduled import VideoChatScheduled
|
|||
from .video_chat_started import VideoChatStarted
|
||||
from .video_note import VideoNote
|
||||
from .voice import Voice
|
||||
from .voice_chat_ended import VoiceChatEnded
|
||||
from .voice_chat_participants_invited import VoiceChatParticipantsInvited
|
||||
from .voice_chat_scheduled import VoiceChatScheduled
|
||||
from .voice_chat_started import VoiceChatStarted
|
||||
from .web_app_data import WebAppData
|
||||
from .web_app_info import WebAppInfo
|
||||
from .webhook_info import WebhookInfo
|
||||
|
|
@ -170,10 +166,6 @@ __all__ = (
|
|||
"WebAppData",
|
||||
"ProximityAlertTriggered",
|
||||
"MessageAutoDeleteTimerChanged",
|
||||
"VoiceChatScheduled",
|
||||
"VoiceChatStarted",
|
||||
"VoiceChatEnded",
|
||||
"VoiceChatParticipantsInvited",
|
||||
"VideoChatScheduled",
|
||||
"VideoChatStarted",
|
||||
"VideoChatEnded",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import TelegramObject
|
||||
|
|
@ -15,5 +16,5 @@ class VideoChatScheduled(TelegramObject):
|
|||
Source: https://core.telegram.org/bots/api#videochatscheduled
|
||||
"""
|
||||
|
||||
start_date: int
|
||||
start_date: datetime
|
||||
"""Point in time (Unix timestamp) when the video chat is supposed to be started by a chat administrator"""
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from .base import TelegramObject
|
||||
|
||||
|
||||
class VoiceChatEnded(TelegramObject):
|
||||
"""
|
||||
This object represents a service message about a voice chat ended in the chat.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#voicechatended
|
||||
"""
|
||||
|
||||
duration: int
|
||||
"""Voice chat duration in seconds"""
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
|
||||
from .base import TelegramObject
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .user import User
|
||||
|
||||
|
||||
class VoiceChatParticipantsInvited(TelegramObject):
|
||||
"""
|
||||
This object represents a service message about new members invited to a voice chat.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#voicechatparticipantsinvited
|
||||
"""
|
||||
|
||||
users: Optional[List[User]] = None
|
||||
"""*Optional*. New members that were invited to the voice chat"""
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from .base import TelegramObject
|
||||
|
||||
|
||||
class VoiceChatScheduled(TelegramObject):
|
||||
"""
|
||||
This object represents a service message about a voice chat scheduled in the chat.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#voicechatscheduled
|
||||
"""
|
||||
|
||||
start_date: int
|
||||
"""Point in time (Unix timestamp) when the voice chat is supposed to be started by a chat administrator"""
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from .base import TelegramObject
|
||||
|
||||
|
||||
class VoiceChatStarted(TelegramObject):
|
||||
"""
|
||||
This object represents a service message about a voice chat started in the chat. Currently holds no information.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#voicechatstarted
|
||||
"""
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
from typing import Optional
|
||||
|
||||
from aiogram.types import TelegramObject
|
||||
from aiogram.types.web_app_user import WebAppUser
|
||||
|
||||
|
||||
class WebAppInitData(TelegramObject):
|
||||
query_id: Optional[str] = None
|
||||
"""Optional. A unique identifier for the Web App session, required for sending messages via the answerWebAppQuery method."""
|
||||
user: Optional[WebAppUser] = None
|
||||
"""Optional. An object containing data about the current user."""
|
||||
receiver: Optional[WebAppUser] = None
|
||||
"""Optional. An object containing data about the chat partner of the current user in the chat where the bot was launched via the attachment menu. Returned only for Web Apps launched via the attachment menu."""
|
||||
start_param: Optional[str] = None
|
||||
"""Optional. The value of the startattach parameter, passed via link. Only returned for Web Apps when launched from the attachment menu via link. The value of the start_param parameter will also be passed in the GET-parameter tgWebAppStartParam, so the Web App can load the correct interface right away."""
|
||||
auth_date: int
|
||||
"""Unix time when the form was opened."""
|
||||
hash: str
|
||||
"""A hash of all passed parameters, which the bot server can use to check their validity."""
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
from typing import Optional
|
||||
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
|
||||
class WebAppUser(TelegramObject):
|
||||
"""
|
||||
This object contains the data of the Web App user.
|
||||
|
||||
Source: https://core.telegram.org/bots/webapps#webappuser
|
||||
"""
|
||||
|
||||
id: int
|
||||
"""A unique identifier for the user or bot. This number may have more than 32 significant bits and some programming languages may have difficulty/silent defects in interpreting it. It has at most 52 significant bits, so a 64-bit integer or a double-precision float type is safe for storing this identifier."""
|
||||
is_bot: Optional[bool] = None
|
||||
"""Optional. True, if this user is a bot. Returns in the receiver field only."""
|
||||
first_name: str
|
||||
"""First name of the user or bot."""
|
||||
last_name: Optional[str] = None
|
||||
"""Optional. Last name of the user or bot."""
|
||||
username: Optional[str] = None
|
||||
"""Optional. Username of the user or bot."""
|
||||
language_code: Optional[str] = None
|
||||
"""Optional. IETF language tag of the user's language. Returns in user field only."""
|
||||
photo_url: Optional[str] = None
|
||||
"""Optional. URL of the user’s profile photo. The photo can be in .jpeg or .svg formats. Only returned for Web Apps launched from the attachment menu."""
|
||||
|
|
@ -1,11 +1,56 @@
|
|||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
from datetime import datetime
|
||||
from operator import itemgetter
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Callable, Optional
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
from aiogram.types.web_app_init_data import WebAppInitData
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
|
||||
class WebAppUser(TelegramObject):
|
||||
"""
|
||||
This object contains the data of the Web App user.
|
||||
|
||||
Source: https://core.telegram.org/bots/webapps#webappuser
|
||||
"""
|
||||
|
||||
id: int
|
||||
"""A unique identifier for the user or bot. This number may have more than 32 significant bits and some programming languages may have difficulty/silent defects in interpreting it. It has at most 52 significant bits, so a 64-bit integer or a double-precision float type is safe for storing this identifier."""
|
||||
is_bot: Optional[bool] = None
|
||||
"""True, if this user is a bot. Returns in the receiver field only."""
|
||||
first_name: str
|
||||
"""First name of the user or bot."""
|
||||
last_name: Optional[str] = None
|
||||
"""Last name of the user or bot."""
|
||||
username: Optional[str] = None
|
||||
"""Username of the user or bot."""
|
||||
language_code: Optional[str] = None
|
||||
"""IETF language tag of the user's language. Returns in user field only."""
|
||||
photo_url: Optional[str] = None
|
||||
"""URL of the user’s profile photo. The photo can be in .jpeg or .svg formats. Only returned for Web Apps launched from the attachment menu."""
|
||||
|
||||
|
||||
class WebAppInitData(TelegramObject):
|
||||
"""
|
||||
This object contains data that is transferred to the Web App when it is opened. It is empty if the Web App was launched from a keyboard button.
|
||||
|
||||
Source: https://core.telegram.org/bots/webapps#webappinitdata
|
||||
"""
|
||||
|
||||
query_id: Optional[str] = None
|
||||
"""A unique identifier for the Web App session, required for sending messages via the answerWebAppQuery method."""
|
||||
user: Optional[WebAppUser] = None
|
||||
"""An object containing data about the current user."""
|
||||
receiver: Optional[WebAppUser] = None
|
||||
"""An object containing data about the chat partner of the current user in the chat where the bot was launched via the attachment menu. Returned only for Web Apps launched via the attachment menu."""
|
||||
start_param: Optional[str] = None
|
||||
"""The value of the startattach parameter, passed via link. Only returned for Web Apps when launched from the attachment menu via link. The value of the start_param parameter will also be passed in the GET-parameter tgWebAppStartParam, so the Web App can load the correct interface right away."""
|
||||
auth_date: datetime
|
||||
"""Unix time when the form was opened."""
|
||||
hash: str
|
||||
"""A hash of all passed parameters, which the bot server can use to check their validity."""
|
||||
|
||||
|
||||
def check_webapp_signature(token: str, init_data: str) -> bool:
|
||||
|
|
@ -14,13 +59,13 @@ def check_webapp_signature(token: str, init_data: str) -> bool:
|
|||
|
||||
Source: https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app
|
||||
|
||||
:param token:
|
||||
:param init_data:
|
||||
:param token: bot Token
|
||||
:param init_data: data from frontend to be validated
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
parsed_data = dict(parse_qsl(init_data))
|
||||
except ValueError:
|
||||
parsed_data = dict(parse_qsl(init_data, strict_parsing=True))
|
||||
except ValueError: # pragma: no cover
|
||||
# Init data is not a valid query string
|
||||
return False
|
||||
if "hash" not in parsed_data:
|
||||
|
|
@ -38,16 +83,19 @@ def check_webapp_signature(token: str, init_data: str) -> bool:
|
|||
return calculated_hash == hash_
|
||||
|
||||
|
||||
def parse_init_data(
|
||||
def parse_webapp_init_data(
|
||||
init_data: str,
|
||||
*,
|
||||
_loads: Callable[..., Any] = json.loads,
|
||||
loads: Callable[..., Any] = json.loads,
|
||||
) -> WebAppInitData:
|
||||
"""
|
||||
Parse WebApp init data and return it as dict
|
||||
Parse WebApp init data and return it as WebAppInitData object
|
||||
|
||||
:param init_data:
|
||||
:param _loads:
|
||||
This method doesn't make any security check, so you shall not trust to this data,
|
||||
use :code:`safe_parse_webapp_init_data` instead.
|
||||
|
||||
:param init_data: data from frontend to be parsed
|
||||
:param loads:
|
||||
:return:
|
||||
"""
|
||||
result = {}
|
||||
|
|
@ -55,7 +103,7 @@ def parse_init_data(
|
|||
if (value.startswith("[") and value.endswith("]")) or (
|
||||
value.startswith("{") and value.endswith("}")
|
||||
):
|
||||
value = _loads(value)
|
||||
value = loads(value)
|
||||
result[key] = value
|
||||
return WebAppInitData(**result)
|
||||
|
||||
|
|
@ -64,16 +112,18 @@ def safe_parse_webapp_init_data(
|
|||
token: str,
|
||||
init_data: str,
|
||||
*,
|
||||
_loads: Callable[..., Any] = json.loads,
|
||||
loads: Callable[..., Any] = json.loads,
|
||||
) -> WebAppInitData:
|
||||
"""
|
||||
Validate WebApp init data and return it as dict
|
||||
Validate raw WebApp init data and return it as WebAppInitData object
|
||||
|
||||
:param token:
|
||||
:param init_data:
|
||||
:param _loads:
|
||||
Raise :type:`ValueError` when data is invalid
|
||||
|
||||
:param token: bot token
|
||||
:param init_data: data from frontend to be parsed and validated
|
||||
:param loads:
|
||||
:return:
|
||||
"""
|
||||
if check_webapp_signature(token, init_data):
|
||||
return parse_init_data(init_data, _loads=_loads)
|
||||
return parse_webapp_init_data(init_data, loads=loads)
|
||||
raise ValueError("Invalid init data signature")
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
##############
|
||||
VoiceChatEnded
|
||||
##############
|
||||
|
||||
|
||||
.. automodule:: aiogram.types.voice_chat_ended
|
||||
:members:
|
||||
:member-order: bysource
|
||||
:undoc-members: True
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
############################
|
||||
VoiceChatParticipantsInvited
|
||||
############################
|
||||
|
||||
|
||||
.. automodule:: aiogram.types.voice_chat_participants_invited
|
||||
:members:
|
||||
:member-order: bysource
|
||||
:undoc-members: True
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
##################
|
||||
VoiceChatScheduled
|
||||
##################
|
||||
|
||||
|
||||
.. automodule:: aiogram.types.voice_chat_scheduled
|
||||
:members:
|
||||
:member-order: bysource
|
||||
:undoc-members: True
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
################
|
||||
VoiceChatStarted
|
||||
################
|
||||
|
||||
|
||||
.. automodule:: aiogram.types.voice_chat_started
|
||||
:members:
|
||||
:member-order: bysource
|
||||
:undoc-members: True
|
||||
|
|
@ -7,3 +7,4 @@ Utils
|
|||
keyboard
|
||||
i18n
|
||||
chat_action
|
||||
web_app
|
||||
|
|
|
|||
55
docs/utils/web_app.rst
Normal file
55
docs/utils/web_app.rst
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
======
|
||||
WebApз
|
||||
======
|
||||
|
||||
Telegram Bot API 6.0 announces a revolution in the development of chatbots using WebApp feature.
|
||||
|
||||
You can read more details on it in the official `blog <https://telegram.org/blog/notifications-bots#bot-revolution>`_
|
||||
and `documentation <https://core.telegram.org/bots/webapps>`_.
|
||||
|
||||
`aiogram` implements simple utils to remove headache with the data validation from Telegram WebApp on the backend side.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
For example from frontend you will pass :code:`application/x-www-form-urlencoded` POST request
|
||||
with :code:`_auth` field in body and wants to return User info inside response as :code:`application/json`
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from aiogram.utils.web_app import safe_parse_webapp_init_data
|
||||
from aiohttp.web_request import Request
|
||||
from aiohttp.web_response import json_response
|
||||
|
||||
async def check_data_handler(request: Request):
|
||||
bot: Bot = request.app["bot"]
|
||||
|
||||
data = await request.post() # application/x-www-form-urlencoded
|
||||
try:
|
||||
data = safe_parse_webapp_init_data(token=bot.token, init_data=data["_auth"])
|
||||
except ValueError:
|
||||
return json_response({"ok": False, "err": "Unauthorized"}, status=401)
|
||||
return json_response({"ok": True, "data": data.user.dict()})
|
||||
|
||||
Functions
|
||||
=========
|
||||
|
||||
.. autofunction:: aiogram.utils.web_app.check_webapp_signature
|
||||
|
||||
.. autofunction:: aiogram.utils.web_app.parse_webapp_init_data
|
||||
|
||||
.. autofunction:: aiogram.utils.web_app.safe_parse_webapp_init_data
|
||||
|
||||
|
||||
Types
|
||||
=====
|
||||
|
||||
.. autoclass:: aiogram.utils.web_app.WebAppInitData
|
||||
:members:
|
||||
:member-order: bysource
|
||||
:undoc-members: True
|
||||
|
||||
.. autoclass:: aiogram.utils.web_app.WebAppUser
|
||||
:members:
|
||||
:member-order: bysource
|
||||
:undoc-members: True
|
||||
|
|
@ -1,19 +1,18 @@
|
|||
import pytest
|
||||
|
||||
from aiogram.methods import AnswerWebAppQuery, Request
|
||||
from aiogram.types import SentWebAppMessage
|
||||
from aiogram.types import InlineQueryResult, SentWebAppMessage
|
||||
from tests.mocked_bot import MockedBot
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
class TestAnswerWebAppQuery:
|
||||
@pytest.mark.asyncio
|
||||
async def test_method(self, bot: MockedBot):
|
||||
prepare_result = bot.add_result_for(AnswerWebAppQuery, ok=True, result=None)
|
||||
prepare_result = bot.add_result_for(AnswerWebAppQuery, ok=True, result=SentWebAppMessage())
|
||||
|
||||
response: SentWebAppMessage = await AnswerWebAppQuery(
|
||||
web_app_query_id=...,
|
||||
result=...,
|
||||
web_app_query_id="test",
|
||||
result=InlineQueryResult(),
|
||||
)
|
||||
request: Request = bot.get_request()
|
||||
assert request.method == "answerWebAppQuery"
|
||||
|
|
@ -22,11 +21,11 @@ class TestAnswerWebAppQuery:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot_method(self, bot: MockedBot):
|
||||
prepare_result = bot.add_result_for(AnswerWebAppQuery, ok=True, result=None)
|
||||
prepare_result = bot.add_result_for(AnswerWebAppQuery, ok=True, result=SentWebAppMessage())
|
||||
|
||||
response: SentWebAppMessage = await bot.answer_web_app_query(
|
||||
web_app_query_id=...,
|
||||
result=...,
|
||||
web_app_query_id="test",
|
||||
result=InlineQueryResult(),
|
||||
)
|
||||
request: Request = bot.get_request()
|
||||
assert request.method == "answerWebAppQuery"
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import pytest
|
||||
|
||||
from aiogram.methods import GetChatMenuButton, Request
|
||||
from aiogram.types import MenuButton
|
||||
from aiogram.types import MenuButton, MenuButtonDefault
|
||||
from tests.mocked_bot import MockedBot
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
class TestGetChatMenuButton:
|
||||
@pytest.mark.asyncio
|
||||
async def test_method(self, bot: MockedBot):
|
||||
prepare_result = bot.add_result_for(GetChatMenuButton, ok=True, result=None)
|
||||
prepare_result = bot.add_result_for(GetChatMenuButton, ok=True, result=MenuButtonDefault())
|
||||
|
||||
response: MenuButton = await GetChatMenuButton()
|
||||
request: Request = bot.get_request()
|
||||
|
|
@ -19,7 +18,7 @@ class TestGetChatMenuButton:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot_method(self, bot: MockedBot):
|
||||
prepare_result = bot.add_result_for(GetChatMenuButton, ok=True, result=None)
|
||||
prepare_result = bot.add_result_for(GetChatMenuButton, ok=True, result=MenuButtonDefault())
|
||||
|
||||
response: MenuButton = await bot.get_chat_menu_button()
|
||||
request: Request = bot.get_request()
|
||||
|
|
|
|||
|
|
@ -5,11 +5,23 @@ from aiogram.types import ChatAdministratorRights
|
|||
from tests.mocked_bot import MockedBot
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
class TestGetMyDefaultAdministratorRights:
|
||||
@pytest.mark.asyncio
|
||||
async def test_method(self, bot: MockedBot):
|
||||
prepare_result = bot.add_result_for(GetMyDefaultAdministratorRights, ok=True, result=None)
|
||||
prepare_result = bot.add_result_for(
|
||||
GetMyDefaultAdministratorRights,
|
||||
ok=True,
|
||||
result=ChatAdministratorRights(
|
||||
is_anonymous=False,
|
||||
can_manage_chat=False,
|
||||
can_delete_messages=False,
|
||||
can_manage_video_chats=False,
|
||||
can_restrict_members=False,
|
||||
can_promote_members=False,
|
||||
can_change_info=False,
|
||||
can_invite_users=False,
|
||||
),
|
||||
)
|
||||
|
||||
response: ChatAdministratorRights = await GetMyDefaultAdministratorRights()
|
||||
request: Request = bot.get_request()
|
||||
|
|
@ -19,7 +31,20 @@ class TestGetMyDefaultAdministratorRights:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot_method(self, bot: MockedBot):
|
||||
prepare_result = bot.add_result_for(GetMyDefaultAdministratorRights, ok=True, result=None)
|
||||
prepare_result = bot.add_result_for(
|
||||
GetMyDefaultAdministratorRights,
|
||||
ok=True,
|
||||
result=ChatAdministratorRights(
|
||||
is_anonymous=False,
|
||||
can_manage_chat=False,
|
||||
can_delete_messages=False,
|
||||
can_manage_video_chats=False,
|
||||
can_restrict_members=False,
|
||||
can_promote_members=False,
|
||||
can_change_info=False,
|
||||
can_invite_users=False,
|
||||
),
|
||||
)
|
||||
|
||||
response: ChatAdministratorRights = await bot.get_my_default_administrator_rights()
|
||||
request: Request = bot.get_request()
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class TestGetStickerSet:
|
|||
name="test",
|
||||
title="test",
|
||||
is_animated=False,
|
||||
is_video=False,
|
||||
contains_masks=False,
|
||||
stickers=[
|
||||
Sticker(
|
||||
|
|
@ -23,6 +24,7 @@ class TestGetStickerSet:
|
|||
width=42,
|
||||
height=42,
|
||||
is_animated=False,
|
||||
is_video=False,
|
||||
file_unique_id="file id",
|
||||
)
|
||||
],
|
||||
|
|
@ -42,6 +44,7 @@ class TestGetStickerSet:
|
|||
name="test",
|
||||
title="test",
|
||||
is_animated=False,
|
||||
is_video=False,
|
||||
contains_masks=False,
|
||||
stickers=[
|
||||
Sticker(
|
||||
|
|
@ -49,6 +52,7 @@ class TestGetStickerSet:
|
|||
width=42,
|
||||
height=42,
|
||||
is_animated=False,
|
||||
is_video=False,
|
||||
file_unique_id="file id",
|
||||
)
|
||||
],
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class TestSendSticker:
|
|||
width=42,
|
||||
height=42,
|
||||
is_animated=False,
|
||||
is_video=False,
|
||||
file_unique_id="file id",
|
||||
),
|
||||
chat=Chat(id=42, type="private"),
|
||||
|
|
@ -45,6 +46,7 @@ class TestSendSticker:
|
|||
width=42,
|
||||
height=42,
|
||||
is_animated=False,
|
||||
is_video=False,
|
||||
file_unique_id="file id",
|
||||
),
|
||||
chat=Chat(id=42, type="private"),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
import pytest
|
||||
|
||||
from aiogram.api.methods import Request, SetChatMenuButton
|
||||
from aiogram.methods import Request, SetChatMenuButton
|
||||
from tests.mocked_bot import MockedBot
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
class TestSetChatMenuButton:
|
||||
@pytest.mark.asyncio
|
||||
async def test_method(self, bot: MockedBot):
|
||||
prepare_result = bot.add_result_for(SetChatMenuButton, ok=True, result=None)
|
||||
prepare_result = bot.add_result_for(SetChatMenuButton, ok=True, result=True)
|
||||
|
||||
response: bool = await SetChatMenuButton()
|
||||
request: Request = bot.get_request()
|
||||
|
|
@ -18,7 +17,7 @@ class TestSetChatMenuButton:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot_method(self, bot: MockedBot):
|
||||
prepare_result = bot.add_result_for(SetChatMenuButton, ok=True, result=None)
|
||||
prepare_result = bot.add_result_for(SetChatMenuButton, ok=True, result=True)
|
||||
|
||||
response: bool = await bot.set_chat_menu_button()
|
||||
request: Request = bot.get_request()
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
import pytest
|
||||
|
||||
from aiogram.api.methods import Request, SetMyDefaultAdministratorRights
|
||||
from aiogram.methods import Request, SetMyDefaultAdministratorRights
|
||||
from tests.mocked_bot import MockedBot
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
class TestSetMyDefaultAdministratorRights:
|
||||
@pytest.mark.asyncio
|
||||
async def test_method(self, bot: MockedBot):
|
||||
prepare_result = bot.add_result_for(SetMyDefaultAdministratorRights, ok=True, result=None)
|
||||
prepare_result = bot.add_result_for(SetMyDefaultAdministratorRights, ok=True, result=True)
|
||||
|
||||
response: bool = await SetMyDefaultAdministratorRights()
|
||||
request: Request = bot.get_request()
|
||||
|
|
@ -18,7 +17,7 @@ class TestSetMyDefaultAdministratorRights:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot_method(self, bot: MockedBot):
|
||||
prepare_result = bot.add_result_for(SetMyDefaultAdministratorRights, ok=True, result=None)
|
||||
prepare_result = bot.add_result_for(SetMyDefaultAdministratorRights, ok=True, result=True)
|
||||
|
||||
response: bool = await bot.set_my_default_administrator_rights()
|
||||
request: Request = bot.get_request()
|
||||
|
|
|
|||
|
|
@ -52,11 +52,13 @@ from aiogram.types import (
|
|||
User,
|
||||
Venue,
|
||||
Video,
|
||||
VideoChatEnded,
|
||||
VideoChatParticipantsInvited,
|
||||
VideoChatScheduled,
|
||||
VideoChatStarted,
|
||||
VideoNote,
|
||||
Voice,
|
||||
VoiceChatEnded,
|
||||
VoiceChatParticipantsInvited,
|
||||
VoiceChatStarted,
|
||||
WebAppData,
|
||||
)
|
||||
from aiogram.types.message import ContentType, Message
|
||||
|
||||
|
|
@ -122,6 +124,7 @@ TEST_MESSAGE_STICKER = Message(
|
|||
width=42,
|
||||
height=42,
|
||||
is_animated=False,
|
||||
is_video=False,
|
||||
),
|
||||
chat=Chat(id=42, type="private"),
|
||||
from_user=User(id=42, is_bot=False, first_name="Test"),
|
||||
|
|
@ -318,29 +321,38 @@ TEST_MESSAGE_MESSAGE_AUTO_DELETE_TIMER_CHANGED = Message(
|
|||
message_auto_delete_timer_changed=MessageAutoDeleteTimerChanged(message_auto_delete_time=42),
|
||||
from_user=User(id=42, is_bot=False, first_name="Test"),
|
||||
)
|
||||
TEST_MESSAGE_VOICE_CHAT_STARTED = Message(
|
||||
TEST_MESSAGE_VIDEO_CHAT_STARTED = Message(
|
||||
message_id=42,
|
||||
date=datetime.datetime.now(),
|
||||
chat=Chat(id=42, type="private"),
|
||||
from_user=User(id=42, is_bot=False, first_name="Test"),
|
||||
voice_chat_started=VoiceChatStarted(),
|
||||
video_chat_started=VideoChatStarted(),
|
||||
)
|
||||
TEST_MESSAGE_VOICE_CHAT_ENDED = Message(
|
||||
TEST_MESSAGE_VIDEO_CHAT_ENDED = Message(
|
||||
message_id=42,
|
||||
date=datetime.datetime.now(),
|
||||
chat=Chat(id=42, type="private"),
|
||||
from_user=User(id=42, is_bot=False, first_name="Test"),
|
||||
voice_chat_ended=VoiceChatEnded(duration=42),
|
||||
video_chat_ended=VideoChatEnded(duration=42),
|
||||
)
|
||||
TEST_MESSAGE_VOICE_CHAT_PARTICIPANTS_INVITED = Message(
|
||||
TEST_MESSAGE_VIDEO_CHAT_PARTICIPANTS_INVITED = Message(
|
||||
message_id=42,
|
||||
date=datetime.datetime.now(),
|
||||
chat=Chat(id=42, type="private"),
|
||||
from_user=User(id=42, is_bot=False, first_name="Test"),
|
||||
voice_chat_participants_invited=VoiceChatParticipantsInvited(
|
||||
video_chat_participants_invited=VideoChatParticipantsInvited(
|
||||
users=[User(id=69, is_bot=False, first_name="Test")]
|
||||
),
|
||||
)
|
||||
TEST_MESSAGE_VIDEO_CHAT_SCHEDULED = Message(
|
||||
message_id=42,
|
||||
date=datetime.datetime.now(),
|
||||
chat=Chat(id=42, type="private"),
|
||||
from_user=User(id=42, is_bot=False, first_name="Test"),
|
||||
video_chat_scheduled=VideoChatScheduled(
|
||||
start_date=datetime.datetime.now(),
|
||||
),
|
||||
)
|
||||
TEST_MESSAGE_DICE = Message(
|
||||
message_id=42,
|
||||
date=datetime.datetime.now(),
|
||||
|
|
@ -348,6 +360,13 @@ TEST_MESSAGE_DICE = Message(
|
|||
dice=Dice(value=6, emoji="X"),
|
||||
from_user=User(id=42, is_bot=False, first_name="Test"),
|
||||
)
|
||||
TEST_MESSAGE_WEB_APP_DATA = Message(
|
||||
message_id=42,
|
||||
date=datetime.datetime.now(),
|
||||
chat=Chat(id=42, type="private"),
|
||||
web_app_data=WebAppData(data="test", button_text="Test"),
|
||||
from_user=User(id=42, is_bot=False, first_name="Test"),
|
||||
)
|
||||
TEST_MESSAGE_UNKNOWN = Message(
|
||||
message_id=42,
|
||||
date=datetime.datetime.now(),
|
||||
|
|
@ -391,13 +410,15 @@ class TestMessage:
|
|||
TEST_MESSAGE_MESSAGE_AUTO_DELETE_TIMER_CHANGED,
|
||||
ContentType.MESSAGE_AUTO_DELETE_TIMER_CHANGED,
|
||||
],
|
||||
[TEST_MESSAGE_VOICE_CHAT_STARTED, ContentType.VOICE_CHAT_STARTED],
|
||||
[TEST_MESSAGE_VOICE_CHAT_ENDED, ContentType.VOICE_CHAT_ENDED],
|
||||
[TEST_MESSAGE_VIDEO_CHAT_SCHEDULED, ContentType.VIDEO_CHAT_SCHEDULED],
|
||||
[TEST_MESSAGE_VIDEO_CHAT_STARTED, ContentType.VIDEO_CHAT_STARTED],
|
||||
[TEST_MESSAGE_VIDEO_CHAT_ENDED, ContentType.VIDEO_CHAT_ENDED],
|
||||
[
|
||||
TEST_MESSAGE_VOICE_CHAT_PARTICIPANTS_INVITED,
|
||||
ContentType.VOICE_CHAT_PARTICIPANTS_INVITED,
|
||||
TEST_MESSAGE_VIDEO_CHAT_PARTICIPANTS_INVITED,
|
||||
ContentType.VIDEO_CHAT_PARTICIPANTS_INVITED,
|
||||
],
|
||||
[TEST_MESSAGE_DICE, ContentType.DICE],
|
||||
[TEST_MESSAGE_WEB_APP_DATA, ContentType.WEB_APP_DATA],
|
||||
[TEST_MESSAGE_UNKNOWN, ContentType.UNKNOWN],
|
||||
],
|
||||
)
|
||||
|
|
@ -535,9 +556,9 @@ class TestMessage:
|
|||
[TEST_MESSAGE_PASSPORT_DATA, None],
|
||||
[TEST_MESSAGE_POLL, SendPoll],
|
||||
[TEST_MESSAGE_MESSAGE_AUTO_DELETE_TIMER_CHANGED, None],
|
||||
[TEST_MESSAGE_VOICE_CHAT_STARTED, None],
|
||||
[TEST_MESSAGE_VOICE_CHAT_ENDED, None],
|
||||
[TEST_MESSAGE_VOICE_CHAT_PARTICIPANTS_INVITED, None],
|
||||
[TEST_MESSAGE_VIDEO_CHAT_STARTED, None],
|
||||
[TEST_MESSAGE_VIDEO_CHAT_ENDED, None],
|
||||
[TEST_MESSAGE_VIDEO_CHAT_PARTICIPANTS_INVITED, None],
|
||||
[TEST_MESSAGE_DICE, SendDice],
|
||||
[TEST_MESSAGE_UNKNOWN, None],
|
||||
],
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ class TestChatMemberUpdatedStatusFilter:
|
|||
"can_be_edited": True,
|
||||
"can_manage_chat": True,
|
||||
"can_delete_messages": True,
|
||||
"can_manage_voice_chats": True,
|
||||
"can_manage_video_chats": True,
|
||||
"can_restrict_members": True,
|
||||
"can_promote_members": True,
|
||||
"can_change_info": True,
|
||||
|
|
|
|||
80
tests/test_utils/test_web_app.py
Normal file
80
tests/test_utils/test_web_app.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import pytest
|
||||
|
||||
from aiogram.utils.web_app import (
|
||||
WebAppInitData,
|
||||
check_webapp_signature,
|
||||
parse_webapp_init_data,
|
||||
safe_parse_webapp_init_data,
|
||||
)
|
||||
|
||||
|
||||
class TestWebApp:
|
||||
@pytest.mark.parametrize(
|
||||
"token,case,result",
|
||||
[
|
||||
[
|
||||
"42:TEST",
|
||||
"auth_date=1650385342"
|
||||
"&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D"
|
||||
"&query_id=test"
|
||||
"&hash=46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803",
|
||||
True,
|
||||
],
|
||||
[
|
||||
"42:INVALID",
|
||||
"auth_date=1650385342"
|
||||
"&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D"
|
||||
"&query_id=test"
|
||||
"&hash=46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803",
|
||||
False,
|
||||
],
|
||||
[
|
||||
"42:TEST",
|
||||
"user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D&query_id=test&hash=test",
|
||||
False,
|
||||
],
|
||||
[
|
||||
"42:TEST",
|
||||
"user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D&query_id=test",
|
||||
False,
|
||||
],
|
||||
["42:TEST", "", False],
|
||||
["42:TEST", "test&foo=bar=baz", False],
|
||||
],
|
||||
)
|
||||
def test_check_webapp_signature(self, token, case, result):
|
||||
assert check_webapp_signature(token, case) is result
|
||||
|
||||
def test_parse_web_app_init_data(self):
|
||||
parsed = parse_webapp_init_data(
|
||||
"auth_date=1650385342"
|
||||
"&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D"
|
||||
"&query_id=test"
|
||||
"&hash=46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803",
|
||||
)
|
||||
assert isinstance(parsed, WebAppInitData)
|
||||
assert parsed.user
|
||||
assert parsed.user.first_name == "Test"
|
||||
assert parsed.user.id == 42
|
||||
assert parsed.query_id == "test"
|
||||
assert parsed.hash == "46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803"
|
||||
assert parsed.auth_date.year == 2022
|
||||
|
||||
def test_valid_safe_parse_webapp_init_data(self):
|
||||
assert safe_parse_webapp_init_data(
|
||||
"42:TEST",
|
||||
"auth_date=1650385342"
|
||||
"&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D"
|
||||
"&query_id=test"
|
||||
"&hash=46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803",
|
||||
)
|
||||
|
||||
def test_invalid_safe_parse_webapp_init_data(self):
|
||||
with pytest.raises(ValueError):
|
||||
safe_parse_webapp_init_data(
|
||||
"42:TOKEN",
|
||||
"auth_date=1650385342"
|
||||
"&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D"
|
||||
"&query_id=test"
|
||||
"&hash=test",
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue