diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst index cf2c7d1cccd..fbc5ae7bd9c 100644 --- a/docs/source/telegram.at-tree.rst +++ b/docs/source/telegram.at-tree.rst @@ -20,6 +20,10 @@ Available Types telegram.botname telegram.botshortdescription telegram.businessconnection + telegram.businessintro + telegram.businesslocation + telegram.businessopeninghours + telegram.businessopeninghoursinterval telegram.businessmessagesdeleted telegram.callbackquery telegram.chat diff --git a/docs/source/telegram.businessintro.rst b/docs/source/telegram.businessintro.rst new file mode 100644 index 00000000000..4870258e5b4 --- /dev/null +++ b/docs/source/telegram.businessintro.rst @@ -0,0 +1,6 @@ +BusinessIntro +================== + +.. autoclass:: telegram.BusinessIntro + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.businesslocation.rst b/docs/source/telegram.businesslocation.rst new file mode 100644 index 00000000000..1a1b8893b65 --- /dev/null +++ b/docs/source/telegram.businesslocation.rst @@ -0,0 +1,6 @@ +BusinessLocation +================== + +.. autoclass:: telegram.BusinessLocation + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.businessopeninghours.rst b/docs/source/telegram.businessopeninghours.rst new file mode 100644 index 00000000000..cab989c8475 --- /dev/null +++ b/docs/source/telegram.businessopeninghours.rst @@ -0,0 +1,6 @@ +BusinessOpeningHours +==================== + +.. autoclass:: telegram.BusinessOpeningHours + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.businessopeninghoursinterval.rst b/docs/source/telegram.businessopeninghoursinterval.rst new file mode 100644 index 00000000000..241379dbcfb --- /dev/null +++ b/docs/source/telegram.businessopeninghoursinterval.rst @@ -0,0 +1,6 @@ +BusinessOpeningHoursInterval +============================ + +.. autoclass:: telegram.BusinessOpeningHoursInterval + :members: + :show-inheritance: \ No newline at end of file diff --git a/telegram/__init__.py b/telegram/__init__.py index f12e5b8da61..89104373100 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -37,7 +37,11 @@ "BotName", "BotShortDescription", "BusinessConnection", + "BusinessIntro", + "BusinessLocation", "BusinessMessagesDeleted", + "BusinessOpeningHours", + "BusinessOpeningHoursInterval", "CallbackGame", "CallbackQuery", "Chat", @@ -242,7 +246,14 @@ ) from ._botdescription import BotDescription, BotShortDescription from ._botname import BotName -from ._business import BusinessConnection, BusinessMessagesDeleted +from ._business import ( + BusinessConnection, + BusinessIntro, + BusinessLocation, + BusinessMessagesDeleted, + BusinessOpeningHours, + BusinessOpeningHoursInterval, +) from ._callbackquery import CallbackQuery from ._chat import Chat from ._chatadministratorrights import ChatAdministratorRights diff --git a/telegram/_business.py b/telegram/_business.py index 953d5d967a6..d208de31092 100644 --- a/telegram/_business.py +++ b/telegram/_business.py @@ -20,9 +20,11 @@ """This module contains the Telegram Business related classes.""" from datetime import datetime -from typing import TYPE_CHECKING, Optional, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._chat import Chat +from telegram._files.location import Location +from telegram._files.sticker import Sticker from telegram._telegramobject import TelegramObject from telegram._user import User from telegram._utils.argumentparsing import parse_sequence_arg @@ -183,3 +185,261 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessMess data["chat"] = Chat.de_json(data.get("chat"), bot) return super().de_json(data=data, bot=bot) + + +class BusinessIntro(TelegramObject): + """ + This object represents the intro of a business account. + + Objects of this class are comparable in terms of equality. + Two objects of this class are considered equal, if their + :attr:`title`, :attr:`message` and :attr:`sticker` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + title (:obj:`str`, optional): Title text of the business intro. + message (:obj:`str`, optional): Message text of the business intro. + sticker (:class:`telegram.Sticker`, optional): Sticker of the business intro. + + Attributes: + title (:obj:`str`): Optional. Title text of the business intro. + message (:obj:`str`): Optional. Message text of the business intro. + sticker (:class:`telegram.Sticker`): Optional. Sticker of the business intro. + """ + + __slots__ = ( + "message", + "sticker", + "title", + ) + + def __init__( + self, + title: Optional[str] = None, + message: Optional[str] = None, + sticker: Optional[Sticker] = None, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.title: Optional[str] = title + self.message: Optional[str] = message + self.sticker: Optional[Sticker] = sticker + + self._id_attrs = (self.title, self.message, self.sticker) + + self._freeze() + + @classmethod + def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessIntro"]: + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + if not data: + return None + + data["sticker"] = Sticker.de_json(data.get("sticker"), bot) + + return super().de_json(data=data, bot=bot) + + +class BusinessLocation(TelegramObject): + """ + This object represents the location of a business account. + + Objects of this class are comparable in terms of equality. + Two objects of this class are considered equal, if their + :attr:`address` is equal. + + .. versionadded:: NEXT.VERSION + + Args: + address (:obj:`str`): Address of the business. + location (:class:`telegram.Location`, optional): Location of the business. + + Attributes: + address (:obj:`str`): Address of the business. + location (:class:`telegram.Location`): Optional. Location of the business. + """ + + __slots__ = ( + "address", + "location", + ) + + def __init__( + self, + address: str, + location: Optional[Location] = None, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.address: str = address + self.location: Optional[Location] = location + + self._id_attrs = (self.address,) + + self._freeze() + + @classmethod + def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessLocation"]: + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + if not data: + return None + + data["location"] = Location.de_json(data.get("location"), bot) + + return super().de_json(data=data, bot=bot) + + +class BusinessOpeningHoursInterval(TelegramObject): + """ + This object represents the time intervals describing business opening hours. + + Objects of this class are comparable in terms of equality. + Two objects of this class are considered equal, if their + :attr:`opening_minute` and :attr:`closing_minute` are equal. + + .. versionadded:: NEXT.VERSION + + Examples: + A day has (24 * 60 =) 1440 minutes, a week has (7 * 1440 =) 10080 minutes. + Starting the the minute's sequence from Monday, example values of + :attr:`opening_minute`, :attr:`closing_minute` will map to the following day times: + + * Monday - 8am to 8:30pm: + - ``opening_minute = 480`` :guilabel:`8 * 60` + - ``closing_minute = 1230`` :guilabel:`20 * 60 + 30` + * Tuesday - 24 hours: + - ``opening_minute = 1440`` :guilabel:`24 * 60` + - ``closing_minute = 2879`` :guilabel:`2 * 24 * 60 - 1` + * Sunday - 12am - 11:58pm: + - ``opening_minute = 8640`` :guilabel:`6 * 24 * 60` + - ``closing_minute = 10078`` :guilabel:`7 * 24 * 60 - 2` + + Args: + opening_minute (:obj:`int`): The minute's sequence number in a week, starting on Monday, + marking the start of the time interval during which the business is open; + 0 - 7 * 24 * 60. + closing_minute (:obj:`int`): The minute's + sequence number in a week, starting on Monday, marking the end of the time interval + during which the business is open; 0 - 8 * 24 * 60 + + Attributes: + opening_minute (:obj:`int`): The minute's sequence number in a week, starting on Monday, + marking the start of the time interval during which the business is open; + 0 - 7 * 24 * 60. + closing_minute (:obj:`int`): The minute's + sequence number in a week, starting on Monday, marking the end of the time interval + during which the business is open; 0 - 8 * 24 * 60 + """ + + __slots__ = ("_closing_time", "_opening_time", "closing_minute", "opening_minute") + + def __init__( + self, + opening_minute: int, + closing_minute: int, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.opening_minute: int = opening_minute + self.closing_minute: int = closing_minute + + self._opening_time: Optional[Tuple[int, int, int]] = None + self._closing_time: Optional[Tuple[int, int, int]] = None + + self._id_attrs = (self.opening_minute, self.closing_minute) + + self._freeze() + + def _parse_minute(self, minute: int) -> Tuple[int, int, int]: + return (minute // 1440, minute % 1440 // 60, minute % 1440 % 60) + + @property + def opening_time(self) -> Tuple[int, int, int]: + """Convenience attribute. A :obj:`tuple` parsed from :attr:`opening_minute`. It contains + the `weekday`, `hour` and `minute` in the same ranges as :attr:`datetime.datetime.weekday`, + :attr:`datetime.datetime.hour` and :attr:`datetime.datetime.minute` + + Returns: + Tuple[:obj:`int`, :obj:`int`, :obj:`int`]: + """ + if self._opening_time is None: + self._opening_time = self._parse_minute(self.opening_minute) + return self._opening_time + + @property + def closing_time(self) -> Tuple[int, int, int]: + """Convenience attribute. A :obj:`tuple` parsed from :attr:`closing_minute`. It contains + the `weekday`, `hour` and `minute` in the same ranges as :attr:`datetime.datetime.weekday`, + :attr:`datetime.datetime.hour` and :attr:`datetime.datetime.minute` + + Returns: + Tuple[:obj:`int`, :obj:`int`, :obj:`int`]: + """ + if self._closing_time is None: + self._closing_time = self._parse_minute(self.closing_minute) + return self._closing_time + + +class BusinessOpeningHours(TelegramObject): + """ + This object represents the opening hours of a business account. + + Objects of this class are comparable in terms of equality. + Two objects of this class are considered equal, if their + :attr:`time_zone_name` and :attr:`opening_hours` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + time_zone_name (:obj:`str`): Unique name of the time zone for which the opening + hours are defined. + opening_hours (Sequence[:class:`telegram.BusinessOpeningHoursInterval`]): List of + time intervals describing business opening hours. + + Attributes: + time_zone_name (:obj:`str`): Unique name of the time zone for which the opening + hours are defined. + opening_hours (Sequence[:class:`telegram.BusinessOpeningHoursInterval`]): List of + time intervals describing business opening hours. + """ + + __slots__ = ("opening_hours", "time_zone_name") + + def __init__( + self, + time_zone_name: str, + opening_hours: Sequence[BusinessOpeningHoursInterval], + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.time_zone_name: str = time_zone_name + self.opening_hours: Sequence[BusinessOpeningHoursInterval] = parse_sequence_arg( + opening_hours + ) + + self._id_attrs = (self.time_zone_name, self.opening_hours) + + self._freeze() + + @classmethod + def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessOpeningHours"]: + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + if not data: + return None + + data["opening_hours"] = BusinessOpeningHoursInterval.de_list( + data.get("opening_hours"), bot + ) + + return super().de_json(data=data, bot=bot) diff --git a/telegram/_chat.py b/telegram/_chat.py index ce78b9e1982..a6b7928e157 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -45,6 +45,9 @@ Animation, Audio, Bot, + BusinessIntro, + BusinessLocation, + BusinessOpeningHours, ChatInviteLink, ChatMember, Contact, @@ -170,6 +173,21 @@ class Chat(TelegramObject): only in :meth:`telegram.Bot.get_chat`. .. versionadded:: 20.0 + business_intro (:class:`telegram.BusinessIntro`, optional): For private chats with + business accounts, the intro of the business. Returned only in + :meth:`telegram.Bot.get_chat`. + + .. versionadded:: NEXT.VERSION + business_location (:class:`telegram.BusinessLocation`, optional): For private chats with + business accounts, the location of the business. Returned only in + :meth:`telegram.Bot.get_chat`. + + .. versionadded:: NEXT.VERSION + business_opening_hours (:class:`telegram.BusinessOpeningHours`, optional): For private + chats with business accounts, the opening hours of the business. Returned only in + :meth:`telegram.Bot.get_chat`. + + .. versionadded:: NEXT.VERSION available_reactions (Sequence[:class:`telegram.ReactionType`], optional): List of available reactions allowed in the chat. If omitted, then all of :const:`telegram.constants.ReactionEmoji` are allowed. Returned only in @@ -321,6 +339,21 @@ class Chat(TelegramObject): obtained via :meth:`~telegram.Bot.get_chat`. .. versionadded:: 20.0 + business_intro (:class:`telegram.BusinessIntro`): Optional. For private chats with + business accounts, the intro of the business. Returned only in + :meth:`telegram.Bot.get_chat`. + + .. versionadded:: NEXT.VERSION + business_location (:class:`telegram.BusinessLocation`): Optional. For private chats with + business accounts, the location of the business. Returned only in + :meth:`telegram.Bot.get_chat`. + + .. versionadded:: NEXT.VERSION + business_opening_hours (:class:`telegram.BusinessOpeningHours`): Optional. For private + chats with business accounts, the opening hours of the business. Returned only in + :meth:`telegram.Bot.get_chat`. + + .. versionadded:: NEXT.VERSION available_reactions (Tuple[:class:`telegram.ReactionType`]): Optional. List of available reactions allowed in the chat. If omitted, then all of :const:`telegram.constants.ReactionEmoji` are allowed. Returned only in @@ -401,6 +434,9 @@ class Chat(TelegramObject): "background_custom_emoji_id", "bio", "birthdate", + "business_intro", + "business_location", + "business_opening_hours", "can_set_sticker_set", "custom_emoji_sticker_set_name", "description", @@ -491,6 +527,9 @@ def __init__( custom_emoji_sticker_set_name: Optional[str] = None, birthdate: Optional[Birthdate] = None, personal_chat: Optional["Chat"] = None, + business_intro: Optional["BusinessIntro"] = None, + business_location: Optional["BusinessLocation"] = None, + business_opening_hours: Optional["BusinessOpeningHours"] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -542,6 +581,9 @@ def __init__( self.custom_emoji_sticker_set_name: Optional[str] = custom_emoji_sticker_set_name self.birthdate: Optional[Birthdate] = birthdate self.personal_chat: Optional["Chat"] = personal_chat + self.business_intro: Optional["BusinessIntro"] = business_intro + self.business_location: Optional["BusinessLocation"] = business_location + self.business_opening_hours: Optional["BusinessOpeningHours"] = business_opening_hours self._id_attrs = (self.id,) @@ -604,7 +646,12 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Chat"]: ) data["photo"] = ChatPhoto.de_json(data.get("photo"), bot) - from telegram import Message # pylint: disable=import-outside-toplevel + from telegram import ( # pylint: disable=import-outside-toplevel + BusinessIntro, + BusinessLocation, + BusinessOpeningHours, + Message, + ) data["pinned_message"] = Message.de_json(data.get("pinned_message"), bot) data["permissions"] = ChatPermissions.de_json(data.get("permissions"), bot) @@ -612,6 +659,11 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Chat"]: data["available_reactions"] = ReactionType.de_list(data.get("available_reactions"), bot) data["birthdate"] = Birthdate.de_json(data.get("birthdate"), bot) data["personal_chat"] = cls.de_json(data.get("personal_chat"), bot) + data["business_intro"] = BusinessIntro.de_json(data.get("business_intro"), bot) + data["business_location"] = BusinessLocation.de_json(data.get("business_location"), bot) + data["business_opening_hours"] = BusinessOpeningHours.de_json( + data.get("business_opening_hours"), bot + ) api_kwargs = {} # This is a deprecated field that TG still returns for backwards compatibility diff --git a/tests/test_business.py b/tests/test_business.py index 5514847a753..da6838d6d47 100644 --- a/tests/test_business.py +++ b/tests/test_business.py @@ -20,7 +20,18 @@ import pytest -from telegram import BusinessConnection, BusinessMessagesDeleted, Chat, User +from telegram import ( + BusinessConnection, + BusinessIntro, + BusinessLocation, + BusinessMessagesDeleted, + BusinessOpeningHours, + BusinessOpeningHoursInterval, + Chat, + Location, + Sticker, + User, +) from telegram._utils.datetime import UTC, to_timestamp from tests.auxil.slots import mro_slots @@ -35,6 +46,17 @@ class TestBusinessBase: message_ids = (123, 321) business_connection_id = "123" chat = Chat(123, "test_chat") + title = "Business Title" + message = "Business description" + sticker = Sticker("sticker_id", "unique_id", 50, 50, True, False, Sticker.REGULAR) + address = "address" + location = Location(-23.691288, 46.788279) + opening_minute = 0 + closing_minute = 60 + time_zone_name = "Country/City" + opening_hours = [ + BusinessOpeningHoursInterval(opening, opening + 60) for opening in (0, 24 * 60) + ] @pytest.fixture(scope="module") @@ -58,6 +80,39 @@ def business_messages_deleted(): ) +@pytest.fixture(scope="module") +def business_intro(): + return BusinessIntro( + TestBusinessBase.title, + TestBusinessBase.message, + TestBusinessBase.sticker, + ) + + +@pytest.fixture(scope="module") +def business_location(): + return BusinessLocation( + TestBusinessBase.address, + TestBusinessBase.location, + ) + + +@pytest.fixture(scope="module") +def business_opening_hours_interval(): + return BusinessOpeningHoursInterval( + TestBusinessBase.opening_minute, + TestBusinessBase.closing_minute, + ) + + +@pytest.fixture(scope="module") +def business_opening_hours(): + return BusinessOpeningHours( + TestBusinessBase.time_zone_name, + TestBusinessBase.opening_hours, + ) + + class TestBusinessConnectionWithoutRequest(TestBusinessBase): def test_slots(self, business_connection): bc = business_connection @@ -170,3 +225,188 @@ def test_equality(self): assert bmd1 != bmd3 assert hash(bmd1) != hash(bmd3) + + +class TestBusinessIntroWithoutRequest(TestBusinessBase): + def test_slot_behaviour(self, business_intro): + intro = business_intro + for attr in intro.__slots__: + assert getattr(intro, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(intro)) == len(set(mro_slots(intro))), "duplicate slot" + + def test_to_dict(self, business_intro): + intro_dict = business_intro.to_dict() + assert isinstance(intro_dict, dict) + assert intro_dict["title"] == self.title + assert intro_dict["message"] == self.message + assert intro_dict["sticker"] == self.sticker.to_dict() + + def test_de_json(self): + json_dict = { + "title": self.title, + "message": self.message, + "sticker": self.sticker.to_dict(), + } + intro = BusinessIntro.de_json(json_dict, None) + assert intro.title == self.title + assert intro.message == self.message + assert intro.sticker == self.sticker + assert intro.api_kwargs == {} + assert isinstance(intro, BusinessIntro) + + def test_equality(self): + intro1 = BusinessIntro(self.title, self.message, self.sticker) + intro2 = BusinessIntro(self.title, self.message, self.sticker) + intro3 = BusinessIntro("Other Business", self.message, self.sticker) + + assert intro1 == intro2 + assert hash(intro1) == hash(intro2) + assert intro1 is not intro2 + + assert intro1 != intro3 + assert hash(intro1) != hash(intro3) + + +class TestBusinessLocationWithoutRequest(TestBusinessBase): + def test_slot_behaviour(self, business_location): + inst = business_location + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_to_dict(self, business_location): + blc_dict = business_location.to_dict() + assert isinstance(blc_dict, dict) + assert blc_dict["address"] == self.address + assert blc_dict["location"] == self.location.to_dict() + + def test_de_json(self): + json_dict = { + "address": self.address, + "location": self.location.to_dict(), + } + blc = BusinessLocation.de_json(json_dict, None) + assert blc.address == self.address + assert blc.location == self.location + assert blc.api_kwargs == {} + assert isinstance(blc, BusinessLocation) + + def test_equality(self): + blc1 = BusinessLocation(self.address, self.location) + blc2 = BusinessLocation(self.address, self.location) + blc3 = BusinessLocation("Other Address", self.location) + + assert blc1 == blc2 + assert hash(blc1) == hash(blc2) + assert blc1 is not blc2 + + assert blc1 != blc3 + assert hash(blc1) != hash(blc3) + + +class TestBusinessOpeningHoursIntervalWithoutRequest(TestBusinessBase): + def test_slot_behaviour(self, business_opening_hours_interval): + inst = business_opening_hours_interval + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_to_dict(self, business_opening_hours_interval): + bohi_dict = business_opening_hours_interval.to_dict() + assert isinstance(bohi_dict, dict) + assert bohi_dict["opening_minute"] == self.opening_minute + assert bohi_dict["closing_minute"] == self.closing_minute + + def test_de_json(self): + json_dict = { + "opening_minute": self.opening_minute, + "closing_minute": self.closing_minute, + } + bohi = BusinessOpeningHoursInterval.de_json(json_dict, None) + assert bohi.opening_minute == self.opening_minute + assert bohi.closing_minute == self.closing_minute + assert bohi.api_kwargs == {} + assert isinstance(bohi, BusinessOpeningHoursInterval) + + def test_equality(self): + bohi1 = BusinessOpeningHoursInterval(self.opening_minute, self.closing_minute) + bohi2 = BusinessOpeningHoursInterval(self.opening_minute, self.closing_minute) + bohi3 = BusinessOpeningHoursInterval(61, 100) + + assert bohi1 == bohi2 + assert hash(bohi1) == hash(bohi2) + assert bohi1 is not bohi2 + + assert bohi1 != bohi3 + assert hash(bohi1) != hash(bohi3) + + @pytest.mark.parametrize( + ("opening_minute", "expected"), + [ # openings per docstring + (8 * 60, (0, 8, 0)), + (24 * 60, (1, 0, 0)), + (6 * 24 * 60, (6, 0, 0)), + ], + ) + def test_opening_time(self, opening_minute, expected): + bohi = BusinessOpeningHoursInterval(opening_minute, -0) + + opening_time = bohi.opening_time + assert opening_time == expected + + cached = bohi.opening_time + assert cached is opening_time + + @pytest.mark.parametrize( + ("closing_minute", "expected"), + [ # closings per docstring + (20 * 60 + 30, (0, 20, 30)), + (2 * 24 * 60 - 1, (1, 23, 59)), + (7 * 24 * 60 - 2, (6, 23, 58)), + ], + ) + def test_closing_time(self, closing_minute, expected): + bohi = BusinessOpeningHoursInterval(-0, closing_minute) + + closing_time = bohi.closing_time + assert closing_time == expected + + cached = bohi.closing_time + assert cached is closing_time + + +class TestBusinessOpeningHoursWithoutRequest(TestBusinessBase): + def test_slot_behaviour(self, business_opening_hours): + inst = business_opening_hours + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_to_dict(self, business_opening_hours): + boh_dict = business_opening_hours.to_dict() + assert isinstance(boh_dict, dict) + assert boh_dict["time_zone_name"] == self.time_zone_name + assert boh_dict["opening_hours"] == [opening.to_dict() for opening in self.opening_hours] + + def test_de_json(self): + json_dict = { + "time_zone_name": self.time_zone_name, + "opening_hours": [opening.to_dict() for opening in self.opening_hours], + } + boh = BusinessOpeningHours.de_json(json_dict, None) + assert boh.time_zone_name == self.time_zone_name + assert boh.opening_hours == tuple(self.opening_hours) + assert boh.api_kwargs == {} + assert isinstance(boh, BusinessOpeningHours) + + def test_equality(self): + boh1 = BusinessOpeningHours(self.time_zone_name, self.opening_hours) + boh2 = BusinessOpeningHours(self.time_zone_name, self.opening_hours) + boh3 = BusinessOpeningHours("Other/Timezone", self.opening_hours) + + assert boh1 == boh2 + assert hash(boh1) == hash(boh2) + assert boh1 is not boh2 + + assert boh1 != boh3 + assert hash(boh1) != hash(boh3) diff --git a/tests/test_chat.py b/tests/test_chat.py index 85a78aa1435..11ef38dda15 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -23,6 +23,10 @@ from telegram import ( Birthdate, Bot, + BusinessIntro, + BusinessLocation, + BusinessOpeningHours, + BusinessOpeningHoursInterval, Chat, ChatLocation, ChatPermissions, @@ -75,6 +79,9 @@ def chat(bot): profile_background_custom_emoji_id=TestChatBase.profile_background_custom_emoji_id, unrestrict_boost_count=TestChatBase.unrestrict_boost_count, custom_emoji_sticker_set_name=TestChatBase.custom_emoji_sticker_set_name, + business_intro=TestChatBase.business_intro, + business_location=TestChatBase.business_location, + business_opening_hours=TestChatBase.business_opening_hours, birthdate=Birthdate(1, 1), personal_chat=TestChatBase.personal_chat, ) @@ -116,6 +123,12 @@ class TestChatBase: ReactionTypeEmoji(ReactionEmoji.THUMBS_DOWN), ReactionTypeCustomEmoji("custom_emoji_id"), ] + business_intro = BusinessIntro("Title", "Description", None) + business_location = BusinessLocation("Address", Location(123, 456)) + business_opening_hours = BusinessOpeningHours( + "Country/City", + [BusinessOpeningHoursInterval(opening, opening + 60) for opening in (0, 24 * 60)], + ) accent_color_id = 1 background_custom_emoji_id = "background_custom_emoji_id" profile_accent_color_id = 2 @@ -144,6 +157,9 @@ def test_de_json(self, bot): "permissions": self.permissions.to_dict(), "slow_mode_delay": self.slow_mode_delay, "bio": self.bio, + "business_intro": self.business_intro.to_dict(), + "business_location": self.business_location.to_dict(), + "business_opening_hours": self.business_opening_hours.to_dict(), "has_protected_content": self.has_protected_content, "has_visible_history": self.has_visible_history, "has_private_forwards": self.has_private_forwards, @@ -181,6 +197,9 @@ def test_de_json(self, bot): assert chat.permissions == self.permissions assert chat.slow_mode_delay == self.slow_mode_delay assert chat.bio == self.bio + assert chat.business_intro == self.business_intro + assert chat.business_location == self.business_location + assert chat.business_opening_hours == self.business_opening_hours assert chat.has_protected_content == self.has_protected_content assert chat.has_visible_history == self.has_visible_history assert chat.has_private_forwards == self.has_private_forwards @@ -243,6 +262,9 @@ def test_to_dict(self, chat): assert chat_dict["permissions"] == chat.permissions.to_dict() assert chat_dict["slow_mode_delay"] == chat.slow_mode_delay assert chat_dict["bio"] == chat.bio + assert chat_dict["business_intro"] == chat.business_intro.to_dict() + assert chat_dict["business_location"] == chat.business_location.to_dict() + assert chat_dict["business_opening_hours"] == chat.business_opening_hours.to_dict() assert chat_dict["has_private_forwards"] == chat.has_private_forwards assert chat_dict["has_protected_content"] == chat.has_protected_content assert chat_dict["has_visible_history"] == chat.has_visible_history