From 6ee05fb901fb237f8c237c4cc5746f434afe2660 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Wed, 11 Dec 2019 21:31:31 +0200 Subject: [PATCH] Add tests for content types --- aiogram/api/types/__init__.py | 3 +- aiogram/dispatcher/filters/__init__.py | 2 +- .../{content_type.py => content_types.py} | 17 +- tests/test_api/test_types/test_message.py | 354 ++++++++++++++++++ .../test_event/test_observer.py | 1 + .../test_filters/test_content_types.py | 24 ++ 6 files changed, 391 insertions(+), 10 deletions(-) rename aiogram/dispatcher/filters/{content_type.py => content_types.py} (59%) create mode 100644 tests/test_api/test_types/test_message.py create mode 100644 tests/test_dispatcher/test_filters/test_content_types.py diff --git a/aiogram/api/types/__init__.py b/aiogram/api/types/__init__.py index dc77b7a6..07d604e4 100644 --- a/aiogram/api/types/__init__.py +++ b/aiogram/api/types/__init__.py @@ -58,7 +58,7 @@ from .labeled_price import LabeledPrice from .location import Location from .login_url import LoginUrl from .mask_position import MaskPosition -from .message import Message +from .message import ContentType, Message from .message_entity import MessageEntity from .order_info import OrderInfo from .passport_data import PassportData @@ -104,6 +104,7 @@ __all__ = ( "User", "Chat", "Message", + "ContentType", "MessageEntity", "PhotoSize", "Audio", diff --git a/aiogram/dispatcher/filters/__init__.py b/aiogram/dispatcher/filters/__init__.py index bafbe17d..637fe181 100644 --- a/aiogram/dispatcher/filters/__init__.py +++ b/aiogram/dispatcher/filters/__init__.py @@ -2,8 +2,8 @@ from typing import Dict, Tuple, Union from .base import BaseFilter from .command import Command, CommandObject +from .content_types import ContentTypesFilter from .text import Text -from .content_type import ContentTypesFilter __all__ = ( "BUILTIN_FILTERS", diff --git a/aiogram/dispatcher/filters/content_type.py b/aiogram/dispatcher/filters/content_types.py similarity index 59% rename from aiogram/dispatcher/filters/content_type.py rename to aiogram/dispatcher/filters/content_types.py index ec30a376..0f8134e2 100644 --- a/aiogram/dispatcher/filters/content_type.py +++ b/aiogram/dispatcher/filters/content_types.py @@ -1,6 +1,6 @@ from typing import Any, Dict, List, Optional, Union -from pydantic import root_validator +from pydantic import validator from ...api.types import Message from ...api.types.message import ContentType @@ -10,17 +10,18 @@ from .base import BaseFilter class ContentTypesFilter(BaseFilter): content_types: Optional[List[str]] = None - @root_validator - def validate_constraints(cls, values: Dict[str, Any]) -> Dict[str, Any]: - if "content_types" not in values or not values["content_types"]: - values["content_types"] = [ContentType.TEXT] + @validator("content_types", always=True) + def _validate_content_types(cls, value: Optional[List[str]]) -> Optional[List[str]]: + if not value: + value = [ContentType.TEXT] allowed_content_types = set(ContentType.all()) - bad_content_types = set(values["content_types"]) - allowed_content_types + bad_content_types = set(value) - allowed_content_types if bad_content_types: raise ValueError(f"Invalid content types {bad_content_types} is not allowed here") - return values + return value async def __call__(self, message: Message) -> Union[bool, Dict[str, Any]]: - if not self.content_types: + if not self.content_types: # pragma: no cover + # Is impossible but needed for valid typechecking return False return ContentType.ANY in self.content_types or message.content_type in self.content_types diff --git a/tests/test_api/test_types/test_message.py b/tests/test_api/test_types/test_message.py new file mode 100644 index 00000000..f1497be8 --- /dev/null +++ b/tests/test_api/test_types/test_message.py @@ -0,0 +1,354 @@ +import datetime + +import pytest + +from aiogram.api.types import ( + Animation, + Audio, + Chat, + Contact, + Document, + EncryptedCredentials, + Game, + Invoice, + Location, + PassportData, + PhotoSize, + Poll, + PollOption, + Sticker, + SuccessfulPayment, + User, + Venue, + Video, + VideoNote, + Voice, +) +from aiogram.api.types.message import ContentType, Message + + +class TestMessage: + @pytest.mark.parametrize( + "message,content_type", + [ + [ + Message( + message_id=42, + date=datetime.datetime.now(), + text="test", + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.TEXT, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + audio=Audio(file_id="file id", duration=42), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.AUDIO, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + animation=Animation(file_id="file id", width=42, height=42, duration=0), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.ANIMATION, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + document=Document(file_id="file id"), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.DOCUMENT, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + game=Game( + title="title", + description="description", + photo=[PhotoSize(file_id="file id", width=42, height=42)], + ), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.GAME, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + photo=[PhotoSize(file_id="file id", width=42, height=42)], + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.PHOTO, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + sticker=Sticker(file_id="file id", width=42, height=42, is_animated=False), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.STICKER, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + video=Video(file_id="file id", width=42, height=42, duration=0), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.VIDEO, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + video_note=VideoNote(file_id="file id", length=0, duration=0), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.VIDEO_NOTE, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + voice=Voice(file_id="file id", duration=0), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.VOICE, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + contact=Contact(phone_number="911", first_name="911"), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.CONTACT, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + venue=Venue( + location=Location(latitude=3.14, longitude=3.14), + title="Cupboard Under the Stairs", + address="Under the stairs, 4 Privet Drive, " + "Little Whinging, Surrey, England, Great Britain", + ), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.VENUE, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + location=Location(longitude=3.14, latitude=3.14), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.LOCATION, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + new_chat_members=[User(id=42, is_bot=False, first_name="Test")], + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.NEW_CHAT_MEMBERS, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + left_chat_member=User(id=42, is_bot=False, first_name="Test"), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.LEFT_CHAT_MEMBER, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + invoice=Invoice( + title="test", + description="test", + start_parameter="brilliant", + currency="BTC", + total_amount=1, + ), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.INVOICE, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + successful_payment=SuccessfulPayment( + currency="BTC", + total_amount=42, + invoice_payload="payload", + telegram_payment_charge_id="charge", + provider_payment_charge_id="payment", + ), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.SUCCESSFUL_PAYMENT, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + connected_website="token", + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.CONNECTED_WEBSITE, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + migrate_from_chat_id=42, + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.MIGRATE_FROM_CHAT_ID, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + migrate_to_chat_id=42, + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.MIGRATE_TO_CHAT_ID, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + pinned_message=Message( + message_id=42, + date=datetime.datetime.now(), + text="pinned", + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.PINNED_MESSAGE, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + new_chat_title="test", + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.NEW_CHAT_TITLE, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + new_chat_photo=[PhotoSize(file_id="file id", width=42, height=42)], + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.NEW_CHAT_PHOTO, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + delete_chat_photo=True, + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.DELETE_CHAT_PHOTO, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + group_chat_created=True, + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.GROUP_CHAT_CREATED, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + passport_data=PassportData( + data=[], + credentials=EncryptedCredentials(data="test", hash="test", secret="test"), + ), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.PASSPORT_DATA, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + poll=Poll( + id="QA", + question="Q", + options=[ + PollOption(text="A", voter_count=0), + PollOption(text="B", voter_count=0), + ], + is_closed=False, + ), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + ContentType.POLL, + ], + [ + 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"), + ), + ContentType.UNKNOWN, + ], + ], + ) + def test_content_type(self, message: Message, content_type: str): + assert message.content_type == content_type diff --git a/tests/test_dispatcher/test_event/test_observer.py b/tests/test_dispatcher/test_event/test_observer.py index c052fd01..a6978375 100644 --- a/tests/test_dispatcher/test_event/test_observer.py +++ b/tests/test_dispatcher/test_event/test_observer.py @@ -3,6 +3,7 @@ import functools from typing import Any, Awaitable, Callable, Dict, NoReturn, Union import pytest + from aiogram.api.types import Chat, Message, User from aiogram.dispatcher.event.handler import HandlerObject from aiogram.dispatcher.event.observer import EventObserver, SkipHandler, TelegramEventObserver diff --git a/tests/test_dispatcher/test_filters/test_content_types.py b/tests/test_dispatcher/test_filters/test_content_types.py new file mode 100644 index 00000000..8efa5084 --- /dev/null +++ b/tests/test_dispatcher/test_filters/test_content_types.py @@ -0,0 +1,24 @@ +import pytest +from pydantic import ValidationError + +from aiogram.dispatcher.filters import ContentTypesFilter + + +class TestContentTypesFilter: + def test_validator_empty(self): + filter_ = ContentTypesFilter() + assert filter_.content_types == ["text"] + + def test_validator_empty_list(self): + filter_ = ContentTypesFilter(content_types=[]) + assert filter_.content_types == ["text"] + + @pytest.mark.parametrize("values", [["text", "photo"], ["sticker"]]) + def test_validator_with_values(self, values): + filter_ = ContentTypesFilter(content_types=values) + assert filter_.content_types == values + + @pytest.mark.parametrize("values", [["test"], ["text", "test"], ["TEXT"]]) + def test_validator_with_bad_values(self, values): + with pytest.raises(ValidationError): + ContentTypesFilter(content_types=values)