From 2ca1ce27624363040820408147355f742eb40547 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sun, 19 May 2024 02:18:27 -0400 Subject: [PATCH 1/4] Remove xdist_group marker from tests --- tests/auxil/pytest_classes.py | 8 ++------ tests/test_bot.py | 32 +++++++------------------------- 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/tests/auxil/pytest_classes.py b/tests/auxil/pytest_classes.py index 5586a8ea0b7..20ba32c9e4e 100644 --- a/tests/auxil/pytest_classes.py +++ b/tests/auxil/pytest_classes.py @@ -36,12 +36,8 @@ def _get_bot_user(token: str) -> User: # This is important in e.g. bot equality tests. The other parameters like first_name don't # matter as much. In the future we may provide a way to get all the correct info from the token user_id = int(token.split(":")[0]) - first_name = bot_info.get( - "name", - ) - username = bot_info.get( - "username", - ).strip("@") + first_name = bot_info.get("name") + username = bot_info.get("username").strip("@") return User( user_id, first_name, diff --git a/tests/test_bot.py b/tests/test_bot.py index 34f25e6ce39..80cc0e6230c 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -2808,7 +2808,6 @@ async def test_edit_reply_markup(self, bot, message): async def test_edit_reply_markup_inline(self): pass - @pytest.mark.xdist_group("getUpdates_and_webhook") # TODO: Actually send updates to the test bot so this can be tested properly async def test_get_updates(self, bot): await bot.delete_webhook() # make sure there is no webhook set if webhook tests failed @@ -2875,7 +2874,6 @@ async def catch_timeouts(*args, **kwargs): await bot.get_updates(read_timeout=read_timeout, timeout=timeout) assert caught_read_timeout == expected - @pytest.mark.xdist_group("getUpdates_and_webhook") @pytest.mark.parametrize("use_ip", [True, False]) # local file path as file_input is tested below in test_set_webhook_params @pytest.mark.parametrize("file_input", ["bytes", "file_handle"]) @@ -3013,10 +3011,8 @@ async def test_send_game_default_protect_content(self, default_bot, chat_id, val protected = await default_bot.send_game(chat_id, "test_game", protect_content=val) assert protected.has_protected_content is val - @pytest.mark.xdist_group("game") @xfail - async def test_set_game_score_1(self, bot, chat_id): - # NOTE: numbering of methods assures proper order between test_set_game_scoreX methods + async def test_set_game_score_and_high_scores(self, bot, chat_id): # First, test setting a score. game_short_name = "test_game" game = await bot.send_game(chat_id, game_short_name) @@ -3033,10 +3029,6 @@ async def test_set_game_score_1(self, bot, chat_id): assert message.game.animation.file_unique_id == game.game.animation.file_unique_id assert message.game.text != game.game.text - @pytest.mark.xdist_group("game") - @xfail - async def test_set_game_score_2(self, bot, chat_id): - # NOTE: numbering of methods assures proper order between test_set_game_scoreX methods # Test setting a score higher than previous game_short_name = "test_game" game = await bot.send_game(chat_id, game_short_name) @@ -3056,10 +3048,6 @@ async def test_set_game_score_2(self, bot, chat_id): assert message.game.animation.file_unique_id == game.game.animation.file_unique_id assert message.game.text == game.game.text - @pytest.mark.xdist_group("game") - @xfail - async def test_set_game_score_3(self, bot, chat_id): - # NOTE: numbering of methods assures proper order between test_set_game_scoreX methods # Test setting a score lower than previous (should raise error) game_short_name = "test_game" game = await bot.send_game(chat_id, game_short_name) @@ -3071,10 +3059,6 @@ async def test_set_game_score_3(self, bot, chat_id): user_id=chat_id, score=score, chat_id=game.chat_id, message_id=game.message_id ) - @pytest.mark.xdist_group("game") - @xfail - async def test_set_game_score_4(self, bot, chat_id): - # NOTE: numbering of methods assures proper order between test_set_game_scoreX methods # Test force setting a lower score game_short_name = "test_game" game = await bot.send_game(chat_id, game_short_name) @@ -3099,9 +3083,6 @@ async def test_set_game_score_4(self, bot, chat_id): game2 = await bot.send_game(chat_id, game_short_name) assert str(score) in game2.game.text - @pytest.mark.xdist_group("game") - @xfail - async def test_get_game_high_scores(self, bot, chat_id): # We need a game to get the scores for game_short_name = "test_game" game = await bot.send_game(chat_id, game_short_name) @@ -3767,9 +3748,10 @@ async def test_copy_messages(self, bot, chat_id): ) msg1, msg2 = await tasks - copy_messages = await bot.copy_messages( - chat_id, from_chat_id=chat_id, message_ids=(msg1.message_id, msg2.message_id) - ) + ids = [msg1.message_id, msg2.message_id] + ids.sort() # TG requires the ids to be in ascending + + copy_messages = await bot.copy_messages(chat_id, from_chat_id=chat_id, message_ids=ids) assert isinstance(copy_messages, tuple) tasks = asyncio.gather( @@ -3781,8 +3763,8 @@ async def test_copy_messages(self, bot, chat_id): forward_msg1 = temp_msg1.reply_to_message forward_msg2 = temp_msg2.reply_to_message - assert forward_msg1.text == msg1.text - assert forward_msg2.text == msg2.text + assert forward_msg1.text in (msg1.text, msg2.text) + assert forward_msg2.text in (msg1.text, msg2.text) # Continue testing arbitrary callback data here with actual requests: async def test_replace_callback_data_send_message(self, cdc_bot, chat_id): From cb5425652d7b90bd8de372ea324b3eba308f7f49 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Tue, 21 May 2024 16:21:28 -0400 Subject: [PATCH 2/4] Add new conftest file in _files/ --- pyproject.toml | 2 +- tests/_files/conftest.py | 106 ++++++++++++++++++++++++++++++ tests/_files/test_animation.py | 14 ---- tests/_files/test_audio.py | 12 ---- tests/_files/test_document.py | 12 ---- tests/_files/test_inputmedia.py | 78 ++++++++-------------- tests/_files/test_inputsticker.py | 3 +- tests/_files/test_photo.py | 28 -------- tests/_files/test_sticker.py | 6 -- tests/_files/test_video.py | 12 ---- tests/_files/test_videonote.py | 2 +- tests/auxil/constants.py | 4 ++ tests/conftest.py | 26 +++++++- tests/test_bot.py | 5 +- tests/test_forum.py | 29 +------- 15 files changed, 169 insertions(+), 170 deletions(-) create mode 100644 tests/_files/conftest.py diff --git a/pyproject.toml b/pyproject.toml index b02870776ca..4620cd6a7e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ markers = [ "no_req", "req", ] -asyncio_mode = "auto" +# asyncio_mode = "auto" log_format = "%(funcName)s - Line %(lineno)d - %(message)s" # log_level = "DEBUG" # uncomment to see DEBUG logs diff --git a/tests/_files/conftest.py b/tests/_files/conftest.py new file mode 100644 index 00000000000..ff12819ce1b --- /dev/null +++ b/tests/_files/conftest.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2024 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""Module to provide fixtures most of which are used in test_inputmedia.py.""" +import pytest + +from tests.auxil.files import data_file +from tests.auxil.networking import expect_bad_request + + +@pytest.fixture() +def animation_file(): + with data_file("game.gif").open("rb") as f: + yield f + + +@pytest.fixture(scope="session") +async def animation(bot, chat_id, aiolib): + with data_file("game.gif").open("rb") as f, data_file("thumb.jpg").open("rb") as thumb: + return ( + await bot.send_animation(chat_id, animation=f, read_timeout=50, thumbnail=thumb) + ).animation + + +@pytest.fixture() +def audio_file(): + with data_file("telegram.mp3").open("rb") as f: + yield f + + +@pytest.fixture(scope="session") +async def audio(bot, chat_id, aiolib): + with data_file("telegram.mp3").open("rb") as f, data_file("thumb.jpg").open("rb") as thumb: + return (await bot.send_audio(chat_id, audio=f, read_timeout=50, thumbnail=thumb)).audio + + +@pytest.fixture() +def document_file(): + with data_file("telegram.png").open("rb") as f: + yield f + + +@pytest.fixture(scope="session") +async def document(bot, chat_id, aiolib): + with data_file("telegram.png").open("rb") as f: + return (await bot.send_document(chat_id, document=f, read_timeout=50)).document + + +@pytest.fixture() +def photo_file(): + with data_file("telegram.jpg").open("rb") as f: + yield f + + +@pytest.fixture(scope="session") +async def photolist(bot, chat_id, aiolib): + async def func(): + with data_file("telegram.jpg").open("rb") as f: + return (await bot.send_photo(chat_id, photo=f, read_timeout=50)).photo + + return await expect_bad_request( + func, "Type of file mismatch", "Telegram did not accept the file." + ) + + +@pytest.fixture(scope="session") +def thumb(photolist): + return photolist[0] + + +@pytest.fixture(scope="session") +def photo(photolist): + return photolist[-1] + + +@pytest.fixture() +def video_file(): + with data_file("telegram.mp4").open("rb") as f: + yield f + + +@pytest.fixture(scope="session") +async def video(bot, chat_id, aiolib): + with data_file("telegram.mp4").open("rb") as f: + return (await bot.send_video(chat_id, video=f, read_timeout=50)).video + + +@pytest.fixture() +def video_sticker_file(): + with data_file("telegram_video_sticker.webm").open("rb") as f: + yield f diff --git a/tests/_files/test_animation.py b/tests/_files/test_animation.py index f14504a75fd..97ad6048287 100644 --- a/tests/_files/test_animation.py +++ b/tests/_files/test_animation.py @@ -37,20 +37,6 @@ from tests.auxil.slots import mro_slots -@pytest.fixture() -def animation_file(): - with data_file("game.gif").open("rb") as f: - yield f - - -@pytest.fixture(scope="module") -async def animation(bot, chat_id): - with data_file("game.gif").open("rb") as f, data_file("thumb.jpg").open("rb") as thumb: - return ( - await bot.send_animation(chat_id, animation=f, read_timeout=50, thumbnail=thumb) - ).animation - - class TestAnimationBase: animation_file_id = "CgADAQADngIAAuyVeEez0xRovKi9VAI" animation_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e" diff --git a/tests/_files/test_audio.py b/tests/_files/test_audio.py index 12857ddc6e9..dd6810ef77a 100644 --- a/tests/_files/test_audio.py +++ b/tests/_files/test_audio.py @@ -37,18 +37,6 @@ from tests.auxil.slots import mro_slots -@pytest.fixture() -def audio_file(): - with data_file("telegram.mp3").open("rb") as f: - yield f - - -@pytest.fixture(scope="module") -async def audio(bot, chat_id): - with data_file("telegram.mp3").open("rb") as f, data_file("thumb.jpg").open("rb") as thumb: - return (await bot.send_audio(chat_id, audio=f, read_timeout=50, thumbnail=thumb)).audio - - class TestAudioBase: caption = "Test *audio*" performer = "Leandro Toledo" diff --git a/tests/_files/test_document.py b/tests/_files/test_document.py index 5d078fced20..2cf41cf4519 100644 --- a/tests/_files/test_document.py +++ b/tests/_files/test_document.py @@ -37,18 +37,6 @@ from tests.auxil.slots import mro_slots -@pytest.fixture() -def document_file(): - with data_file("telegram.png").open("rb") as f: - yield f - - -@pytest.fixture(scope="module") -async def document(bot, chat_id): - with data_file("telegram.png").open("rb") as f: - return (await bot.send_document(chat_id, document=f, read_timeout=50)).document - - class TestDocumentBase: caption = "DocumentTest - *Caption*" document_file_url = "https://python-telegram-bot.org/static/testfiles/telegram.gif" diff --git a/tests/_files/test_inputmedia.py b/tests/_files/test_inputmedia.py index 6febe12c87f..c6c29a0736e 100644 --- a/tests/_files/test_inputmedia.py +++ b/tests/_files/test_inputmedia.py @@ -36,32 +36,14 @@ ReplyParameters, ) from telegram.constants import InputMediaType, ParseMode - -# noinspection PyUnresolvedReferences from telegram.error import BadRequest from telegram.request import RequestData -from tests._files.test_animation import animation, animation_file # noqa: F401 from tests.auxil.files import data_file from tests.auxil.networking import expect_bad_request from tests.auxil.slots import mro_slots -# noinspection PyUnresolvedReferences -from tests.test_forum import emoji_id, real_topic # noqa: F401 - from ..auxil.build_messages import make_message -# noinspection PyUnresolvedReferences -from .test_audio import audio, audio_file # noqa: F401 - -# noinspection PyUnresolvedReferences -from .test_document import document, document_file # noqa: F401 - -# noinspection PyUnresolvedReferences -from .test_photo import photo, photo_file, photolist, thumb # noqa: F401 - -# noinspection PyUnresolvedReferences -from .test_video import video, video_file # noqa: F401 - @pytest.fixture(scope="module") def input_media_video(class_thumb_file): @@ -183,7 +165,7 @@ def test_to_dict(self, input_media_video): assert input_media_video_dict["supports_streaming"] == input_media_video.supports_streaming assert input_media_video_dict["has_spoiler"] == input_media_video.has_spoiler - def test_with_video(self, video): # noqa: F811 + def test_with_video(self, video): # fixture found in test_video input_media_video = InputMediaVideo(video, caption="test 3") assert input_media_video.type == self.type_ @@ -193,7 +175,7 @@ def test_with_video(self, video): # noqa: F811 assert input_media_video.duration == video.duration assert input_media_video.caption == "test 3" - def test_with_video_file(self, video_file): # noqa: F811 + def test_with_video_file(self, video_file): # fixture found in test_video input_media_video = InputMediaVideo(video_file, caption="test 3") assert input_media_video.type == self.type_ @@ -267,14 +249,14 @@ def test_to_dict(self, input_media_photo): ] assert input_media_photo_dict["has_spoiler"] == input_media_photo.has_spoiler - def test_with_photo(self, photo): # noqa: F811 + def test_with_photo(self, photo): # fixture found in test_photo input_media_photo = InputMediaPhoto(photo, caption="test 2") assert input_media_photo.type == self.type_ assert input_media_photo.media == photo.file_id assert input_media_photo.caption == "test 2" - def test_with_photo_file(self, photo_file): # noqa: F811 + def test_with_photo_file(self, photo_file): # fixture found in test_photo input_media_photo = InputMediaPhoto(photo_file, caption="test 2") assert input_media_photo.type == self.type_ @@ -332,14 +314,14 @@ def test_to_dict(self, input_media_animation): assert input_media_animation_dict["duration"] == input_media_animation.duration assert input_media_animation_dict["has_spoiler"] == input_media_animation.has_spoiler - def test_with_animation(self, animation): # noqa: F811 + def test_with_animation(self, animation): # fixture found in test_animation input_media_animation = InputMediaAnimation(animation, caption="test 2") assert input_media_animation.type == self.type_ assert input_media_animation.media == animation.file_id assert input_media_animation.caption == "test 2" - def test_with_animation_file(self, animation_file): # noqa: F811 + def test_with_animation_file(self, animation_file): # fixture found in test_animation input_media_animation = InputMediaAnimation(animation_file, caption="test 2") assert input_media_animation.type == self.type_ @@ -400,7 +382,7 @@ def test_to_dict(self, input_media_audio): ce.to_dict() for ce in input_media_audio.caption_entities ] - def test_with_audio(self, audio): # noqa: F811 + def test_with_audio(self, audio): # fixture found in test_audio input_media_audio = InputMediaAudio(audio, caption="test 3") assert input_media_audio.type == self.type_ @@ -410,7 +392,7 @@ def test_with_audio(self, audio): # noqa: F811 assert input_media_audio.title == audio.title assert input_media_audio.caption == "test 3" - def test_with_audio_file(self, audio_file): # noqa: F811 + def test_with_audio_file(self, audio_file): # fixture found in test_audio input_media_audio = InputMediaAudio(audio_file, caption="test 3") assert input_media_audio.type == self.type_ @@ -471,14 +453,14 @@ def test_to_dict(self, input_media_document): == input_media_document.disable_content_type_detection ) - def test_with_document(self, document): # noqa: F811 + def test_with_document(self, document): # fixture found in test_document input_media_document = InputMediaDocument(document, caption="test 3") assert input_media_document.type == self.type_ assert input_media_document.media == document.file_id assert input_media_document.caption == "test 3" - def test_with_document_file(self, document_file): # noqa: F811 + def test_with_document_file(self, document_file): # fixture found in test_document input_media_document = InputMediaDocument(document_file, caption="test 3") assert input_media_document.type == self.type_ @@ -494,7 +476,7 @@ def test_with_local_files(self): @pytest.fixture(scope="module") -def media_group(photo, thumb): # noqa: F811 +def media_group(photo, thumb): return [ InputMediaPhoto(photo, caption="*photo* 1", parse_mode="Markdown"), InputMediaPhoto(thumb, caption="photo 2", parse_mode="HTML"), @@ -505,12 +487,12 @@ def media_group(photo, thumb): # noqa: F811 @pytest.fixture(scope="module") -def media_group_no_caption_args(photo, thumb): # noqa: F811 +def media_group_no_caption_args(photo, thumb): return [InputMediaPhoto(photo), InputMediaPhoto(thumb), InputMediaPhoto(photo)] @pytest.fixture(scope="module") -def media_group_no_caption_only_caption_entities(photo, thumb): # noqa: F811 +def media_group_no_caption_only_caption_entities(photo, thumb): return [ InputMediaPhoto(photo, caption_entities=[MessageEntity(MessageEntity.BOLD, 0, 5)]), InputMediaPhoto(photo, caption_entities=[MessageEntity(MessageEntity.BOLD, 0, 5)]), @@ -518,7 +500,7 @@ def media_group_no_caption_only_caption_entities(photo, thumb): # noqa: F811 @pytest.fixture(scope="module") -def media_group_no_caption_only_parse_mode(photo, thumb): # noqa: F811 +def media_group_no_caption_only_parse_mode(photo, thumb): return [ InputMediaPhoto(photo, parse_mode="Markdown"), InputMediaPhoto(thumb, parse_mode="HTML"), @@ -549,10 +531,10 @@ async def test_send_media_group_custom_filename( self, bot, chat_id, - photo_file, # noqa: F811 - animation_file, # noqa: F811 - audio_file, # noqa: F811 - video_file, # noqa: F811 + photo_file, + animation_file, + audio_file, + video_file, monkeypatch, ): async def make_assertion(url, request_data: RequestData, *args, **kwargs): @@ -576,7 +558,7 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): await bot.send_media_group(chat_id, media) async def test_send_media_group_with_thumbs( - self, bot, chat_id, video_file, photo_file, monkeypatch # noqa: F811 + self, bot, chat_id, video_file, photo_file, monkeypatch ): async def make_assertion(method, url, request_data: RequestData, *args, **kwargs): nonlocal input_video @@ -594,7 +576,7 @@ async def make_assertion(method, url, request_data: RequestData, *args, **kwargs await bot.send_media_group(chat_id, [input_video, input_video]) async def test_edit_message_media_with_thumb( - self, bot, chat_id, video_file, photo_file, monkeypatch # noqa: F811 + self, bot, chat_id, video_file, photo_file, monkeypatch ): async def make_assertion( method: str, url: str, request_data: Optional[RequestData] = None, *args, **kwargs @@ -663,9 +645,7 @@ async def test_send_media_group_photo(self, bot, chat_id, media_group): mes.caption_entities == (MessageEntity(MessageEntity.BOLD, 0, 5),) for mes in messages ) - async def test_send_media_group_new_files( - self, bot, chat_id, video_file, photo_file # noqa: F811 - ): + async def test_send_media_group_new_files(self, bot, chat_id, video_file, photo_file): async def func(): return await bot.send_media_group( chat_id, @@ -701,7 +681,7 @@ async def test_send_media_group_different_sequences( assert all(mes.media_group_id == messages[0].media_group_id for mes in messages) async def test_send_media_group_with_message_thread_id( - self, bot, real_topic, forum_group_id, media_group # noqa: F811 + self, bot, real_topic, forum_group_id, media_group ): messages = await bot.send_media_group( forum_group_id, @@ -800,9 +780,7 @@ async def test_send_media_group_all_args(self, bot, raw_bot, chat_id, media_grou ) assert all(mes.has_protected_content for mes in messages) - async def test_send_media_group_with_spoiler( - self, bot, chat_id, photo_file, video_file # noqa: F811 - ): + async def test_send_media_group_with_spoiler(self, bot, chat_id, photo_file, video_file): # Media groups can't contain Animations, so that is tested in test_animation.py media = [ InputMediaPhoto(photo_file, has_spoiler=True), @@ -945,11 +923,11 @@ async def test_edit_message_media_default_parse_mode( chat_id, default_bot, media_type, - animation, # noqa: F811 - document, # noqa: F811 - audio, # noqa: F811 - photo, # noqa: F811 - video, # noqa: F811 + animation, + document, + audio, + photo, + video, ): html_caption = "bold italic code" markdown_caption = "*bold* _italic_ `code`" diff --git a/tests/_files/test_inputsticker.py b/tests/_files/test_inputsticker.py index 680a0167099..24a41f837bd 100644 --- a/tests/_files/test_inputsticker.py +++ b/tests/_files/test_inputsticker.py @@ -21,7 +21,6 @@ from telegram import InputSticker, MaskPosition from telegram._files.inputfile import InputFile -from tests._files.test_sticker import video_sticker_file # noqa: F401 from tests.auxil.files import data_file from tests.auxil.slots import mro_slots @@ -77,7 +76,7 @@ def test_to_dict(self, input_sticker): assert input_sticker_dict["keywords"] == list(input_sticker.keywords) assert input_sticker_dict["format"] == input_sticker.format - def test_with_sticker_input_types(self, video_sticker_file): # noqa: F811 + def test_with_sticker_input_types(self, video_sticker_file): sticker = InputSticker(sticker=video_sticker_file, emoji_list=["👍"], format="video") assert isinstance(sticker.sticker, InputFile) sticker = InputSticker(data_file("telegram_video_sticker.webm"), ["👍"], "video") diff --git a/tests/_files/test_photo.py b/tests/_files/test_photo.py index d8be6e81473..2c1f19c7418 100644 --- a/tests/_files/test_photo.py +++ b/tests/_files/test_photo.py @@ -34,37 +34,9 @@ ) from tests.auxil.build_messages import make_message from tests.auxil.files import data_file -from tests.auxil.networking import expect_bad_request from tests.auxil.slots import mro_slots -@pytest.fixture() -def photo_file(): - with data_file("telegram.jpg").open("rb") as f: - yield f - - -@pytest.fixture(scope="module") -async def photolist(bot, chat_id): - async def func(): - with data_file("telegram.jpg").open("rb") as f: - return (await bot.send_photo(chat_id, photo=f, read_timeout=50)).photo - - return await expect_bad_request( - func, "Type of file mismatch", "Telegram did not accept the file." - ) - - -@pytest.fixture(scope="module") -def thumb(photolist): - return photolist[0] - - -@pytest.fixture(scope="module") -def photo(photolist): - return photolist[-1] - - class TestPhotoBase: width = 800 height = 800 diff --git a/tests/_files/test_sticker.py b/tests/_files/test_sticker.py index bf60272ba04..7bc8758cf2f 100644 --- a/tests/_files/test_sticker.py +++ b/tests/_files/test_sticker.py @@ -77,12 +77,6 @@ async def animated_sticker(bot, chat_id): return (await bot.send_sticker(chat_id, sticker=f, read_timeout=50)).sticker -@pytest.fixture() -def video_sticker_file(): - with data_file("telegram_video_sticker.webm").open("rb") as f: - yield f - - @pytest.fixture(scope="module") def video_sticker(bot, chat_id): with data_file("telegram_video_sticker.webm").open("rb") as f: diff --git a/tests/_files/test_video.py b/tests/_files/test_video.py index c7fedbb8cf0..5875a8f82f2 100644 --- a/tests/_files/test_video.py +++ b/tests/_files/test_video.py @@ -37,18 +37,6 @@ from tests.auxil.slots import mro_slots -@pytest.fixture() -def video_file(): - with data_file("telegram.mp4").open("rb") as f: - yield f - - -@pytest.fixture(scope="module") -async def video(bot, chat_id): - with data_file("telegram.mp4").open("rb") as f: - return (await bot.send_video(chat_id, video=f, read_timeout=50)).video - - class TestVideoBase: width = 360 height = 640 diff --git a/tests/_files/test_videonote.py b/tests/_files/test_videonote.py index 625e85eba87..01e7479460d 100644 --- a/tests/_files/test_videonote.py +++ b/tests/_files/test_videonote.py @@ -244,7 +244,7 @@ async def test_send_all_args(self, bot, chat_id, video_note_file, video_note, th assert message.video_note.thumbnail.height == self.thumb_height assert message.has_protected_content - async def test_get_and_download(self, bot, video_note, chat_id, tmp_file): + async def test_get_and_download(self, bot, video_note, tmp_file): new_file = await bot.get_file(video_note.file_id) assert new_file.file_size == self.file_size diff --git a/tests/auxil/constants.py b/tests/auxil/constants.py index 8ec314bfbe5..5c90824ba4c 100644 --- a/tests/auxil/constants.py +++ b/tests/auxil/constants.py @@ -20,3 +20,7 @@ # THIS KEY IS OBVIOUSLY COMPROMISED # DO NOT USE IN PRODUCTION! PRIVATE_KEY = b"-----BEGIN RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEA0AvEbNaOnfIL3GjB8VI4M5IaWe+GcK8eSPHkLkXREIsaddum\r\nwPBm/+w8lFYdnY+O06OEJrsaDtwGdU//8cbGJ/H/9cJH3dh0tNbfszP7nTrQD+88\r\nydlcYHzClaG8G+oTe9uEZSVdDXj5IUqR0y6rDXXb9tC9l+oSz+ShYg6+C4grAb3E\r\nSTv5khZ9Zsi/JEPWStqNdpoNuRh7qEYc3t4B/a5BH7bsQENyJSc8AWrfv+drPAEe\r\njQ8xm1ygzWvJp8yZPwOIYuL+obtANcoVT2G2150Wy6qLC0bD88Bm40GqLbSazueC\r\nRHZRug0B9rMUKvKc4FhG4AlNzBCaKgIcCWEqKwIDAQABAoIBACcIjin9d3Sa3S7V\r\nWM32JyVF3DvTfN3XfU8iUzV7U+ZOswA53eeFM04A/Ly4C4ZsUNfUbg72O8Vd8rg/\r\n8j1ilfsYpHVvphwxaHQlfIMa1bKCPlc/A6C7b2GLBtccKTbzjARJA2YWxIaqk9Nz\r\nMjj1IJK98i80qt29xRnMQ5sqOO3gn2SxTErvNchtBiwOH8NirqERXig8VCY6fr3n\r\nz7ZImPU3G/4qpD0+9ULrt9x/VkjqVvNdK1l7CyAuve3D7ha3jPMfVHFtVH5gqbyp\r\nKotyIHAyD+Ex3FQ1JV+H7DkP0cPctQiss7OiO9Zd9C1G2OrfQz9el7ewAPqOmZtC\r\nKjB3hUECgYEA/4MfKa1cvaCqzd3yUprp1JhvssVkhM1HyucIxB5xmBcVLX2/Kdhn\r\nhiDApZXARK0O9IRpFF6QVeMEX7TzFwB6dfkyIePsGxputA5SPbtBlHOvjZa8omMl\r\nEYfNa8x/mJkvSEpzvkWPascuHJWv1cEypqphu/70DxubWB5UKo/8o6cCgYEA0HFy\r\ncgwPMB//nltHGrmaQZPFT7/Qgl9ErZT3G9S8teWY4o4CXnkdU75tBoKAaJnpSfX3\r\nq8VuRerF45AFhqCKhlG4l51oW7TUH50qE3GM+4ivaH5YZB3biwQ9Wqw+QyNLAh/Q\r\nnS4/Wwb8qC9QuyEgcCju5lsCaPEXZiZqtPVxZd0CgYEAshBG31yZjO0zG1TZUwfy\r\nfN3euc8mRgZpSdXIHiS5NSyg7Zr8ZcUSID8jAkJiQ3n3OiAsuq1MGQ6kNa582kLT\r\nFPQdI9Ea8ahyDbkNR0gAY9xbM2kg/Gnro1PorH9PTKE0ekSodKk1UUyNrg4DBAwn\r\nqE6E3ebHXt/2WmqIbUD653ECgYBQCC8EAQNX3AFegPd1GGxU33Lz4tchJ4kMCNU0\r\nN2NZh9VCr3nTYjdTbxsXU8YP44CCKFG2/zAO4kymyiaFAWEOn5P7irGF/JExrjt4\r\nibGy5lFLEq/HiPtBjhgsl1O0nXlwUFzd7OLghXc+8CPUJaz5w42unqT3PBJa40c3\r\nQcIPdQKBgBnSb7BcDAAQ/Qx9juo/RKpvhyeqlnp0GzPSQjvtWi9dQRIu9Pe7luHc\r\nm1Img1EO1OyE3dis/rLaDsAa2AKu1Yx6h85EmNjavBqP9wqmFa0NIQQH8fvzKY3/\r\nP8IHY6009aoamLqYaexvrkHVq7fFKiI6k8myMJ6qblVNFv14+KXU\r\n-----END RSA PRIVATE KEY-----" # noqa: E501 + +TEST_MSG_TEXT = "Topics are forever" +TEST_TOPIC_ICON_COLOR = 0x6FB9F0 +TEST_TOPIC_NAME = "Sad bot true: real stories" diff --git a/tests/conftest.py b/tests/conftest.py index 213bcff4a23..bef86d71144 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -40,7 +40,7 @@ from telegram.ext.filters import MessageFilter, UpdateFilter from tests.auxil.build_messages import DATE from tests.auxil.ci_bots import BOT_INFO_PROVIDER -from tests.auxil.constants import PRIVATE_KEY +from tests.auxil.constants import PRIVATE_KEY, TEST_TOPIC_ICON_COLOR, TEST_TOPIC_NAME from tests.auxil.envvars import RUN_TEST_OFFICIAL, TEST_WITH_OPT_DEPS from tests.auxil.files import data_file from tests.auxil.networking import NonchalantHttpxRequest @@ -239,6 +239,30 @@ def class_thumb_file(): yield f +@pytest.fixture(scope="session") +async def emoji_id(bot): + emoji_sticker_list = await bot.get_forum_topic_icon_stickers() + first_sticker = emoji_sticker_list[0] + return first_sticker.custom_emoji_id + + +@pytest.fixture() +async def real_topic(bot, emoji_id, forum_group_id): + result = await bot.create_forum_topic( + chat_id=forum_group_id, + name=TEST_TOPIC_NAME, + icon_color=TEST_TOPIC_ICON_COLOR, + icon_custom_emoji_id=emoji_id, + ) + + yield result + + result = await bot.delete_forum_topic( + chat_id=forum_group_id, message_thread_id=result.message_thread_id + ) + assert result is True, "Topic was not deleted" + + @pytest.fixture( scope="class", params=[{"class": MessageFilter}, {"class": UpdateFilter}], diff --git a/tests/test_bot.py b/tests/test_bot.py index 80cc0e6230c..8dd4506c602 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -99,11 +99,10 @@ from tests.auxil.pytest_classes import PytestBot, PytestExtBot, make_bot from tests.auxil.slots import mro_slots -from ._files.test_photo import photo_file from .auxil.build_messages import make_message -@pytest.fixture() +@pytest.fixture(scope="module") async def message(bot, chat_id): # mostly used in tests for edit_message out = await bot.send_message( chat_id, "Text", disable_web_page_preview=True, disable_notification=True @@ -1090,7 +1089,7 @@ async def test_rtm_aswr_mutually_exclusive_reply_parameters(self, bot, chat_id): ) # Test with send media group - media = InputMediaPhoto(photo_file) + media = InputMediaPhoto("") with pytest.raises(ValueError, match="`reply_to_message_id` and"): await bot.send_media_group( chat_id, media, reply_to_message_id=1, reply_parameters=True diff --git a/tests/test_forum.py b/tests/test_forum.py index 55419a3854f..85f5b7b007b 100644 --- a/tests/test_forum.py +++ b/tests/test_forum.py @@ -32,19 +32,9 @@ Sticker, ) from telegram.error import BadRequest +from tests.auxil.constants import TEST_MSG_TEXT, TEST_TOPIC_ICON_COLOR, TEST_TOPIC_NAME from tests.auxil.slots import mro_slots -TEST_MSG_TEXT = "Topics are forever" -TEST_TOPIC_ICON_COLOR = 0x6FB9F0 -TEST_TOPIC_NAME = "Sad bot true: real stories" - - -@pytest.fixture(scope="module") -async def emoji_id(bot): - emoji_sticker_list = await bot.get_forum_topic_icon_stickers() - first_sticker = emoji_sticker_list[0] - return first_sticker.custom_emoji_id - @pytest.fixture(scope="module") async def forum_topic_object(forum_group_id, emoji_id): @@ -56,23 +46,6 @@ async def forum_topic_object(forum_group_id, emoji_id): ) -@pytest.fixture() -async def real_topic(bot, emoji_id, forum_group_id): - result = await bot.create_forum_topic( - chat_id=forum_group_id, - name=TEST_TOPIC_NAME, - icon_color=TEST_TOPIC_ICON_COLOR, - icon_custom_emoji_id=emoji_id, - ) - - yield result - - result = await bot.delete_forum_topic( - chat_id=forum_group_id, message_thread_id=result.message_thread_id - ) - assert result is True, "Topic was not deleted" - - class TestForumTopicWithoutRequest: def test_slot_behaviour(self, forum_topic_object): inst = forum_topic_object From 8404481254e0b1c68d3e72d10e0cade40c50b92d Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Wed, 22 May 2024 08:25:50 -0400 Subject: [PATCH 3/4] some test maintainence in test_bot.py --- tests/test_bot.py | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/tests/test_bot.py b/tests/test_bot.py index 8dd4506c602..267f0fcb53d 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -86,7 +86,7 @@ ParseMode, ReactionEmoji, ) -from telegram.error import BadRequest, EndPointNotFound, InvalidToken, NetworkError +from telegram.error import BadRequest, EndPointNotFound, InvalidToken from telegram.ext import ExtBot, InvalidCallbackData from telegram.helpers import escape_markdown from telegram.request import BaseRequest, HTTPXRequest, RequestData @@ -2118,6 +2118,19 @@ async def do_request(*args, **kwargs): obj = await bot.get_business_connection(business_connection_id=bci) assert isinstance(obj, BusinessConnection) + async def test_send_chat_action_all_args(self, bot, chat_id, monkeypatch): + async def make_assertion(*args, **_): + kwargs = args[1] + return ( + kwargs["chat_id"] == chat_id + and kwargs["action"] == "action" + and kwargs["message_thread_id"] == 1 + and kwargs["business_connection_id"] == 3 + ) + + monkeypatch.setattr(bot, "_post", make_assertion) + assert await bot.send_chat_action(chat_id, "action", 1, 3) + class TestBotWithRequest: """ @@ -2209,8 +2222,6 @@ async def test_forward_messages(self, bot, chat_id): async def test_delete_message(self, bot, chat_id): message = await bot.send_message(chat_id, text="will be deleted") - await asyncio.sleep(2) - assert await bot.delete_message(chat_id=chat_id, message_id=message.message_id) is True async def test_delete_message_old_message(self, bot, chat_id): @@ -2223,9 +2234,9 @@ async def test_delete_message_old_message(self, bot, chat_id): # test modules. No need to duplicate here. async def test_delete_messages(self, bot, chat_id): - msg1 = await bot.send_message(chat_id, text="will be deleted") - msg2 = await bot.send_message(chat_id, text="will be deleted") - await asyncio.sleep(2) + msg1 = bot.send_message(chat_id, text="will be deleted") + msg2 = bot.send_message(chat_id, text="will be deleted") + msg1, msg2 = await asyncio.gather(msg1, msg2) assert await bot.delete_messages(chat_id=chat_id, message_ids=(msg1.id, msg2.id)) is True @@ -2298,18 +2309,6 @@ async def test_send_contact(self, bot, chat_id): assert message.contact.last_name == last_name assert message.has_protected_content - async def test_send_chat_action_all_args(self, bot, chat_id, monkeypatch): - async def make_assertion(*args, **_): - kwargs = args[1] - return ( - kwargs["chat_id"] == chat_id - and kwargs["action"] == "action" - and kwargs["message_thread_id"] == 1 - ) - - monkeypatch.setattr(bot, "_post", make_assertion) - assert await bot.send_chat_action(chat_id, "action", 1) - # TODO: Add bot to group to test polls too @pytest.mark.parametrize( "reply_markup", @@ -2914,9 +2913,6 @@ async def test_leave_chat(self, bot): with pytest.raises(BadRequest, match="Chat not found"): await bot.leave_chat(-123456) - with pytest.raises(NetworkError, match="Chat not found"): - await bot.leave_chat(-123456) - async def test_get_chat(self, bot, super_group_id): chat = await bot.get_chat(super_group_id) assert chat.type == "supergroup" From 28fc7e7197a83fc2f4b36831e283ca731083c319 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Wed, 22 May 2024 16:50:47 -0400 Subject: [PATCH 4/4] Make more use of asyncio concurrency in test_bot.py Introduces tz_bots, a tradeoff of slightly more complexity in a unit test for ~55% shorter test duration --- pyproject.toml | 2 +- tests/conftest.py | 29 ++++++ tests/test_bot.py | 245 +++++++++++++++++++++++++++------------------- 3 files changed, 172 insertions(+), 104 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4620cd6a7e3..b02870776ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ markers = [ "no_req", "req", ] -# asyncio_mode = "auto" +asyncio_mode = "auto" log_format = "%(funcName)s - Line %(lineno)d - %(message)s" # log_level = "DEBUG" # uncomment to see DEBUG logs diff --git a/tests/conftest.py b/tests/conftest.py index bef86d71144..4f7534bf675 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -180,6 +180,23 @@ async def tz_bot(timezone, bot_info): return default_bot +@pytest.fixture(scope="session") +async def tz_bots(tzinfos, bot_info): + bots = [] + if len(bots) == 3: + return bots + for tzinfo in tzinfos: + defaults = Defaults(tzinfo=tzinfo) + try: + bots.append(_default_bots[defaults]) + except KeyError: + default_bot = make_bot(bot_info, defaults=defaults) + await default_bot.initialize() + _default_bots[defaults] = default_bot + bots.append(default_bot) + return bots + + @pytest.fixture(scope="session") def chat_id(bot_info): return bot_info["chat_id"] @@ -309,6 +326,18 @@ def tzinfo(request): return BasicTimezone(offset=datetime.timedelta(hours=hours_offset), name=request.param) +@pytest.fixture(scope="session") +def tzinfos(): + """Returns a list of timezone objects for testing.""" + if TEST_WITH_OPT_DEPS: + return [pytz.timezone("Europe/Berlin"), pytz.timezone("Asia/Singapore"), pytz.utc] + return [ + BasicTimezone(offset=datetime.timedelta(hours=2), name="Europe/Berlin"), + BasicTimezone(offset=datetime.timedelta(hours=8), name="Asia/Singapore"), + BasicTimezone(offset=datetime.timedelta(hours=0), name="UTC"), + ] + + @pytest.fixture(scope="session") def timezone(tzinfo): return tzinfo diff --git a/tests/test_bot.py b/tests/test_bot.py index 267f0fcb53d..3ef45227e47 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -102,7 +102,7 @@ from .auxil.build_messages import make_message -@pytest.fixture(scope="module") +@pytest.fixture() async def message(bot, chat_id): # mostly used in tests for edit_message out = await bot.send_message( chat_id, "Text", disable_web_page_preview=True, disable_notification=True @@ -2427,38 +2427,56 @@ async def test_send_open_period(self, bot, super_group_id, open_period, close_da assert new_message.poll.id == message.poll.id assert new_message.poll.is_closed - async def test_send_close_date_default_tz(self, tz_bot, super_group_id): + async def test_send_close_date_default_tz(self, tz_bots, super_group_id): question = "Is this a test?" answers = ["Yes", "No", "Maybe"] reply_markup = InlineKeyboardMarkup.from_button( InlineKeyboardButton(text="text", callback_data="data") ) - aware_close_date = dtm.datetime.now(tz=tz_bot.defaults.tzinfo) + dtm.timedelta(seconds=5) - close_date = aware_close_date.replace(tzinfo=None) + coros = [] + for tz_bot in tz_bots: + aware_close_date = dtm.datetime.now(tz=tz_bot.defaults.tzinfo) + dtm.timedelta( + seconds=5 + ) + close_date = aware_close_date.replace(tzinfo=None) - msg = await tz_bot.send_poll( # The timezone returned from this is always converted to UTC - chat_id=super_group_id, - question=question, - options=answers, - close_date=close_date, - read_timeout=60, - ) - msg.poll._unfreeze() - # Sometimes there can be a few seconds delay, so don't let the test fail due to that- - msg.poll.close_date = msg.poll.close_date.astimezone(aware_close_date.tzinfo) - assert abs(msg.poll.close_date - aware_close_date) <= dtm.timedelta(seconds=5) + coros.append( + tz_bot.send_poll( # The timezone returned from this is always converted + chat_id=super_group_id, # to UTC + question=question, + options=answers, + close_date=close_date, + read_timeout=60, + ) + ) + + messages = await asyncio.gather(*coros) + + for msg in messages: + msg.poll._unfreeze() + # Sometimes there can be a few seconds delay, so don't let the test fail due to that- + msg.poll.close_date = msg.poll.close_date.astimezone(aware_close_date.tzinfo) + assert abs(msg.poll.close_date - aware_close_date) <= dtm.timedelta(seconds=5) await asyncio.sleep(5.1) - new_message = await tz_bot.edit_message_reply_markup( - chat_id=super_group_id, - message_id=msg.message_id, - reply_markup=reply_markup, - read_timeout=60, - ) - assert new_message.poll.id == msg.poll.id - assert new_message.poll.is_closed + edit_msg_coros = [] + for tz_bot, msg in zip(tz_bots, messages): + edit_msg_coros.append( + tz_bot.edit_message_reply_markup( + chat_id=super_group_id, + message_id=msg.message_id, + reply_markup=reply_markup, + read_timeout=60, + ) + ) + + poll_ids = [msg.poll.id for msg in messages] + for new_message_coro in asyncio.as_completed(edit_msg_coros): + new_message = await new_message_coro + assert new_message.poll.id in poll_ids + assert new_message.poll.is_closed async def test_send_poll_explanation_entities(self, bot, chat_id): test_string = "Italic Bold Code" @@ -2644,8 +2662,8 @@ async def test_get_one_user_profile_photo(self, bot, chat_id): assert user_profile_photos.total_count == 1 assert user_profile_photos.photos[0][0].file_size == 5403 - async def test_edit_message_text(self, bot, message): - message = await bot.edit_message_text( + async def test_edit_message_text_parse_mode_and_entities(self, bot, message): + msg_1 = bot.edit_message_text( text="new_text", chat_id=message.chat_id, message_id=message.message_id, @@ -2653,64 +2671,57 @@ async def test_edit_message_text(self, bot, message): disable_web_page_preview=True, ) - assert message.text == "new_text" - - async def test_edit_message_text_entities(self, bot, message): test_string = "Italic Bold Code" entities = [ MessageEntity(MessageEntity.ITALIC, 0, 6), MessageEntity(MessageEntity.ITALIC, 7, 4), MessageEntity(MessageEntity.ITALIC, 12, 4), ] - message = await bot.edit_message_text( + msg_2 = bot.edit_message_text( text=test_string, chat_id=message.chat_id, message_id=message.message_id, entities=entities, ) - - assert message.text == test_string - assert message.entities == tuple(entities) + msg_1, msg_2 = await asyncio.gather(msg_1, msg_2) + assert msg_1.text_html == "new_text" + assert msg_2.text == test_string + assert msg_2.entities == tuple(entities) @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) async def test_edit_message_text_default_parse_mode(self, default_bot, message): test_string = "Italic Bold Code" test_markdown_string = "_Italic_ *Bold* `Code`" + test_markdown_string_2 = "_Italic_ *Bold* `Code` 2" - message = await default_bot.edit_message_text( + msg = await default_bot.edit_message_text( text=test_markdown_string, chat_id=message.chat_id, message_id=message.message_id, disable_web_page_preview=True, ) - assert message.text_markdown == test_markdown_string - assert message.text == test_string + assert msg.text_markdown == test_markdown_string + assert msg.text == test_string - message = await default_bot.edit_message_text( + msg = await default_bot.edit_message_text( text=test_markdown_string, chat_id=message.chat_id, message_id=message.message_id, parse_mode=None, disable_web_page_preview=True, ) - assert message.text == test_markdown_string - assert message.text_markdown == escape_markdown(test_markdown_string) + assert msg.text == test_markdown_string + assert msg.text_markdown == escape_markdown(test_markdown_string) - message = await default_bot.edit_message_text( - text=test_markdown_string, - chat_id=message.chat_id, - message_id=message.message_id, - disable_web_page_preview=True, - ) - message = await default_bot.edit_message_text( - text=test_markdown_string, + msg = await default_bot.edit_message_text( + text=test_markdown_string_2, chat_id=message.chat_id, message_id=message.message_id, parse_mode="HTML", disable_web_page_preview=True, ) - assert message.text == test_markdown_string - assert message.text_markdown == escape_markdown(test_markdown_string) + assert msg.text == test_markdown_string_2 + assert msg.text_markdown == escape_markdown(test_markdown_string_2) @pytest.mark.skip(reason="need reference to an inline message") async def test_edit_message_text_inline(self): @@ -2748,37 +2759,33 @@ async def test_edit_message_caption_entities(self, bot, media_message): async def test_edit_message_caption_default_parse_mode(self, default_bot, media_message): test_string = "Italic Bold Code" test_markdown_string = "_Italic_ *Bold* `Code`" + test_markdown_string_2 = "_Italic_ *Bold* `Code` 2" - message = await default_bot.edit_message_caption( + msg = await default_bot.edit_message_caption( caption=test_markdown_string, chat_id=media_message.chat_id, message_id=media_message.message_id, ) - assert message.caption_markdown == test_markdown_string - assert message.caption == test_string + assert msg.caption_markdown == test_markdown_string + assert msg.caption == test_string - message = await default_bot.edit_message_caption( + msg = await default_bot.edit_message_caption( caption=test_markdown_string, chat_id=media_message.chat_id, message_id=media_message.message_id, parse_mode=None, ) - assert message.caption == test_markdown_string - assert message.caption_markdown == escape_markdown(test_markdown_string) + assert msg.caption == test_markdown_string + assert msg.caption_markdown == escape_markdown(test_markdown_string) - message = await default_bot.edit_message_caption( - caption=test_markdown_string, - chat_id=media_message.chat_id, - message_id=media_message.message_id, - ) - message = await default_bot.edit_message_caption( - caption=test_markdown_string, + msg = await default_bot.edit_message_caption( + caption=test_markdown_string_2, chat_id=media_message.chat_id, message_id=media_message.message_id, parse_mode="HTML", ) - assert message.caption == test_markdown_string - assert message.caption_markdown == escape_markdown(test_markdown_string) + assert msg.caption == test_markdown_string_2 + assert msg.caption_markdown == escape_markdown(test_markdown_string_2) async def test_edit_message_caption_with_parse_mode(self, bot, media_message): message = await bot.edit_message_caption( @@ -3253,53 +3260,85 @@ async def test_advanced_chat_invite_links(self, bot, channel_id, datetime): assert revoked_invite_link.invite_link == invite_link.invite_link assert revoked_invite_link.is_revoked - async def test_advanced_chat_invite_links_default_tzinfo(self, tz_bot, channel_id): + async def test_advanced_chat_invite_links_default_tzinfo(self, tz_bots, channel_id): # we are testing this all in one function in order to save api calls add_seconds = dtm.timedelta(0, 70) - aware_expire_date = dtm.datetime.now(tz=tz_bot.defaults.tzinfo) + add_seconds - time_in_future = aware_expire_date.replace(tzinfo=None) - invite_link = await tz_bot.create_chat_invite_link( - channel_id, expire_date=time_in_future, member_limit=10 - ) - assert invite_link.invite_link - assert not invite_link.invite_link.endswith("...") - assert abs(invite_link.expire_date - aware_expire_date) < dtm.timedelta(seconds=1) - assert invite_link.member_limit == 10 + new_inv_link_coros = [] + aware_expire_dates = [] + invite_links = [] + for tz_bot in tz_bots: + aware_expire_date = dtm.datetime.now(tz=tz_bot.defaults.tzinfo) + add_seconds + aware_expire_dates.append(aware_expire_date) + time_in_future = aware_expire_date.replace(tzinfo=None) + + new_inv_link_coros.append( + tz_bot.create_chat_invite_link( + channel_id, expire_date=time_in_future, member_limit=10 + ) + ) + for inv_link_coro in asyncio.as_completed(new_inv_link_coros): + invite_link = await inv_link_coro + assert invite_link.invite_link + assert not invite_link.invite_link.endswith("...") + assert abs(invite_link.expire_date - aware_expire_date) < dtm.timedelta(seconds=1) + assert invite_link.member_limit == 10 + invite_links.append(invite_link) - add_seconds = dtm.timedelta(0, 80) - aware_expire_date += add_seconds - time_in_future = aware_expire_date.replace(tzinfo=None) + assert all(invite_links) - edited_invite_link = await tz_bot.edit_chat_invite_link( - channel_id, - invite_link.invite_link, - expire_date=time_in_future, - member_limit=20, - name="NewName", - ) - assert edited_invite_link.invite_link == invite_link.invite_link - assert abs(edited_invite_link.expire_date - aware_expire_date) < dtm.timedelta(seconds=1) - assert edited_invite_link.name == "NewName" - assert edited_invite_link.member_limit == 20 + add_seconds = dtm.timedelta(0, 80) + aware_expire_dates = [aed + add_seconds for aed in aware_expire_dates] + edit_inv_link_coros = [] + + for tz_bot, aed, inv_link in zip(tz_bots, aware_expire_dates, invite_links): + time_in_future = aed.replace(tzinfo=None) + edit_inv_link_coros.append( + tz_bot.edit_chat_invite_link( + channel_id, + inv_link.invite_link, + expire_date=time_in_future, + member_limit=20, + name="NewName", + ) + ) - edited_invite_link = await tz_bot.edit_chat_invite_link( - channel_id, - invite_link.invite_link, - name="EvenNewerName", - creates_join_request=True, - ) - assert edited_invite_link.invite_link == invite_link.invite_link - assert not edited_invite_link.expire_date - assert edited_invite_link.name == "EvenNewerName" - assert edited_invite_link.creates_join_request - assert edited_invite_link.member_limit is None + for edit_inv_link_coro in asyncio.as_completed(edit_inv_link_coros): + edited_invite_link = await edit_inv_link_coro + # We can use any of the aware dates since they were generated at the same time + aed = aware_expire_dates[0] + assert edited_invite_link.invite_link in [il.invite_link for il in invite_links] + assert abs(edited_invite_link.expire_date - aed) < dtm.timedelta(seconds=1) + assert edited_invite_link.name == "NewName" + assert edited_invite_link.member_limit == 20 + + edit_inv_link_coros = [] + for tz_bot, inv_link in zip(tz_bots, invite_links): + edit_inv_link_coros.append( + tz_bot.edit_chat_invite_link( + channel_id, + inv_link.invite_link, + name="EvenNewerName", + creates_join_request=True, + ) + ) - revoked_invite_link = await tz_bot.revoke_chat_invite_link( - channel_id, invite_link.invite_link - ) - assert revoked_invite_link.invite_link == invite_link.invite_link - assert revoked_invite_link.is_revoked + for edit_inv_link_coro in asyncio.as_completed(edit_inv_link_coros): + edited_invite_link = await edit_inv_link_coro + assert edited_invite_link.invite_link in [il.invite_link for il in invite_links] + assert not edited_invite_link.expire_date + assert edited_invite_link.name == "EvenNewerName" + assert edited_invite_link.creates_join_request + assert edited_invite_link.member_limit is None + + revoke_coros = [] + for tz_bot, inv_link in zip(tz_bots, invite_links): + revoke_coros.append(tz_bot.revoke_chat_invite_link(channel_id, inv_link.invite_link)) + + for revoke_coro in asyncio.as_completed(revoke_coros): + revoked_invite_link = await revoke_coro + assert revoked_invite_link.invite_link in [il.invite_link for il in invite_links] + assert revoked_invite_link.is_revoked async def test_approve_chat_join_request(self, bot, chat_id, channel_id): # TODO: Need incoming join request to properly test