Fix tests, improved docs

This commit is contained in:
Alex Root Junior 2022-04-19 21:03:26 +03:00
parent e5644248f9
commit c8ad7552a2
No known key found for this signature in database
GPG key ID: 074C1D455EBEA4AC
27 changed files with 296 additions and 206 deletions

View file

@ -12,3 +12,4 @@ python:
path: .
extra_requirements:
- docs
- redis

1
CHANGES/890.feature.rst Normal file
View file

@ -0,0 +1 @@
Added full support of `Telegram Bot API 6.0 <https://core.telegram.org/bots/api-changelog#april-16-2022>`_

View file

@ -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",

View file

@ -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"""

View file

@ -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"""

View file

@ -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"""

View file

@ -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"""

View file

@ -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
"""

View file

@ -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."""

View file

@ -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 users profile photo. The photo can be in .jpeg or .svg formats. Only returned for Web Apps launched from the attachment menu."""

View file

@ -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 users 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")

View file

@ -1,9 +0,0 @@
##############
VoiceChatEnded
##############
.. automodule:: aiogram.types.voice_chat_ended
:members:
:member-order: bysource
:undoc-members: True

View file

@ -1,9 +0,0 @@
############################
VoiceChatParticipantsInvited
############################
.. automodule:: aiogram.types.voice_chat_participants_invited
:members:
:member-order: bysource
:undoc-members: True

View file

@ -1,9 +0,0 @@
##################
VoiceChatScheduled
##################
.. automodule:: aiogram.types.voice_chat_scheduled
:members:
:member-order: bysource
:undoc-members: True

View file

@ -1,9 +0,0 @@
################
VoiceChatStarted
################
.. automodule:: aiogram.types.voice_chat_started
:members:
:member-order: bysource
:undoc-members: True

View file

@ -7,3 +7,4 @@ Utils
keyboard
i18n
chat_action
web_app

55
docs/utils/web_app.rst Normal file
View 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

View file

@ -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"

View file

@ -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()

View file

@ -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()

View file

@ -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",
)
],

View file

@ -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"),

View file

@ -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()

View file

@ -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()

View file

@ -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],
],

View file

@ -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,

View 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",
)