diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst index 6e5e2190b..6a21efc9f 100644 --- a/.github/CONTRIBUTING.rst +++ b/.github/CONTRIBUTING.rst @@ -84,35 +84,13 @@ Here's how to make a one-off code change. - In addition, PTB uses some formatting/styling and linting tools in the pre-commit setup. Some of those tools also have command line tools that can help to run these tools outside of the pre-commit step. If you'd like to leverage that, please have a look at the `pre-commit config file`_ for an overview of which tools (and which versions of them) are used. For example, we use `Black`_ for code formatting. Plugins for Black exist for some `popular editors`_. You can use those instead of manually formatting everything. - - Please ensure that the code you write is well-tested. + - Please ensure that the code you write is well-tested and that all automated tests still pass. We + have dedicated an `testing page`_ to help you with that. - - In addition to that, we provide the `dev` marker for pytest. If you write one or multiple tests and want to run only those, you can decorate them via `@pytest.mark.dev` and then run it with minimal overhead with `pytest ./path/to/test_file.py -m dev`. - - - Don’t break backward compatibility. + - Don't break backward compatibility. - Add yourself to the AUTHORS.rst_ file in an alphabetical fashion. - - Before making a commit ensure that all automated tests still pass: - - .. code-block:: bash - - $ pytest -v - - Since the tests can take a while to run, you can speed things up by running them in parallel - using `pytest-xdist`_ (note that this may effect the result of the test in some rare cases): - - .. code-block:: bash - - $ pytest -v -n auto --dist=loadfile - - To run ``test_official`` (particularly useful if you made API changes), run - - .. code-block:: bash - - $ export TEST_OFFICIAL=true - - prior to running the tests. - - If you want run style & type checks before committing run .. code-block:: bash @@ -287,4 +265,4 @@ break the API classes. For example: .. _`RTD build`: https://docs.python-telegram-bot.org/en/doc-fixes .. _`CSI`: https://standards.mousepawmedia.com/en/stable/csi.html .. _`section`: #documenting -.. _`pytest-xdist`: https://github.com/pytest-dev/pytest-xdist +.. _`testing page`: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/tests/README.rst diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d81f0e1f7..1d042053a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,42 +44,23 @@ jobs: # The first & second one are achieved by mocking the corresponding import # See test_helpers.py & test_no_passport.py for details run: | - # Test without passport - pytest -v --cov -k test_no_passport.py + # We test without optional dependencies first. This includes: + # - without pytz + # - without jobqueue + # - without ratelimiter + # - without webhooks + # - without arbitrary callback data + # - without socks suppport + TO_TEST="test_no_passport.py or test_datetime.py or test_defaults.py or test_jobqueue.py or test_applicationbuilder.py or test_ratelimiter.py or test_updater.py or test_callbackdatacache.py or test_request.py" + pytest -v --cov -k "${TO_TEST}" status=$? - # test without pytz - pytest -v --cov --cov-append -k test_datetime.py - status=$(( $? > status ? $? : status)) - pytest -v --cov --cov-append -k test_defaults.py - status=$(( $? > status ? $? : status)) - - # test without pytz & jobqueue - pytest -v --cov --cov-append -k test_jobqueue.py - pytest -v --cov --cov-append -k test_applicationbuilder.py - status=$(( $? > status ? $? : status)) - - # Test without ratelimiter - pytest -v --cov --cov-append -k test_ratelimiter.py - status=$(( $? > status ? $? : status)) - - # Test without webhooks - pytest -v --cov --cov-append -k test_updater.py - status=$(( $? > status ? $? : status)) - - # Test without callback-data - pytest -v --cov --cov-append -k test_callbackdatacache.py - status=$(( $? > status ? $? : status)) - - # Test without socks - pytest -v --cov --cov-append -k test_request.py - status=$(( $? > status ? $? : status)) - # Test the rest export TEST_WITH_OPT_DEPS='true' pip install -r requirements-opts.txt # `-n auto --dist loadfile` uses pytest-xdist to run each test file on a different CPU - # worker + # worker. Increasing number of workers has little effect on test duration, but it seems + # to increase flakyness, specially on python 3.7 with --dist=loadgroup. pytest -v --cov --cov-append -n auto --dist loadfile status=$(( $? > status ? $? : status)) exit ${status} diff --git a/.github/workflows/type_completeness.yml b/.github/workflows/type_completeness.yml index 5cc33f2e6..39d6b7146 100644 --- a/.github/workflows/type_completeness.yml +++ b/.github/workflows/type_completeness.yml @@ -37,7 +37,7 @@ jobs: - name: Compare Completeness uses: jannekem/run-python-script-action@v1 with: - script: | + script: | import json import os from pathlib import Path diff --git a/docs/source/index.rst b/docs/source/index.rst index c9e356dc7..adccdbef5 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -32,7 +32,6 @@ GitHub Repository Telegram Channel Telegram User Group - contributing coc - - + contributing + testing diff --git a/docs/source/testing.rst b/docs/source/testing.rst new file mode 100644 index 000000000..c32693c24 --- /dev/null +++ b/docs/source/testing.rst @@ -0,0 +1 @@ +.. include:: ../../tests/README.rst \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index 10db73911..347a8ab0d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,10 +1,10 @@ -pre-commit +pre-commit # needed for pre-commit hooks in the git commit command +# For the test suite pytest==7.2.1 -pytest-asyncio==0.20.3 -pytest-timeout==2.1.0 # used to timeout tests +pytest-asyncio==0.20.3 # needed because pytest doesn't come with native support for coroutines as tests pytest-xdist==3.1.0 # xdist runs tests in parallel - flaky # Used for flaky tests (flaky decorator) beautifulsoup4 # used in test_official for parsing tg docs + wheel # required for building the wheels for releases diff --git a/setup.cfg b/setup.cfg index a0e0eb856..60c2cf574 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,7 +36,10 @@ filterwarnings = ; Unfortunately due to https://github.com/pytest-dev/pytest/issues/8343 we can't have this here ; and instead do a trick directly in tests/conftest.py ; ignore::telegram.utils.deprecate.TelegramDeprecationWarning -markers = dev: If you want to test a specific test, use this +markers = + dev: If you want to test a specific test, use this + no_req + req asyncio_mode = auto [coverage:run] diff --git a/tests/README.rst b/tests/README.rst new file mode 100644 index 000000000..fb2ffe14b --- /dev/null +++ b/tests/README.rst @@ -0,0 +1,101 @@ +============== +Testing in PTB +============== + +PTB uses `pytest`_ for testing. To run the tests, you need to +have pytest installed along with a few other dependencies. You can find the list of dependencies +in the ``requirements-dev.txt`` file in the root of the repository. + +Running tests +============= + +To run the entire test suite, you can use the following command: + +.. code-block:: bash + + $ pytest + +This will run all the tests, including the ones which make a request to the Telegram servers, which +may take a long time (total > 13 mins). To run only the tests that don't require a connection, you +can run the following command: + +.. code-block:: bash + + $ pytest -m no_req + +Or alternatively, you can run the following command to run only the tests that require a connection: + +.. code-block:: bash + + $ pytest -m req + +To further speed up the tests, you can run them in parallel using the ``-n`` flag (requires `pytest-xdist`_). But beware that +this will use multiple CPU cores on your machine. The ``--dist`` flag is used to specify how the +tests will be distributed across the cores. The ``loadgroup`` option is used to distribute the tests +such that tests marked with ``@pytest.mark.xdist_group("name")`` are run on the same core — important if you want avoid race conditions in some tests: + +.. code-block:: bash + + $ pytest -n auto --dist=loadgroup + +This will result in a significant speedup, but may cause some tests to fail. If you want to run +the failed tests in isolation, you can use the ``--lf`` flag: + +.. code-block:: bash + + $ pytest --lf + + +Writing tests +============= + +PTB has a separate test file for every file in the ``telegram.*`` namespace. Further, the tests for +the ``telegram`` module are split into two classes, based on whether the test methods in them make a +request or not. When writing tests, make sure to split them into these two classes, and make sure +to name the test class as: ``TestXXXWithoutRequest`` for tests that don't make a request, and ``TestXXXWithRequest`` for tests that do. + +Writing tests is a creative process; allowing you to design your test however you'd like, but there +are a few conventions that you should follow: + +- Each new test class needs a ``test_slot_behaviour``, ``test_to_dict``, ``test_de_json`` and + ``test_equality`` (in most cases). + +- Make use of pytest's fixtures and parametrize wherever possible. Having knowledge of pytest's + tooling can help you as well. You can look at the existing tests for examples and inspiration. + +If you have made some API changes, you may want to run ``test_official`` to validate that the changes are +complete and correct. To run it, export an environment variable first: + +.. code-block:: bash + + $ export TEST_OFFICIAL=true + +and then run ``pytest tests/test_official.py``. + +We also have another marker, ``@pytest.mark.dev``, which you can add to tests that you want to run selectively. +Use as follows: + +.. code-block:: bash + + $ pytest -m dev + + +Bots used in tests +================== + +If you run the tests locally, the test setup will use one of the two public bots available. Which +bot of the two gets chosen for the test session is random. Whereas when the tests on the +Github Actions CI are run, the test setup allocates a different, but same bot for every combination of Python version and +OS. + +Thus, number of bots used for testing locally is 2 (called as fallback bots), and on the CI, +its [3.7, 3.8, 3.9, 3.10, 3.11] x [ubuntu-latest, macos-latest, windows-latest] = 15. Bringing the +total number of bots used for testing to 17. + + +That's it! If you have any questions, feel free to ask them in the `PTB dev +group`_. + +.. _pytest: https://docs.pytest.org/en/stable/ +.. _pytest-xdist: https://pypi.org/project/pytest-xdist/ +.. _PTB dev group: https://t.me/pythontelegrambotgroup \ No newline at end of file diff --git a/tests/bots.py b/tests/bots.py index c5432dc49..70849d45a 100644 --- a/tests/bots.py +++ b/tests/bots.py @@ -45,26 +45,27 @@ if GITHUB_ACTION is not None and BOTS is not None and JOB_INDEX is not None: BOTS = json.loads(base64.b64decode(BOTS).decode("utf-8")) JOB_INDEX = int(JOB_INDEX) -FALLBACKS = json.loads(base64.b64decode(FALLBACKS).decode("utf-8")) +FALLBACKS = json.loads(base64.b64decode(FALLBACKS).decode("utf-8")) # type: list[dict[str, str]] -def get(name, fallback): - # If we have TOKEN, PAYMENT_PROVIDER_TOKEN, CHAT_ID, SUPER_GROUP_ID, - # CHANNEL_ID, BOT_NAME, or BOT_USERNAME in the environment, then use that - val = os.getenv(name.upper()) - if val: - return val +class BotInfoProvider: + def __init__(self): + self._cached = {} + def get_info(self): + if self._cached: + return self._cached + self._cached = {k: get(k, v) for k, v in random.choice(FALLBACKS).items()} + return self._cached + + +def get(key, fallback): # If we're running as a github action then fetch bots from the repo secrets if GITHUB_ACTION is not None and BOTS is not None and JOB_INDEX is not None: try: - return BOTS[JOB_INDEX][name] - except (KeyError, IndexError): + return BOTS[JOB_INDEX][key] + except (IndexError, KeyError): pass # Otherwise go with the fallback return fallback - - -def get_bot(): - return {k: get(k, v) for k, v in random.choice(FALLBACKS).items()} diff --git a/tests/conftest.py b/tests/conftest.py index d0f827a4c..4756931d2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,7 +22,7 @@ import os import re import sys from pathlib import Path -from typing import Callable, Optional +from typing import Callable, Dict, List, Optional import pytest from httpx import AsyncClient, Response @@ -48,16 +48,49 @@ from telegram.ext.filters import MessageFilter, UpdateFilter from telegram.request import RequestData from telegram.request._httpxrequest import HTTPXRequest from tests.auxil.object_conversions import env_var_2_bool -from tests.bots import get_bot +from tests.bots import BotInfoProvider + +BOT_INFO = BotInfoProvider() # This is here instead of in setup.cfg due to https://github.com/pytest-dev/pytest/issues/8343 -def pytest_runtestloop(session): +def pytest_runtestloop(session: pytest.Session): session.add_marker( pytest.mark.filterwarnings("ignore::telegram.warnings.PTBDeprecationWarning") ) +def pytest_collection_modifyitems(items: List[pytest.Item]): + """Here we add a flaky marker to all request making tests and a (no_)req marker to the rest.""" + for item in items: # items are the test methods + parent = item.parent # Get the parent of the item (class, or module if defined outside) + if parent is None: # should never happen, but just in case + return + if ( # Check if the class name ends with 'WithRequest' and if it has no flaky marker + parent.name.endswith("WithRequest") + and not parent.get_closest_marker( # get_closest_marker gets pytest.marks with `name` + name="flaky" + ) # don't add/override any previously set markers + and not parent.get_closest_marker(name="req") + ): # Add the flaky marker with a rerun filter to the class + parent.add_marker(pytest.mark.flaky(3, 1, rerun_filter=no_rerun_after_xfail_or_flood)) + parent.add_marker(pytest.mark.req) + # Add the no_req marker to all classes that end with 'WithoutRequest' and don't have it + elif parent.name.endswith("WithoutRequest") and not parent.get_closest_marker( + name="no_req" + ): + parent.add_marker(pytest.mark.no_req) + + +def no_rerun_after_xfail_or_flood(error, name, test: pytest.Function, plugin): + """Don't rerun tests that have xfailed when marked with xfail, or when we hit a flood limit.""" + xfail_present = test.get_closest_marker(name="xfail") + did_we_flood = "flood" in getattr(error[1], "msg", "") # _pytest.outcomes.XFailed has 'msg' + if xfail_present or did_we_flood: + return False + return True + + GITHUB_ACTION = os.getenv("GITHUB_ACTION", False) if GITHUB_ACTION: @@ -86,19 +119,12 @@ def event_loop(request): # loop.close() # instead of closing here, do that at the every end of the test session -# Related to the above, see https://stackoverflow.com/a/67307042/10606962 -def pytest_sessionfinish(session, exitstatus): - asyncio.get_event_loop().close() - - @pytest.fixture(scope="session") -def bot_info(): - return get_bot() +def bot_info() -> Dict[str, str]: + return BOT_INFO.get_info() # Below classes are used to monkeypatch attributes since parent classes don't have __dict__ - - class TestHttpxRequest(HTTPXRequest): async def _request_wrapper( self, @@ -132,6 +158,10 @@ class DictExtBot(ExtBot): # Makes it easier to work with the bot in tests self._unfreeze() + # Here we override get_me for caching because we don't want to call the API repeatedly in tests + async def get_me(self, *args, **kwargs): + return await mocked_get_me(self) + class DictBot(Bot): def __init__(self, *args, **kwargs): @@ -139,11 +169,42 @@ class DictBot(Bot): # Makes it easier to work with the bot in tests self._unfreeze() + # Here we override get_me for caching because we don't want to call the API repeatedly in tests + async def get_me(self, *args, **kwargs): + return await mocked_get_me(self) + class DictApplication(Application): pass +async def mocked_get_me(bot: Bot): + if bot._bot_user is None: + bot._bot_user = get_bot_user(bot.token) + return bot._bot_user + + +def get_bot_user(token: str) -> User: + """Used to return a mock user in bot.get_me(). This saves API calls on every init.""" + bot_info = BOT_INFO.get_info() + # We don't take token from bot_info, because we need to make a bot with a specific ID. So we + # generate the correct user_id from the token (token from bot_info is random each test run). + # 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("@") + return User( + user_id, + first_name, + is_bot=True, + username=username, + can_join_groups=True, + can_read_all_group_messages=False, + supports_inline_queries=True, + ) + + @pytest.fixture(scope="session") async def bot(bot_info): """Makes an ExtBot instance with the given bot_info""" @@ -151,6 +212,12 @@ async def bot(bot_info): yield _bot +@pytest.fixture(scope="function") +def one_time_bot(bot_info): + """A function scoped bot since the session bot would shutdown when `async with app` finishes""" + return make_bot(bot_info) + + @pytest.fixture(scope="session") async def cdc_bot(bot_info): """Makes an ExtBot instance with the given bot_info that uses arbitrary callback_data""" @@ -170,20 +237,36 @@ async def raw_bot(bot_info): yield _bot -@pytest.fixture(scope="function") +# Here we store the default bots so that we don't have to create them again and again. +# They are initialized but not shutdown on pytest_sessionfinish because it is causing +# problems with the event loop (Event loop is closed). +default_bots = {} + + +@pytest.fixture(scope="session") async def default_bot(request, bot_info): param = request.param if hasattr(request, "param") else {} + defaults = Defaults(**param) - default_bot = make_bot(bot_info, defaults=Defaults(**param)) - async with default_bot: - yield default_bot + # If the bot is already created, return it. Else make a new one. + default_bot = default_bots.get(defaults, None) + if default_bot is None: + default_bot = make_bot(bot_info, defaults=defaults) + await default_bot.initialize() + default_bots[defaults] = default_bot # Defaults object is hashable + return default_bot -@pytest.fixture(scope="function") +@pytest.fixture(scope="session") async def tz_bot(timezone, bot_info): - default_bot = make_bot(bot_info, defaults=Defaults(tzinfo=timezone)) - async with default_bot: - yield default_bot + defaults = Defaults(tzinfo=timezone) + try: # If the bot is already created, return it. Saves time since get_me is not called again. + return default_bots[defaults] + except KeyError: + default_bot = make_bot(bot_info, defaults=defaults) + await default_bot.initialize() + default_bots[defaults] = default_bot + return default_bot @pytest.fixture(scope="session") @@ -243,16 +326,14 @@ def data_file(filename: str) -> Path: @pytest.fixture(scope="function") def thumb_file(): - f = data_file("thumb.jpg").open("rb") - yield f - f.close() + with data_file("thumb.jpg").open("rb") as f: + yield f -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def class_thumb_file(): - f = data_file("thumb.jpg").open("rb") - yield f - f.close() + with data_file("thumb.jpg").open("rb") as f: + yield f def make_bot(bot_info=None, **kwargs): @@ -285,7 +366,7 @@ def make_message(text, **kwargs): """ bot = kwargs.pop("bot", None) if bot is None: - bot = make_bot(get_bot()) + bot = make_bot(BOT_INFO.get_info()) message = Message( message_id=1, from_user=kwargs.pop("user", User(id=1, first_name="", is_bot=False)), @@ -401,7 +482,7 @@ class BasicTimezone(datetime.tzinfo): return datetime.timedelta(0) -@pytest.fixture(params=["Europe/Berlin", "Asia/Singapore", "UTC"]) +@pytest.fixture(scope="session", params=["Europe/Berlin", "Asia/Singapore", "UTC"]) def tzinfo(request): if TEST_WITH_OPT_DEPS: return pytz.timezone(request.param) @@ -410,7 +491,7 @@ def tzinfo(request): return BasicTimezone(offset=datetime.timedelta(hours=hours_offset), name=request.param) -@pytest.fixture() +@pytest.fixture(scope="session") def timezone(tzinfo): return tzinfo diff --git a/tests/test_animation.py b/tests/test_animation.py index 69c34c6a3..2a344f132 100644 --- a/tests/test_animation.py +++ b/tests/test_animation.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio import os from pathlib import Path @@ -35,21 +36,19 @@ from tests.conftest import data_file @pytest.fixture(scope="function") def animation_file(): - f = data_file("game.gif").open("rb") - yield f - f.close() - - -@pytest.fixture(scope="class") -async def animation(bot, chat_id): with data_file("game.gif").open("rb") as f: - thumb = data_file("thumb.jpg") + 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, thumb=thumb.open("rb")) + await bot.send_animation(chat_id, animation=f, read_timeout=50, thumb=thumb) ).animation -class TestAnimation: +class TestAnimationBase: animation_file_id = "CgADAQADngIAAuyVeEez0xRovKi9VAI" animation_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e" width = 320 @@ -63,6 +62,8 @@ class TestAnimation: file_size = 5859 caption = "Test *animation*" + +class TestAnimationWithoutRequest(TestAnimationBase): def test_slot_behaviour(self, animation, mro_slots): for attr in animation.__slots__: assert getattr(animation, attr, "err") != "err", f"got extra slot '{attr}'" @@ -80,215 +81,6 @@ class TestAnimation: assert animation.file_name.startswith("game.gif") == self.file_name.startswith("game.gif") assert isinstance(animation.thumb, PhotoSize) - @pytest.mark.flaky(3, 1) - async def test_send_all_args(self, bot, chat_id, animation_file, animation, thumb_file): - message = await bot.send_animation( - chat_id, - animation_file, - duration=self.duration, - width=self.width, - height=self.height, - caption=self.caption, - parse_mode="Markdown", - disable_notification=False, - protect_content=True, - thumb=thumb_file, - has_spoiler=True, - ) - - assert isinstance(message.animation, Animation) - assert isinstance(message.animation.file_id, str) - assert isinstance(message.animation.file_unique_id, str) - assert message.animation.file_id != "" - assert message.animation.file_unique_id != "" - assert message.animation.file_name == animation.file_name - assert message.animation.mime_type == animation.mime_type - assert message.animation.file_size == animation.file_size - assert message.animation.thumb.width == self.width - assert message.animation.thumb.height == self.height - assert message.has_protected_content - try: - assert message.has_media_spoiler - except AssertionError: - pytest.xfail("This is a bug on Telegram's end") - - @pytest.mark.flaky(3, 1) - async def test_send_animation_custom_filename(self, bot, chat_id, animation_file, monkeypatch): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - return list(request_data.multipart_data.values())[0][0] == "custom_filename" - - monkeypatch.setattr(bot.request, "post", make_assertion) - - assert await bot.send_animation(chat_id, animation_file, filename="custom_filename") - monkeypatch.delattr(bot.request, "post") - - @pytest.mark.flaky(3, 1) - async def test_get_and_download(self, bot, animation): - path = Path("game.gif") - if path.is_file(): - path.unlink() - - new_file = await bot.get_file(animation.file_id) - - assert new_file.file_id == animation.file_id - assert new_file.file_path.startswith("https://") - - new_filepath = await new_file.download_to_drive("game.gif") - - assert new_filepath.is_file() - - @pytest.mark.flaky(3, 1) - async def test_send_animation_url_file(self, bot, chat_id, animation): - message = await bot.send_animation( - chat_id=chat_id, animation=self.animation_file_url, caption=self.caption - ) - - assert message.caption == self.caption - - assert isinstance(message.animation, Animation) - assert isinstance(message.animation.file_id, str) - assert isinstance(message.animation.file_unique_id, str) - assert message.animation.file_id != "" - assert message.animation.file_unique_id != "" - - assert message.animation.duration == animation.duration - assert message.animation.file_name.startswith( - "game.gif" - ) == animation.file_name.startswith("game.gif") - assert message.animation.mime_type == animation.mime_type - - @pytest.mark.flaky(3, 1) - async def test_send_animation_caption_entities(self, bot, chat_id, animation): - test_string = "Italic Bold Code" - entities = [ - MessageEntity(MessageEntity.ITALIC, 0, 6), - MessageEntity(MessageEntity.ITALIC, 7, 4), - MessageEntity(MessageEntity.ITALIC, 12, 4), - ] - message = await bot.send_animation( - chat_id, animation, caption=test_string, caption_entities=entities - ) - - assert message.caption == test_string - assert message.caption_entities == tuple(entities) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_animation_default_parse_mode_1(self, default_bot, chat_id, animation_file): - test_string = "Italic Bold Code" - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_animation( - chat_id, animation_file, caption=test_markdown_string - ) - assert message.caption_markdown == test_markdown_string - assert message.caption == test_string - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_animation_default_parse_mode_2(self, default_bot, chat_id, animation_file): - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_animation( - chat_id, animation_file, caption=test_markdown_string, parse_mode=None - ) - assert message.caption == test_markdown_string - assert message.caption_markdown == escape_markdown(test_markdown_string) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_animation_default_parse_mode_3(self, default_bot, chat_id, animation_file): - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_animation( - chat_id, animation_file, caption=test_markdown_string, parse_mode="HTML" - ) - assert message.caption == test_markdown_string - assert message.caption_markdown == escape_markdown(test_markdown_string) - - @pytest.mark.parametrize("local_mode", [True, False]) - async def test_send_animation_local_files(self, monkeypatch, bot, chat_id, local_mode): - try: - bot._local_mode = local_mode - # For just test that the correct paths are passed as we have no local bot API set up - test_flag = False - file = data_file("telegram.jpg") - expected = file.as_uri() - - async def make_assertion(_, data, *args, **kwargs): - nonlocal test_flag - if local_mode: - test_flag = data.get("animation") == expected and data.get("thumb") == expected - else: - test_flag = isinstance(data.get("animation"), InputFile) and isinstance( - data.get("thumb"), InputFile - ) - - monkeypatch.setattr(bot, "_post", make_assertion) - await bot.send_animation(chat_id, file, thumb=file) - assert test_flag - monkeypatch.delattr(bot, "_post") - finally: - bot._local_mode = False - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize( - "default_bot,custom", - [ - ({"allow_sending_without_reply": True}, None), - ({"allow_sending_without_reply": False}, None), - ({"allow_sending_without_reply": False}, True), - ], - indirect=["default_bot"], - ) - async def test_send_animation_default_allow_sending_without_reply( - self, default_bot, chat_id, animation, custom - ): - reply_to_message = await default_bot.send_message(chat_id, "test") - await reply_to_message.delete() - if custom is not None: - message = await default_bot.send_animation( - chat_id, - animation, - allow_sending_without_reply=custom, - reply_to_message_id=reply_to_message.message_id, - ) - assert message.reply_to_message is None - elif default_bot.defaults.allow_sending_without_reply: - message = await default_bot.send_animation( - chat_id, animation, reply_to_message_id=reply_to_message.message_id - ) - assert message.reply_to_message is None - else: - with pytest.raises(BadRequest, match="message not found"): - await default_bot.send_animation( - chat_id, animation, reply_to_message_id=reply_to_message.message_id - ) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) - async def test_send_animation_default_protect_content(self, default_bot, chat_id, animation): - animation_protected = await default_bot.send_animation(chat_id, animation) - assert animation_protected.has_protected_content - ani_unprotected = await default_bot.send_animation( - chat_id, animation, protect_content=False - ) - assert not ani_unprotected.has_protected_content - - @pytest.mark.flaky(3, 1) - async def test_resend(self, bot, chat_id, animation): - message = await bot.send_animation(chat_id, animation.file_id) - - assert message.animation == animation - - async def test_send_with_animation(self, monkeypatch, bot, chat_id, animation): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - return request_data.json_parameters["animation"] == animation.file_id - - monkeypatch.setattr(bot.request, "post", make_assertion) - message = await bot.send_animation(animation=animation, chat_id=chat_id) - assert message - def test_de_json(self, bot, animation): json_dict = { "file_id": self.animation_file_id, @@ -323,33 +115,6 @@ class TestAnimation: assert animation_dict["mime_type"] == animation.mime_type assert animation_dict["file_size"] == animation.file_size - @pytest.mark.flaky(3, 1) - async def test_error_send_empty_file(self, bot, chat_id): - animation_file = open(os.devnull, "rb") - - with pytest.raises(TelegramError): - await bot.send_animation(chat_id=chat_id, animation=animation_file) - - @pytest.mark.flaky(3, 1) - async def test_error_send_empty_file_id(self, bot, chat_id): - with pytest.raises(TelegramError): - await bot.send_animation(chat_id=chat_id, animation="") - - async def test_error_send_without_required_args(self, bot, chat_id): - with pytest.raises(TypeError): - await bot.send_animation(chat_id=chat_id) - - async def test_get_file_instance_method(self, monkeypatch, animation): - async def make_assertion(*_, **kwargs): - return kwargs["file_id"] == animation.file_id - - assert check_shortcut_signature(Animation.get_file, Bot.get_file, ["file_id"], []) - assert await check_shortcut_call(animation.get_file, animation.get_bot(), "get_file") - assert await check_defaults_handling(animation.get_file, animation.get_bot()) - - monkeypatch.setattr(animation.get_bot(), "get_file", make_assertion) - assert await animation.get_file() - def test_equality(self): a = Animation( self.animation_file_id, @@ -371,3 +136,222 @@ class TestAnimation: assert a != e assert hash(a) != hash(e) + + async def test_send_animation_custom_filename(self, bot, chat_id, animation_file, monkeypatch): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return list(request_data.multipart_data.values())[0][0] == "custom_filename" + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.send_animation(chat_id, animation_file, filename="custom_filename") + + @pytest.mark.parametrize("local_mode", [True, False]) + async def test_send_animation_local_files(self, monkeypatch, bot, chat_id, local_mode): + try: + bot._local_mode = local_mode + # For just test that the correct paths are passed as we have no local bot API set up + test_flag = False + file = data_file("telegram.jpg") + expected = file.as_uri() + + async def make_assertion(_, data, *args, **kwargs): + nonlocal test_flag + if local_mode: + test_flag = data.get("animation") == expected and data.get("thumb") == expected + else: + test_flag = isinstance(data.get("animation"), InputFile) and isinstance( + data.get("thumb"), InputFile + ) + + monkeypatch.setattr(bot, "_post", make_assertion) + await bot.send_animation(chat_id, file, thumb=file) + assert test_flag + finally: + bot._local_mode = False + + async def test_send_with_animation(self, monkeypatch, bot, chat_id, animation): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return request_data.json_parameters["animation"] == animation.file_id + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.send_animation(animation=animation, chat_id=chat_id) + + async def test_get_file_instance_method(self, monkeypatch, animation): + async def make_assertion(*_, **kwargs): + return kwargs["file_id"] == animation.file_id + + assert check_shortcut_signature(Animation.get_file, Bot.get_file, ["file_id"], []) + assert await check_shortcut_call(animation.get_file, animation.get_bot(), "get_file") + assert await check_defaults_handling(animation.get_file, animation.get_bot()) + + monkeypatch.setattr(animation.get_bot(), "get_file", make_assertion) + assert await animation.get_file() + + +class TestAnimationWithRequest(TestAnimationBase): + async def test_send_all_args(self, bot, chat_id, animation_file, animation, thumb_file): + message = await bot.send_animation( + chat_id, + animation_file, + duration=self.duration, + width=self.width, + height=self.height, + caption=self.caption, + parse_mode="Markdown", + disable_notification=False, + protect_content=True, + thumb=thumb_file, + has_spoiler=True, + ) + + assert isinstance(message.animation, Animation) + assert isinstance(message.animation.file_id, str) + assert isinstance(message.animation.file_unique_id, str) + assert message.animation.file_id != "" + assert message.animation.file_unique_id != "" + assert message.animation.file_name == animation.file_name + assert message.animation.mime_type == animation.mime_type + assert message.animation.file_size == animation.file_size + assert message.animation.thumb.width == self.width + assert message.animation.thumb.height == self.height + assert message.has_protected_content + try: + assert message.has_media_spoiler + except AssertionError: + pytest.xfail("This is a bug on Telegram's end") + + async def test_get_and_download(self, bot, animation): + path = Path("game.gif") + if path.is_file(): + path.unlink() + + new_file = await bot.get_file(animation.file_id) + + assert new_file.file_path.startswith("https://") + + new_filepath = await new_file.download_to_drive("game.gif") + assert new_filepath.is_file() + + async def test_send_animation_url_file(self, bot, chat_id, animation): + message = await bot.send_animation( + chat_id=chat_id, animation=self.animation_file_url, caption=self.caption + ) + + assert message.caption == self.caption + + assert isinstance(message.animation, Animation) + assert isinstance(message.animation.file_id, str) + assert isinstance(message.animation.file_unique_id, str) + assert message.animation.file_id != "" + assert message.animation.file_unique_id != "" + + assert message.animation.duration == animation.duration + assert message.animation.file_name.startswith( + "game.gif" + ) == animation.file_name.startswith("game.gif") + assert message.animation.mime_type == animation.mime_type + + async def test_send_animation_caption_entities(self, bot, chat_id, animation): + test_string = "Italic Bold Code" + entities = [ + MessageEntity(MessageEntity.ITALIC, 0, 6), + MessageEntity(MessageEntity.ITALIC, 7, 4), + MessageEntity(MessageEntity.ITALIC, 12, 4), + ] + message = await bot.send_animation( + chat_id, animation, caption=test_string, caption_entities=entities + ) + + assert message.caption == test_string + assert message.caption_entities == tuple(entities) + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_animation_default_parse_mode_1(self, default_bot, chat_id, animation_file): + test_string = "Italic Bold Code" + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_animation( + chat_id, animation_file, caption=test_markdown_string + ) + assert message.caption_markdown == test_markdown_string + assert message.caption == test_string + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_animation_default_parse_mode_2(self, default_bot, chat_id, animation_file): + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_animation( + chat_id, animation_file, caption=test_markdown_string, parse_mode=None + ) + assert message.caption == test_markdown_string + assert message.caption_markdown == escape_markdown(test_markdown_string) + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_animation_default_parse_mode_3(self, default_bot, chat_id, animation_file): + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_animation( + chat_id, animation_file, caption=test_markdown_string, parse_mode="HTML" + ) + assert message.caption == test_markdown_string + assert message.caption_markdown == escape_markdown(test_markdown_string) + + @pytest.mark.parametrize( + "default_bot,custom", + [ + ({"allow_sending_without_reply": True}, None), + ({"allow_sending_without_reply": False}, None), + ({"allow_sending_without_reply": False}, True), + ], + indirect=["default_bot"], + ) + async def test_send_animation_default_allow_sending_without_reply( + self, default_bot, chat_id, animation, custom + ): + reply_to_message = await default_bot.send_message(chat_id, "test") + await reply_to_message.delete() + if custom is not None: + message = await default_bot.send_animation( + chat_id, + animation, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = await default_bot.send_animation( + chat_id, animation, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match="message not found"): + await default_bot.send_animation( + chat_id, animation, reply_to_message_id=reply_to_message.message_id + ) + + @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) + async def test_send_animation_default_protect_content(self, default_bot, chat_id, animation): + tasks = asyncio.gather( + default_bot.send_animation(chat_id, animation), + default_bot.send_animation(chat_id, animation, protect_content=False), + ) + anim_protected, anim_unprotected = await tasks + assert anim_protected.has_protected_content + assert not anim_unprotected.has_protected_content + + async def test_resend(self, bot, chat_id, animation): + message = await bot.send_animation(chat_id, animation.file_id) + assert message.animation == animation + + async def test_error_send_empty_file(self, bot, chat_id): + animation_file = open(os.devnull, "rb") + + with pytest.raises(TelegramError): + await bot.send_animation(chat_id=chat_id, animation=animation_file) + + async def test_error_send_empty_file_id(self, bot, chat_id): + with pytest.raises(TelegramError): + await bot.send_animation(chat_id=chat_id, animation="") + + async def test_error_send_without_required_args(self, bot, chat_id): + with pytest.raises(TypeError): + await bot.send_animation(chat_id=chat_id) diff --git a/tests/test_application.py b/tests/test_application.py index a458f6ae7..b80c6a334 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -53,7 +53,13 @@ from telegram.ext import ( filters, ) from telegram.warnings import PTBUserWarning -from tests.conftest import PROJECT_ROOT_PATH, call_after, make_message_update, send_webhook_message +from tests.conftest import ( + PROJECT_ROOT_PATH, + call_after, + make_bot, + make_message_update, + send_webhook_message, +) class CustomContext(CallbackContext): @@ -113,8 +119,8 @@ class TestApplication: ): self.received = context.error.message - async def test_slot_behaviour(self, bot, mro_slots): - async with ApplicationBuilder().token(bot.token).build() as app: + async def test_slot_behaviour(self, one_time_bot, mro_slots): + async with ApplicationBuilder().bot(one_time_bot).build() as app: for at in app.__slots__: at = f"_Application{at}" if at.startswith("__") and not at.endswith("__") else at assert getattr(app, at, "err") != "err", f"got extra slot '{at}'" @@ -144,12 +150,12 @@ class TestApplication: "concurrent_updates, expected", [(0, 0), (4, 4), (False, 0), (True, 256)] ) @pytest.mark.filterwarnings("ignore: `Application` instances should") - def test_init(self, bot, concurrent_updates, expected): + def test_init(self, one_time_bot, concurrent_updates, expected): update_queue = asyncio.Queue() job_queue = JobQueue() persistence = PicklePersistence("file_path") context_types = ContextTypes() - updater = Updater(bot=bot, update_queue=update_queue) + updater = Updater(bot=one_time_bot, update_queue=update_queue) async def post_init(application: Application) -> None: pass @@ -161,7 +167,7 @@ class TestApplication: pass app = Application( - bot=bot, + bot=one_time_bot, update_queue=update_queue, job_queue=job_queue, persistence=persistence, @@ -172,7 +178,7 @@ class TestApplication: post_shutdown=post_shutdown, post_stop=post_stop, ) - assert app.bot is bot + assert app.bot is one_time_bot assert app.update_queue is update_queue assert app.job_queue is job_queue assert app.persistence is persistence @@ -196,7 +202,7 @@ class TestApplication: with pytest.raises(ValueError, match="must be a non-negative"): Application( - bot=bot, + bot=one_time_bot, update_queue=update_queue, job_queue=job_queue, persistence=persistence, @@ -208,19 +214,19 @@ class TestApplication: post_stop=None, ) - def test_job_queue(self, bot, app, recwarn): + def test_job_queue(self, one_time_bot, app, recwarn): expected_warning = ( "No `JobQueue` set up. To use `JobQueue`, you must install PTB via " "`pip install python-telegram-bot[job-queue]`." ) assert app.job_queue is app._job_queue - application = ApplicationBuilder().token(bot.token).job_queue(None).build() + application = ApplicationBuilder().bot(one_time_bot).job_queue(None).build() assert application.job_queue is None assert len(recwarn) == 1 assert str(recwarn[0].message) == expected_warning assert recwarn[0].filename == __file__, "wrong stacklevel" - def test_custom_context_init(self, bot): + def test_custom_context_init(self, one_time_bot): cc = ContextTypes( context=CustomContext, user_data=int, @@ -228,14 +234,14 @@ class TestApplication: bot_data=complex, ) - application = ApplicationBuilder().token(bot.token).context_types(cc).build() + application = ApplicationBuilder().bot(one_time_bot).context_types(cc).build() assert isinstance(application.user_data[1], int) assert isinstance(application.chat_data[1], float) assert isinstance(application.bot_data, complex) @pytest.mark.parametrize("updater", (True, False)) - async def test_initialize(self, bot, monkeypatch, updater): + async def test_initialize(self, one_time_bot, monkeypatch, updater): """Initialization of persistence is tested test_basepersistence""" self.test_flag = set() @@ -251,18 +257,18 @@ class TestApplication: ) if updater: - app = ApplicationBuilder().token(bot.token).build() + app = ApplicationBuilder().bot(one_time_bot).build() await app.initialize() assert self.test_flag == {"bot", "updater"} await app.shutdown() else: - app = ApplicationBuilder().token(bot.token).updater(None).build() + app = ApplicationBuilder().bot(one_time_bot).updater(None).build() await app.initialize() assert self.test_flag == {"bot"} await app.shutdown() @pytest.mark.parametrize("updater", (True, False)) - async def test_shutdown(self, bot, monkeypatch, updater): + async def test_shutdown(self, one_time_bot, monkeypatch, updater): """Shutdown of persistence is tested in test_basepersistence""" self.test_flag = set() @@ -278,11 +284,11 @@ class TestApplication: ) if updater: - async with ApplicationBuilder().token(bot.token).build(): + async with ApplicationBuilder().bot(one_time_bot).build(): pass assert self.test_flag == {"bot", "updater"} else: - async with ApplicationBuilder().token(bot.token).updater(None).build(): + async with ApplicationBuilder().bot(one_time_bot).updater(None).build(): pass assert self.test_flag == {"bot"} @@ -329,12 +335,12 @@ class TestApplication: await app.shutdown() await app.stop() - async def test_start_not_running_after_failure(self, bot, monkeypatch): + async def test_start_not_running_after_failure(self, one_time_bot, monkeypatch): def start(_): raise Exception("Test Exception") monkeypatch.setattr(JobQueue, "start", start) - app = ApplicationBuilder().token(bot.token).job_queue(JobQueue()).build() + app = ApplicationBuilder().bot(one_time_bot).job_queue(JobQueue()).build() async with app: with pytest.raises(Exception, match="Test Exception"): @@ -405,12 +411,12 @@ class TestApplication: @pytest.mark.parametrize("job_queue", (True, False)) @pytest.mark.filterwarnings("ignore::telegram.warnings.PTBUserWarning") - async def test_start_stop_processing_updates(self, bot, job_queue): + async def test_start_stop_processing_updates(self, one_time_bot, job_queue): # TODO: repeat a similar test for create_task, persistence processing and job queue if job_queue: - app = ApplicationBuilder().token(bot.token).build() + app = ApplicationBuilder().bot(one_time_bot).build() else: - app = ApplicationBuilder().token(bot.token).job_queue(None).build() + app = ApplicationBuilder().bot(one_time_bot).job_queue(None).build() async def callback(u, c): self.received = u @@ -440,9 +446,12 @@ class TestApplication: await asyncio.sleep(0.05) assert app.update_queue.empty() assert self.received == 1 - - await app.updater.start_polling() - await app.stop() + try: # just in case start_polling times out + await app.updater.start_polling() + except TelegramError: + pytest.xfail("start_polling timed out") + else: + await app.stop() assert not app.running # app.stop() should not stop the updater! assert app.updater.running @@ -476,7 +485,7 @@ class TestApplication: async def one(update, context): self.received = context - def two(update, context): + async def two(update, context): if update.message.text == "test": if context is not self.received: pytest.fail("Expected same context object, got different") @@ -643,7 +652,7 @@ class TestApplication: await asyncio.sleep(0.05) await app.stop() - async def test_flow_stop(self, app, bot): + async def test_flow_stop(self, app, one_time_bot): passed = [] async def start1(b, u): @@ -668,7 +677,8 @@ class TestApplication: ], ), ) - update.message.set_bot(bot) + await one_time_bot.initialize() + update.message.set_bot(one_time_bot) async with app: # If ApplicationHandlerStop raised handlers in other groups should not be called. @@ -679,18 +689,18 @@ class TestApplication: await app.process_update(update) assert passed == ["start1"] - async def test_flow_stop_by_error_handler(self, app, bot): + async def test_flow_stop_by_error_handler(self, app): passed = [] - exception = Exception("General excepition") + exception = Exception("General exception") - async def start1(b, u): + async def start1(u, c): passed.append("start1") raise exception - async def start2(b, u): + async def start2(u, c): passed.append("start2") - async def start3(b, u): + async def start3(u, c): passed.append("start3") async def error(u, c): @@ -728,7 +738,7 @@ class TestApplication: # Higher groups should still be called assert self.count == 42 - async def test_error_in_handler_part_2(self, app, bot): + async def test_error_in_handler_part_2(self, app, one_time_bot): passed = [] err = Exception("General exception") @@ -758,7 +768,8 @@ class TestApplication: ], ), ) - update.message.set_bot(bot) + await one_time_bot.initialize() + update.message.set_bot(one_time_bot) async with app: # If an unhandled exception was caught, no further handlers from the same group should @@ -837,7 +848,7 @@ class TestApplication: await app.stop() - async def test_custom_context_error_handler(self, bot): + async def test_custom_context_error_handler(self, one_time_bot): async def error_handler(_, context): self.received = ( type(context), @@ -848,7 +859,7 @@ class TestApplication: application = ( ApplicationBuilder() - .token(bot.token) + .bot(one_time_bot) .context_types( ContextTypes( context=CustomContext, bot_data=int, user_data=float, chat_data=complex @@ -866,8 +877,8 @@ class TestApplication: await asyncio.sleep(0.05) assert self.received == (CustomContext, float, complex, int) - async def test_custom_context_handler_callback(self, bot): - def callback(_, context): + async def test_custom_context_handler_callback(self, one_time_bot): + async def callback(_, context): self.received = ( type(context), type(context.user_data), @@ -877,7 +888,7 @@ class TestApplication: application = ( ApplicationBuilder() - .token(bot.token) + .bot(one_time_bot) .context_types( ContextTypes( context=CustomContext, bot_data=int, user_data=float, chat_data=complex @@ -971,7 +982,7 @@ class TestApplication: ), "incorrect stacklevel!" async def test_non_blocking_no_error_handler(self, app, caplog): - app.add_handler(TypeHandler(object, self.callback_raise_error, block=False)) + app.add_handler(TypeHandler(object, self.callback_raise_error("Test error"), block=False)) with caplog.at_level(logging.ERROR): async with app: @@ -997,7 +1008,7 @@ class TestApplication: app.add_error_handler(async_error_handler, block=False) app.add_error_handler(normal_error_handler) - app.add_handler(TypeHandler(object, self.callback_raise_error, block=handler_block)) + app.add_handler(TypeHandler(object, self.callback_raise_error("err"), block=handler_block)) async with app: await app.start() @@ -1041,14 +1052,15 @@ class TestApplication: ), "incorrect stacklevel!" @pytest.mark.parametrize(["block", "expected_output"], [(False, 0), (True, 5)]) - async def test_default_block_error_handler(self, bot, block, expected_output): + async def test_default_block_error_handler(self, bot_info, block, expected_output): async def error_handler(*args, **kwargs): await asyncio.sleep(0.1) self.count = 5 - app = Application.builder().token(bot.token).defaults(Defaults(block=block)).build() + bot = make_bot(bot_info, defaults=Defaults(block=block)) + app = Application.builder().bot(bot).build() async with app: - app.add_handler(TypeHandler(object, self.callback_raise_error)) + app.add_handler(TypeHandler(object, self.callback_raise_error("error"))) app.add_error_handler(error_handler) await app.process_update(1) await asyncio.sleep(0.05) @@ -1057,8 +1069,9 @@ class TestApplication: assert self.count == 5 @pytest.mark.parametrize(["block", "expected_output"], [(False, 0), (True, 5)]) - async def test_default_block_handler(self, bot, block, expected_output): - app = Application.builder().token(bot.token).defaults(Defaults(block=block)).build() + async def test_default_block_handler(self, bot_info, block, expected_output): + bot = make_bot(bot_info, defaults=Defaults(block=block)) + app = Application.builder().bot(bot).build() async with app: app.add_handler(TypeHandler(object, self.callback_set_count(5, sleep=0.1))) await app.process_update(1) @@ -1072,7 +1085,7 @@ class TestApplication: async def test_nonblocking_handler_raises_and_non_blocking_error_handler_raises( self, app, caplog, handler_block, error_handler_block ): - handler = TypeHandler(object, self.callback_raise_error, block=handler_block) + handler = TypeHandler(object, self.callback_raise_error("error"), block=handler_block) app.add_handler(handler) app.add_error_handler(self.error_handler_raise_error, block=error_handler_block) @@ -1303,10 +1316,12 @@ class TestApplication: await app.stop() @pytest.mark.parametrize("concurrent_updates", (15, 50, 100)) - async def test_concurrent_updates(self, bot, concurrent_updates): + async def test_concurrent_updates(self, one_time_bot, concurrent_updates): # We don't test with `True` since the large number of parallel coroutines quickly leads # to test instabilities - app = Application.builder().token(bot.token).concurrent_updates(concurrent_updates).build() + app = ( + Application.builder().bot(one_time_bot).concurrent_updates(concurrent_updates).build() + ) events = {i: asyncio.Event() for i in range(app.concurrent_updates + 10)} queue = asyncio.Queue() for event in events.values(): @@ -1337,8 +1352,8 @@ class TestApplication: await app.stop() - async def test_concurrent_updates_done_on_shutdown(self, bot): - app = Application.builder().token(bot.token).concurrent_updates(True).build() + async def test_concurrent_updates_done_on_shutdown(self, one_time_bot): + app = Application.builder().bot(one_time_bot).concurrent_updates(True).build() event = asyncio.Event() async def callback(update, context): @@ -1422,7 +1437,7 @@ class TestApplication: platform.system() == "Windows", reason="Can't send signals without stopping whole process on windows", ) - def test_run_polling_post_init(self, bot, monkeypatch): + def test_run_polling_post_init(self, one_time_bot, monkeypatch): events = [] async def get_updates(*args, **kwargs): @@ -1443,7 +1458,7 @@ class TestApplication: async def post_init(app: Application) -> None: events.append("post_init") - app = Application.builder().token(bot.token).post_init(post_init).build() + app = Application.builder().bot(one_time_bot).post_init(post_init).build() app.bot._unfreeze() monkeypatch.setattr(app.bot, "get_updates", get_updates) monkeypatch.setattr( @@ -1465,7 +1480,7 @@ class TestApplication: platform.system() == "Windows", reason="Can't send signals without stopping whole process on windows", ) - def test_run_polling_post_shutdown(self, bot, monkeypatch): + def test_run_polling_post_shutdown(self, one_time_bot, monkeypatch): events = [] async def get_updates(*args, **kwargs): @@ -1486,7 +1501,7 @@ class TestApplication: async def post_shutdown(app: Application) -> None: events.append("post_shutdown") - app = Application.builder().token(bot.token).post_shutdown(post_shutdown).build() + app = Application.builder().bot(one_time_bot).post_shutdown(post_shutdown).build() app.bot._unfreeze() monkeypatch.setattr(app.bot, "get_updates", get_updates) monkeypatch.setattr( @@ -1691,7 +1706,7 @@ class TestApplication: platform.system() == "Windows", reason="Can't send signals without stopping whole process on windows", ) - def test_run_webhook_post_init(self, bot, monkeypatch): + def test_run_webhook_post_init(self, one_time_bot, monkeypatch): events = [] async def delete_webhook(*args, **kwargs): @@ -1718,7 +1733,7 @@ class TestApplication: async def post_init(app: Application) -> None: events.append("post_init") - app = Application.builder().token(bot.token).post_init(post_init).build() + app = Application.builder().bot(one_time_bot).post_init(post_init).build() app.bot._unfreeze() monkeypatch.setattr(app.bot, "set_webhook", set_webhook) monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) @@ -1751,7 +1766,7 @@ class TestApplication: platform.system() == "Windows", reason="Can't send signals without stopping whole process on windows", ) - def test_run_webhook_post_shutdown(self, bot, monkeypatch): + def test_run_webhook_post_shutdown(self, one_time_bot, monkeypatch): events = [] async def delete_webhook(*args, **kwargs): @@ -1778,7 +1793,7 @@ class TestApplication: async def post_shutdown(app: Application) -> None: events.append("post_shutdown") - app = Application.builder().token(bot.token).post_shutdown(post_shutdown).build() + app = Application.builder().bot(one_time_bot).post_shutdown(post_shutdown).build() app.bot._unfreeze() monkeypatch.setattr(app.bot, "set_webhook", set_webhook) monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) @@ -1883,7 +1898,7 @@ class TestApplication: platform.system() == "Windows", reason="Can't send signals without stopping whole process on windows", ) - def test_run_webhook_parameters_passing(self, bot, monkeypatch): + def test_run_webhook_parameters_passing(self, one_time_bot, monkeypatch): # Check that we pass them correctly async def start_webhook(_, **kwargs): @@ -1898,7 +1913,7 @@ class TestApplication: monkeypatch.setattr(Updater, "start_webhook", start_webhook) monkeypatch.setattr(Updater, "stop", stop) - app = ApplicationBuilder().token(bot.token).build() + app = ApplicationBuilder().bot(one_time_bot).build() app_signature = inspect.signature(app.run_webhook) for name, param in updater_signature.parameters.items(): @@ -1939,8 +1954,8 @@ class TestApplication: assert set(self.received.keys()) == set(expected.keys()) assert self.received == expected - def test_run_without_updater(self, bot): - app = ApplicationBuilder().token(bot.token).updater(None).build() + def test_run_without_updater(self, one_time_bot): + app = ApplicationBuilder().bot(one_time_bot).updater(None).build() with pytest.raises(RuntimeError, match="only available if the application has an Updater"): app.run_webhook() @@ -1950,7 +1965,7 @@ class TestApplication: @pytest.mark.parametrize("method", ["start", "initialize"]) @pytest.mark.filterwarnings("ignore::telegram.warnings.PTBUserWarning") - def test_run_error_in_application(self, bot, monkeypatch, method): + def test_run_error_in_application(self, one_time_bot, monkeypatch, method): shutdowns = [] async def raise_method(*args, **kwargs): @@ -1971,7 +1986,7 @@ class TestApplication: monkeypatch.setattr( Updater, "shutdown", call_after(Updater.shutdown, after_shutdown("updater")) ) - app = ApplicationBuilder().token(bot.token).build() + app = ApplicationBuilder().bot(one_time_bot).build() with pytest.raises(RuntimeError, match="Test Exception"): app.run_polling(close_loop=False) @@ -1986,7 +2001,7 @@ class TestApplication: @pytest.mark.parametrize("method", ["start_polling", "start_webhook"]) @pytest.mark.filterwarnings("ignore::telegram.warnings.PTBUserWarning") - def test_run_error_in_updater(self, bot, monkeypatch, method): + def test_run_error_in_updater(self, one_time_bot, monkeypatch, method): shutdowns = [] async def raise_method(*args, **kwargs): @@ -2007,7 +2022,7 @@ class TestApplication: monkeypatch.setattr( Updater, "shutdown", call_after(Updater.shutdown, after_shutdown("updater")) ) - app = ApplicationBuilder().token(bot.token).build() + app = ApplicationBuilder().bot(one_time_bot).build() with pytest.raises(RuntimeError, match="Test Exception"): if "polling" in method: app.run_polling(close_loop=False) @@ -2023,12 +2038,12 @@ class TestApplication: reason="Only really relevant on windows", ) @pytest.mark.parametrize("method", ["start_polling", "start_webhook"]) - def test_run_stop_signal_warning_windows(self, bot, method, recwarn, monkeypatch): + def test_run_stop_signal_warning_windows(self, one_time_bot, method, recwarn, monkeypatch): async def raise_method(*args, **kwargs): raise RuntimeError("Prevent Actually Running") monkeypatch.setattr(Application, "initialize", raise_method) - app = ApplicationBuilder().token(bot.token).build() + app = ApplicationBuilder().bot(one_time_bot).build() with pytest.raises(RuntimeError, match="Prevent Actually Running"): if "polling" in method: @@ -2054,7 +2069,7 @@ class TestApplication: assert len(recwarn) == 0 - @pytest.mark.timeout(6) + @pytest.mark.flaky(3, 1) # loop.call_later will error the test when a flood error is received def test_signal_handlers(self, app, monkeypatch): # this test should make sure that signal handlers are set by default on Linux + Mac, # and not on Windows. @@ -2068,11 +2083,10 @@ class TestApplication: loop = asyncio.get_event_loop() monkeypatch.setattr(loop, "add_signal_handler", signal_handler_test) - async def abort_app(): - await asyncio.sleep(2) + def abort_app(): raise SystemExit - loop.create_task(abort_app()) + loop.call_later(0.6, abort_app) app.run_polling(close_loop=False) @@ -2082,7 +2096,7 @@ class TestApplication: assert received_signals == [signal.SIGINT, signal.SIGTERM, signal.SIGABRT] received_signals.clear() - loop.create_task(abort_app()) + loop.call_later(0.6, abort_app) app.run_webhook(port=49152, webhook_url="example.com", close_loop=False) if platform.system() == "Windows": diff --git a/tests/test_applicationbuilder.py b/tests/test_applicationbuilder.py index e45fb90cd..754585e51 100644 --- a/tests/test_applicationbuilder.py +++ b/tests/test_applicationbuilder.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. import asyncio -import os from dataclasses import dataclass import httpx @@ -38,10 +37,7 @@ from telegram.ext import ( from telegram.ext._applicationbuilder import _BOT_CHECKS from telegram.request import HTTPXRequest -from .auxil.object_conversions import env_var_2_bool -from .conftest import PRIVATE_KEY, data_file - -TEST_WITH_OPT_DEPS = env_var_2_bool(os.getenv("TEST_WITH_OPT_DEPS", True)) +from .conftest import PRIVATE_KEY, TEST_WITH_OPT_DEPS, data_file @pytest.fixture(scope="function") diff --git a/tests/test_audio.py b/tests/test_audio.py index 2a44dca0d..322e61f9e 100644 --- a/tests/test_audio.py +++ b/tests/test_audio.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio import os from pathlib import Path @@ -35,20 +36,17 @@ from tests.conftest import data_file @pytest.fixture(scope="function") def audio_file(): - with open(data_file("telegram.mp3"), "rb") as f: + with data_file("telegram.mp3").open("rb") as f: yield f -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") async def audio(bot, chat_id): - with data_file("telegram.mp3").open("rb") as f: - thumb = data_file("thumb.jpg") - return ( - await bot.send_audio(chat_id, audio=f, read_timeout=50, thumb=thumb.open("rb")) - ).audio + 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, thumb=thumb)).audio -class TestAudio: +class TestAudioBase: caption = "Test *audio*" performer = "Leandro Toledo" title = "Teste" @@ -65,6 +63,8 @@ class TestAudio: audio_file_id = "5a3128a4d2a04750b5b58397f3b5e812" audio_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e" + +class TestAudioWithoutRequest(TestAudioBase): def test_slot_behaviour(self, audio, mro_slots): for attr in audio.__slots__: assert getattr(audio, attr, "err") != "err", f"got extra slot '{attr}'" @@ -88,176 +88,6 @@ class TestAudio: assert audio.thumb.width == self.thumb_width assert audio.thumb.height == self.thumb_height - @pytest.mark.flaky(3, 1) - async def test_send_all_args(self, bot, chat_id, audio_file, thumb_file): - message = await bot.send_audio( - chat_id, - audio=audio_file, - caption=self.caption, - duration=self.duration, - performer=self.performer, - title=self.title, - disable_notification=False, - protect_content=True, - parse_mode="Markdown", - thumb=thumb_file, - ) - - assert message.caption == self.caption.replace("*", "") - - assert isinstance(message.audio, Audio) - assert isinstance(message.audio.file_id, str) - assert isinstance(message.audio.file_unique_id, str) - assert message.audio.file_unique_id is not None - assert message.audio.file_id is not None - assert message.audio.duration == self.duration - assert message.audio.performer == self.performer - assert message.audio.title == self.title - assert message.audio.file_name == self.file_name - assert message.audio.mime_type == self.mime_type - assert message.audio.file_size == self.file_size - assert message.audio.thumb.file_size == self.thumb_file_size - assert message.audio.thumb.width == self.thumb_width - assert message.audio.thumb.height == self.thumb_height - assert message.has_protected_content - - @pytest.mark.flaky(3, 1) - async def test_send_audio_custom_filename(self, bot, chat_id, audio_file, monkeypatch): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - return list(request_data.multipart_data.values())[0][0] == "custom_filename" - - monkeypatch.setattr(bot.request, "post", make_assertion) - - assert await bot.send_audio(chat_id, audio_file, filename="custom_filename") - - @pytest.mark.flaky(3, 1) - async def test_get_and_download(self, bot, audio): - path = Path("telegram.mp3") - if path.is_file(): - path.unlink() - - new_file = await bot.get_file(audio.file_id) - - assert new_file.file_size == self.file_size - assert new_file.file_id == audio.file_id - assert new_file.file_unique_id == audio.file_unique_id - assert str(new_file.file_path).startswith("https://") - - await new_file.download_to_drive("telegram.mp3") - - assert path.is_file() - - @pytest.mark.flaky(3, 1) - async def test_send_mp3_url_file(self, bot, chat_id, audio): - message = await bot.send_audio( - chat_id=chat_id, audio=self.audio_file_url, caption=self.caption - ) - - assert message.caption == self.caption - - assert isinstance(message.audio, Audio) - assert isinstance(message.audio.file_id, str) - assert isinstance(message.audio.file_unique_id, str) - assert message.audio.file_unique_id is not None - assert message.audio.file_id is not None - assert message.audio.duration == audio.duration - assert message.audio.mime_type == audio.mime_type - assert message.audio.file_size == audio.file_size - - @pytest.mark.flaky(3, 1) - async def test_resend(self, bot, chat_id, audio): - message = await bot.send_audio(chat_id=chat_id, audio=audio.file_id) - - assert message.audio == audio - - async def test_send_with_audio(self, monkeypatch, bot, chat_id, audio): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - return request_data.json_parameters["audio"] == audio.file_id - - monkeypatch.setattr(bot.request, "post", make_assertion) - message = await bot.send_audio(audio=audio, chat_id=chat_id) - assert message - - @pytest.mark.flaky(3, 1) - async def test_send_audio_caption_entities(self, bot, chat_id, audio): - test_string = "Italic Bold Code" - entities = [ - MessageEntity(MessageEntity.ITALIC, 0, 6), - MessageEntity(MessageEntity.ITALIC, 7, 4), - MessageEntity(MessageEntity.ITALIC, 12, 4), - ] - message = await bot.send_audio( - chat_id, audio, caption=test_string, caption_entities=entities - ) - - assert message.caption == test_string - assert message.caption_entities == tuple(entities) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_audio_default_parse_mode_1(self, default_bot, chat_id, audio_file): - test_string = "Italic Bold Code" - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_audio(chat_id, audio_file, caption=test_markdown_string) - assert message.caption_markdown == test_markdown_string - assert message.caption == test_string - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_audio_default_parse_mode_2(self, default_bot, chat_id, audio_file): - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_audio( - chat_id, audio_file, caption=test_markdown_string, parse_mode=None - ) - assert message.caption == test_markdown_string - assert message.caption_markdown == escape_markdown(test_markdown_string) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_audio_default_parse_mode_3(self, default_bot, chat_id, audio_file): - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_audio( - chat_id, audio_file, caption=test_markdown_string, parse_mode="HTML" - ) - assert message.caption == test_markdown_string - assert message.caption_markdown == escape_markdown(test_markdown_string) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) - async def test_send_audio_default_protect_content(self, default_bot, chat_id, audio): - protected_audio = await default_bot.send_audio(chat_id, audio) - assert protected_audio.has_protected_content - unprotected = await default_bot.send_audio(chat_id, audio, protect_content=False) - assert not unprotected.has_protected_content - - @pytest.mark.parametrize("local_mode", [True, False]) - async def test_send_audio_local_files(self, monkeypatch, bot, chat_id, local_mode): - try: - bot._local_mode = local_mode - # For just test that the correct paths are passed as we have no local bot API set up - test_flag = False - file = data_file("telegram.jpg") - expected = file.as_uri() - - async def make_assertion(_, data, *args, **kwargs): - nonlocal test_flag - if local_mode: - test_flag = data.get("audio") == expected and data.get("thumb") == expected - else: - test_flag = isinstance(data.get("audio"), InputFile) and isinstance( - data.get("thumb"), InputFile - ) - - monkeypatch.setattr(bot, "_post", make_assertion) - await bot.send_audio(chat_id, file, thumb=file) - assert test_flag - monkeypatch.delattr(bot, "_post") - finally: - bot._local_mode = False - def test_de_json(self, bot, audio): json_dict = { "file_id": self.audio_file_id, @@ -294,33 +124,6 @@ class TestAudio: assert audio_dict["file_size"] == audio.file_size assert audio_dict["file_name"] == audio.file_name - @pytest.mark.flaky(3, 1) - async def test_error_send_empty_file(self, bot, chat_id): - audio_file = open(os.devnull, "rb") - - with pytest.raises(TelegramError): - await bot.send_audio(chat_id=chat_id, audio=audio_file) - - @pytest.mark.flaky(3, 1) - async def test_error_send_empty_file_id(self, bot, chat_id): - with pytest.raises(TelegramError): - await bot.send_audio(chat_id=chat_id, audio="") - - async def test_error_send_without_required_args(self, bot, chat_id): - with pytest.raises(TypeError): - await bot.send_audio(chat_id=chat_id) - - async def test_get_file_instance_method(self, monkeypatch, audio): - async def make_assertion(*_, **kwargs): - return kwargs["file_id"] == audio.file_id - - assert check_shortcut_signature(Audio.get_file, Bot.get_file, ["file_id"], []) - assert await check_shortcut_call(audio.get_file, audio.get_bot(), "get_file") - assert await check_defaults_handling(audio.get_file, audio.get_bot()) - - monkeypatch.setattr(audio._bot, "get_file", make_assertion) - assert await audio.get_file() - def test_equality(self, audio): a = Audio(audio.file_id, audio.file_unique_id, audio.duration) b = Audio("", audio.file_unique_id, audio.duration) @@ -340,3 +143,187 @@ class TestAudio: assert a != e assert hash(a) != hash(e) + + async def test_send_with_audio(self, monkeypatch, bot, chat_id, audio): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return request_data.json_parameters["audio"] == audio.file_id + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.send_audio(audio=audio, chat_id=chat_id) + + async def test_send_audio_custom_filename(self, bot, chat_id, audio_file, monkeypatch): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return list(request_data.multipart_data.values())[0][0] == "custom_filename" + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.send_audio(chat_id, audio_file, filename="custom_filename") + + @pytest.mark.parametrize("local_mode", [True, False]) + async def test_send_audio_local_files(self, monkeypatch, bot, chat_id, local_mode): + try: + bot._local_mode = local_mode + # For just test that the correct paths are passed as we have no local bot API set up + test_flag = False + file = data_file("telegram.jpg") + expected = file.as_uri() + + async def make_assertion(_, data, *args, **kwargs): + nonlocal test_flag + if local_mode: + test_flag = data.get("audio") == expected and data.get("thumb") == expected + else: + test_flag = isinstance(data.get("audio"), InputFile) and isinstance( + data.get("thumb"), InputFile + ) + + monkeypatch.setattr(bot, "_post", make_assertion) + await bot.send_audio(chat_id, file, thumb=file) + assert test_flag + finally: + bot._local_mode = False + + async def test_get_file_instance_method(self, monkeypatch, audio): + async def make_assertion(*_, **kwargs): + return kwargs["file_id"] == audio.file_id + + assert check_shortcut_signature(Audio.get_file, Bot.get_file, ["file_id"], []) + assert await check_shortcut_call(audio.get_file, audio.get_bot(), "get_file") + assert await check_defaults_handling(audio.get_file, audio.get_bot()) + + monkeypatch.setattr(audio._bot, "get_file", make_assertion) + assert await audio.get_file() + + +class TestAudioWithRequest(TestAudioBase): + async def test_send_all_args(self, bot, chat_id, audio_file, thumb_file): + message = await bot.send_audio( + chat_id, + audio=audio_file, + caption=self.caption, + duration=self.duration, + performer=self.performer, + title=self.title, + disable_notification=False, + protect_content=True, + parse_mode="Markdown", + thumb=thumb_file, + ) + + assert message.caption == self.caption.replace("*", "") + + assert isinstance(message.audio, Audio) + assert isinstance(message.audio.file_id, str) + assert isinstance(message.audio.file_unique_id, str) + assert message.audio.file_unique_id is not None + assert message.audio.file_id is not None + assert message.audio.duration == self.duration + assert message.audio.performer == self.performer + assert message.audio.title == self.title + assert message.audio.file_name == self.file_name + assert message.audio.mime_type == self.mime_type + assert message.audio.file_size == self.file_size + assert message.audio.thumb.file_size == self.thumb_file_size + assert message.audio.thumb.width == self.thumb_width + assert message.audio.thumb.height == self.thumb_height + assert message.has_protected_content + + async def test_get_and_download(self, bot, chat_id, audio): + path = Path("telegram.mp3") + if path.is_file(): + path.unlink() + + new_file = await bot.get_file(audio.file_id) + + assert new_file.file_size == self.file_size + assert new_file.file_unique_id == audio.file_unique_id + assert str(new_file.file_path).startswith("https://") + + await new_file.download_to_drive("telegram.mp3") + assert path.is_file() + + async def test_send_mp3_url_file(self, bot, chat_id, audio): + message = await bot.send_audio( + chat_id=chat_id, audio=self.audio_file_url, caption=self.caption + ) + + assert message.caption == self.caption + + assert isinstance(message.audio, Audio) + assert isinstance(message.audio.file_id, str) + assert isinstance(message.audio.file_unique_id, str) + assert message.audio.file_unique_id is not None + assert message.audio.file_id is not None + assert message.audio.duration == audio.duration + assert message.audio.mime_type == audio.mime_type + assert message.audio.file_size == audio.file_size + + async def test_send_audio_caption_entities(self, bot, chat_id, audio): + test_string = "Italic Bold Code" + entities = [ + MessageEntity(MessageEntity.ITALIC, 0, 6), + MessageEntity(MessageEntity.ITALIC, 7, 4), + MessageEntity(MessageEntity.ITALIC, 12, 4), + ] + message = await bot.send_audio( + chat_id, audio, caption=test_string, caption_entities=entities + ) + + assert message.caption == test_string + assert message.caption_entities == tuple(entities) + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_audio_default_parse_mode_1(self, default_bot, chat_id, audio_file): + test_string = "Italic Bold Code" + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_audio(chat_id, audio_file, caption=test_markdown_string) + assert message.caption_markdown == test_markdown_string + assert message.caption == test_string + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_audio_default_parse_mode_2(self, default_bot, chat_id, audio_file): + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_audio( + chat_id, audio_file, caption=test_markdown_string, parse_mode=None + ) + assert message.caption == test_markdown_string + assert message.caption_markdown == escape_markdown(test_markdown_string) + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_audio_default_parse_mode_3(self, default_bot, chat_id, audio_file): + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_audio( + chat_id, audio_file, caption=test_markdown_string, parse_mode="HTML" + ) + assert message.caption == test_markdown_string + assert message.caption_markdown == escape_markdown(test_markdown_string) + + @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) + async def test_send_audio_default_protect_content(self, default_bot, chat_id, audio): + tasks = asyncio.gather( + default_bot.send_audio(chat_id, audio), + default_bot.send_audio(chat_id, audio, protect_content=False), + ) + protected, unprotected = await tasks + assert protected.has_protected_content + assert not unprotected.has_protected_content + + async def test_resend(self, bot, chat_id, audio): + message = await bot.send_audio(chat_id=chat_id, audio=audio.file_id) + assert message.audio == audio + + async def test_error_send_empty_file(self, bot, chat_id): + audio_file = open(os.devnull, "rb") + + with pytest.raises(TelegramError): + await bot.send_audio(chat_id=chat_id, audio=audio_file) + + async def test_error_send_empty_file_id(self, bot, chat_id): + with pytest.raises(TelegramError): + await bot.send_audio(chat_id=chat_id, audio="") + + async def test_error_send_without_required_args(self, bot, chat_id): + with pytest.raises(TypeError): + await bot.send_audio(chat_id=chat_id) diff --git a/tests/test_basepersistence.py b/tests/test_basepersistence.py index bb40fcf04..fbc70540d 100644 --- a/tests/test_basepersistence.py +++ b/tests/test_basepersistence.py @@ -43,7 +43,7 @@ from telegram.ext import ( filters, ) from telegram.warnings import PTBUserWarning -from tests.conftest import DictApplication, make_message_update +from tests.conftest import DictApplication, make_bot, make_message_update class HandlerStates(int, enum.Enum): @@ -227,7 +227,11 @@ class PappInput(NamedTuple): def build_papp( - token: str, store_data: dict = None, update_interval: float = None, fill_data: bool = False + bot_info: dict = None, + token: str = None, + store_data: dict = None, + update_interval: float = None, + fill_data: bool = False, ) -> Application: store_data = PersistenceInput(**(store_data or {})) if update_interval is not None: @@ -237,12 +241,15 @@ def build_papp( else: persistence = TrackingPersistence(store_data=store_data, fill_data=fill_data) + if bot_info is not None: + bot = make_bot(bot_info, arbitrary_callback_data=True) + else: + bot = make_bot(token=token, arbitrary_callback_data=True) return ( ApplicationBuilder() - .token(token) + .bot(bot) .persistence(persistence) .application_class(DictApplication) - .arbitrary_callback_data(True) .build() ) @@ -252,7 +259,7 @@ def build_conversation_handler(name: str, persistent: bool = True) -> BaseHandle @pytest.fixture(scope="function") -def papp(request, bot) -> Application: +def papp(request, bot_info) -> Application: papp_input = request.param store_data = {} if papp_input.bot_data is not None: @@ -265,7 +272,7 @@ def papp(request, bot) -> Application: store_data["callback_data"] = papp_input.callback_data app = build_papp( - bot.token, + bot_info=bot_info, store_data=store_data, update_interval=papp_input.update_interval, fill_data=papp_input.fill_data, @@ -998,7 +1005,7 @@ class TestBasePersistence: assert papp.persistence.dropped_chat_ids == {1: 1} assert papp.persistence.updated_chat_ids == {2: 1} - async def test_errors_while_persisting(self, bot, caplog): + async def test_errors_while_persisting(self, bot_info, caplog): class ErrorPersistence(TrackingPersistence): def raise_error(self): raise Exception("PersistenceError") @@ -1032,8 +1039,7 @@ class TestBasePersistence: app = ( ApplicationBuilder() - .token(bot.token) - .arbitrary_callback_data(True) + .bot(make_bot(bot_info, arbitrary_callback_data=True)) .persistence(ErrorPersistence()) .build() ) diff --git a/tests/test_bot.py b/tests/test_bot.py index 28b946c8f..39e848922 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -24,7 +24,6 @@ import logging import pickle import re import socket -import sys import time from collections import defaultdict @@ -77,7 +76,15 @@ from telegram.helpers import escape_markdown from telegram.request import BaseRequest, HTTPXRequest, RequestData from tests.auxil.bot_method_checks import check_defaults_handling from tests.bots import FALLBACKS -from tests.conftest import GITHUB_ACTION, data_file, expect_bad_request, make_bot +from tests.conftest import ( + GITHUB_ACTION, + TEST_WITH_OPT_DEPS, + DictBot, + DictExtBot, + data_file, + expect_bad_request, + make_bot, +) def to_camel_case(snake_str): @@ -88,29 +95,22 @@ def to_camel_case(snake_str): return components[0] + "".join(x.title() for x in components[1:]) -@pytest.fixture(scope="class") -async def message(bot, chat_id): - to_reply_to = await bot.send_message( - chat_id, "Text", disable_web_page_preview=True, disable_notification=True - ) +@pytest.fixture(scope="function") +async def message(bot, chat_id): # mostly used in tests for edit_message out = await bot.send_message( - chat_id, - "Text", - reply_to_message_id=to_reply_to.message_id, - disable_web_page_preview=True, - disable_notification=True, + chat_id, "Text", disable_web_page_preview=True, disable_notification=True ) out._unfreeze() return out -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") async def media_message(bot, chat_id): with data_file("telegram.ogg").open("rb") as f: return await bot.send_voice(chat_id, voice=f, caption="my caption", read_timeout=10) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def chat_permissions(): return ChatPermissions(can_send_messages=False, can_change_info=False, can_invite_users=False) @@ -126,7 +126,7 @@ def inline_results_callback(page=None): return None -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_results(): return inline_results_callback() @@ -140,7 +140,7 @@ xfail = pytest.mark.xfail( ) -def bot_methods(ext_bot=True): +def bot_methods(ext_bot=True, include_camel_case=False): arg_values = [] ids = [] non_api_methods = [ @@ -160,6 +160,8 @@ def bot_methods(ext_bot=True): for name, attribute in inspect.getmembers(cls, predicate=inspect.isfunction): if name.startswith("_") or name in non_api_methods: continue + if not include_camel_case and any(x.isupper() for x in name): + continue arg_values.append((cls, name, attribute)) ids.append(f"{cls.__name__}.{name}") @@ -190,7 +192,7 @@ class InputMessageContentDWPP(InputMessageContent): self.disable_web_page_preview = disable_web_page_preview -class TestBot: +class TestBotWithoutRequest: """ Most are executed on tg.ext.ExtBot, as that class only extends the functionality of tg.bot @@ -198,16 +200,6 @@ class TestBot: is tested in `test_callbackdatacache` """ - @staticmethod - def localize(dt, tzinfo): - try: - return tzinfo.localize(dt) - except AttributeError: - # We get here if tzinfo is not a pytz timezone but a datetime.timezone class - # This test class should never be run in if pytz is not installed, we intentionally - # fail if this branch is ever reached. - sys.exit(1) - test_flag = None @pytest.fixture(scope="function", autouse=True) @@ -221,14 +213,28 @@ class TestBot: assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" - async def test_initialize_and_shutdown(self, bot, monkeypatch): + async def test_no_token_passed(self): + with pytest.raises(InvalidToken, match="You must pass the token"): + Bot("") + + async def test_to_dict(self, bot): + to_dict_bot = bot.to_dict() + + assert isinstance(to_dict_bot, dict) + assert to_dict_bot["id"] == bot.id + assert to_dict_bot["username"] == bot.username + assert to_dict_bot["first_name"] == bot.first_name + if bot.last_name: + assert to_dict_bot["last_name"] == bot.last_name + + async def test_initialize_and_shutdown(self, bot: DictExtBot, monkeypatch): async def initialize(*args, **kwargs): self.test_flag = ["initialize"] async def stop(*args, **kwargs): self.test_flag.append("stop") - temp_bot = Bot(token=bot.token) + temp_bot = DictBot(token=bot.token) orig_stop = temp_bot.request.shutdown try: @@ -255,7 +261,7 @@ class TestBot: monkeypatch.setattr(HTTPXRequest, "initialize", initialize) monkeypatch.setattr(HTTPXRequest, "shutdown", shutdown) - test_bot = Bot(bot.token) + test_bot = DictBot(bot.token) await test_bot.initialize() await test_bot.initialize() await test_bot.initialize() @@ -267,14 +273,6 @@ class TestBot: assert self.received["init"] == 2 assert self.received["shutdown"] == 2 - async def test_multiple_init_cycles(self, bot): - # nothing really to assert - this should just not fail - test_bot = Bot(bot.token) - async with test_bot: - await test_bot.get_me() - async with test_bot: - await test_bot.get_me() - async def test_context_manager(self, monkeypatch, bot): async def initialize(): self.test_flag = ["initialize"] @@ -306,80 +304,24 @@ class TestBot: assert self.test_flag == "stop" - async def test_log_decorator(self, bot, caplog): - # Second argument makes sure that we ignore logs from e.g. httpx - with caplog.at_level(logging.DEBUG, logger="telegram"): - await bot.get_me() - # Only for stabilizing this test- - if len(caplog.records) == 4: - for idx, record in enumerate(caplog.records): - print(record) - if record.getMessage().startswith("Task was destroyed but it is pending"): - caplog.records.pop(idx) - if record.getMessage().startswith("Task exception was never retrieved"): - caplog.records.pop(idx) - assert len(caplog.records) == 3 - assert caplog.records[0].getMessage().startswith("Entering: get_me") - assert caplog.records[-1].getMessage().startswith("Exiting: get_me") + async def test_equality(self): + async with make_bot(token=FALLBACKS[0]["token"]) as a, make_bot( + token=FALLBACKS[0]["token"] + ) as b, make_bot(token=FALLBACKS[1]["token"]) as c, Bot(token=FALLBACKS[0]["token"]) as d: + e = Update(123456789) - @bot_methods(ext_bot=False) - def test_api_methods_have_log_decorator(self, bot_class, bot_method_name, bot_method): - """Check that all bot methods have the log decorator ...""" - # not islower() skips the camelcase aliases - if not bot_method_name.islower(): - return - source = inspect.getsource(bot_method) - assert ( - # Use re.match to only match at *the beginning* of the string - re.match(rf"\s*\@\_log\s*async def {bot_method_name}", source) - ), f"{bot_method_name} is missing the @_log decorator" + assert a == b + assert hash(a) == hash(b) + assert a is not b - @pytest.mark.parametrize( - "acd_in,maxsize", - [(True, 1024), (False, 1024), (0, 0), (None, None)], - ) - async def test_callback_data_maxsize(self, bot, acd_in, maxsize): - async with ExtBot(bot.token, arbitrary_callback_data=acd_in) as acd_bot: - if acd_in is not False: - assert acd_bot.callback_data_cache.maxsize == maxsize - else: - assert acd_bot.callback_data_cache is None + assert a != c + assert hash(a) != hash(c) - async def test_no_token_passed(self): - with pytest.raises(InvalidToken, match="You must pass the token"): - Bot("") + assert a != d + assert hash(a) != hash(d) - async def test_invalid_token_server_response(self): - with pytest.raises(InvalidToken, match="The token `12` was rejected by the server."): - async with make_bot(token="12"): - pass - - async def test_unknown_kwargs(self, bot, monkeypatch): - async def post(url, request_data: RequestData, *args, **kwargs): - data = request_data.json_parameters - if not all([data["unknown_kwarg_1"] == "7", data["unknown_kwarg_2"] == "5"]): - pytest.fail("got wrong parameters") - return True - - monkeypatch.setattr(bot.request, "post", post) - await bot.send_message( - 123, "text", api_kwargs={"unknown_kwarg_1": 7, "unknown_kwarg_2": 5} - ) - - @pytest.mark.flaky(3, 1) - async def test_get_me_and_properties(self, bot: Bot): - get_me_bot = await bot.get_me() - - assert isinstance(get_me_bot, User) - assert get_me_bot.id == bot.id - assert get_me_bot.username == bot.username - assert get_me_bot.first_name == bot.first_name - assert get_me_bot.last_name == bot.last_name - assert get_me_bot.name == bot.name - assert get_me_bot.can_join_groups == bot.can_join_groups - assert get_me_bot.can_read_all_group_messages == bot.can_read_all_group_messages - assert get_me_bot.supports_inline_queries == bot.supports_inline_queries - assert f"https://t.me/{get_me_bot.username}" == bot.link + assert a != e + assert hash(a) != hash(e) @pytest.mark.parametrize( "attribute", @@ -403,35 +345,19 @@ class TestBot: finally: await bot.shutdown() - async def test_equality(self): - async with make_bot(token=FALLBACKS[0]["token"]) as a, make_bot( - token=FALLBACKS[0]["token"] - ) as b, make_bot(token=FALLBACKS[1]["token"]) as c, Bot(token=FALLBACKS[0]["token"]) as d: - e = Update(123456789) + async def test_get_me_and_properties(self, bot): + get_me_bot = await ExtBot(bot.token).get_me() - assert a == b - assert hash(a) == hash(b) - assert a is not b - - assert a != c - assert hash(a) != hash(c) - - assert a != d - assert hash(a) != hash(d) - - assert a != e - assert hash(a) != hash(e) - - @pytest.mark.flaky(3, 1) - async def test_to_dict(self, bot): - to_dict_bot = bot.to_dict() - - assert isinstance(to_dict_bot, dict) - assert to_dict_bot["id"] == bot.id - assert to_dict_bot["username"] == bot.username - assert to_dict_bot["first_name"] == bot.first_name - if bot.last_name: - assert to_dict_bot["last_name"] == bot.last_name + assert isinstance(get_me_bot, User) + assert get_me_bot.id == bot.id + assert get_me_bot.username == bot.username + assert get_me_bot.first_name == bot.first_name + assert get_me_bot.last_name == bot.last_name + assert get_me_bot.name == bot.name + assert get_me_bot.can_join_groups == bot.can_join_groups + assert get_me_bot.can_read_all_group_messages == bot.can_read_all_group_messages + assert get_me_bot.supports_inline_queries == bot.supports_inline_queries + assert f"https://t.me/{get_me_bot.username}" == bot.link def test_bot_pickling_error(self, bot): with pytest.raises(pickle.PicklingError, match="Bot objects cannot be pickled"): @@ -441,9 +367,68 @@ class TestBot: with pytest.raises(TypeError, match="Bot objects cannot be deepcopied"): copy.deepcopy(bot) + @bot_methods(ext_bot=False) + def test_api_methods_have_log_decorator(self, bot_class, bot_method_name, bot_method): + """Check that all bot methods have the log decorator ...""" + # not islower() skips the camelcase aliases + if not bot_method_name.islower(): + return + source = inspect.getsource(bot_method) + assert ( + # Use re.match to only match at *the beginning* of the string + re.match(rf"\s*\@\_log\s*async def {bot_method_name}", source) + ), f"{bot_method_name} is missing the @_log decorator" + + async def test_log_decorator(self, bot: DictExtBot, caplog): + # Second argument makes sure that we ignore logs from e.g. httpx + with caplog.at_level(logging.DEBUG, logger="telegram"): + await ExtBot(bot.token).get_me() + # Only for stabilizing this test- + if len(caplog.records) == 4: + for idx, record in enumerate(caplog.records): + print(record) + if record.getMessage().startswith("Task was destroyed but it is pending"): + caplog.records.pop(idx) + if record.getMessage().startswith("Task exception was never retrieved"): + caplog.records.pop(idx) + assert len(caplog.records) == 3 + assert caplog.records[0].getMessage().startswith("Entering: get_me") + assert caplog.records[-1].getMessage().startswith("Exiting: get_me") + + @bot_methods() + def test_camel_case_aliases(self, bot_class, bot_method_name, bot_method): + camel_case_name = to_camel_case(bot_method_name) + camel_case_function = getattr(bot_class, camel_case_name, False) + assert camel_case_function is not False, f"{camel_case_name} not found" + assert camel_case_function is bot_method, f"{camel_case_name} is not {bot_method}" + + @bot_methods() + def test_coroutine_functions(self, bot_class, bot_method_name, bot_method): + """Check that all bot methods are defined as async def ...""" + meth = getattr(bot_method, "__wrapped__", bot_method) # to unwrap the @_log decorator + assert inspect.iscoroutinefunction(meth), f"{bot_method_name} must be a coroutine function" + + @bot_methods() + def test_api_kwargs_and_timeouts_present(self, bot_class, bot_method_name, bot_method): + """Check that all bot methods have `api_kwargs` and timeout params.""" + param_names = inspect.signature(bot_method).parameters.keys() + params = ("pool_timeout", "read_timeout", "connect_timeout", "write_timeout", "api_kwargs") + + for param in params: + assert param in param_names, f"{bot_method_name} is missing the parameter `{param}`" + + rate_arg = "rate_limit_args" + if bot_method_name.replace("_", "").lower() != "getupdates" and bot_class is ExtBot: + assert rate_arg in param_names, f"{bot_method} is missing the parameter `{rate_arg}`" + @bot_methods(ext_bot=False) async def test_defaults_handling( - self, bot_class, bot_method_name, bot_method, bot, raw_bot, monkeypatch + self, + bot_class, + bot_method_name: str, + bot_method, + bot: DictExtBot, + raw_bot: DictBot, ): """ Here we check that the bot methods handle tg.ext.Defaults correctly. This has two parts: @@ -513,473 +498,18 @@ class TestBot: param.kind == ext_signature.parameters[param_name].kind ), f"Wrong parameter kind for parameter {param_name} of method {name}" - @pytest.mark.flaky(3, 1) - async def test_forward_message(self, bot, chat_id, message): - forward_message = await bot.forward_message( - chat_id, from_chat_id=chat_id, message_id=message.message_id + async def test_unknown_kwargs(self, bot, monkeypatch): + async def post(url, request_data: RequestData, *args, **kwargs): + data = request_data.json_parameters + if not all([data["unknown_kwarg_1"] == "7", data["unknown_kwarg_2"] == "5"]): + pytest.fail("got wrong parameters") + return True + + monkeypatch.setattr(bot.request, "post", post) + await bot.send_message( + 123, "text", api_kwargs={"unknown_kwarg_1": 7, "unknown_kwarg_2": 5} ) - assert forward_message.text == message.text - assert forward_message.forward_from.username == message.from_user.username - assert isinstance(forward_message.forward_date, dtm.datetime) - - async def test_forward_protected_message(self, bot, message, chat_id): - to_forward_protected = await bot.send_message( - chat_id, "cant forward me", protect_content=True - ) - assert to_forward_protected.has_protected_content - - with pytest.raises(BadRequest, match="can't be forwarded"): - await to_forward_protected.forward(chat_id) - - to_forward_unprotected = await bot.send_message( - chat_id, "forward me", protect_content=False - ) - assert not to_forward_unprotected.has_protected_content - forwarded_but_now_protected = await to_forward_unprotected.forward( - chat_id, protect_content=True - ) - assert forwarded_but_now_protected.has_protected_content - with pytest.raises(BadRequest, match="can't be forwarded"): - await forwarded_but_now_protected.forward(chat_id) - - @pytest.mark.flaky(3, 1) - 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 - - @pytest.mark.flaky(3, 1) - async def test_delete_message_old_message(self, bot, chat_id): - with pytest.raises(BadRequest): - # Considering that the first message is old enough - await bot.delete_message(chat_id=chat_id, message_id=1) - - # send_photo, send_audio, send_document, send_sticker, send_video, send_voice, send_video_note, - # send_media_group and send_animation are tested in their respective test modules. No need to - # duplicate here. - - @pytest.mark.flaky(3, 1) - async def test_send_venue(self, bot, chat_id): - longitude = -46.788279 - latitude = -23.691288 - title = "title" - address = "address" - foursquare_id = "foursquare id" - foursquare_type = "foursquare type" - google_place_id = "google_place id" - google_place_type = "google_place type" - - message = await bot.send_venue( - chat_id=chat_id, - title=title, - address=address, - latitude=latitude, - longitude=longitude, - foursquare_id=foursquare_id, - foursquare_type=foursquare_type, - protect_content=True, - ) - - assert message.venue - assert message.venue.title == title - assert message.venue.address == address - assert message.venue.location.latitude == latitude - assert message.venue.location.longitude == longitude - assert message.venue.foursquare_id == foursquare_id - assert message.venue.foursquare_type == foursquare_type - assert message.venue.google_place_id is None - assert message.venue.google_place_type is None - assert message.has_protected_content - - message = await bot.send_venue( - chat_id=chat_id, - title=title, - address=address, - latitude=latitude, - longitude=longitude, - google_place_id=google_place_id, - google_place_type=google_place_type, - protect_content=True, - ) - - assert message.venue - assert message.venue.title == title - assert message.venue.address == address - assert message.venue.location.latitude == latitude - assert message.venue.location.longitude == longitude - assert message.venue.google_place_id == google_place_id - assert message.venue.google_place_type == google_place_type - assert message.venue.foursquare_id is None - assert message.venue.foursquare_type is None - assert message.has_protected_content - - @pytest.mark.flaky(3, 1) - async def test_send_contact(self, bot, chat_id): - phone_number = "+11234567890" - first_name = "Leandro" - last_name = "Toledo" - message = await bot.send_contact( - chat_id=chat_id, - phone_number=phone_number, - first_name=first_name, - last_name=last_name, - protect_content=True, - ) - - assert message.contact - assert message.contact.phone_number == phone_number - assert message.contact.first_name == first_name - assert message.contact.last_name == last_name - assert message.has_protected_content - - # TODO: Add bot to group to test polls too - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize( - "reply_markup", - [ - None, - InlineKeyboardMarkup.from_button( - InlineKeyboardButton(text="text", callback_data="data") - ), - InlineKeyboardMarkup.from_button( - InlineKeyboardButton(text="text", callback_data="data") - ).to_dict(), - ], - ) - async def test_send_and_stop_poll(self, bot, super_group_id, reply_markup): - question = "Is this a test?" - answers = ["Yes", "No", "Maybe"] - message = await bot.send_poll( - chat_id=super_group_id, - question=question, - options=answers, - is_anonymous=False, - allows_multiple_answers=True, - read_timeout=60, - protect_content=True, - ) - - assert message.poll - assert message.poll.question == question - assert message.poll.options[0].text == answers[0] - assert message.poll.options[1].text == answers[1] - assert message.poll.options[2].text == answers[2] - assert not message.poll.is_anonymous - assert message.poll.allows_multiple_answers - assert not message.poll.is_closed - assert message.poll.type == Poll.REGULAR - assert message.has_protected_content - - # Since only the poll and not the complete message is returned, we can't check that the - # reply_markup is correct. So we just test that sending doesn't give an error. - poll = await bot.stop_poll( - chat_id=super_group_id, - message_id=message.message_id, - reply_markup=reply_markup, - read_timeout=60, - ) - assert isinstance(poll, Poll) - assert poll.is_closed - assert poll.options[0].text == answers[0] - assert poll.options[0].voter_count == 0 - assert poll.options[1].text == answers[1] - assert poll.options[1].voter_count == 0 - assert poll.options[2].text == answers[2] - assert poll.options[2].voter_count == 0 - assert poll.question == question - assert poll.total_voter_count == 0 - - explanation = "[Here is a link](https://google.com)" - explanation_entities = [ - MessageEntity(MessageEntity.TEXT_LINK, 0, 14, url="https://google.com") - ] - message_quiz = await bot.send_poll( - chat_id=super_group_id, - question=question, - options=answers, - type=Poll.QUIZ, - correct_option_id=2, - is_closed=True, - explanation=explanation, - explanation_parse_mode=ParseMode.MARKDOWN_V2, - ) - assert message_quiz.poll.correct_option_id == 2 - assert message_quiz.poll.type == Poll.QUIZ - assert message_quiz.poll.is_closed - assert message_quiz.poll.explanation == "Here is a link" - assert message_quiz.poll.explanation_entities == tuple(explanation_entities) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize( - ["open_period", "close_date"], [(5, None), (None, True)], ids=["open_period", "close_date"] - ) - async def test_send_open_period(self, bot, super_group_id, open_period, close_date): - question = "Is this a test?" - answers = ["Yes", "No", "Maybe"] - reply_markup = InlineKeyboardMarkup.from_button( - InlineKeyboardButton(text="text", callback_data="data") - ) - - if close_date: - close_date = dtm.datetime.utcnow() + dtm.timedelta(seconds=5.05) - - message = await bot.send_poll( - chat_id=super_group_id, - question=question, - options=answers, - is_anonymous=False, - allows_multiple_answers=True, - read_timeout=60, - open_period=open_period, - close_date=close_date, - ) - await asyncio.sleep(5.1) - new_message = await bot.edit_message_reply_markup( - chat_id=super_group_id, - message_id=message.message_id, - reply_markup=reply_markup, - read_timeout=60, - ) - assert new_message.poll.id == message.poll.id - assert new_message.poll.is_closed - - @pytest.mark.flaky(3, 1) - async def test_send_close_date_default_tz(self, tz_bot, 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) - - 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) - - 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 - - @pytest.mark.flaky(3, 1) - async def test_send_poll_explanation_entities(self, bot, chat_id): - test_string = "Italic Bold Code" - entities = [ - MessageEntity(MessageEntity.ITALIC, 0, 6), - MessageEntity(MessageEntity.ITALIC, 7, 4), - MessageEntity(MessageEntity.ITALIC, 12, 4), - ] - message = await bot.send_poll( - chat_id, - "question", - options=["a", "b"], - correct_option_id=0, - type=Poll.QUIZ, - explanation=test_string, - explanation_entities=entities, - ) - - assert message.poll.explanation == test_string - assert message.poll.explanation_entities == tuple(entities) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_poll_default_parse_mode(self, default_bot, super_group_id): - explanation = "Italic Bold Code" - explanation_markdown = "_Italic_ *Bold* `Code`" - question = "Is this a test?" - answers = ["Yes", "No", "Maybe"] - - message = await default_bot.send_poll( - chat_id=super_group_id, - question=question, - options=answers, - type=Poll.QUIZ, - correct_option_id=2, - is_closed=True, - explanation=explanation_markdown, - ) - assert message.poll.explanation == explanation - assert message.poll.explanation_entities == ( - MessageEntity(MessageEntity.ITALIC, 0, 6), - MessageEntity(MessageEntity.BOLD, 7, 4), - MessageEntity(MessageEntity.CODE, 12, 4), - ) - - message = await default_bot.send_poll( - chat_id=super_group_id, - question=question, - options=answers, - type=Poll.QUIZ, - correct_option_id=2, - is_closed=True, - explanation=explanation_markdown, - explanation_parse_mode=None, - ) - assert message.poll.explanation == explanation_markdown - assert message.poll.explanation_entities == () - - message = await default_bot.send_poll( - chat_id=super_group_id, - question=question, - options=answers, - type=Poll.QUIZ, - correct_option_id=2, - is_closed=True, - explanation=explanation_markdown, - explanation_parse_mode="HTML", - ) - assert message.poll.explanation == explanation_markdown - assert message.poll.explanation_entities == () - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize( - "default_bot,custom", - [ - ({"allow_sending_without_reply": True}, None), - ({"allow_sending_without_reply": False}, None), - ({"allow_sending_without_reply": False}, True), - ], - indirect=["default_bot"], - ) - async def test_send_poll_default_allow_sending_without_reply( - self, default_bot, chat_id, custom - ): - question = "Is this a test?" - answers = ["Yes", "No", "Maybe"] - reply_to_message = await default_bot.send_message(chat_id, "test") - await reply_to_message.delete() - if custom is not None: - message = await default_bot.send_poll( - chat_id, - question=question, - options=answers, - allow_sending_without_reply=custom, - reply_to_message_id=reply_to_message.message_id, - ) - assert message.reply_to_message is None - elif default_bot.defaults.allow_sending_without_reply: - message = await default_bot.send_poll( - chat_id, - question=question, - options=answers, - reply_to_message_id=reply_to_message.message_id, - ) - assert message.reply_to_message is None - else: - with pytest.raises(BadRequest, match="message not found"): - await default_bot.send_poll( - chat_id, - question=question, - options=answers, - reply_to_message_id=reply_to_message.message_id, - ) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) - async def test_send_poll_default_protect_content(self, chat_id, default_bot): - protected_poll = await default_bot.send_poll(chat_id, "Test", ["1", "2"]) - assert protected_poll.has_protected_content - unprotect_poll = await default_bot.send_poll( - chat_id, "test", ["1", "2"], protect_content=False - ) - assert not unprotect_poll.has_protected_content - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("emoji", Dice.ALL_EMOJI + [None]) - async def test_send_dice(self, bot, chat_id, emoji): - message = await bot.send_dice(chat_id, emoji=emoji, protect_content=True) - - assert message.dice - assert message.has_protected_content - if emoji is None: - assert message.dice.emoji == Dice.DICE - else: - assert message.dice.emoji == emoji - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize( - "default_bot,custom", - [ - ({"allow_sending_without_reply": True}, None), - ({"allow_sending_without_reply": False}, None), - ({"allow_sending_without_reply": False}, True), - ], - indirect=["default_bot"], - ) - async def test_send_dice_default_allow_sending_without_reply( - self, default_bot, chat_id, custom - ): - reply_to_message = await default_bot.send_message(chat_id, "test") - await reply_to_message.delete() - if custom is not None: - message = await default_bot.send_dice( - chat_id, - allow_sending_without_reply=custom, - reply_to_message_id=reply_to_message.message_id, - ) - assert message.reply_to_message is None - elif default_bot.defaults.allow_sending_without_reply: - message = await default_bot.send_dice( - chat_id, - reply_to_message_id=reply_to_message.message_id, - ) - assert message.reply_to_message is None - else: - with pytest.raises(BadRequest, match="message not found"): - await default_bot.send_dice( - chat_id, reply_to_message_id=reply_to_message.message_id - ) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) - async def test_send_dice_default_protect_content(self, chat_id, default_bot): - protected_dice = await default_bot.send_dice(chat_id) - assert protected_dice.has_protected_content - unprotected_dice = await default_bot.send_dice(chat_id, protect_content=False) - assert not unprotected_dice.has_protected_content - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("chat_action", list(ChatAction)) - async def test_send_chat_action(self, bot, chat_id, chat_action): - assert await bot.send_chat_action(chat_id, chat_action) - - async def test_wrong_chat_action(self, bot, chat_id): - with pytest.raises(BadRequest, match="Wrong parameter action"): - await bot.send_chat_action(chat_id, "unknown action") - - async def test_send_chat_action_all_args(self, bot, chat_id, provider_token, 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) - - @pytest.mark.asyncio async def test_answer_web_app_query(self, bot, raw_bot, monkeypatch): params = False @@ -1024,7 +554,6 @@ class TestBot: == copied_result.input_message_content.parse_mode ) - @pytest.mark.asyncio @pytest.mark.parametrize( "default_bot", [{"parse_mode": "Markdown", "disable_web_page_preview": True}], @@ -1380,12 +909,6 @@ class TestBot: copied_results[idx].input_message_content, "disable_web_page_preview", None ) - async def test_answer_inline_query_current_offset_error(self, bot, inline_results): - with pytest.raises(ValueError, match=("`current_offset` and `next_offset`")): - await bot.answer_inline_query( - 1234, results=inline_results, next_offset=42, current_offset=51 - ) - @pytest.mark.parametrize( "current_offset,num_results,id_offset,expected_next_offset", [ @@ -1476,23 +999,12 @@ class TestBot: 1234, results=inline_results_callback, current_offset=6 ) - @pytest.mark.flaky(3, 1) - async def test_get_user_profile_photos(self, bot, chat_id): - user_profile_photos = await bot.get_user_profile_photos(chat_id) - - assert user_profile_photos.photos[0][0].file_size == 5403 - - @pytest.mark.flaky(3, 1) - async def test_get_one_user_profile_photo(self, bot, chat_id): - user_profile_photos = await bot.get_user_profile_photos(chat_id, offset=0, limit=1) - assert user_profile_photos.photos[0][0].file_size == 5403 - # get_file is tested multiple times in the test_*media* modules. # Here we only test the behaviour for bot apis in local mode async def test_get_file_local_mode(self, bot, monkeypatch): path = str(data_file("game.gif")) - async def _post(*args, **kwargs): + async def make_assertion(*args, **kwargs): return { "file_id": None, "file_unique_id": None, @@ -1500,12 +1012,11 @@ class TestBot: "file_path": path, } - monkeypatch.setattr(bot, "_post", _post) + monkeypatch.setattr(bot, "_post", make_assertion) resulting_path = (await bot.get_file("file_id")).file_path assert bot.token not in resulting_path assert resulting_path == path - monkeypatch.delattr(bot, "_post") # TODO: Needs improvement. No feasible way to test until bots can add members. async def test_ban_chat_member(self, monkeypatch, bot): @@ -1524,7 +1035,6 @@ class TestBot: assert await bot.ban_chat_member(2, 32, until_date=until) assert await bot.ban_chat_member(2, 32, until_date=1577887200) assert await bot.ban_chat_member(2, 32, revoke_messages=True) - monkeypatch.delattr(bot.request, "post") async def test_ban_chat_member_default_tz(self, monkeypatch, tz_bot): until = dtm.datetime(2020, 1, 11, 16, 13) @@ -1553,7 +1063,6 @@ class TestBot: monkeypatch.setattr(bot.request, "post", make_assertion) assert await bot.ban_chat_sender_chat(2, 32) - monkeypatch.delattr(bot.request, "post") # TODO: Needs improvement. @pytest.mark.parametrize("only_if_banned", [True, False, None]) @@ -1620,261 +1129,6 @@ class TestBot: 23, text="answer", show_alert=True, url="no_url", cache_time=1 ) - @pytest.mark.flaky(3, 1) - async def test_edit_message_text(self, bot, message): - message = await bot.edit_message_text( - text="new_text", - chat_id=message.chat_id, - message_id=message.message_id, - parse_mode="HTML", - disable_web_page_preview=True, - ) - - assert message.text == "new_text" - - @pytest.mark.flaky(3, 1) - 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( - 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) - - @pytest.mark.flaky(3, 1) - @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`" - - 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, - ) - assert message.text_markdown == test_markdown_string - assert message.text == test_string - - message = 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) - - 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, - 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) - - @pytest.mark.skip(reason="need reference to an inline message") - async def test_edit_message_text_inline(self): - pass - - @pytest.mark.flaky(3, 1) - async def test_edit_message_caption(self, bot, media_message): - message = await bot.edit_message_caption( - caption="new_caption", - chat_id=media_message.chat_id, - message_id=media_message.message_id, - ) - - assert message.caption == "new_caption" - - @pytest.mark.flaky(3, 1) - async def test_edit_message_caption_entities(self, bot, media_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_caption( - caption=test_string, - chat_id=media_message.chat_id, - message_id=media_message.message_id, - caption_entities=entities, - ) - - assert message.caption == test_string - assert message.caption_entities == tuple(entities) - - # edit_message_media is tested in test_inputmedia - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - 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`" - - message = 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 - - message = 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) - - 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, - 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) - - @pytest.mark.flaky(3, 1) - async def test_edit_message_caption_with_parse_mode(self, bot, media_message): - message = await bot.edit_message_caption( - caption="new *caption*", - parse_mode="Markdown", - chat_id=media_message.chat_id, - message_id=media_message.message_id, - ) - - assert message.caption == "new caption" - - @pytest.mark.skip(reason="need reference to an inline message") - async def test_edit_message_caption_inline(self): - pass - - @pytest.mark.flaky(3, 1) - async def test_edit_reply_markup(self, bot, message): - new_markup = InlineKeyboardMarkup([[InlineKeyboardButton(text="test", callback_data="1")]]) - message = await bot.edit_message_reply_markup( - chat_id=message.chat_id, message_id=message.message_id, reply_markup=new_markup - ) - - assert message is not True - - @pytest.mark.skip(reason="need reference to an inline message") - async def test_edit_reply_markup_inline(self): - pass - - # TODO: Actually send updates to the test bot so this can be tested properly - @pytest.mark.flaky(3, 1) - async def test_get_updates(self, bot): - await bot.delete_webhook() # make sure there is no webhook set if webhook tests failed - updates = await bot.get_updates(timeout=1) - - assert isinstance(updates, tuple) - if updates: - assert isinstance(updates[0], Update) - - async def test_get_updates_invalid_callback_data(self, cdc_bot, monkeypatch): - bot = cdc_bot - - async def post(*args, **kwargs): - return [ - Update( - 17, - callback_query=CallbackQuery( - id=1, - from_user=None, - chat_instance=123, - data="invalid data", - message=Message( - 1, - from_user=User(1, "", False), - date=dtm.datetime.utcnow(), - chat=Chat(1, ""), - text="Webhook", - ), - ), - ).to_dict() - ] - - try: - await bot.delete_webhook() # make sure there is no webhook set if webhook tests failed - monkeypatch.setattr(BaseRequest, "post", post) - updates = await bot.get_updates(timeout=1) - - assert isinstance(updates, tuple) - assert len(updates) == 1 - assert isinstance(updates[0].callback_query.data, InvalidCallbackData) - - finally: - # Reset b/c bots scope is session - bot.callback_data_cache.clear_callback_data() - bot.callback_data_cache.clear_callback_queries() - - @pytest.mark.flaky(3, 1) - @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"]) - async def test_set_webhook_get_webhook_info_and_delete_webhook(self, bot, use_ip, file_input): - url = "https://python-telegram-bot.org/test/webhook" - # Get the ip address of the website - dynamically just in case it ever changes - ip = socket.gethostbyname("python-telegram-bot.org") - max_connections = 7 - allowed_updates = ["message"] - file_input = ( - data_file("sslcert.pem").read_bytes() - if file_input == "bytes" - else data_file("sslcert.pem").open("rb") - ) - await bot.set_webhook( - url, - max_connections=max_connections, - allowed_updates=allowed_updates, - ip_address=ip if use_ip else None, - certificate=file_input if use_ip else None, - ) - - await asyncio.sleep(1) - live_info = await bot.get_webhook_info() - assert live_info.url == url - assert live_info.max_connections == max_connections - assert live_info.allowed_updates == tuple(allowed_updates) - assert live_info.ip_address == ip - assert live_info.has_custom_certificate == use_ip - - await bot.delete_webhook() - await asyncio.sleep(1) - info = await bot.get_webhook_info() - assert info.url == "" - assert info.ip_address is None - assert info.has_custom_certificate is False - @pytest.mark.parametrize("drop_pending_updates", [True, False]) async def test_set_webhook_delete_webhook_drop_pending_updates( self, bot, drop_pending_updates, monkeypatch @@ -1934,207 +1188,6 @@ class TestBot: "SoSecretToken", ) - @pytest.mark.flaky(3, 1) - 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) - - @pytest.mark.flaky(3, 1) - async def test_get_chat(self, bot, super_group_id): - chat = await bot.get_chat(super_group_id) - - assert chat.type == "supergroup" - assert chat.title == f">>> telegram.Bot(test) @{bot.username}" - assert chat.id == int(super_group_id) - - @pytest.mark.flaky(3, 1) - async def test_get_chat_administrators(self, bot, channel_id): - admins = await bot.get_chat_administrators(channel_id) - assert isinstance(admins, tuple) - - for a in admins: - assert a.status in ("administrator", "creator") - - @pytest.mark.flaky(3, 1) - async def test_get_chat_member_count(self, bot, channel_id): - count = await bot.get_chat_member_count(channel_id) - assert isinstance(count, int) - assert count > 3 - - @pytest.mark.flaky(3, 1) - async def test_get_chat_member(self, bot, channel_id, chat_id): - chat_member = await bot.get_chat_member(channel_id, chat_id) - - assert chat_member.status == "administrator" - assert chat_member.user.first_name == "PTB" - assert chat_member.user.last_name == "Test user" - - @pytest.mark.skip(reason="Not implemented since we need a supergroup with many members") - async def test_set_chat_sticker_set(self): - pass - - @pytest.mark.skip(reason="Not implemented since we need a supergroup with many members") - async def test_delete_chat_sticker_set(self): - pass - - @pytest.mark.flaky(3, 1) - async def test_send_game(self, bot, chat_id): - game_short_name = "test_game" - message = await bot.send_game(chat_id, game_short_name, protect_content=True) - - assert message.game - assert message.game.description == ( - "A no-op test game, for python-telegram-bot bot framework testing." - ) - assert message.game.animation.file_id != "" - # We added some test bots later and for some reason the file size is not the same for them - # so we accept three different sizes here. Shouldn't be too much of - assert message.game.photo[0].file_size in [851, 4928, 850] - assert message.has_protected_content - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize( - "default_bot,custom", - [ - ({"allow_sending_without_reply": True}, None), - ({"allow_sending_without_reply": False}, None), - ({"allow_sending_without_reply": False}, True), - ], - indirect=["default_bot"], - ) - async def test_send_game_default_allow_sending_without_reply( - self, default_bot, chat_id, custom - ): - game_short_name = "test_game" - reply_to_message = await default_bot.send_message(chat_id, "test") - await reply_to_message.delete() - if custom is not None: - message = await default_bot.send_game( - chat_id, - game_short_name, - allow_sending_without_reply=custom, - reply_to_message_id=reply_to_message.message_id, - ) - assert message.reply_to_message is None - elif default_bot.defaults.allow_sending_without_reply: - message = await default_bot.send_game( - chat_id, - game_short_name, - reply_to_message_id=reply_to_message.message_id, - ) - assert message.reply_to_message is None - else: - with pytest.raises(BadRequest, match="message not found"): - await default_bot.send_game( - chat_id, game_short_name, reply_to_message_id=reply_to_message.message_id - ) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize( - "default_bot,val", - [({"protect_content": True}, True), ({"protect_content": False}, None)], - indirect=["default_bot"], - ) - 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 - - @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 - # First, test setting a score. - game_short_name = "test_game" - game = await bot.send_game(chat_id, game_short_name) - - message = await bot.set_game_score( - user_id=chat_id, - score=BASE_GAME_SCORE, # Score value is relevant for other set_game_score_* tests! - chat_id=game.chat_id, - message_id=game.message_id, - ) - - assert message.game.description == game.game.description - assert message.game.photo[0].file_size == game.game.photo[0].file_size - assert message.game.animation.file_unique_id == game.game.animation.file_unique_id - assert message.game.text != game.game.text - - @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) - - score = BASE_GAME_SCORE + 1 - - message = await bot.set_game_score( - user_id=chat_id, - score=score, - chat_id=game.chat_id, - message_id=game.message_id, - disable_edit_message=True, - ) - - assert message.game.description == game.game.description - assert message.game.photo[0].file_size == game.game.photo[0].file_size - assert message.game.animation.file_unique_id == game.game.animation.file_unique_id - assert message.game.text == game.game.text - - @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) - - score = BASE_GAME_SCORE # Even a score equal to previous raises an error. - - with pytest.raises(BadRequest, match="Bot_score_not_modified"): - await bot.set_game_score( - user_id=chat_id, score=score, chat_id=game.chat_id, message_id=game.message_id - ) - - @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) - await asyncio.sleep(1.5) - - score = BASE_GAME_SCORE - 10 - - message = await bot.set_game_score( - user_id=chat_id, - score=score, - chat_id=game.chat_id, - message_id=game.message_id, - force=True, - ) - - assert message.game.description == game.game.description - assert message.game.photo[0].file_size == game.game.photo[0].file_size - assert message.game.animation.file_unique_id == game.game.animation.file_unique_id - - # For some reason the returned message doesn't contain the updated score. need to fetch - # the game again... (the service message is also absent when running the test suite) - game2 = await bot.send_game(chat_id, game_short_name) - assert str(score) in game2.game.text - - @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) - high_scores = await bot.get_game_high_scores(chat_id, game.chat_id, game.message_id) - # We assume that the other game score tests ran within 20 sec - assert high_scores[0].score == BASE_GAME_SCORE - 10 - - # send_invoice and create_invoice_link is tested in test_invoice - # TODO: Needs improvement. Need incoming shipping queries to test async def test_answer_shipping_query_ok(self, monkeypatch, bot): # For now just test that our internals pass the correct data @@ -2223,7 +1276,1272 @@ class TestBot: channel_id, 95205500, chat_permissions, until_date=until_timestamp ) - @pytest.mark.flaky(3, 1) + @pytest.mark.parametrize("local_mode", [True, False]) + async def test_set_chat_photo_local_files(self, monkeypatch, bot, chat_id, local_mode): + try: + bot._local_mode = local_mode + # For just test that the correct paths are passed as we have no local bot API set up + self.test_flag = False + file = data_file("telegram.jpg") + expected = file.as_uri() + + async def make_assertion(_, data, *args, **kwargs): + if local_mode: + self.test_flag = data.get("photo") == expected + else: + self.test_flag = isinstance(data.get("photo"), InputFile) + + monkeypatch.setattr(bot, "_post", make_assertion) + await bot.set_chat_photo(chat_id, file) + assert self.test_flag + finally: + bot._local_mode = False + + async def test_timeout_propagation_explicit(self, monkeypatch, bot, chat_id): + # Use BaseException that's not a subclass of Exception such that + # OkException should not be caught anywhere + class OkException(BaseException): + pass + + timeout = 42 + + async def do_request(*args, **kwargs): + obj = kwargs.get("read_timeout") + if obj == timeout: + raise OkException + + return 200, b'{"ok": true, "result": []}' + + monkeypatch.setattr(bot.request, "do_request", do_request) + + # Test file uploading + with pytest.raises(OkException): + await bot.send_photo( + chat_id, data_file("telegram.jpg").open("rb"), read_timeout=timeout + ) + + # Test JSON submission + with pytest.raises(OkException): + await bot.get_chat_administrators(chat_id, read_timeout=timeout) + + async def test_timeout_propagation_implicit(self, monkeypatch, bot, chat_id): + # Use BaseException that's not a subclass of Exception such that + # OkException should not be caught anywhere + class OkException(BaseException): + pass + + async def do_request(*args, **kwargs): + obj = kwargs.get("write_timeout") + if obj == 20: + raise OkException + + return 200, b'{"ok": true, "result": []}' + + monkeypatch.setattr(bot.request, "do_request", do_request) + + # Test file uploading + with pytest.raises(OkException): + await bot.send_photo(chat_id, data_file("telegram.jpg").open("rb")) + + async def test_log_out(self, monkeypatch, bot): + # We don't actually make a request as to not break the test setup + async def assertion(url, request_data: RequestData, *args, **kwargs): + return request_data.json_parameters == {} and url.split("/")[-1] == "logOut" + + monkeypatch.setattr(bot.request, "post", assertion) + + assert await bot.log_out() + + async def test_close(self, monkeypatch, bot): + # We don't actually make a request as to not break the test setup + async def assertion(url, request_data: RequestData, *args, **kwargs): + return request_data.json_parameters == {} and url.split("/")[-1] == "close" + + monkeypatch.setattr(bot.request, "post", assertion) + + assert await bot.close() + + @pytest.mark.parametrize("json_keyboard", [True, False]) + @pytest.mark.parametrize("caption", ["Test", "", None]) + async def test_copy_message( + self, monkeypatch, bot, chat_id, media_message, json_keyboard, caption + ): + keyboard = InlineKeyboardMarkup( + [[InlineKeyboardButton(text="test", callback_data="test2")]] + ) + + async def post(url, request_data: RequestData, *args, **kwargs): + data = request_data.parameters + if not all( + [ + data["chat_id"] == chat_id, + data["from_chat_id"] == chat_id, + data["message_id"] == media_message.message_id, + data.get("caption") == caption, + data["parse_mode"] == ParseMode.HTML, + data["reply_to_message_id"] == media_message.message_id, + data["reply_markup"] == keyboard.to_json() + if json_keyboard + else keyboard.to_dict(), + data["disable_notification"] is True, + data["caption_entities"] + == [MessageEntity(MessageEntity.BOLD, 0, 4).to_dict()], + data["protect_content"] is True, + data["message_thread_id"] == 1, + ] + ): + pytest.fail("I got wrong parameters in post") + return data + + monkeypatch.setattr(bot.request, "post", post) + await bot.copy_message( + chat_id, + from_chat_id=chat_id, + message_id=media_message.message_id, + caption=caption, + caption_entities=[MessageEntity(MessageEntity.BOLD, 0, 4)], + parse_mode=ParseMode.HTML, + reply_to_message_id=media_message.message_id, + reply_markup=keyboard.to_json() if json_keyboard else keyboard, + disable_notification=True, + protect_content=True, + message_thread_id=1, + ) + + # In the following tests we check that get_updates inserts callback data correctly if necessary + # The same must be done in the webhook updater. This is tested over at test_updater.py, but + # here we test more extensively. + + @pytest.mark.parametrize( + "acd_in,maxsize", + [(True, 1024), (False, 1024), (0, 0), (None, None)], + ) + async def test_callback_data_maxsize(self, bot_info, acd_in, maxsize): + async with make_bot(bot_info, arbitrary_callback_data=acd_in) as acd_bot: + if acd_in is not False: + assert acd_bot.callback_data_cache.maxsize == maxsize + else: + assert acd_bot.callback_data_cache is None + + async def test_arbitrary_callback_data_no_insert(self, monkeypatch, cdc_bot): + """Updates that don't need insertion shouldn't fail obviously""" + bot = cdc_bot + + async def post(*args, **kwargs): + update = Update( + 17, + poll=Poll( + "42", + "question", + options=[PollOption("option", 0)], + total_voter_count=0, + is_closed=False, + is_anonymous=True, + type=Poll.REGULAR, + allows_multiple_answers=False, + ), + ) + return [update.to_dict()] + + try: + monkeypatch.setattr(BaseRequest, "post", post) + updates = await bot.get_updates(timeout=1) + + assert len(updates) == 1 + assert updates[0].update_id == 17 + assert updates[0].poll.id == "42" + finally: + bot.callback_data_cache.clear_callback_data() + bot.callback_data_cache.clear_callback_queries() + + @pytest.mark.parametrize( + "message_type", ["channel_post", "edited_channel_post", "message", "edited_message"] + ) + async def test_arbitrary_callback_data_pinned_message_reply_to_message( + self, cdc_bot, monkeypatch, message_type + ): + bot = cdc_bot + + reply_markup = InlineKeyboardMarkup.from_button( + InlineKeyboardButton(text="text", callback_data="callback_data") + ) + + message = Message( + 1, + dtm.datetime.utcnow(), + None, + reply_markup=bot.callback_data_cache.process_keyboard(reply_markup), + ) + message._unfreeze() + # We do to_dict -> de_json to make sure those aren't the same objects + message.pinned_message = Message.de_json(message.to_dict(), bot) + + async def post(*args, **kwargs): + update = Update( + 17, + **{ + message_type: Message( + 1, + dtm.datetime.utcnow(), + None, + pinned_message=message, + reply_to_message=Message.de_json(message.to_dict(), bot), + ) + }, + ) + return [update.to_dict()] + + try: + monkeypatch.setattr(BaseRequest, "post", post) + updates = await bot.get_updates(timeout=1) + + assert isinstance(updates, tuple) + assert len(updates) == 1 + + effective_message = updates[0][message_type] + assert ( + effective_message.reply_to_message.reply_markup.inline_keyboard[0][0].callback_data + == "callback_data" + ) + assert ( + effective_message.pinned_message.reply_markup.inline_keyboard[0][0].callback_data + == "callback_data" + ) + + pinned_message = effective_message.reply_to_message.pinned_message + assert ( + pinned_message.reply_markup.inline_keyboard[0][0].callback_data == "callback_data" + ) + + finally: + bot.callback_data_cache.clear_callback_data() + bot.callback_data_cache.clear_callback_queries() + + async def test_get_updates_invalid_callback_data(self, cdc_bot, monkeypatch): + bot = cdc_bot + + async def post(*args, **kwargs): + return [ + Update( + 17, + callback_query=CallbackQuery( + id=1, + from_user=None, + chat_instance=123, + data="invalid data", + message=Message( + 1, + from_user=User(1, "", False), + date=dtm.datetime.utcnow(), + chat=Chat(1, ""), + text="Webhook", + ), + ), + ).to_dict() + ] + + try: + monkeypatch.setattr(BaseRequest, "post", post) + updates = await bot.get_updates(timeout=1) + + assert isinstance(updates, tuple) + assert len(updates) == 1 + assert isinstance(updates[0].callback_query.data, InvalidCallbackData) + + finally: + # Reset b/c bots scope is session + bot.callback_data_cache.clear_callback_data() + bot.callback_data_cache.clear_callback_queries() + + # TODO: Needs improvement. We need incoming inline query to test answer. + async def test_replace_callback_data_answer_inline_query(self, monkeypatch, cdc_bot, chat_id): + bot = cdc_bot + + # For now just test that our internals pass the correct data + async def make_assertion( + endpoint, + data=None, + *args, + **kwargs, + ): + inline_keyboard = data["results"][0]["reply_markup"].inline_keyboard + assertion_1 = inline_keyboard[0][1] == no_replace_button + assertion_2 = inline_keyboard[0][0] != replace_button + keyboard, button = ( + inline_keyboard[0][0].callback_data[:32], + inline_keyboard[0][0].callback_data[32:], + ) + assertion_3 = ( + bot.callback_data_cache._keyboard_data[keyboard].button_data[button] + == "replace_test" + ) + assertion_4 = data["results"][1].reply_markup is None + return assertion_1 and assertion_2 and assertion_3 and assertion_4 + + try: + replace_button = InlineKeyboardButton(text="replace", callback_data="replace_test") + no_replace_button = InlineKeyboardButton( + text="no_replace", url="http://python-telegram-bot.org/" + ) + reply_markup = InlineKeyboardMarkup.from_row( + [ + replace_button, + no_replace_button, + ] + ) + + bot.username # call this here so `bot.get_me()` won't be called after mocking + monkeypatch.setattr(bot, "_post", make_assertion) + results = [ + InlineQueryResultArticle( + "11", "first", InputTextMessageContent("first"), reply_markup=reply_markup + ), + InlineQueryResultVoice( + "22", + "https://python-telegram-bot.org/static/testfiles/telegram.ogg", + title="second", + ), + ] + + assert await bot.answer_inline_query(chat_id, results=results) + + finally: + bot.callback_data_cache.clear_callback_data() + bot.callback_data_cache.clear_callback_queries() + + @pytest.mark.parametrize( + "message_type", ["channel_post", "edited_channel_post", "message", "edited_message"] + ) + @pytest.mark.parametrize("self_sender", [True, False]) + async def test_arbitrary_callback_data_via_bot( + self, cdc_bot, monkeypatch, self_sender, message_type + ): + bot = cdc_bot + reply_markup = InlineKeyboardMarkup.from_button( + InlineKeyboardButton(text="text", callback_data="callback_data") + ) + + reply_markup = bot.callback_data_cache.process_keyboard(reply_markup) + message = Message( + 1, + dtm.datetime.utcnow(), + None, + reply_markup=reply_markup, + via_bot=bot.bot if self_sender else User(1, "first", False), + ) + + async def post(*args, **kwargs): + return [Update(17, **{message_type: message}).to_dict()] + + try: + monkeypatch.setattr(BaseRequest, "post", post) + updates = await bot.get_updates(timeout=1) + + assert isinstance(updates, tuple) + assert len(updates) == 1 + + message = updates[0][message_type] + if self_sender: + assert message.reply_markup.inline_keyboard[0][0].callback_data == "callback_data" + else: + assert ( + message.reply_markup.inline_keyboard[0][0].callback_data + == reply_markup.inline_keyboard[0][0].callback_data + ) + finally: + bot.callback_data_cache.clear_callback_data() + bot.callback_data_cache.clear_callback_queries() + + +class TestBotWithRequest: + """ + Most are executed on tg.ext.ExtBot, as that class only extends the functionality of tg.bot + + Behavior for init of ExtBot with missing optional dependency cachetools (for CallbackDataCache) + is tested in `test_callbackdatacache` + """ + + async def test_invalid_token_server_response(self): + with pytest.raises(InvalidToken, match="The token `12` was rejected by the server."): + async with ExtBot(token="12"): + pass + + async def test_multiple_init_cycles(self, bot): + # nothing really to assert - this should just not fail + test_bot = Bot(bot.token) + async with test_bot: + await test_bot.get_me() + async with test_bot: + await test_bot.get_me() + + async def test_forward_message(self, bot, chat_id, message): + forward_message = await bot.forward_message( + chat_id, from_chat_id=chat_id, message_id=message.message_id + ) + + assert forward_message.text == message.text + assert forward_message.forward_from.username == message.from_user.username + assert isinstance(forward_message.forward_date, dtm.datetime) + + async def test_forward_protected_message(self, bot, chat_id): + tasks = asyncio.gather( + bot.send_message(chat_id, "cant forward me", protect_content=True), + bot.send_message(chat_id, "forward me", protect_content=False), + ) + to_forward_protected, to_forward_unprotected = await tasks + + assert to_forward_protected.has_protected_content + assert not to_forward_unprotected.has_protected_content + + forwarded_but_now_protected = await to_forward_unprotected.forward( + chat_id, protect_content=True + ) + assert forwarded_but_now_protected.has_protected_content + + tasks = asyncio.gather( + to_forward_protected.forward(chat_id), + forwarded_but_now_protected.forward(chat_id), + return_exceptions=True, + ) + result = await tasks + assert all("can't be forwarded" in str(exc) for exc in result) + + 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): + with pytest.raises(BadRequest): + # Considering that the first message is old enough + await bot.delete_message(chat_id=chat_id, message_id=1) + + # send_photo, send_audio, send_document, send_sticker, send_video, send_voice, send_video_note, + # send_media_group and send_animation are tested in their respective test modules. No need to + # duplicate here. + + async def test_send_venue(self, bot, chat_id): + longitude = -46.788279 + latitude = -23.691288 + title = "title" + address = "address" + foursquare_id = "foursquare id" + foursquare_type = "foursquare type" + google_place_id = "google_place id" + google_place_type = "google_place type" + + tasks = asyncio.gather( + *( + bot.send_venue( + chat_id=chat_id, + title=title, + address=address, + latitude=latitude, + longitude=longitude, + protect_content=True, + **i, + ) + for i in ( + {"foursquare_id": foursquare_id, "foursquare_type": foursquare_type}, + {"google_place_id": google_place_id, "google_place_type": google_place_type}, + ) + ), + ) + + message, message2 = await tasks + assert message.venue + assert message.venue.title == title + assert message.venue.address == address + assert message.venue.location.latitude == latitude + assert message.venue.location.longitude == longitude + assert message.venue.foursquare_id == foursquare_id + assert message.venue.foursquare_type == foursquare_type + assert message.venue.google_place_id is None + assert message.venue.google_place_type is None + assert message.has_protected_content + + assert message2.venue + assert message2.venue.title == title + assert message2.venue.address == address + assert message2.venue.location.latitude == latitude + assert message2.venue.location.longitude == longitude + assert message2.venue.google_place_id == google_place_id + assert message2.venue.google_place_type == google_place_type + assert message2.venue.foursquare_id is None + assert message2.venue.foursquare_type is None + assert message2.has_protected_content + + async def test_send_contact(self, bot, chat_id): + phone_number = "+11234567890" + first_name = "Leandro" + last_name = "Toledo" + message = await bot.send_contact( + chat_id=chat_id, + phone_number=phone_number, + first_name=first_name, + last_name=last_name, + protect_content=True, + ) + + assert message.contact + assert message.contact.phone_number == phone_number + assert message.contact.first_name == first_name + 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", + [ + None, + InlineKeyboardMarkup.from_button( + InlineKeyboardButton(text="text", callback_data="data") + ), + InlineKeyboardMarkup.from_button( + InlineKeyboardButton(text="text", callback_data="data") + ).to_dict(), + ], + ) + async def test_send_and_stop_poll(self, bot, super_group_id, reply_markup): + question = "Is this a test?" + answers = ["Yes", "No", "Maybe"] + explanation = "[Here is a link](https://google.com)" + explanation_entities = [ + MessageEntity(MessageEntity.TEXT_LINK, 0, 14, url="https://google.com") + ] + + poll_task = asyncio.create_task( + bot.send_poll( + chat_id=super_group_id, + question=question, + options=answers, + is_anonymous=False, + allows_multiple_answers=True, + read_timeout=60, + protect_content=True, + ) + ) + quiz_task = asyncio.create_task( + bot.send_poll( + chat_id=super_group_id, + question=question, + options=answers, + type=Poll.QUIZ, + correct_option_id=2, + is_closed=True, + explanation=explanation, + explanation_parse_mode=ParseMode.MARKDOWN_V2, + ) + ) + + message = await poll_task + assert message.poll + assert message.poll.question == question + assert message.poll.options[0].text == answers[0] + assert message.poll.options[1].text == answers[1] + assert message.poll.options[2].text == answers[2] + assert not message.poll.is_anonymous + assert message.poll.allows_multiple_answers + assert not message.poll.is_closed + assert message.poll.type == Poll.REGULAR + assert message.has_protected_content + + # Since only the poll and not the complete message is returned, we can't check that the + # reply_markup is correct. So we just test that sending doesn't give an error. + poll = await bot.stop_poll( + chat_id=super_group_id, + message_id=message.message_id, + reply_markup=reply_markup, + read_timeout=60, + ) + assert isinstance(poll, Poll) + assert poll.is_closed + assert poll.options[0].text == answers[0] + assert poll.options[0].voter_count == 0 + assert poll.options[1].text == answers[1] + assert poll.options[1].voter_count == 0 + assert poll.options[2].text == answers[2] + assert poll.options[2].voter_count == 0 + assert poll.question == question + assert poll.total_voter_count == 0 + + message_quiz = await quiz_task + assert message_quiz.poll.correct_option_id == 2 + assert message_quiz.poll.type == Poll.QUIZ + assert message_quiz.poll.is_closed + assert message_quiz.poll.explanation == "Here is a link" + assert message_quiz.poll.explanation_entities == tuple(explanation_entities) + assert poll_task.done() and quiz_task.done() + + @pytest.mark.parametrize( + ["open_period", "close_date"], [(5, None), (None, True)], ids=["open_period", "close_date"] + ) + async def test_send_open_period(self, bot, super_group_id, open_period, close_date): + question = "Is this a test?" + answers = ["Yes", "No", "Maybe"] + reply_markup = InlineKeyboardMarkup.from_button( + InlineKeyboardButton(text="text", callback_data="data") + ) + + if close_date: + close_date = dtm.datetime.utcnow() + dtm.timedelta(seconds=5.05) + + message = await bot.send_poll( + chat_id=super_group_id, + question=question, + options=answers, + is_anonymous=False, + allows_multiple_answers=True, + read_timeout=60, + open_period=open_period, + close_date=close_date, + ) + await asyncio.sleep(5.1) + new_message = await bot.edit_message_reply_markup( + chat_id=super_group_id, + message_id=message.message_id, + reply_markup=reply_markup, + read_timeout=60, + ) + 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): + 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) + + 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) + + 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 + + async def test_send_poll_explanation_entities(self, bot, chat_id): + test_string = "Italic Bold Code" + entities = [ + MessageEntity(MessageEntity.ITALIC, 0, 6), + MessageEntity(MessageEntity.ITALIC, 7, 4), + MessageEntity(MessageEntity.ITALIC, 12, 4), + ] + message = await bot.send_poll( + chat_id, + "question", + options=["a", "b"], + correct_option_id=0, + type=Poll.QUIZ, + explanation=test_string, + explanation_entities=entities, + ) + + assert message.poll.explanation == test_string + assert message.poll.explanation_entities == tuple(entities) + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_poll_default_parse_mode(self, default_bot, super_group_id): + explanation = "Italic Bold Code" + explanation_markdown = "_Italic_ *Bold* `Code`" + question = "Is this a test?" + answers = ["Yes", "No", "Maybe"] + + tasks = asyncio.gather( + *( + default_bot.send_poll( + chat_id=super_group_id, + question=question, + options=answers, + type=Poll.QUIZ, + correct_option_id=2, + is_closed=True, + explanation=explanation_markdown, + **i, + ) + for i in ({}, {"explanation_parse_mode": None}, {"explanation_parse_mode": "HTML"}) + ), + ) + message1, message2, message3 = await tasks + assert message1.poll.explanation == explanation + assert message1.poll.explanation_entities == ( + MessageEntity(MessageEntity.ITALIC, 0, 6), + MessageEntity(MessageEntity.BOLD, 7, 4), + MessageEntity(MessageEntity.CODE, 12, 4), + ) + + assert message2.poll.explanation == explanation_markdown + assert message2.poll.explanation_entities == () + + assert message3.poll.explanation == explanation_markdown + assert message3.poll.explanation_entities == () + + @pytest.mark.parametrize( + "default_bot,custom", + [ + ({"allow_sending_without_reply": True}, None), + ({"allow_sending_without_reply": False}, None), + ({"allow_sending_without_reply": False}, True), + ], + indirect=["default_bot"], + ) + async def test_send_poll_default_allow_sending_without_reply( + self, default_bot, chat_id, custom + ): + question = "Is this a test?" + answers = ["Yes", "No", "Maybe"] + reply_to_message = await default_bot.send_message(chat_id, "test") + await reply_to_message.delete() + if custom is not None: + message = await default_bot.send_poll( + chat_id, + question=question, + options=answers, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = await default_bot.send_poll( + chat_id, + question=question, + options=answers, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match="message not found"): + await default_bot.send_poll( + chat_id, + question=question, + options=answers, + reply_to_message_id=reply_to_message.message_id, + ) + + @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) + async def test_send_poll_default_protect_content(self, chat_id, default_bot): + tasks = asyncio.gather( + default_bot.send_poll(chat_id, "Test", ["1", "2"]), + default_bot.send_poll(chat_id, "test", ["1", "2"], protect_content=False), + ) + protected_poll, unprotect_poll = await tasks + assert protected_poll.has_protected_content + assert not unprotect_poll.has_protected_content + + @pytest.mark.parametrize("emoji", Dice.ALL_EMOJI + [None]) + async def test_send_dice(self, bot, chat_id, emoji): + message = await bot.send_dice(chat_id, emoji=emoji, protect_content=True) + + assert message.dice + assert message.has_protected_content + if emoji is None: + assert message.dice.emoji == Dice.DICE + else: + assert message.dice.emoji == emoji + + @pytest.mark.parametrize( + "default_bot,custom", + [ + ({"allow_sending_without_reply": True}, None), + ({"allow_sending_without_reply": False}, None), + ({"allow_sending_without_reply": False}, True), + ], + indirect=["default_bot"], + ) + async def test_send_dice_default_allow_sending_without_reply( + self, default_bot, chat_id, custom + ): + reply_to_message = await default_bot.send_message(chat_id, "test") + await reply_to_message.delete() + if custom is not None: + message = await default_bot.send_dice( + chat_id, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = await default_bot.send_dice( + chat_id, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match="message not found"): + await default_bot.send_dice( + chat_id, reply_to_message_id=reply_to_message.message_id + ) + + @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) + async def test_send_dice_default_protect_content(self, chat_id, default_bot): + tasks = asyncio.gather( + default_bot.send_dice(chat_id), default_bot.send_dice(chat_id, protect_content=False) + ) + protected_dice, unprotected_dice = await tasks + assert protected_dice.has_protected_content + assert not unprotected_dice.has_protected_content + + @pytest.mark.parametrize("chat_action", list(ChatAction)) + async def test_send_chat_action(self, bot, chat_id, chat_action): + assert await bot.send_chat_action(chat_id, chat_action) + + async def test_wrong_chat_action(self, bot, chat_id): + with pytest.raises(BadRequest, match="Wrong parameter action"): + await bot.send_chat_action(chat_id, "unknown action") + + async def test_answer_inline_query_current_offset_error(self, bot, inline_results): + with pytest.raises(ValueError, match=("`current_offset` and `next_offset`")): + await bot.answer_inline_query( + 1234, results=inline_results, next_offset=42, current_offset=51 + ) + + async def test_get_user_profile_photos(self, bot, chat_id): + user_profile_photos = await bot.get_user_profile_photos(chat_id) + assert user_profile_photos.photos[0][0].file_size == 5403 + + async def test_get_one_user_profile_photo(self, bot, chat_id): + user_profile_photos = await bot.get_user_profile_photos(chat_id, offset=0, limit=1) + 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( + text="new_text", + chat_id=message.chat_id, + message_id=message.message_id, + parse_mode="HTML", + 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( + 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) + + @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`" + + 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, + ) + assert message.text_markdown == test_markdown_string + assert message.text == test_string + + message = 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) + + 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, + 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) + + @pytest.mark.skip(reason="need reference to an inline message") + async def test_edit_message_text_inline(self): + pass + + async def test_edit_message_caption(self, bot, media_message): + message = await bot.edit_message_caption( + caption="new_caption", + chat_id=media_message.chat_id, + message_id=media_message.message_id, + ) + + assert message.caption == "new_caption" + + async def test_edit_message_caption_entities(self, bot, media_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_caption( + caption=test_string, + chat_id=media_message.chat_id, + message_id=media_message.message_id, + caption_entities=entities, + ) + + assert message.caption == test_string + assert message.caption_entities == tuple(entities) + + # edit_message_media is tested in test_inputmedia + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + 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`" + + message = 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 + + message = 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) + + 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, + 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) + + async def test_edit_message_caption_with_parse_mode(self, bot, media_message): + message = await bot.edit_message_caption( + caption="new *caption*", + parse_mode="Markdown", + chat_id=media_message.chat_id, + message_id=media_message.message_id, + ) + + assert message.caption == "new caption" + + @pytest.mark.skip(reason="need reference to an inline message") + async def test_edit_message_caption_inline(self): + pass + + async def test_edit_reply_markup(self, bot, message): + new_markup = InlineKeyboardMarkup([[InlineKeyboardButton(text="test", callback_data="1")]]) + message = await bot.edit_message_reply_markup( + chat_id=message.chat_id, message_id=message.message_id, reply_markup=new_markup + ) + + assert message is not True + + @pytest.mark.skip(reason="need reference to an inline 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 + updates = await bot.get_updates(timeout=1) + + assert isinstance(updates, tuple) + if updates: + assert isinstance(updates[0], Update) + + @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"]) + async def test_set_webhook_get_webhook_info_and_delete_webhook(self, bot, use_ip, file_input): + url = "https://python-telegram-bot.org/test/webhook" + # Get the ip address of the website - dynamically just in case it ever changes + ip = socket.gethostbyname("python-telegram-bot.org") + max_connections = 7 + allowed_updates = ["message"] + file_input = ( + data_file("sslcert.pem").read_bytes() + if file_input == "bytes" + else data_file("sslcert.pem").open("rb") + ) + await bot.set_webhook( + url, + max_connections=max_connections, + allowed_updates=allowed_updates, + ip_address=ip if use_ip else None, + certificate=file_input if use_ip else None, + ) + + await asyncio.sleep(1) + live_info = await bot.get_webhook_info() + assert live_info.url == url + assert live_info.max_connections == max_connections + assert live_info.allowed_updates == tuple(allowed_updates) + assert live_info.ip_address == ip + assert live_info.has_custom_certificate == use_ip + + await bot.delete_webhook() + await asyncio.sleep(1) + info = await bot.get_webhook_info() + assert info.url == "" + assert info.ip_address is None + assert info.has_custom_certificate is False + + 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" + assert chat.title == f">>> telegram.Bot(test) @{bot.username}" + assert chat.id == int(super_group_id) + + async def test_get_chat_administrators(self, bot, channel_id): + admins = await bot.get_chat_administrators(channel_id) + assert isinstance(admins, tuple) + + for a in admins: + assert a.status in ("administrator", "creator") + + async def test_get_chat_member_count(self, bot, channel_id): + count = await bot.get_chat_member_count(channel_id) + assert isinstance(count, int) + assert count > 3 + + async def test_get_chat_member(self, bot, channel_id, chat_id): + chat_member = await bot.get_chat_member(channel_id, chat_id) + + assert chat_member.status == "administrator" + assert chat_member.user.first_name == "PTB" + assert chat_member.user.last_name == "Test user" + + @pytest.mark.skip(reason="Not implemented since we need a supergroup with many members") + async def test_set_chat_sticker_set(self): + pass + + @pytest.mark.skip(reason="Not implemented since we need a supergroup with many members") + async def test_delete_chat_sticker_set(self): + pass + + async def test_send_game(self, bot, chat_id): + game_short_name = "test_game" + message = await bot.send_game(chat_id, game_short_name, protect_content=True) + + assert message.game + assert message.game.description == ( + "A no-op test game, for python-telegram-bot bot framework testing." + ) + assert message.game.animation.file_id != "" + # We added some test bots later and for some reason the file size is not the same for them + # so we accept three different sizes here. Shouldn't be too much of + assert message.game.photo[0].file_size in [851, 4928, 850] + assert message.has_protected_content + + @pytest.mark.parametrize( + "default_bot,custom", + [ + ({"allow_sending_without_reply": True}, None), + ({"allow_sending_without_reply": False}, None), + ({"allow_sending_without_reply": False}, True), + ], + indirect=["default_bot"], + ) + async def test_send_game_default_allow_sending_without_reply( + self, default_bot, chat_id, custom + ): + game_short_name = "test_game" + reply_to_message = await default_bot.send_message(chat_id, "test") + await reply_to_message.delete() + if custom is not None: + message = await default_bot.send_game( + chat_id, + game_short_name, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = await default_bot.send_game( + chat_id, + game_short_name, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match="message not found"): + await default_bot.send_game( + chat_id, game_short_name, reply_to_message_id=reply_to_message.message_id + ) + + @pytest.mark.parametrize( + "default_bot,val", + [({"protect_content": True}, True), ({"protect_content": False}, None)], + indirect=["default_bot"], + ) + 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 + # First, test setting a score. + game_short_name = "test_game" + game = await bot.send_game(chat_id, game_short_name) + + message = await bot.set_game_score( + user_id=chat_id, + score=BASE_GAME_SCORE, # Score value is relevant for other set_game_score_* tests! + chat_id=game.chat_id, + message_id=game.message_id, + ) + + assert message.game.description == game.game.description + assert message.game.photo[0].file_size == game.game.photo[0].file_size + 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) + + score = BASE_GAME_SCORE + 1 + + message = await bot.set_game_score( + user_id=chat_id, + score=score, + chat_id=game.chat_id, + message_id=game.message_id, + disable_edit_message=True, + ) + + assert message.game.description == game.game.description + assert message.game.photo[0].file_size == game.game.photo[0].file_size + 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) + + score = BASE_GAME_SCORE # Even a score equal to previous raises an error. + + with pytest.raises(BadRequest, match="Bot_score_not_modified"): + await bot.set_game_score( + 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) + await asyncio.sleep(1.5) + + score = BASE_GAME_SCORE - 10 + + message = await bot.set_game_score( + user_id=chat_id, + score=score, + chat_id=game.chat_id, + message_id=game.message_id, + force=True, + ) + + assert message.game.description == game.game.description + assert message.game.photo[0].file_size == game.game.photo[0].file_size + assert message.game.animation.file_unique_id == game.game.animation.file_unique_id + + # For some reason the returned message doesn't contain the updated score. need to fetch + # the game again... (the service message is also absent when running the test suite) + 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) + high_scores = await bot.get_game_high_scores(chat_id, game.chat_id, game.message_id) + # We assume that the other game score tests ran within 20 sec + assert high_scores[0].score == BASE_GAME_SCORE - 10 + + # send_invoice and create_invoice_link is tested in test_invoice async def test_promote_chat_member(self, bot, channel_id, monkeypatch): # TODO: Add bot to supergroup so this can be tested properly / give bot perms with pytest.raises(BadRequest, match="Not enough rights"): @@ -2282,14 +2600,12 @@ class TestBot: can_manage_topics=12, ) - @pytest.mark.flaky(3, 1) async def test_export_chat_invite_link(self, bot, channel_id): # Each link is unique apparently invite_link = await bot.export_chat_invite_link(channel_id) assert isinstance(invite_link, str) assert invite_link != "" - @pytest.mark.flaky(3, 1) async def test_edit_revoke_chat_invite_link_passing_link_objects(self, bot, channel_id): invite_link = await bot.create_chat_invite_link(chat_id=channel_id) assert invite_link.name is None @@ -2307,7 +2623,6 @@ class TestBot: assert revoked_link.is_revoked is True assert revoked_link.name == "some_name" - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize("creates_join_request", [True, False]) @pytest.mark.parametrize("name", [None, "name"]) async def test_create_chat_invite_link_basics( @@ -2330,7 +2645,7 @@ class TestBot: ) assert revoked_link.is_revoked - @pytest.mark.flaky(3, 1) + @pytest.mark.skipif(not TEST_WITH_OPT_DEPS, reason="This test's implementation requires pytz") @pytest.mark.parametrize("datetime", argvalues=[True, False], ids=["datetime", "integer"]) async def test_advanced_chat_invite_links(self, bot, channel_id, datetime): # we are testing this all in one function in order to save api calls @@ -2338,7 +2653,7 @@ class TestBot: add_seconds = dtm.timedelta(0, 70) time_in_future = timestamp + add_seconds expire_time = time_in_future if datetime else to_timestamp(time_in_future) - aware_time_in_future = self.localize(time_in_future, UTC) + aware_time_in_future = UTC.localize(time_in_future) invite_link = await bot.create_chat_invite_link( channel_id, expire_date=expire_time, member_limit=10 @@ -2351,7 +2666,7 @@ class TestBot: add_seconds = dtm.timedelta(0, 80) time_in_future = timestamp + add_seconds expire_time = time_in_future if datetime else to_timestamp(time_in_future) - aware_time_in_future = self.localize(time_in_future, UTC) + aware_time_in_future = UTC.localize(time_in_future) edited_invite_link = await bot.edit_chat_invite_link( channel_id, @@ -2385,7 +2700,6 @@ class TestBot: assert revoked_invite_link.invite_link == invite_link.invite_link assert revoked_invite_link.is_revoked - @pytest.mark.flaky(3, 1) async def test_advanced_chat_invite_links_default_tzinfo(self, tz_bot, channel_id): # we are testing this all in one function in order to save api calls add_seconds = dtm.timedelta(0, 70) @@ -2434,7 +2748,6 @@ class TestBot: assert revoked_invite_link.invite_link == invite_link.invite_link assert revoked_invite_link.is_revoked - @pytest.mark.flaky(3, 1) async def test_approve_chat_join_request(self, bot, chat_id, channel_id): # TODO: Need incoming join request to properly test # Since we can't create join requests on the fly, we just tests the call to TG @@ -2442,7 +2755,6 @@ class TestBot: with pytest.raises(BadRequest, match="User_already_participant"): await bot.approve_chat_join_request(chat_id=channel_id, user_id=chat_id) - @pytest.mark.flaky(3, 1) async def test_decline_chat_join_request(self, bot, chat_id, channel_id): # TODO: Need incoming join request to properly test # Since we can't create join requests on the fly, we just tests the call to TG @@ -2453,7 +2765,6 @@ class TestBot: with pytest.raises(BadRequest, match="User_already_participant|Hide_requester_missing"): await bot.decline_chat_join_request(chat_id=channel_id, user_id=chat_id) - @pytest.mark.flaky(3, 1) async def test_set_chat_photo(self, bot, channel_id): async def func(): assert await bot.set_chat_photo(channel_id, f) @@ -2463,88 +2774,57 @@ class TestBot: func, "Type of file mismatch", "Telegram did not accept the file." ) - @pytest.mark.parametrize("local_mode", [True, False]) - async def test_set_chat_photo_local_files(self, monkeypatch, bot, chat_id, local_mode): - try: - bot._local_mode = local_mode - # For just test that the correct paths are passed as we have no local bot API set up - test_flag = False - file = data_file("telegram.jpg") - expected = file.as_uri() - - async def make_assertion(_, data, *args, **kwargs): - nonlocal test_flag - if local_mode: - test_flag = data.get("photo") == expected - else: - test_flag = isinstance(data.get("photo"), InputFile) - - monkeypatch.setattr(bot, "_post", make_assertion) - await bot.set_chat_photo(chat_id, file) - assert test_flag - finally: - bot._local_mode = False - - @pytest.mark.flaky(3, 1) async def test_delete_chat_photo(self, bot, channel_id): async def func(): assert await bot.delete_chat_photo(channel_id) await expect_bad_request(func, "Chat_not_modified", "Chat photo was not set.") - @pytest.mark.flaky(3, 1) async def test_set_chat_title(self, bot, channel_id): assert await bot.set_chat_title(channel_id, ">>> telegram.Bot() - Tests") - @pytest.mark.flaky(3, 1) async def test_set_chat_description(self, bot, channel_id): assert await bot.set_chat_description(channel_id, "Time: " + str(time.time())) - @pytest.mark.flaky(3, 1) async def test_pin_and_unpin_message(self, bot, super_group_id): - message1 = await bot.send_message(super_group_id, text="test_pin_message_1") - message2 = await bot.send_message(super_group_id, text="test_pin_message_2") - message3 = await bot.send_message(super_group_id, text="test_pin_message_3") + messages = [] # contains the Messages we sent + pinned_messages_tasks = set() # contains the asyncio.Tasks that pin the messages - assert await bot.pin_chat_message( - chat_id=super_group_id, - message_id=message1.message_id, - disable_notification=True, - read_timeout=10, - ) - await asyncio.sleep(1) + # Let's send 3 messages so we can pin them + awaitables = {bot.send_message(super_group_id, f"test_pin_message_{i}") for i in range(3)} - await bot.pin_chat_message( - chat_id=super_group_id, - message_id=message2.message_id, - disable_notification=True, - read_timeout=10, - ) - await bot.pin_chat_message( - chat_id=super_group_id, - message_id=message3.message_id, - disable_notification=True, - read_timeout=10, - ) - await asyncio.sleep(1) + # We will pin the messages immediately after sending them + for sending_msg in asyncio.as_completed(awaitables): # as_completed sends the messages + msg = await sending_msg + coro = bot.pin_chat_message(super_group_id, msg.message_id, True, read_timeout=10) + pinned_messages_tasks.add(asyncio.create_task(coro)) # start pinning the message + messages.append(msg) - chat = await bot.get_chat(super_group_id) - assert chat.pinned_message == message3 + assert len(messages) == 3 # Check if we sent 3 messages - assert await bot.unpin_chat_message( - super_group_id, - message_id=message2.message_id, - read_timeout=10, - ) - assert await bot.unpin_chat_message( - super_group_id, - read_timeout=10, - ) + assert all([await i for i in pinned_messages_tasks]) # Check if we pinned 3 messages + assert all([i.done() for i in pinned_messages_tasks]) # Check if all tasks are done - assert await bot.unpin_all_chat_messages( - super_group_id, - read_timeout=10, + chat = await bot.get_chat(super_group_id) # get the chat to check the pinned message + assert chat.pinned_message in messages + + # Determine which message is not the most recently pinned + for old_pin_msg in messages: + if chat.pinned_message != old_pin_msg: + break + + # Test unpinning our messages + tasks = asyncio.gather( + bot.unpin_chat_message( # unpins any message except the most recent + chat_id=super_group_id, # because we don't want to accidentally unpin the same msg + message_id=old_pin_msg.message_id, # twice + read_timeout=10, + ), + bot.unpin_chat_message(chat_id=super_group_id, read_timeout=10), # unpins most recent ) + assert all(await tasks) + assert all([i.done() for i in tasks]) + assert await bot.unpin_all_chat_messages(super_group_id, read_timeout=10) # get_sticker_set, upload_sticker_file, create_new_sticker_set, add_sticker_to_set, # set_sticker_position_in_set, delete_sticker_from_set and get_custom_emoji_stickers @@ -2553,53 +2833,6 @@ class TestBot: # get_forum_topic_icon_stickers, edit_forum_topic, general_forum etc... # are tested in the test_forum module. - async def test_timeout_propagation_explicit(self, monkeypatch, bot, chat_id): - # Use BaseException that's not a subclass of Exception such that - # OkException should not be caught anywhere - class OkException(BaseException): - pass - - timeout = 42 - - async def do_request(*args, **kwargs): - obj = kwargs.get("read_timeout") - if obj == timeout: - raise OkException - - return 200, b'{"ok": true, "result": []}' - - monkeypatch.setattr(bot.request, "do_request", do_request) - - # Test file uploading - with pytest.raises(OkException): - await bot.send_photo( - chat_id, data_file("telegram.jpg").open("rb"), read_timeout=timeout - ) - - # Test JSON submission - with pytest.raises(OkException): - await bot.get_chat_administrators(chat_id, read_timeout=timeout) - - async def test_timeout_propagation_implicit(self, monkeypatch, bot, chat_id): - # Use BaseException that's not a subclass of Exception such that - # OkException should not be caught anywhere - class OkException(BaseException): - pass - - async def do_request(*args, **kwargs): - obj = kwargs.get("write_timeout") - if obj == 20: - raise OkException - - return 200, b'{"ok": true, "result": []}' - - monkeypatch.setattr(bot.request, "do_request", do_request) - - # Test file uploading - with pytest.raises(OkException): - await bot.send_photo(chat_id, data_file("telegram.jpg").open("rb")) - - @pytest.mark.flaky(3, 1) async def test_send_message_entities(self, bot, chat_id): test_string = "Italic Bold Code Spoiler" entities = [ @@ -2612,34 +2845,37 @@ class TestBot: assert message.text == test_string assert message.entities == tuple(entities) - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) async def test_send_message_default_parse_mode(self, default_bot, chat_id): test_string = "Italic Bold Code" test_markdown_string = "_Italic_ *Bold* `Code`" - message = await default_bot.send_message(chat_id, test_markdown_string) - assert message.text_markdown == test_markdown_string - assert message.text == test_string + tasks = asyncio.gather( + *( + default_bot.send_message(chat_id, test_markdown_string, **i) + for i in ({}, {"parse_mode": None}, {"parse_mode": "HTML"}) + ) + ) + msg1, msg2, msg3 = await tasks + assert msg1.text_markdown == test_markdown_string + assert msg1.text == test_string - message = await default_bot.send_message(chat_id, test_markdown_string, parse_mode=None) - assert message.text == test_markdown_string - assert message.text_markdown == escape_markdown(test_markdown_string) + assert msg2.text == test_markdown_string + assert msg2.text_markdown == escape_markdown(test_markdown_string) - message = await default_bot.send_message(chat_id, test_markdown_string, parse_mode="HTML") - assert message.text == test_markdown_string - assert message.text_markdown == escape_markdown(test_markdown_string) + assert msg3.text == test_markdown_string + assert msg3.text_markdown == escape_markdown(test_markdown_string) - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) async def test_send_message_default_protect_content(self, default_bot, chat_id): - to_check = await default_bot.send_message(chat_id, "test") + tasks = asyncio.gather( + default_bot.send_message(chat_id, "test"), + default_bot.send_message(chat_id, "test", protect_content=False), + ) + to_check, no_protect = await tasks assert to_check.has_protected_content - - no_protect = await default_bot.send_message(chat_id, "test", protect_content=False) assert not no_protect.has_protected_content - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize( "default_bot,custom", [ @@ -2673,17 +2909,16 @@ class TestBot: chat_id, "test", reply_to_message_id=reply_to_message.message_id ) - @pytest.mark.asyncio async def test_get_set_my_default_administrator_rights(self, bot): # Test that my default administrator rights for group are as all False - await bot.set_my_default_administrator_rights() + assert await bot.set_my_default_administrator_rights() # clear any set rights my_admin_rights_grp = await bot.get_my_default_administrator_rights() assert isinstance(my_admin_rights_grp, ChatAdministratorRights) assert all(not getattr(my_admin_rights_grp, at) for at in my_admin_rights_grp.__slots__) # Test setting my default admin rights for channel my_rights = ChatAdministratorRights.all_rights() - await bot.set_my_default_administrator_rights(my_rights, for_channels=True) + assert await bot.set_my_default_administrator_rights(my_rights, for_channels=True) my_admin_rights_ch = await bot.get_my_default_administrator_rights(for_channels=True) assert my_admin_rights_ch.can_invite_users is my_rights.can_invite_users # tg bug? is_anonymous is False despite setting it True for channels: @@ -2699,7 +2934,6 @@ class TestBot: assert my_admin_rights_ch.can_pin_messages is None # Not returned for channels assert my_admin_rights_ch.can_manage_topics is None # Not returned for channels - @pytest.mark.asyncio async def test_get_set_chat_menu_button(self, bot, chat_id): # Test our chat menu button is commands- menu_button = await bot.get_chat_menu_button() @@ -2709,21 +2943,20 @@ class TestBot: # Test setting our chat menu button to Webapp. my_menu = MenuButtonWebApp("click me!", WebAppInfo("https://telegram.org/")) - await bot.set_chat_menu_button(chat_id=chat_id, menu_button=my_menu) + assert await bot.set_chat_menu_button(chat_id=chat_id, menu_button=my_menu) menu_button = await bot.get_chat_menu_button(chat_id) assert isinstance(menu_button, MenuButtonWebApp) assert menu_button.type == MenuButtonType.WEB_APP assert menu_button.text == my_menu.text assert menu_button.web_app.url == my_menu.web_app.url - await bot.set_chat_menu_button(chat_id=chat_id, menu_button=MenuButtonDefault()) + assert await bot.set_chat_menu_button(chat_id=chat_id, menu_button=MenuButtonDefault()) menu_button = await bot.get_chat_menu_button(chat_id=chat_id) assert isinstance(menu_button, MenuButtonDefault) - @pytest.mark.flaky(3, 1) async def test_set_and_get_my_commands(self, bot): commands = [BotCommand("cmd1", "descr1"), ["cmd2", "descr2"]] - await bot.set_my_commands([]) + assert await bot.set_my_commands([]) assert await bot.get_my_commands() == () assert await bot.set_my_commands(commands) @@ -2731,7 +2964,6 @@ class TestBot: assert bc.command == f"cmd{i+1}" assert bc.description == f"descr{i+1}" - @pytest.mark.flaky(3, 1) async def test_get_set_delete_my_commands_with_scope(self, bot, super_group_id, chat_id): group_cmds = [BotCommand("group_cmd", "visible to this supergroup only")] private_cmds = [BotCommand("private_cmd", "visible to this private chat only")] @@ -2739,26 +2971,32 @@ class TestBot: private_scope = BotCommandScopeChat(chat_id) # Set supergroup command list with lang code and check if the same can be returned from api - await bot.set_my_commands(group_cmds, scope=group_scope, language_code="en") + assert await bot.set_my_commands(group_cmds, scope=group_scope, language_code="en") gotten_group_cmds = await bot.get_my_commands(scope=group_scope, language_code="en") assert len(gotten_group_cmds) == len(group_cmds) assert gotten_group_cmds[0].command == group_cmds[0].command # Set private command list and check if same can be returned from the api - await bot.set_my_commands(private_cmds, scope=private_scope) + assert await bot.set_my_commands(private_cmds, scope=private_scope) gotten_private_cmd = await bot.get_my_commands(scope=private_scope) assert len(gotten_private_cmd) == len(private_cmds) assert gotten_private_cmd[0].command == private_cmds[0].command # Delete command list from that supergroup and private chat- - await bot.delete_my_commands(private_scope) - await bot.delete_my_commands(group_scope, "en") + tasks = asyncio.gather( + bot.delete_my_commands(private_scope), + bot.delete_my_commands(group_scope, "en"), + ) + assert all(await tasks) # Check if its been deleted- - deleted_priv_cmds = await bot.get_my_commands(scope=private_scope) - deleted_grp_cmds = await bot.get_my_commands(scope=group_scope, language_code="en") + tasks = asyncio.gather( + bot.get_my_commands(private_scope), + bot.get_my_commands(group_scope, "en"), + ) + deleted_priv_cmds, deleted_grp_cmds = await tasks assert len(deleted_grp_cmds) == 0 == len(group_cmds) - 1 assert len(deleted_priv_cmds) == 0 == len(private_cmds) - 1 @@ -2766,73 +3004,6 @@ class TestBot: await bot.delete_my_commands() # Delete commands from default scope assert len(await bot.get_my_commands()) == 0 - async def test_log_out(self, monkeypatch, bot): - # We don't actually make a request as to not break the test setup - async def assertion(url, request_data: RequestData, *args, **kwargs): - return request_data.json_parameters == {} and url.split("/")[-1] == "logOut" - - monkeypatch.setattr(bot.request, "post", assertion) - - assert await bot.log_out() - - async def test_close(self, monkeypatch, bot): - # We don't actually make a request as to not break the test setup - async def assertion(url, request_data: RequestData, *args, **kwargs): - return request_data.json_parameters == {} and url.split("/")[-1] == "close" - - monkeypatch.setattr(bot.request, "post", assertion) - - assert await bot.close() - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("json_keyboard", [True, False]) - @pytest.mark.parametrize("caption", ["Test", "", None]) - async def test_copy_message( - self, monkeypatch, bot, chat_id, media_message, json_keyboard, caption - ): - keyboard = InlineKeyboardMarkup( - [[InlineKeyboardButton(text="test", callback_data="test2")]] - ) - - async def post(url, request_data: RequestData, *args, **kwargs): - data = request_data.parameters - if not all( - [ - data["chat_id"] == chat_id, - data["from_chat_id"] == chat_id, - data["message_id"] == media_message.message_id, - data.get("caption") == caption, - data["parse_mode"] == ParseMode.HTML, - data["reply_to_message_id"] == media_message.message_id, - data["reply_markup"] == keyboard.to_json() - if json_keyboard - else keyboard.to_dict(), - data["disable_notification"] is True, - data["caption_entities"] - == [MessageEntity(MessageEntity.BOLD, 0, 4).to_dict()], - data["protect_content"] is True, - data["message_thread_id"] == 1, - ] - ): - pytest.fail("I got wrong parameters in post") - return data - - monkeypatch.setattr(bot.request, "post", post) - await bot.copy_message( - chat_id, - from_chat_id=chat_id, - message_id=media_message.message_id, - caption=caption, - caption_entities=[MessageEntity(MessageEntity.BOLD, 0, 4)], - parse_mode=ParseMode.HTML, - reply_to_message_id=media_message.message_id, - reply_markup=keyboard.to_json() if json_keyboard else keyboard, - disable_notification=True, - protect_content=True, - message_thread_id=1, - ) - - @pytest.mark.flaky(3, 1) async def test_copy_message_without_reply(self, bot, chat_id, media_message): keyboard = InlineKeyboardMarkup( [[InlineKeyboardButton(text="test", callback_data="test2")]] @@ -2858,7 +3029,6 @@ class TestBot: assert len(message.caption_entities) == 1 assert message.reply_markup == keyboard - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize( "default_bot", [ @@ -2899,6 +3069,7 @@ class TestBot: else: assert len(message.caption_entities) == 0 + # Continue testing arbitrary callback data here with actual requests: async def test_replace_callback_data_send_message(self, cdc_bot, chat_id): bot = cdc_bot @@ -2989,63 +3160,7 @@ class TestBot: bot.callback_data_cache.clear_callback_data() bot.callback_data_cache.clear_callback_queries() - # TODO: Needs improvement. We need incoming inline query to test answer. - async def test_replace_callback_data_answer_inline_query(self, monkeypatch, cdc_bot, chat_id): - bot = cdc_bot - - # For now just test that our internals pass the correct data - async def make_assertion( - endpoint, - data=None, - *args, - **kwargs, - ): - inline_keyboard = data["results"][0]["reply_markup"].inline_keyboard - assertion_1 = inline_keyboard[0][1] == no_replace_button - assertion_2 = inline_keyboard[0][0] != replace_button - keyboard, button = ( - inline_keyboard[0][0].callback_data[:32], - inline_keyboard[0][0].callback_data[32:], - ) - assertion_3 = ( - bot.callback_data_cache._keyboard_data[keyboard].button_data[button] - == "replace_test" - ) - assertion_4 = data["results"][1].reply_markup is None - return assertion_1 and assertion_2 and assertion_3 and assertion_4 - - try: - replace_button = InlineKeyboardButton(text="replace", callback_data="replace_test") - no_replace_button = InlineKeyboardButton( - text="no_replace", url="http://python-telegram-bot.org/" - ) - reply_markup = InlineKeyboardMarkup.from_row( - [ - replace_button, - no_replace_button, - ] - ) - - bot.username # call this here so `bot.get_me()` won't be called after mocking - monkeypatch.setattr(bot, "_post", make_assertion) - results = [ - InlineQueryResultArticle( - "11", "first", InputTextMessageContent("first"), reply_markup=reply_markup - ), - InlineQueryResultVoice( - "22", - "https://python-telegram-bot.org/static/testfiles/telegram.ogg", - title="second", - ), - ] - - assert await bot.answer_inline_query(chat_id, results=results) - - finally: - bot.callback_data_cache.clear_callback_data() - bot.callback_data_cache.clear_callback_queries() - - async def test_get_chat_arbitrary_callback_data(self, super_group_id, cdc_bot): + async def test_get_chat_arbitrary_callback_data(self, channel_id, cdc_bot): bot = cdc_bot try: @@ -3054,7 +3169,7 @@ class TestBot: ) message = await bot.send_message( - super_group_id, text="get_chat_arbitrary_callback_data", reply_markup=reply_markup + channel_id, text="get_chat_arbitrary_callback_data", reply_markup=reply_markup ) await message.pin() @@ -3062,110 +3177,10 @@ class TestBot: data = list(bot.callback_data_cache._keyboard_data[keyboard].button_data.values())[0] assert data == "callback_data" - chat = await bot.get_chat(super_group_id) + chat = await bot.get_chat(channel_id) assert chat.pinned_message == message assert chat.pinned_message.reply_markup == reply_markup - finally: - bot.callback_data_cache.clear_callback_data() - bot.callback_data_cache.clear_callback_queries() - await bot.unpin_all_chat_messages(super_group_id) - - # In the following tests we check that get_updates inserts callback data correctly if necessary - # The same must be done in the webhook updater. This is tested over at test_updater.py, but - # here we test more extensively. - - async def test_arbitrary_callback_data_no_insert(self, monkeypatch, cdc_bot): - """Updates that don't need insertion shouldn't fail obviously""" - bot = cdc_bot - - async def post(*args, **kwargs): - update = Update( - 17, - poll=Poll( - "42", - "question", - options=[PollOption("option", 0)], - total_voter_count=0, - is_closed=False, - is_anonymous=True, - type=Poll.REGULAR, - allows_multiple_answers=False, - ), - ) - return [update.to_dict()] - - try: - monkeypatch.setattr(BaseRequest, "post", post) - await bot.delete_webhook() # make sure there is no webhook set if webhook tests failed - updates = await bot.get_updates(timeout=1) - - assert len(updates) == 1 - assert updates[0].update_id == 17 - assert updates[0].poll.id == "42" - finally: - bot.callback_data_cache.clear_callback_data() - bot.callback_data_cache.clear_callback_queries() - - @pytest.mark.parametrize( - "message_type", ["channel_post", "edited_channel_post", "message", "edited_message"] - ) - async def test_arbitrary_callback_data_pinned_message_reply_to_message( - self, super_group_id, cdc_bot, monkeypatch, message_type - ): - bot = cdc_bot - - reply_markup = InlineKeyboardMarkup.from_button( - InlineKeyboardButton(text="text", callback_data="callback_data") - ) - - message = Message( - 1, - dtm.datetime.utcnow(), - None, - reply_markup=bot.callback_data_cache.process_keyboard(reply_markup), - ) - message._unfreeze() - # We do to_dict -> de_json to make sure those aren't the same objects - message.pinned_message = Message.de_json(message.to_dict(), bot) - - async def post(*args, **kwargs): - update = Update( - 17, - **{ - message_type: Message( - 1, - dtm.datetime.utcnow(), - None, - pinned_message=message, - reply_to_message=Message.de_json(message.to_dict(), bot), - ) - }, - ) - return [update.to_dict()] - - try: - monkeypatch.setattr(BaseRequest, "post", post) - await bot.delete_webhook() # make sure there is no webhook set if webhook tests failed - updates = await bot.get_updates(timeout=1) - - assert isinstance(updates, tuple) - assert len(updates) == 1 - - effective_message = updates[0][message_type] - assert ( - effective_message.reply_to_message.reply_markup.inline_keyboard[0][0].callback_data - == "callback_data" - ) - assert ( - effective_message.pinned_message.reply_markup.inline_keyboard[0][0].callback_data - == "callback_data" - ) - - pinned_message = effective_message.reply_to_message.pinned_message - assert ( - pinned_message.reply_markup.inline_keyboard[0][0].callback_data == "callback_data" - ) - + assert await message.unpin() # (not placed in finally block since msg can be unbound) finally: bot.callback_data_cache.clear_callback_data() bot.callback_data_cache.clear_callback_queries() @@ -3185,92 +3200,3 @@ class TestBot: finally: bot.callback_data_cache.clear_callback_data() bot.callback_data_cache.clear_callback_queries() - - @pytest.mark.parametrize( - "message_type", ["channel_post", "edited_channel_post", "message", "edited_message"] - ) - @pytest.mark.parametrize("self_sender", [True, False]) - async def test_arbitrary_callback_data_via_bot( - self, super_group_id, cdc_bot, monkeypatch, self_sender, message_type - ): - bot = cdc_bot - reply_markup = InlineKeyboardMarkup.from_button( - InlineKeyboardButton(text="text", callback_data="callback_data") - ) - - reply_markup = bot.callback_data_cache.process_keyboard(reply_markup) - message = Message( - 1, - dtm.datetime.utcnow(), - None, - reply_markup=reply_markup, - via_bot=bot.bot if self_sender else User(1, "first", False), - ) - - async def post(*args, **kwargs): - return [Update(17, **{message_type: message}).to_dict()] - - try: - monkeypatch.setattr(BaseRequest, "post", post) - await bot.delete_webhook() # make sure there is no webhook set if webhook tests failed - updates = await bot.get_updates(timeout=1) - - assert isinstance(updates, tuple) - assert len(updates) == 1 - - message = updates[0][message_type] - if self_sender: - assert message.reply_markup.inline_keyboard[0][0].callback_data == "callback_data" - else: - assert ( - message.reply_markup.inline_keyboard[0][0].callback_data - == reply_markup.inline_keyboard[0][0].callback_data - ) - finally: - bot.callback_data_cache.clear_callback_data() - bot.callback_data_cache.clear_callback_queries() - - @bot_methods() - def test_camel_case_aliases(self, bot_class, bot_method_name, bot_method): - camel_case_name = to_camel_case(bot_method_name) - camel_case_function = getattr(bot_class, camel_case_name, False) - assert camel_case_function is not False, f"{camel_case_name} not found" - assert camel_case_function is bot_method, f"{camel_case_name} is not {bot_method}" - - @bot_methods() - def test_coroutine_functions(self, bot_class, bot_method_name, bot_method): - """Check that all bot methods are defined as async def ...""" - # not islower() skips the camelcase aliases - if not bot_method_name.islower(): - return - # unfortunately `inspect.iscoroutinefunction` doesn't do the trick directly, - # as the @_log decorator interferes - source = "".join(inspect.getsourcelines(bot_method)[0]) - assert ( - f"async def {bot_method_name}" in source - ), f"{bot_method_name} should be a coroutine function" - - @bot_methods() - def test_api_kwargs_and_timeouts_present(self, bot_class, bot_method_name, bot_method): - """Check that all bot methods have `api_kwargs` and timeout params.""" - param_names = inspect.signature(bot_method).parameters.keys() - assert ( - "pool_timeout" in param_names - ), f"{bot_method_name} is missing the parameter `pool_timeout`" - assert ( - "read_timeout" in param_names - ), f"{bot_method_name} is missing the parameter `read_timeout`" - assert ( - "connect_timeout" in param_names - ), f"{bot_method_name} is missing the parameter `connect_timeout`" - assert ( - "write_timeout" in param_names - ), f"{bot_method_name} is missing the parameter `write_timeout`" - assert ( - "api_kwargs" in param_names - ), f"{bot_method_name} is missing the parameter `api_kwargs`" - - if bot_class is ExtBot and bot_method_name.replace("_", "").lower() != "getupdates": - assert ( - "rate_limit_args" in param_names - ), f"{bot_method_name} of ExtBot is missing the parameter `rate_limit_args`" diff --git a/tests/test_botcommand.py b/tests/test_botcommand.py index e35441037..80b529042 100644 --- a/tests/test_botcommand.py +++ b/tests/test_botcommand.py @@ -22,12 +22,12 @@ import pytest from telegram import BotCommand, Dice -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def bot_command(): return BotCommand(command="start", description="A command") -class TestBotCommand: +class TestBotCommandWithoutRequest: command = "start" description = "A command" diff --git a/tests/test_botcommandscope.py b/tests/test_botcommandscope.py index 8843bc6f9..78472f076 100644 --- a/tests/test_botcommandscope.py +++ b/tests/test_botcommandscope.py @@ -33,7 +33,7 @@ from telegram import ( ) -@pytest.fixture(scope="class", params=["str", "int"]) +@pytest.fixture(scope="module", params=["str", "int"]) def chat_id(request): if request.param == "str": return "@supergroupusername" @@ -57,7 +57,7 @@ def scope_type(request): @pytest.fixture( - scope="class", + scope="module", params=[ BotCommandScopeDefault, BotCommandScopeAllPrivateChats, @@ -82,7 +82,7 @@ def scope_class(request): @pytest.fixture( - scope="class", + scope="module", params=[ (BotCommandScopeDefault, BotCommandScope.DEFAULT), (BotCommandScopeAllPrivateChats, BotCommandScope.ALL_PRIVATE_CHATS), @@ -106,7 +106,7 @@ def scope_class_and_type(request): return request.param -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def bot_command_scope(scope_class_and_type, chat_id): # we use de_json here so that we don't have to worry about which class needs which arguments return scope_class_and_type[0].de_json( @@ -115,7 +115,7 @@ def bot_command_scope(scope_class_and_type, chat_id): # All the scope types are very similar, so we test everything via parametrization -class TestBotCommandScope: +class TestBotCommandScopeWithoutRequest: def test_slot_behaviour(self, bot_command_scope, mro_slots): for attr in bot_command_scope.__slots__: assert getattr(bot_command_scope, attr, "err") != "err", f"got extra slot '{attr}'" diff --git a/tests/test_callbackdatacache.py b/tests/test_callbackdatacache.py index 9b9981f4f..d2bfc4f38 100644 --- a/tests/test_callbackdatacache.py +++ b/tests/test_callbackdatacache.py @@ -16,7 +16,6 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. -import os import time from copy import deepcopy from datetime import datetime @@ -28,7 +27,7 @@ from telegram import CallbackQuery, Chat, InlineKeyboardButton, InlineKeyboardMa from telegram._utils.datetime import UTC from telegram.ext import ExtBot from telegram.ext._callbackdatacache import CallbackDataCache, InvalidCallbackData, _KeyboardData -from tests.auxil.object_conversions import env_var_2_bool +from tests.conftest import TEST_WITH_OPT_DEPS @pytest.fixture(scope="function") @@ -36,9 +35,6 @@ def callback_data_cache(bot): return CallbackDataCache(bot) -TEST_WITH_OPT_DEPS = env_var_2_bool(os.getenv("TEST_WITH_OPT_DEPS", True)) - - @pytest.mark.skipif( TEST_WITH_OPT_DEPS, reason="Only relevant if the optional dependency is not installed", diff --git a/tests/test_callbackquery.py b/tests/test_callbackquery.py index c97f1e276..f13b6c6f3 100644 --- a/tests/test_callbackquery.py +++ b/tests/test_callbackquery.py @@ -32,23 +32,23 @@ from tests.auxil.bot_method_checks import ( @pytest.fixture(scope="function", params=["message", "inline"]) def callback_query(bot, request): cbq = CallbackQuery( - TestCallbackQuery.id_, - TestCallbackQuery.from_user, - TestCallbackQuery.chat_instance, - data=TestCallbackQuery.data, - game_short_name=TestCallbackQuery.game_short_name, + TestCallbackQueryBase.id_, + TestCallbackQueryBase.from_user, + TestCallbackQueryBase.chat_instance, + data=TestCallbackQueryBase.data, + game_short_name=TestCallbackQueryBase.game_short_name, ) cbq.set_bot(bot) cbq._unfreeze() if request.param == "message": - cbq.message = TestCallbackQuery.message + cbq.message = TestCallbackQueryBase.message cbq.message.set_bot(bot) else: - cbq.inline_message_id = TestCallbackQuery.inline_message_id + cbq.inline_message_id = TestCallbackQueryBase.inline_message_id return cbq -class TestCallbackQuery: +class TestCallbackQueryBase: id_ = "id" from_user = User(1, "test_user", False) chat_instance = "chat_instance" @@ -57,6 +57,8 @@ class TestCallbackQuery: inline_message_id = "inline_message_id" game_short_name = "the_game" + +class TestCallbackQueryWithoutRequest(TestCallbackQueryBase): @staticmethod def skip_params(callback_query: CallbackQuery): if callback_query.inline_message_id: @@ -121,6 +123,26 @@ class TestCallbackQuery: assert callback_query_dict["data"] == callback_query.data assert callback_query_dict["game_short_name"] == callback_query.game_short_name + def test_equality(self): + a = CallbackQuery(self.id_, self.from_user, "chat") + b = CallbackQuery(self.id_, self.from_user, "chat") + c = CallbackQuery(self.id_, None, "") + d = CallbackQuery("", None, "chat") + e = Audio(self.id_, "unique_id", 1) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a == c + assert hash(a) == hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) + async def test_answer(self, monkeypatch, callback_query): async def make_assertion(*_, **kwargs): return kwargs["callback_query_id"] == callback_query.id @@ -447,23 +469,3 @@ class TestCallbackQuery: monkeypatch.setattr(callback_query.get_bot(), "copy_message", make_assertion) assert await callback_query.copy_message(1) - - def test_equality(self): - a = CallbackQuery(self.id_, self.from_user, "chat") - b = CallbackQuery(self.id_, self.from_user, "chat") - c = CallbackQuery(self.id_, None, "") - d = CallbackQuery("", None, "chat") - e = Audio(self.id_, "unique_id", 1) - - assert a == b - assert hash(a) == hash(b) - assert a is not b - - assert a == c - assert hash(a) == hash(c) - - assert a != d - assert hash(a) != hash(d) - - assert a != e - assert hash(a) != hash(e) diff --git a/tests/test_chat.py b/tests/test_chat.py index f32cc60b4..e60ea3ea1 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -29,37 +29,37 @@ from tests.auxil.bot_method_checks import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def chat(bot): chat = Chat( - TestChat.id_, - title=TestChat.title, - type=TestChat.type_, - username=TestChat.username, - sticker_set_name=TestChat.sticker_set_name, - can_set_sticker_set=TestChat.can_set_sticker_set, - permissions=TestChat.permissions, - slow_mode_delay=TestChat.slow_mode_delay, - bio=TestChat.bio, - linked_chat_id=TestChat.linked_chat_id, - location=TestChat.location, + TestChatBase.id_, + title=TestChatBase.title, + type=TestChatBase.type_, + username=TestChatBase.username, + sticker_set_name=TestChatBase.sticker_set_name, + can_set_sticker_set=TestChatBase.can_set_sticker_set, + permissions=TestChatBase.permissions, + slow_mode_delay=TestChatBase.slow_mode_delay, + bio=TestChatBase.bio, + linked_chat_id=TestChatBase.linked_chat_id, + location=TestChatBase.location, has_private_forwards=True, has_protected_content=True, join_to_send_messages=True, join_by_request=True, has_restricted_voice_and_video_messages=True, is_forum=True, - active_usernames=TestChat.active_usernames, - emoji_status_custom_emoji_id=TestChat.emoji_status_custom_emoji_id, - has_aggressive_anti_spam_enabled=TestChat.has_aggressive_anti_spam_enabled, - has_hidden_members=TestChat.has_hidden_members, + active_usernames=TestChatBase.active_usernames, + emoji_status_custom_emoji_id=TestChatBase.emoji_status_custom_emoji_id, + has_aggressive_anti_spam_enabled=TestChatBase.has_aggressive_anti_spam_enabled, + has_hidden_members=TestChatBase.has_hidden_members, ) chat.set_bot(bot) chat._unfreeze() return chat -class TestChat: +class TestChatBase: id_ = -28767330 title = "ToledosPalaceBot - Group" type_ = "group" @@ -87,6 +87,8 @@ class TestChat: has_aggressive_anti_spam_enabled = True has_hidden_members = True + +class TestChatWithoutRequest(TestChatBase): def test_slot_behaviour(self, chat, mro_slots): for attr in chat.__slots__: assert getattr(chat, attr, "err") != "err", f"got extra slot '{attr}'" @@ -194,6 +196,26 @@ class TestChat: chat = Chat(id=1, type="private") assert chat.type is ChatType.PRIVATE + def test_equality(self): + a = Chat(self.id_, self.title, self.type_) + b = Chat(self.id_, self.title, self.type_) + c = Chat(self.id_, "", "") + d = Chat(0, self.title, self.type_) + e = User(self.id_, "", False) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a == c + assert hash(a) == hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) + def test_link(self, chat): assert chat.link == f"https://t.me/{chat.username}" chat.username = None @@ -234,7 +256,6 @@ class TestChat: monkeypatch.setattr(chat.get_bot(), "send_chat_action", make_assertion) assert await chat.send_action(action=ChatAction.TYPING) - assert await chat.send_action(action=ChatAction.TYPING) async def test_leave(self, monkeypatch, chat): async def make_assertion(*_, **kwargs): @@ -1251,23 +1272,3 @@ class TestChat: ): chat = Chat(id=1, type="foo", username="user\u2022name") chat.mention_markdown_v2() - - def test_equality(self): - a = Chat(self.id_, self.title, self.type_) - b = Chat(self.id_, self.title, self.type_) - c = Chat(self.id_, "", "") - d = Chat(0, self.title, self.type_) - e = User(self.id_, "", False) - - assert a == b - assert hash(a) == hash(b) - assert a is not b - - assert a == c - assert hash(a) == hash(c) - - assert a != d - assert hash(a) != hash(d) - - assert a != e - assert hash(a) != hash(e) diff --git a/tests/test_chatadministratorrights.py b/tests/test_chatadministratorrights.py index f6044248e..310fe9f4e 100644 --- a/tests/test_chatadministratorrights.py +++ b/tests/test_chatadministratorrights.py @@ -21,7 +21,7 @@ import pytest from telegram import ChatAdministratorRights -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def chat_admin_rights(): return ChatAdministratorRights( can_change_info=True, @@ -39,7 +39,7 @@ def chat_admin_rights(): ) -class TestChatAdministratorRights: +class TestChatAdministratorRightsWithoutRequest: def test_slot_behaviour(self, chat_admin_rights, mro_slots): inst = chat_admin_rights for attr in inst.__slots__: diff --git a/tests/test_chatinvitelink.py b/tests/test_chatinvitelink.py index 276b3856f..66d9ed4dd 100644 --- a/tests/test_chatinvitelink.py +++ b/tests/test_chatinvitelink.py @@ -24,27 +24,27 @@ from telegram import ChatInviteLink, User from telegram._utils.datetime import to_timestamp -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def creator(): return User(1, "First name", False) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def invite_link(creator): return ChatInviteLink( - TestChatInviteLink.link, + TestChatInviteLinkBase.link, creator, - TestChatInviteLink.creates_join_request, - TestChatInviteLink.primary, - TestChatInviteLink.revoked, - expire_date=TestChatInviteLink.expire_date, - member_limit=TestChatInviteLink.member_limit, - name=TestChatInviteLink.name, - pending_join_request_count=TestChatInviteLink.pending_join_request_count, + TestChatInviteLinkBase.creates_join_request, + TestChatInviteLinkBase.primary, + TestChatInviteLinkBase.revoked, + expire_date=TestChatInviteLinkBase.expire_date, + member_limit=TestChatInviteLinkBase.member_limit, + name=TestChatInviteLinkBase.name, + pending_join_request_count=TestChatInviteLinkBase.pending_join_request_count, ) -class TestChatInviteLink: +class TestChatInviteLinkBase: link = "thisialink" creates_join_request = False primary = True @@ -54,6 +54,8 @@ class TestChatInviteLink: name = "LinkName" pending_join_request_count = 42 + +class TestChatInviteLinkWithoutRequest(TestChatInviteLinkBase): def test_slot_behaviour(self, mro_slots, invite_link): for attr in invite_link.__slots__: assert getattr(invite_link, attr, "err") != "err", f"got extra slot '{attr}'" diff --git a/tests/test_chatjoinrequest.py b/tests/test_chatjoinrequest.py index b4abed795..9c9f5b3fe 100644 --- a/tests/test_chatjoinrequest.py +++ b/tests/test_chatjoinrequest.py @@ -29,26 +29,26 @@ from tests.auxil.bot_method_checks import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def time(): return datetime.datetime.now(tz=UTC) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def chat_join_request(bot, time): cjr = ChatJoinRequest( - chat=TestChatJoinRequest.chat, - from_user=TestChatJoinRequest.from_user, + chat=TestChatJoinRequestBase.chat, + from_user=TestChatJoinRequestBase.from_user, date=time, - bio=TestChatJoinRequest.bio, - invite_link=TestChatJoinRequest.invite_link, - user_chat_id=TestChatJoinRequest.from_user.id, + bio=TestChatJoinRequestBase.bio, + invite_link=TestChatJoinRequestBase.invite_link, + user_chat_id=TestChatJoinRequestBase.from_user.id, ) cjr.set_bot(bot) return cjr -class TestChatJoinRequest: +class TestChatJoinRequestBase: chat = Chat(1, Chat.SUPERGROUP) from_user = User(2, "first_name", False) bio = "bio" @@ -61,6 +61,8 @@ class TestChatJoinRequest: is_primary=False, ) + +class TestChatJoinRequestWithoutRequest(TestChatJoinRequestBase): def test_slot_behaviour(self, chat_join_request, mro_slots): inst = chat_join_request for attr in inst.__slots__: diff --git a/tests/test_chatlocation.py b/tests/test_chatlocation.py index 42be03f0e..391e41297 100644 --- a/tests/test_chatlocation.py +++ b/tests/test_chatlocation.py @@ -22,15 +22,17 @@ import pytest from telegram import ChatLocation, Location, User -@pytest.fixture(scope="class") -def chat_location(bot): - return ChatLocation(TestChatLocation.location, TestChatLocation.address) +@pytest.fixture(scope="module") +def chat_location(): + return ChatLocation(TestChatLocationBase.location, TestChatLocationBase.address) -class TestChatLocation: +class TestChatLocationBase: location = Location(123, 456) address = "The Shire" + +class TestChatLocationWithoutRequest(TestChatLocationBase): def test_slot_behaviour(self, chat_location, mro_slots): inst = chat_location for attr in inst.__slots__: diff --git a/tests/test_chatmember.py b/tests/test_chatmember.py index 8aeb37765..ae2680417 100644 --- a/tests/test_chatmember.py +++ b/tests/test_chatmember.py @@ -187,7 +187,7 @@ def chat_member_type(request): ], indirect=True, ) -class TestChatMemberTypes: +class TestChatMemberTypesWithoutRequest: def test_slot_behaviour(self, chat_member_type, mro_slots): inst = chat_member_type for attr in inst.__slots__: diff --git a/tests/test_chatmemberupdated.py b/tests/test_chatmemberupdated.py index 0268d0359..4045d4090 100644 --- a/tests/test_chatmemberupdated.py +++ b/tests/test_chatmemberupdated.py @@ -34,26 +34,26 @@ from telegram import ( from telegram._utils.datetime import UTC, to_timestamp -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def user(): return User(1, "First name", False) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def chat(): return Chat(1, Chat.SUPERGROUP, "Chat") -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def old_chat_member(user): - return ChatMember(user, TestChatMemberUpdated.old_status) + return ChatMember(user, TestChatMemberUpdatedBase.old_status) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def new_chat_member(user): return ChatMemberAdministrator( user, - TestChatMemberUpdated.new_status, + TestChatMemberUpdatedBase.new_status, True, True, True, @@ -66,25 +66,27 @@ def new_chat_member(user): ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def time(): return datetime.datetime.now(tz=UTC) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def invite_link(user): return ChatInviteLink("link", user, False, True, True) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def chat_member_updated(user, chat, old_chat_member, new_chat_member, invite_link, time): return ChatMemberUpdated(chat, user, time, old_chat_member, new_chat_member, invite_link) -class TestChatMemberUpdated: +class TestChatMemberUpdatedBase: old_status = ChatMember.MEMBER new_status = ChatMember.ADMINISTRATOR + +class TestChatMemberUpdatedWithoutRequest(TestChatMemberUpdatedBase): def test_slot_behaviour(self, mro_slots, chat_member_updated): action = chat_member_updated for attr in action.__slots__: diff --git a/tests/test_chatpermissions.py b/tests/test_chatpermissions.py index 584506ec6..1afd5a602 100644 --- a/tests/test_chatpermissions.py +++ b/tests/test_chatpermissions.py @@ -22,7 +22,7 @@ import pytest from telegram import ChatPermissions, User -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def chat_permissions(): return ChatPermissions( can_send_messages=True, @@ -43,7 +43,7 @@ def chat_permissions(): ) -class TestChatPermissions: +class TestChatPermissionsBase: can_send_messages = True can_send_media_messages = True can_send_polls = True @@ -60,6 +60,8 @@ class TestChatPermissions: can_send_video_notes = False can_send_voice_notes = None + +class TestChatPermissionsWithoutRequest(TestChatPermissionsBase): def test_slot_behaviour(self, chat_permissions, mro_slots): inst = chat_permissions for attr in inst.__slots__: diff --git a/tests/test_chatphoto.py b/tests/test_chatphoto.py index 3b6c8cb95..d2d4ad4fa 100644 --- a/tests/test_chatphoto.py +++ b/tests/test_chatphoto.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio import os from pathlib import Path @@ -35,12 +36,11 @@ from tests.conftest import data_file, expect_bad_request @pytest.fixture(scope="function") def chatphoto_file(): - f = data_file("telegram.jpg").open("rb") - yield f - f.close() + with data_file("telegram.jpg").open("rb") as f: + yield f -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") async def chat_photo(bot, super_group_id): async def func(): return (await bot.get_chat(super_group_id, read_timeout=50)).photo @@ -50,61 +50,20 @@ async def chat_photo(bot, super_group_id): ) -class TestChatPhoto: +class TestChatPhotoBase: chatphoto_small_file_id = "smallCgADAQADngIAAuyVeEez0xRovKi9VAI" chatphoto_big_file_id = "bigCgADAQADngIAAuyVeEez0xRovKi9VAI" chatphoto_small_file_unique_id = "smalladc3145fd2e84d95b64d68eaa22aa33e" chatphoto_big_file_unique_id = "bigadc3145fd2e84d95b64d68eaa22aa33e" chatphoto_file_url = "https://python-telegram-bot.org/static/testfiles/telegram.jpg" + +class TestChatPhotoWithoutRequest(TestChatPhotoBase): def test_slot_behaviour(self, chat_photo, mro_slots): for attr in chat_photo.__slots__: assert getattr(chat_photo, attr, "err") != "err", f"got extra slot '{attr}'" assert len(mro_slots(chat_photo)) == len(set(mro_slots(chat_photo))), "duplicate slot" - @pytest.mark.flaky(3, 1) - async def test_send_all_args( - self, bot, super_group_id, chatphoto_file, chat_photo, thumb_file - ): - async def func(): - assert await bot.set_chat_photo(super_group_id, chatphoto_file) - - await expect_bad_request( - func, "Type of file mismatch", "Telegram did not accept the file." - ) - - @pytest.mark.flaky(3, 1) - async def test_get_and_download(self, bot, chat_photo): - jpg_file = Path("telegram.jpg") - if jpg_file.is_file(): - jpg_file.unlink() - - new_file = await bot.get_file(chat_photo.small_file_id) - - assert new_file.file_unique_id == chat_photo.small_file_unique_id - assert new_file.file_path.startswith("https://") - - await new_file.download_to_drive(jpg_file) - - assert jpg_file.is_file() - - new_file = await bot.get_file(chat_photo.big_file_id) - - assert new_file.file_unique_id == chat_photo.big_file_unique_id - assert new_file.file_path.startswith("https://") - - await new_file.download_to_drive(jpg_file) - - assert jpg_file.is_file() - - async def test_send_with_chat_photo(self, monkeypatch, bot, super_group_id, chat_photo): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - return request_data.parameters["photo"] == chat_photo.to_dict() - - monkeypatch.setattr(bot.request, "post", make_assertion) - message = await bot.set_chat_photo(photo=chat_photo, chat_id=super_group_id) - assert message - def test_de_json(self, bot, chat_photo): json_dict = { "small_file_id": self.chatphoto_small_file_id, @@ -128,46 +87,6 @@ class TestChatPhoto: assert chat_photo_dict["small_file_unique_id"] == chat_photo.small_file_unique_id assert chat_photo_dict["big_file_unique_id"] == chat_photo.big_file_unique_id - @pytest.mark.flaky(3, 1) - async def test_error_send_empty_file(self, bot, super_group_id): - chatphoto_file = open(os.devnull, "rb") - - with pytest.raises(TelegramError): - await bot.set_chat_photo(chat_id=super_group_id, photo=chatphoto_file) - - @pytest.mark.flaky(3, 1) - async def test_error_send_empty_file_id(self, bot, super_group_id): - with pytest.raises(TelegramError): - await bot.set_chat_photo(chat_id=super_group_id, photo="") - - async def test_error_send_without_required_args(self, bot, super_group_id): - with pytest.raises(TypeError): - await bot.set_chat_photo(chat_id=super_group_id) - - async def test_get_small_file_instance_method(self, monkeypatch, chat_photo): - async def make_assertion(*_, **kwargs): - return kwargs["file_id"] == chat_photo.small_file_id - - assert check_shortcut_signature(ChatPhoto.get_small_file, Bot.get_file, ["file_id"], []) - assert await check_shortcut_call( - chat_photo.get_small_file, chat_photo.get_bot(), "get_file" - ) - assert await check_defaults_handling(chat_photo.get_small_file, chat_photo.get_bot()) - - monkeypatch.setattr(chat_photo.get_bot(), "get_file", make_assertion) - assert await chat_photo.get_small_file() - - async def test_get_big_file_instance_method(self, monkeypatch, chat_photo): - async def make_assertion(*_, **kwargs): - return kwargs["file_id"] == chat_photo.big_file_id - - assert check_shortcut_signature(ChatPhoto.get_big_file, Bot.get_file, ["file_id"], []) - assert await check_shortcut_call(chat_photo.get_big_file, chat_photo.get_bot(), "get_file") - assert await check_defaults_handling(chat_photo.get_big_file, chat_photo.get_bot()) - - monkeypatch.setattr(chat_photo.get_bot(), "get_file", make_assertion) - assert await chat_photo.get_big_file() - def test_equality(self): a = ChatPhoto( self.chatphoto_small_file_id, @@ -199,3 +118,80 @@ class TestChatPhoto: assert a != e assert hash(a) != hash(e) + + async def test_send_with_chat_photo(self, monkeypatch, bot, super_group_id, chat_photo): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return request_data.parameters["photo"] == chat_photo.to_dict() + + monkeypatch.setattr(bot.request, "post", make_assertion) + message = await bot.set_chat_photo(photo=chat_photo, chat_id=super_group_id) + assert message + + async def test_get_small_file_instance_method(self, monkeypatch, chat_photo): + async def make_assertion(*_, **kwargs): + return kwargs["file_id"] == chat_photo.small_file_id + + assert check_shortcut_signature(ChatPhoto.get_small_file, Bot.get_file, ["file_id"], []) + assert await check_shortcut_call( + chat_photo.get_small_file, chat_photo.get_bot(), "get_file" + ) + assert await check_defaults_handling(chat_photo.get_small_file, chat_photo.get_bot()) + + monkeypatch.setattr(chat_photo.get_bot(), "get_file", make_assertion) + assert await chat_photo.get_small_file() + + async def test_get_big_file_instance_method(self, monkeypatch, chat_photo): + async def make_assertion(*_, **kwargs): + return kwargs["file_id"] == chat_photo.big_file_id + + assert check_shortcut_signature(ChatPhoto.get_big_file, Bot.get_file, ["file_id"], []) + assert await check_shortcut_call(chat_photo.get_big_file, chat_photo.get_bot(), "get_file") + assert await check_defaults_handling(chat_photo.get_big_file, chat_photo.get_bot()) + + monkeypatch.setattr(chat_photo.get_bot(), "get_file", make_assertion) + assert await chat_photo.get_big_file() + + +class TestChatPhotoWithRequest: + async def test_get_and_download(self, bot, chat_photo): + jpg_file = Path("telegram.jpg") + if jpg_file.is_file(): + jpg_file.unlink() + + tasks = {bot.get_file(chat_photo.small_file_id), bot.get_file(chat_photo.big_file_id)} + asserts = [] + + for task in asyncio.as_completed(tasks): + file = await task + if file.file_unique_id == chat_photo.small_file_unique_id: + asserts.append("small") + elif file.file_unique_id == chat_photo.big_file_unique_id: + asserts.append("big") + assert file.file_path.startswith("https://") + + await file.download_to_drive(jpg_file) + assert jpg_file.is_file() + + assert "small" in asserts and "big" in asserts + + async def test_send_all_args(self, bot, super_group_id, chatphoto_file): + async def func(): + assert await bot.set_chat_photo(super_group_id, chatphoto_file) + + await expect_bad_request( + func, "Type of file mismatch", "Telegram did not accept the file." + ) + + async def test_error_send_empty_file(self, bot, super_group_id): + chatphoto_file = open(os.devnull, "rb") + + with pytest.raises(TelegramError): + await bot.set_chat_photo(chat_id=super_group_id, photo=chatphoto_file) + + async def test_error_send_empty_file_id(self, bot, super_group_id): + with pytest.raises(TelegramError): + await bot.set_chat_photo(chat_id=super_group_id, photo="") + + async def test_error_send_without_required_args(self, bot, super_group_id): + with pytest.raises(TypeError): + await bot.set_chat_photo(chat_id=super_group_id) diff --git a/tests/test_choseninlineresult.py b/tests/test_choseninlineresult.py index c49790a5c..dc9ea7ca1 100644 --- a/tests/test_choseninlineresult.py +++ b/tests/test_choseninlineresult.py @@ -22,22 +22,26 @@ import pytest from telegram import ChosenInlineResult, Location, User, Voice -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def user(): user = User(1, "First name", False) user._unfreeze() return user -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def chosen_inline_result(user): - return ChosenInlineResult(TestChosenInlineResult.result_id, user, TestChosenInlineResult.query) + return ChosenInlineResult( + TestChosenInlineResultBase.result_id, user, TestChosenInlineResultBase.query + ) -class TestChosenInlineResult: +class TestChosenInlineResultBase: result_id = "result id" query = "query text" + +class TestChosenInlineResultWithoutRequest(TestChosenInlineResultBase): def test_slot_behaviour(self, chosen_inline_result, mro_slots): inst = chosen_inline_result for attr in inst.__slots__: diff --git a/tests/test_constants.py b/tests/test_constants.py index eb271300e..f69199ceb 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -16,10 +16,9 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio import json -import pytest - from telegram import constants from telegram._utils.enum import IntEnum, StringEnum from telegram.error import BadRequest @@ -36,7 +35,7 @@ class IntEnumTest(IntEnum): BAR = 2 -class TestConstants: +class TestConstantsWithoutRequest: """Also test _utils.enum.StringEnum on the fly because tg.constants is currently the only place where that class is used.""" @@ -110,30 +109,6 @@ class TestConstants: assert hash(IntEnumTest.FOO) == hash(1) - @pytest.mark.flaky(3, 1) - async def test_max_message_length(self, bot, chat_id): - await bot.send_message(chat_id=chat_id, text="a" * constants.MessageLimit.MAX_TEXT_LENGTH) - - with pytest.raises( - BadRequest, - match="Message is too long", - ): - await bot.send_message( - chat_id=chat_id, text="a" * (constants.MessageLimit.MAX_TEXT_LENGTH + 1) - ) - - @pytest.mark.flaky(3, 1) - async def test_max_caption_length(self, bot, chat_id): - good_caption = "a" * constants.MessageLimit.CAPTION_LENGTH - with data_file("telegram.png").open("rb") as f: - good_msg = await bot.send_photo(photo=f, caption=good_caption, chat_id=chat_id) - assert good_msg.caption == good_caption - - bad_caption = good_caption + "Z" - match = "Message caption is too long" - with pytest.raises(BadRequest, match=match), data_file("telegram.png").open("rb") as f: - await bot.send_photo(photo=f, caption=bad_caption, chat_id=chat_id) - def test_bot_api_version_and_info(self): assert constants.BOT_API_VERSION == str(constants.BOT_API_VERSION_INFO) assert constants.BOT_API_VERSION_INFO == tuple( @@ -151,3 +126,29 @@ class TestConstants: assert vi < (vi[0] + 1, vi[1] + 1) assert vi[0] == vi.major assert vi[1] == vi.minor + + +class TestConstantsWithRequest: + async def test_max_message_length(self, bot, chat_id): + good_text = "a" * constants.MessageLimit.MAX_TEXT_LENGTH + bad_text = good_text + "Z" + tasks = asyncio.gather( + bot.send_message(chat_id, text=good_text), + bot.send_message(chat_id, text=bad_text), + return_exceptions=True, + ) + good_msg, bad_msg = await tasks + assert good_msg.text == good_text + assert isinstance(bad_msg, BadRequest) and "Message is too long" in str(bad_msg) + + async def test_max_caption_length(self, bot, chat_id): + good_caption = "a" * constants.MessageLimit.CAPTION_LENGTH + bad_caption = good_caption + "Z" + tasks = asyncio.gather( + bot.send_photo(chat_id, data_file("telegram.png").read_bytes(), good_caption), + bot.send_photo(chat_id, data_file("telegram.png").read_bytes(), bad_caption), + return_exceptions=True, + ) + good_msg, bad_msg = await tasks + assert good_msg.caption == good_caption + assert isinstance(bad_msg, BadRequest) and "Message caption is too long" in str(bad_msg) diff --git a/tests/test_contact.py b/tests/test_contact.py index 32cefb97c..9160474ef 100644 --- a/tests/test_contact.py +++ b/tests/test_contact.py @@ -17,6 +17,8 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio + import pytest from telegram import Contact, Voice @@ -24,22 +26,24 @@ from telegram.error import BadRequest from telegram.request import RequestData -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def contact(): return Contact( - TestContact.phone_number, - TestContact.first_name, - TestContact.last_name, - TestContact.user_id, + TestContactBase.phone_number, + TestContactBase.first_name, + TestContactBase.last_name, + TestContactBase.user_id, ) -class TestContact: +class TestContactBase: phone_number = "+11234567890" first_name = "Leandro" last_name = "Toledo" user_id = 23 + +class TestContactWithoutRequest(TestContactBase): def test_slot_behaviour(self, contact, mro_slots): for attr in contact.__slots__: assert getattr(contact, attr, "err") != "err", f"got extra slot '{attr}'" @@ -68,6 +72,48 @@ class TestContact: assert contact.last_name == self.last_name assert contact.user_id == self.user_id + def test_to_dict(self, contact): + contact_dict = contact.to_dict() + + assert isinstance(contact_dict, dict) + assert contact_dict["phone_number"] == contact.phone_number + assert contact_dict["first_name"] == contact.first_name + assert contact_dict["last_name"] == contact.last_name + assert contact_dict["user_id"] == contact.user_id + + def test_equality(self): + a = Contact(self.phone_number, self.first_name) + b = Contact(self.phone_number, self.first_name) + c = Contact(self.phone_number, "") + d = Contact("", self.first_name) + e = Voice("", "unique_id", 0) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a == c + assert hash(a) == hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) + + async def test_send_contact_without_required(self, bot, chat_id): + with pytest.raises(ValueError, match="Either contact or phone_number and first_name"): + await bot.send_contact(chat_id=chat_id) + + async def test_send_mutually_exclusive(self, bot, chat_id, contact): + with pytest.raises(ValueError, match="Not both"): + await bot.send_contact( + chat_id=chat_id, + contact=contact, + phone_number=contact.phone_number, + first_name=contact.first_name, + ) + async def test_send_with_contact(self, monkeypatch, bot, chat_id, contact): async def make_assertion(url, request_data: RequestData, *args, **kwargs): data = request_data.json_parameters @@ -77,10 +123,10 @@ class TestContact: return phone and first and last monkeypatch.setattr(bot.request, "post", make_assertion) - message = await bot.send_contact(contact=contact, chat_id=chat_id) - assert message + assert await bot.send_contact(contact=contact, chat_id=chat_id) - @pytest.mark.flaky(3, 1) + +class TestContactWithRequest(TestContactBase): @pytest.mark.parametrize( "default_bot,custom", [ @@ -114,54 +160,12 @@ class TestContact: chat_id, contact=contact, reply_to_message_id=reply_to_message.message_id ) - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) async def test_send_contact_default_protect_content(self, chat_id, default_bot, contact): - protected = await default_bot.send_contact(chat_id, contact=contact) - assert protected.has_protected_content - unprotected = await default_bot.send_contact( - chat_id, contact=contact, protect_content=False + tasks = asyncio.gather( + default_bot.send_contact(chat_id, contact=contact), + default_bot.send_contact(chat_id, contact=contact, protect_content=False), ) + protected, unprotected = await tasks + assert protected.has_protected_content assert not unprotected.has_protected_content - - async def test_send_contact_without_required(self, bot, chat_id): - with pytest.raises(ValueError, match="Either contact or phone_number and first_name"): - await bot.send_contact(chat_id=chat_id) - - async def test_send_mutually_exclusive(self, bot, chat_id, contact): - with pytest.raises(ValueError, match="Not both"): - await bot.send_contact( - chat_id=chat_id, - contact=contact, - phone_number=contact.phone_number, - first_name=contact.first_name, - ) - - def test_to_dict(self, contact): - contact_dict = contact.to_dict() - - assert isinstance(contact_dict, dict) - assert contact_dict["phone_number"] == contact.phone_number - assert contact_dict["first_name"] == contact.first_name - assert contact_dict["last_name"] == contact.last_name - assert contact_dict["user_id"] == contact.user_id - - def test_equality(self): - a = Contact(self.phone_number, self.first_name) - b = Contact(self.phone_number, self.first_name) - c = Contact(self.phone_number, "") - d = Contact("", self.first_name) - e = Voice("", "unique_id", 0) - - assert a == b - assert hash(a) == hash(b) - assert a is not b - - assert a == c - assert hash(a) == hash(c) - - assert a != d - assert hash(a) != hash(d) - - assert a != e - assert hash(a) != hash(e) diff --git a/tests/test_conversationhandler.py b/tests/test_conversationhandler.py index 3e3e01033..8f873e001 100644 --- a/tests/test_conversationhandler.py +++ b/tests/test_conversationhandler.py @@ -24,7 +24,6 @@ from warnings import filterwarnings import pytest from telegram import ( - Bot, CallbackQuery, Chat, ChosenInlineResult, @@ -45,7 +44,6 @@ from telegram.ext import ( CommandHandler, ConversationHandler, Defaults, - ExtBot, InlineQueryHandler, JobQueue, MessageHandler, @@ -59,7 +57,7 @@ from telegram.ext import ( filters, ) from telegram.warnings import PTBUserWarning -from tests.conftest import make_command_message +from tests.conftest import DictBot, make_bot, make_command_message @pytest.fixture(scope="class") @@ -1255,7 +1253,7 @@ class TestConversationHandler: await app.process_update(Update(update_id=2, message=brew_message)) assert handler.check_update(Update(0, message=pour_coffee_message)) # assert handler.conversations.get((self.group.id, user1.id)) == self.BREWING - await asyncio.sleep(0.7) + await asyncio.sleep(0.75) assert handler.check_update(Update(0, message=start_message)) # assert handler.conversations.get((self.group.id, user1.id)) is None @@ -2113,7 +2111,7 @@ class TestConversationHandler: @pytest.mark.parametrize("handler_block", [True, False, None]) @pytest.mark.parametrize("ext_bot", [True, False], ids=["ExtBot", "Bot"]) async def test_blocking_resolution_order( - self, bot, default_block, ch_block, handler_block, ext_bot + self, bot_info, default_block, ch_block, handler_block, ext_bot ): event = asyncio.Event() @@ -2149,7 +2147,7 @@ class TestConversationHandler: fallbacks=[fallback], ) - bot = ExtBot(bot.token, defaults=defaults) if ext_bot else Bot(bot.token) + bot = make_bot(bot_info, defaults=defaults) if ext_bot else DictBot(bot_info["token"]) app = ApplicationBuilder().bot(bot).build() app.add_handler(conv_handler) @@ -2160,7 +2158,7 @@ class TestConversationHandler: fallback_message.set_bot(bot) # This loop makes sure that we test all of entry points, states handler & fallbacks - for message in [start_message, start_message, fallback_message]: + for message in [start_message, fallback_message]: process_update_task = asyncio.create_task( app.process_update(Update(0, message=message)) ) diff --git a/tests/test_datetime.py b/tests/test_datetime.py index 431b05530..1d7d0e190 100644 --- a/tests/test_datetime.py +++ b/tests/test_datetime.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. import datetime as dtm -import os import time import pytest @@ -26,18 +25,22 @@ from telegram._utils import datetime as tg_dtm from telegram.ext import Defaults # sample time specification values categorised into absolute / delta / time-of-day -from tests.auxil.object_conversions import env_var_2_bool +from tests.conftest import TEST_WITH_OPT_DEPS -ABSOLUTE_TIME_SPECS = [ - dtm.datetime.now(tz=dtm.timezone(dtm.timedelta(hours=-7))).replace(second=0, microsecond=0), - dtm.datetime.utcnow().replace(second=0, microsecond=0), -] +# We do not parametrize tests with these variables, since there's a tiny chance that there is an +# error while collecting the tests (happens when time goes from HH:59:00 -> HH+1:00:00) when we +# run the test suite with multiple workers DELTA_TIME_SPECS = [dtm.timedelta(hours=3, seconds=42, milliseconds=2), 30, 7.5] TIME_OF_DAY_TIME_SPECS = [ dtm.time(12, 42, tzinfo=dtm.timezone(dtm.timedelta(hours=-7))), dtm.time(12, 42), ] RELATIVE_TIME_SPECS = DELTA_TIME_SPECS + TIME_OF_DAY_TIME_SPECS + +ABSOLUTE_TIME_SPECS = [ + dtm.datetime.now(tz=dtm.timezone(dtm.timedelta(hours=-7))), + dtm.datetime.utcnow(), +] TIME_SPECS = ABSOLUTE_TIME_SPECS + RELATIVE_TIME_SPECS """ @@ -49,7 +52,6 @@ Because imports in pytest are intricate, we just run with the TEST_WITH_OPT_DEPS=False environment variable in addition to the regular test suite. """ -TEST_WITH_OPT_DEPS = env_var_2_bool(os.getenv("TEST_WITH_OPT_DEPS", True)) class TestDatetime: @@ -97,12 +99,15 @@ class TestDatetime: with pytest.raises(ValueError): tg_dtm.to_float_timestamp(dtm.datetime(2019, 11, 11), reference_timestamp=123) - @pytest.mark.parametrize("time_spec", DELTA_TIME_SPECS, ids=str) - def test_to_float_timestamp_delta(self, time_spec): + # see note on parametrization at the top of this file + def test_to_float_timestamp_delta(self): """Conversion from a 'delta' time specification to timestamp""" reference_t = 0 - delta = time_spec.total_seconds() if hasattr(time_spec, "total_seconds") else time_spec - assert tg_dtm.to_float_timestamp(time_spec, reference_t) == reference_t + delta + for i in DELTA_TIME_SPECS: + delta = i.total_seconds() if hasattr(i, "total_seconds") else i + assert ( + tg_dtm.to_float_timestamp(i, reference_t) == reference_t + delta + ), f"failed for {i}" def test_to_float_timestamp_time_of_day(self): """Conversion from time-of-day specification to timestamp""" @@ -130,22 +135,24 @@ class TestDatetime: ref_t + (-utc_offset.total_seconds() % (24 * 60 * 60)) ) - @pytest.mark.parametrize("time_spec", RELATIVE_TIME_SPECS, ids=str) - def test_to_float_timestamp_default_reference(self, time_spec): + # see note on parametrization at the top of this file + def test_to_float_timestamp_default_reference(self): """The reference timestamp for relative time specifications should default to now""" - now = time.time() - assert tg_dtm.to_float_timestamp(time_spec) == pytest.approx( - tg_dtm.to_float_timestamp(time_spec, reference_timestamp=now) - ) + for i in RELATIVE_TIME_SPECS: + now = time.time() + assert tg_dtm.to_float_timestamp(i) == pytest.approx( + tg_dtm.to_float_timestamp(i, reference_timestamp=now) + ), f"Failed for {i}" def test_to_float_timestamp_error(self): with pytest.raises(TypeError, match="Defaults"): tg_dtm.to_float_timestamp(Defaults()) - @pytest.mark.parametrize("time_spec", TIME_SPECS, ids=str) - def test_to_timestamp(self, time_spec): + # see note on parametrization at the top of this file + def test_to_timestamp(self): # delegate tests to `to_float_timestamp` - assert tg_dtm.to_timestamp(time_spec) == int(tg_dtm.to_float_timestamp(time_spec)) + for i in TIME_SPECS: + assert tg_dtm.to_timestamp(i) == int(tg_dtm.to_float_timestamp(i)), f"Failed for {i}" def test_to_timestamp_none(self): # this 'convenience' behaviour has been left left for backwards compatibility diff --git a/tests/test_defaults.py b/tests/test_defaults.py index 565ba4b6f..88048031b 100644 --- a/tests/test_defaults.py +++ b/tests/test_defaults.py @@ -19,15 +19,12 @@ import datetime as dtm import inspect -import os import pytest from telegram import User from telegram.ext import Defaults -from tests.auxil.object_conversions import env_var_2_bool - -TEST_WITH_OPT_DEPS = env_var_2_bool(os.getenv("TEST_WITH_OPT_DEPS", True)) +from tests.conftest import TEST_WITH_OPT_DEPS class TestDefault: diff --git a/tests/test_dice.py b/tests/test_dice.py index bfef75cf3..182b3276c 100644 --- a/tests/test_dice.py +++ b/tests/test_dice.py @@ -22,14 +22,16 @@ import pytest from telegram import BotCommand, Dice -@pytest.fixture(scope="class", params=Dice.ALL_EMOJI) +@pytest.fixture(scope="module", params=Dice.ALL_EMOJI) def dice(request): return Dice(value=5, emoji=request.param) -class TestDice: +class TestDiceBase: value = 4 + +class TestDiceWithoutRequest(TestDiceBase): def test_slot_behaviour(self, dice, mro_slots): for attr in dice.__slots__: assert getattr(dice, attr, "err") != "err", f"got extra slot '{attr}'" diff --git a/tests/test_document.py b/tests/test_document.py index 0cdf56099..93574a231 100644 --- a/tests/test_document.py +++ b/tests/test_document.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio import os from pathlib import Path @@ -35,18 +36,17 @@ from tests.conftest import data_file @pytest.fixture(scope="function") def document_file(): - f = data_file("telegram.png").open("rb") - yield f - f.close() + with data_file("telegram.png").open("rb") as f: + yield f -@pytest.fixture(scope="class") +@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 TestDocument: +class TestDocumentBase: caption = "DocumentTest - *Caption*" document_file_url = "https://python-telegram-bot.org/static/testfiles/telegram.gif" file_size = 12948 @@ -58,6 +58,8 @@ class TestDocument: document_file_id = "5a3128a4d2a04750b5b58397f3b5e812" document_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e" + +class TestDocumentWithoutRequest(TestDocumentBase): def test_slot_behaviour(self, document, mro_slots): for attr in document.__slots__: assert getattr(document, attr, "err") != "err", f"got extra slot '{attr}'" @@ -78,7 +80,141 @@ class TestDocument: assert document.thumb.width == self.thumb_width assert document.thumb.height == self.thumb_height - @pytest.mark.flaky(3, 1) + def test_de_json(self, bot, document): + json_dict = { + "file_id": self.document_file_id, + "file_unique_id": self.document_file_unique_id, + "thumb": document.thumb.to_dict(), + "file_name": self.file_name, + "mime_type": self.mime_type, + "file_size": self.file_size, + } + test_document = Document.de_json(json_dict, bot) + assert test_document.api_kwargs == {} + + assert test_document.file_id == self.document_file_id + assert test_document.file_unique_id == self.document_file_unique_id + assert test_document.thumb == document.thumb + assert test_document.file_name == self.file_name + assert test_document.mime_type == self.mime_type + assert test_document.file_size == self.file_size + + def test_to_dict(self, document): + document_dict = document.to_dict() + + assert isinstance(document_dict, dict) + assert document_dict["file_id"] == document.file_id + assert document_dict["file_unique_id"] == document.file_unique_id + assert document_dict["file_name"] == document.file_name + assert document_dict["mime_type"] == document.mime_type + assert document_dict["file_size"] == document.file_size + + def test_equality(self, document): + a = Document(document.file_id, document.file_unique_id) + b = Document("", document.file_unique_id) + d = Document("", "") + e = Voice(document.file_id, document.file_unique_id, 0) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) + + async def test_error_send_without_required_args(self, bot, chat_id): + with pytest.raises(TypeError): + await bot.send_document(chat_id=chat_id) + + @pytest.mark.parametrize("disable_content_type_detection", [True, False, None]) + async def test_send_with_document( + self, monkeypatch, bot, chat_id, document, disable_content_type_detection + ): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + data = request_data.parameters + type_detection = ( + data.get("disable_content_type_detection") == disable_content_type_detection + ) + return data["document"] == document.file_id and type_detection + + monkeypatch.setattr(bot.request, "post", make_assertion) + + message = await bot.send_document( + document=document, + chat_id=chat_id, + disable_content_type_detection=disable_content_type_detection, + ) + + assert message + + @pytest.mark.parametrize("local_mode", [True, False]) + async def test_send_document_local_files(self, monkeypatch, bot, chat_id, local_mode): + try: + bot._local_mode = local_mode + # For just test that the correct paths are passed as we have no local bot API set up + test_flag = False + file = data_file("telegram.jpg") + expected = file.as_uri() + + async def make_assertion(_, data, *args, **kwargs): + nonlocal test_flag + if local_mode: + test_flag = data.get("document") == expected and data.get("thumb") == expected + else: + test_flag = isinstance(data.get("document"), InputFile) and isinstance( + data.get("thumb"), InputFile + ) + + monkeypatch.setattr(bot, "_post", make_assertion) + await bot.send_document(chat_id, file, thumb=file) + assert test_flag + finally: + bot._local_mode = False + + async def test_get_file_instance_method(self, monkeypatch, document): + async def make_assertion(*_, **kwargs): + return kwargs["file_id"] == document.file_id + + assert check_shortcut_signature(Document.get_file, Bot.get_file, ["file_id"], []) + assert await check_shortcut_call(document.get_file, document.get_bot(), "get_file") + assert await check_defaults_handling(document.get_file, document.get_bot()) + + monkeypatch.setattr(document.get_bot(), "get_file", make_assertion) + assert await document.get_file() + + +class TestDocumentWithRequest(TestDocumentBase): + async def test_error_send_empty_file(self, bot, chat_id): + with open(os.devnull, "rb") as f: + with pytest.raises(TelegramError): + await bot.send_document(chat_id=chat_id, document=f) + + async def test_error_send_empty_file_id(self, bot, chat_id): + with pytest.raises(TelegramError): + await bot.send_document(chat_id=chat_id, document="") + + async def test_get_and_download(self, bot, document, chat_id): + path = Path("telegram.png") + if path.is_file(): + path.unlink() + + new_file = await bot.get_file(document.file_id) + + assert new_file.file_size == document.file_size + assert new_file.file_unique_id == document.file_unique_id + assert new_file.file_path.startswith("https://") + + await new_file.download_to_drive("telegram.png") + + assert path.is_file() + + async def test_send_resend(self, bot, chat_id, document): + message = await bot.send_document(chat_id=chat_id, document=document.file_id) + assert message.document == document + async def test_send_all_args(self, bot, chat_id, document_file, document, thumb_file): message = await bot.send_document( chat_id, @@ -105,24 +241,6 @@ class TestDocument: assert message.document.thumb.height == self.thumb_height assert message.has_protected_content - @pytest.mark.flaky(3, 1) - async def test_get_and_download(self, bot, document): - path = Path("telegram.png") - if path.is_file(): - path.unlink() - - new_file = await bot.get_file(document.file_id) - - assert new_file.file_size == document.file_size - assert new_file.file_id == document.file_id - assert new_file.file_unique_id == document.file_unique_id - assert new_file.file_path.startswith("https://") - - await new_file.download_to_drive("telegram.png") - - assert path.is_file() - - @pytest.mark.flaky(3, 1) async def test_send_url_gif_file(self, bot, chat_id): message = await bot.send_document(chat_id, self.document_file_url) @@ -138,34 +256,16 @@ class TestDocument: assert document.mime_type == "image/gif" assert document.file_size == 3878 - @pytest.mark.flaky(3, 1) - async def test_send_resend(self, bot, chat_id, document): - message = await bot.send_document(chat_id=chat_id, document=document.file_id) - - assert message.document == document - - @pytest.mark.parametrize("disable_content_type_detection", [True, False, None]) - async def test_send_with_document( - self, monkeypatch, bot, chat_id, document, disable_content_type_detection - ): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - data = request_data.parameters - type_detection = ( - data.get("disable_content_type_detection") == disable_content_type_detection - ) - return data["document"] == document.file_id and type_detection - - monkeypatch.setattr(bot.request, "post", make_assertion) - - message = await bot.send_document( - document=document, - chat_id=chat_id, - disable_content_type_detection=disable_content_type_detection, + @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) + async def test_send_document_default_protect_content(self, chat_id, default_bot, document): + tasks = asyncio.gather( + default_bot.send_document(chat_id, document), + default_bot.send_document(chat_id, document, protect_content=False), ) + protected, unprotected = await tasks + assert protected.has_protected_content + assert not unprotected.has_protected_content - assert message - - @pytest.mark.flaky(3, 1) async def test_send_document_caption_entities(self, bot, chat_id, document): test_string = "Italic Bold Code" entities = [ @@ -180,7 +280,6 @@ class TestDocument: assert message.caption == test_string assert message.caption_entities == tuple(entities) - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) async def test_send_document_default_parse_mode_1(self, default_bot, chat_id, document): test_string = "Italic Bold Code" @@ -190,7 +289,6 @@ class TestDocument: assert message.caption_markdown == test_markdown_string assert message.caption == test_string - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) async def test_send_document_default_parse_mode_2(self, default_bot, chat_id, document): test_markdown_string = "_Italic_ *Bold* `Code`" @@ -201,7 +299,6 @@ class TestDocument: assert message.caption == test_markdown_string assert message.caption_markdown == escape_markdown(test_markdown_string) - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) async def test_send_document_default_parse_mode_3(self, default_bot, chat_id, document): test_markdown_string = "_Italic_ *Bold* `Code`" @@ -212,7 +309,6 @@ class TestDocument: assert message.caption == test_markdown_string assert message.caption_markdown == escape_markdown(test_markdown_string) - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize( "default_bot,custom", [ @@ -245,106 +341,3 @@ class TestDocument: await default_bot.send_document( chat_id, document, reply_to_message_id=reply_to_message.message_id ) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) - async def test_send_document_default_protect_content(self, chat_id, default_bot, document): - protected = await default_bot.send_document(chat_id, document) - assert protected.has_protected_content - unprotected = await default_bot.send_document(chat_id, document, protect_content=False) - assert not unprotected.has_protected_content - - @pytest.mark.parametrize("local_mode", [True, False]) - async def test_send_document_local_files(self, monkeypatch, bot, chat_id, local_mode): - try: - bot._local_mode = local_mode - # For just test that the correct paths are passed as we have no local bot API set up - test_flag = False - file = data_file("telegram.jpg") - expected = file.as_uri() - - async def make_assertion(_, data, *args, **kwargs): - nonlocal test_flag - if local_mode: - test_flag = data.get("document") == expected and data.get("thumb") == expected - else: - test_flag = isinstance(data.get("document"), InputFile) and isinstance( - data.get("thumb"), InputFile - ) - - monkeypatch.setattr(bot, "_post", make_assertion) - await bot.send_document(chat_id, file, thumb=file) - assert test_flag - finally: - bot._local_mode = False - - def test_de_json(self, bot, document): - json_dict = { - "file_id": self.document_file_id, - "file_unique_id": self.document_file_unique_id, - "thumb": document.thumb.to_dict(), - "file_name": self.file_name, - "mime_type": self.mime_type, - "file_size": self.file_size, - } - test_document = Document.de_json(json_dict, bot) - assert test_document.api_kwargs == {} - - assert test_document.file_id == self.document_file_id - assert test_document.file_unique_id == self.document_file_unique_id - assert test_document.thumb == document.thumb - assert test_document.file_name == self.file_name - assert test_document.mime_type == self.mime_type - assert test_document.file_size == self.file_size - - def test_to_dict(self, document): - document_dict = document.to_dict() - - assert isinstance(document_dict, dict) - assert document_dict["file_id"] == document.file_id - assert document_dict["file_unique_id"] == document.file_unique_id - assert document_dict["file_name"] == document.file_name - assert document_dict["mime_type"] == document.mime_type - assert document_dict["file_size"] == document.file_size - - @pytest.mark.flaky(3, 1) - async def test_error_send_empty_file(self, bot, chat_id): - with open(os.devnull, "rb") as f: - with pytest.raises(TelegramError): - await bot.send_document(chat_id=chat_id, document=f) - - @pytest.mark.flaky(3, 1) - async def test_error_send_empty_file_id(self, bot, chat_id): - with pytest.raises(TelegramError): - await bot.send_document(chat_id=chat_id, document="") - - async def test_error_send_without_required_args(self, bot, chat_id): - with pytest.raises(TypeError): - await bot.send_document(chat_id=chat_id) - - async def test_get_file_instance_method(self, monkeypatch, document): - async def make_assertion(*_, **kwargs): - return kwargs["file_id"] == document.file_id - - assert check_shortcut_signature(Document.get_file, Bot.get_file, ["file_id"], []) - assert await check_shortcut_call(document.get_file, document.get_bot(), "get_file") - assert await check_defaults_handling(document.get_file, document.get_bot()) - - monkeypatch.setattr(document.get_bot(), "get_file", make_assertion) - assert await document.get_file() - - def test_equality(self, document): - a = Document(document.file_id, document.file_unique_id) - b = Document("", document.file_unique_id) - d = Document("", "") - e = Voice(document.file_id, document.file_unique_id, 0) - - assert a == b - assert hash(a) == hash(b) - assert a is not b - - assert a != d - assert hash(a) != hash(d) - - assert a != e - assert hash(a) != hash(e) diff --git a/tests/test_encryptedcredentials.py b/tests/test_encryptedcredentials.py index 6845749a4..ad9345458 100644 --- a/tests/test_encryptedcredentials.py +++ b/tests/test_encryptedcredentials.py @@ -22,20 +22,22 @@ import pytest from telegram import EncryptedCredentials, PassportElementError -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def encrypted_credentials(): return EncryptedCredentials( - TestEncryptedCredentials.data, - TestEncryptedCredentials.hash, - TestEncryptedCredentials.secret, + TestEncryptedCredentialsBase.data, + TestEncryptedCredentialsBase.hash, + TestEncryptedCredentialsBase.secret, ) -class TestEncryptedCredentials: +class TestEncryptedCredentialsBase: data = "data" hash = "hash" secret = "secret" + +class TestEncryptedCredentialsWithoutRequest(TestEncryptedCredentialsBase): def test_slot_behaviour(self, encrypted_credentials, mro_slots): inst = encrypted_credentials for attr in inst.__slots__: diff --git a/tests/test_encryptedpassportelement.py b/tests/test_encryptedpassportelement.py index 32c9badd9..a4f938e9f 100644 --- a/tests/test_encryptedpassportelement.py +++ b/tests/test_encryptedpassportelement.py @@ -22,22 +22,22 @@ import pytest from telegram import EncryptedPassportElement, PassportElementError, PassportFile -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def encrypted_passport_element(): return EncryptedPassportElement( - TestEncryptedPassportElement.type_, + TestEncryptedPassportElementBase.type_, "this is a hash", - data=TestEncryptedPassportElement.data, - phone_number=TestEncryptedPassportElement.phone_number, - email=TestEncryptedPassportElement.email, - files=TestEncryptedPassportElement.files, - front_side=TestEncryptedPassportElement.front_side, - reverse_side=TestEncryptedPassportElement.reverse_side, - selfie=TestEncryptedPassportElement.selfie, + data=TestEncryptedPassportElementBase.data, + phone_number=TestEncryptedPassportElementBase.phone_number, + email=TestEncryptedPassportElementBase.email, + files=TestEncryptedPassportElementBase.files, + front_side=TestEncryptedPassportElementBase.front_side, + reverse_side=TestEncryptedPassportElementBase.reverse_side, + selfie=TestEncryptedPassportElementBase.selfie, ) -class TestEncryptedPassportElement: +class TestEncryptedPassportElementBase: type_ = "type" hash = "this is a hash" data = "data" @@ -48,6 +48,8 @@ class TestEncryptedPassportElement: reverse_side = PassportFile("file_id", 50, 0, 25) selfie = PassportFile("file_id", 50, 0, 25) + +class TestEncryptedPassportElementWithoutRequest(TestEncryptedPassportElementBase): def test_slot_behaviour(self, encrypted_passport_element, mro_slots): inst = encrypted_passport_element for attr in inst.__slots__: diff --git a/tests/test_file.py b/tests/test_file.py index 5ac195c5a..30c75b878 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -27,20 +27,20 @@ from telegram.error import TelegramError from tests.conftest import data_file -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def file(bot): file = File( - TestFile.file_id, - TestFile.file_unique_id, - file_path=TestFile.file_path, - file_size=TestFile.file_size, + TestFileBase.file_id, + TestFileBase.file_unique_id, + file_path=TestFileBase.file_path, + file_size=TestFileBase.file_size, ) file.set_bot(bot) file._unfreeze() return file -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def encrypted_file(bot): # check https://github.com/python-telegram-bot/python-telegram-bot/wiki/\ # PTB-test-writing-knowledge-base#how-to-generate-encrypted-passport-files @@ -49,13 +49,18 @@ def encrypted_file(bot): "Oq3G4sX+bKZthoyms1YlPqvWou9esb+z0Bi/KqQUG8s=", "Pt7fKPgYWKA/7a8E64Ea1X8C+Wf7Ky1tF4ANBl63vl4=", ) - ef = File(TestFile.file_id, TestFile.file_unique_id, TestFile.file_size, TestFile.file_path) + ef = File( + TestFileBase.file_id, + TestFileBase.file_unique_id, + TestFileBase.file_size, + TestFileBase.file_path, + ) ef.set_bot(bot) ef.set_credentials(fc) return ef -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def encrypted_local_file(bot): # check encrypted_file() for the source of the fc values fc = FileCredentials( @@ -63,9 +68,9 @@ def encrypted_local_file(bot): "Pt7fKPgYWKA/7a8E64Ea1X8C+Wf7Ky1tF4ANBl63vl4=", ) ef = File( - TestFile.file_id, - TestFile.file_unique_id, - TestFile.file_size, + TestFileBase.file_id, + TestFileBase.file_unique_id, + TestFileBase.file_size, file_path=str(data_file("image_encrypted.jpg")), ) ef.set_bot(bot) @@ -73,19 +78,19 @@ def encrypted_local_file(bot): return ef -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def local_file(bot): file = File( - TestFile.file_id, - TestFile.file_unique_id, + TestFileBase.file_id, + TestFileBase.file_unique_id, file_path=str(data_file("local_file.txt")), - file_size=TestFile.file_size, + file_size=TestFileBase.file_size, ) file.set_bot(bot) return file -class TestFile: +class TestFileBase: file_id = "NOTVALIDDOESNOTMATTER" file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e" file_path = ( @@ -94,6 +99,8 @@ class TestFile: file_size = 28232 file_content = "Saint-Saëns".encode() # Intentionally contains unicode chars. + +class TestFileWithoutRequest(TestFileBase): def test_slot_behaviour(self, file, mro_slots): for attr in file.__slots__: assert getattr(file, attr, "err") != "err", f"got extra slot '{attr}'" @@ -123,10 +130,25 @@ class TestFile: assert file_dict["file_path"] == file.file_path assert file_dict["file_size"] == file.file_size - @pytest.mark.flaky(3, 1) - async def test_error_get_empty_file_id(self, bot): - with pytest.raises(TelegramError): - await bot.get_file(file_id="") + def test_equality(self, bot): + a = File(self.file_id, self.file_unique_id, bot) + b = File("", self.file_unique_id, bot) + c = File(self.file_id, self.file_unique_id, None) + d = File("", "", bot) + e = Voice(self.file_id, self.file_unique_id, 0) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a == c + assert hash(a) == hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) async def test_download(self, monkeypatch, file): async def test(*args, **kwargs): @@ -140,9 +162,6 @@ class TestFile: finally: out_file.unlink() - async def test_download_local_file(self, local_file): - assert await local_file.download_to_drive() == Path(local_file.file_path) - @pytest.mark.parametrize( "custom_path_type", [str, Path], ids=["str custom_path", "pathlib.Path custom_path"] ) @@ -161,20 +180,6 @@ class TestFile: os.close(file_handle) custom_path.unlink() - @pytest.mark.parametrize( - "custom_path_type", [str, Path], ids=["str custom_path", "pathlib.Path custom_path"] - ) - async def test_download_custom_path_local_file(self, local_file, custom_path_type): - file_handle, custom_path = mkstemp() - custom_path = Path(custom_path) - try: - out_file = await local_file.download_to_drive(custom_path_type(custom_path)) - assert out_file == custom_path - assert out_file.read_bytes() == self.file_content - finally: - os.close(file_handle) - custom_path.unlink() - async def test_download_no_filename(self, monkeypatch, file): async def test(*args, **kwargs): return self.file_content @@ -200,12 +205,6 @@ class TestFile: custom_fobj.seek(0) assert custom_fobj.read() == self.file_content - async def test_download_file_obj_local_file(self, local_file): - with TemporaryFile() as custom_fobj: - await local_file.download_to_memory(out=custom_fobj) - custom_fobj.seek(0) - assert custom_fobj.read() == self.file_content - async def test_download_bytearray(self, monkeypatch, file): async def test(*args, **kwargs): return self.file_content @@ -223,18 +222,6 @@ class TestFile: assert buf2[len(buf) :] == buf assert buf2[: len(buf)] == buf - async def test_download_bytearray_local_file(self, local_file): - # Check that a download to a newly allocated bytearray works. - buf = await local_file.download_as_bytearray() - assert buf == bytearray(self.file_content) - - # Check that a download to a given bytearray works (extends the bytearray). - buf2 = buf[:] - buf3 = await local_file.download_as_bytearray(buf=buf2) - assert buf3 is buf2 - assert buf2[len(buf) :] == buf - assert buf2[: len(buf)] == buf - async def test_download_encrypted(self, monkeypatch, bot, encrypted_file): async def test(*args, **kwargs): return data_file("image_encrypted.jpg").read_bytes() @@ -257,29 +244,6 @@ class TestFile: custom_fobj.seek(0) assert custom_fobj.read() == data_file("image_decrypted.jpg").read_bytes() - async def test_download_local_file_encrypted(self, encrypted_local_file): - out_file = await encrypted_local_file.download_to_drive() - try: - assert out_file.read_bytes() == data_file("image_decrypted.jpg").read_bytes() - finally: - out_file.unlink() - - @pytest.mark.parametrize( - "custom_path_type", [str, Path], ids=["str custom_path", "pathlib.Path custom_path"] - ) - async def test_download_custom_path_local_file_encrypted( - self, encrypted_local_file, custom_path_type - ): - file_handle, custom_path = mkstemp() - custom_path = Path(custom_path) - try: - out_file = await encrypted_local_file.download_to_drive(custom_path_type(custom_path)) - assert out_file == custom_path - assert out_file.read_bytes() == data_file("image_decrypted.jpg").read_bytes() - finally: - os.close(file_handle) - custom_path.unlink() - async def test_download_file_obj_local_file_encrypted(self, monkeypatch, encrypted_local_file): async def test(*args, **kwargs): return data_file("image_encrypted.jpg").read_bytes() @@ -307,6 +271,70 @@ class TestFile: assert buf2[len(buf) :] == buf assert buf2[: len(buf)] == buf + +class TestFileWithRequest(TestFileBase): + async def test_error_get_empty_file_id(self, bot): + with pytest.raises(TelegramError): + await bot.get_file(file_id="") + + async def test_download_local_file(self, local_file): + assert await local_file.download_to_drive() == Path(local_file.file_path) + + @pytest.mark.parametrize( + "custom_path_type", [str, Path], ids=["str custom_path", "pathlib.Path custom_path"] + ) + async def test_download_custom_path_local_file(self, local_file, custom_path_type): + file_handle, custom_path = mkstemp() + custom_path = Path(custom_path) + try: + out_file = await local_file.download_to_drive(custom_path_type(custom_path)) + assert out_file == custom_path + assert out_file.read_bytes() == self.file_content + finally: + os.close(file_handle) + custom_path.unlink() + + async def test_download_file_obj_local_file(self, local_file): + with TemporaryFile() as custom_fobj: + await local_file.download_to_memory(out=custom_fobj) + custom_fobj.seek(0) + assert custom_fobj.read() == self.file_content + + @pytest.mark.parametrize( + "custom_path_type", [str, Path], ids=["str custom_path", "pathlib.Path custom_path"] + ) + async def test_download_custom_path_local_file_encrypted( + self, encrypted_local_file, custom_path_type + ): + file_handle, custom_path = mkstemp() + custom_path = Path(custom_path) + try: + out_file = await encrypted_local_file.download_to_drive(custom_path_type(custom_path)) + assert out_file == custom_path + assert out_file.read_bytes() == data_file("image_decrypted.jpg").read_bytes() + finally: + os.close(file_handle) + custom_path.unlink() + + async def test_download_local_file_encrypted(self, encrypted_local_file): + out_file = await encrypted_local_file.download_to_drive() + try: + assert out_file.read_bytes() == data_file("image_decrypted.jpg").read_bytes() + finally: + out_file.unlink() + + async def test_download_bytearray_local_file(self, local_file): + # Check that a download to a newly allocated bytearray works. + buf = await local_file.download_as_bytearray() + assert buf == bytearray(self.file_content) + + # Check that a download to a given bytearray works (extends the bytearray). + buf2 = buf[:] + buf3 = await local_file.download_as_bytearray(buf=buf2) + assert buf3 is buf2 + assert buf2[len(buf) :] == buf + assert buf2[: len(buf)] == buf + async def test_download_bytearray_local_file_encrypted(self, encrypted_local_file): # Check that a download to a newly allocated bytearray works. buf = await encrypted_local_file.download_as_bytearray() @@ -318,23 +346,3 @@ class TestFile: assert buf3 is buf2 assert buf2[len(buf) :] == buf assert buf2[: len(buf)] == buf - - def test_equality(self, bot): - a = File(self.file_id, self.file_unique_id, bot) - b = File("", self.file_unique_id, bot) - c = File(self.file_id, self.file_unique_id, None) - d = File("", "", bot) - e = Voice(self.file_id, self.file_unique_id, 0) - - assert a == b - assert hash(a) == hash(b) - assert a is not b - - assert a == c - assert hash(a) == hash(c) - - assert a != d - assert hash(a) != hash(d) - - assert a != e - assert hash(a) != hash(e) diff --git a/tests/test_forcereply.py b/tests/test_forcereply.py index e847fbcd7..18822a33f 100644 --- a/tests/test_forcereply.py +++ b/tests/test_forcereply.py @@ -22,30 +22,23 @@ import pytest from telegram import ForceReply, ReplyKeyboardRemove -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def force_reply(): - return ForceReply( - TestForceReply.selective, - TestForceReply.input_field_placeholder, - ) + return ForceReply(TestForceReplyBase.selective, TestForceReplyBase.input_field_placeholder) -class TestForceReply: +class TestForceReplyBase: force_reply = True selective = True input_field_placeholder = "force replies can be annoying if not used properly" + +class TestForceReplyWithoutRequest(TestForceReplyBase): def test_slot_behaviour(self, force_reply, mro_slots): for attr in force_reply.__slots__: assert getattr(force_reply, attr, "err") != "err", f"got extra slot '{attr}'" assert len(mro_slots(force_reply)) == len(set(mro_slots(force_reply))), "duplicate slot" - @pytest.mark.flaky(3, 1) - async def test_send_message_with_force_reply(self, bot, chat_id, force_reply): - message = await bot.send_message(chat_id, "text", reply_markup=force_reply) - - assert message.text == "text" - def test_expected(self, force_reply): assert force_reply.force_reply == self.force_reply assert force_reply.selective == self.selective @@ -73,3 +66,9 @@ class TestForceReply: assert a != d assert hash(a) != hash(d) + + +class TestForceReplyWithRequest(TestForceReplyBase): + async def test_send_message_with_force_reply(self, bot, chat_id, force_reply): + message = await bot.send_message(chat_id, "text", reply_markup=force_reply) + assert message.text == "text" diff --git a/tests/test_forum.py b/tests/test_forum.py index 5cbadc018..67001ae6c 100644 --- a/tests/test_forum.py +++ b/tests/test_forum.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio import datetime import pytest @@ -44,7 +45,7 @@ async def emoji_id(bot): return first_sticker.custom_emoji_id -@pytest.fixture +@pytest.fixture(scope="module") async def forum_topic_object(forum_group_id, emoji_id): return ForumTopic( message_thread_id=forum_group_id, @@ -54,7 +55,7 @@ async def forum_topic_object(forum_group_id, emoji_id): ) -@pytest.fixture +@pytest.fixture(scope="function") async def real_topic(bot, emoji_id, forum_group_id): result = await bot.create_forum_topic( chat_id=forum_group_id, @@ -71,13 +72,12 @@ async def real_topic(bot, emoji_id, forum_group_id): assert result is True, "Topic was not deleted" -class TestForumTopic: +class TestForumTopicWithoutRequest: def test_slot_behaviour(self, mro_slots, forum_topic_object): - for attr in forum_topic_object.__slots__: - assert getattr(forum_topic_object, attr, "err") != "err", f"got extra slot '{attr}'" - assert len(mro_slots(forum_topic_object)) == len( - set(mro_slots(forum_topic_object)) - ), "duplicate slot" + inst = forum_topic_object + 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" async def test_expected_values(self, emoji_id, forum_group_id, forum_topic_object): assert forum_topic_object.message_thread_id == forum_group_id @@ -152,8 +152,7 @@ class TestForumTopic: assert hash(a) != hash(e) -@pytest.mark.flaky(3, 1) -class TestForumMethods: +class TestForumMethodsWithRequest: async def test_create_forum_topic(self, real_topic): result = real_topic assert isinstance(result, ForumTopic) @@ -237,22 +236,20 @@ class TestForumMethods: async def test_unpin_all_forum_topic_messages(self, bot, forum_group_id, real_topic): message_thread_id = real_topic.message_thread_id + pin_msg_tasks = set() - msgs = [ - await ( - await bot.send_message( - chat_id=forum_group_id, text=TEST_MSG_TEXT, message_thread_id=message_thread_id - ) - ).pin() + awaitables = { + bot.send_message(forum_group_id, TEST_MSG_TEXT, message_thread_id=message_thread_id) for _ in range(2) - ] + } + for coro in asyncio.as_completed(awaitables): + msg = await coro + pin_msg_tasks.add(asyncio.create_task(msg.pin())) - assert all(msgs) is True, "Message(s) were not pinned" + assert all([await task for task in pin_msg_tasks]) is True, "Message(s) were not pinned" # We need 2 or more pinned msgs for this to work, else we get Chat_not_modified error - result = await bot.unpin_all_forum_topic_messages( - chat_id=forum_group_id, message_thread_id=message_thread_id - ) + result = await bot.unpin_all_forum_topic_messages(forum_group_id, message_thread_id) assert result is True, "Failed to unpin all the messages in forum topic" async def test_edit_general_forum_topic(self, bot, forum_group_id): @@ -304,12 +301,12 @@ class TestForumMethods: assert result is True, "Failed to reopen general forum topic" -@pytest.fixture +@pytest.fixture(scope="module") def topic_created(): return ForumTopicCreated(name=TEST_TOPIC_NAME, icon_color=TEST_TOPIC_ICON_COLOR) -class TestForumTopicCreated: +class TestForumTopicCreatedWithoutRequest: def test_slot_behaviour(self, topic_created, mro_slots): for attr in topic_created.__slots__: assert getattr(topic_created, attr, "err") != "err", f"got extra slot '{attr}'" @@ -358,7 +355,7 @@ class TestForumTopicCreated: assert hash(a) != hash(d) -class TestForumTopicClosed: +class TestForumTopicClosedWithoutRequest: def test_slot_behaviour(self, mro_slots): action = ForumTopicClosed() for attr in action.__slots__: @@ -376,7 +373,7 @@ class TestForumTopicClosed: assert action_dict == {} -class TestForumTopicReopened: +class TestForumTopicReopenedWithoutRequest: def test_slot_behaviour(self, mro_slots): action = ForumTopicReopened() for attr in action.__slots__: diff --git a/tests/test_game.py b/tests/test_game.py index a2ee9eae5..1ad8f4d16 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -22,21 +22,21 @@ import pytest from telegram import Animation, Game, MessageEntity, PhotoSize -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def game(): game = Game( - TestGame.title, - TestGame.description, - TestGame.photo, - text=TestGame.text, - text_entities=TestGame.text_entities, - animation=TestGame.animation, + TestGameBase.title, + TestGameBase.description, + TestGameBase.photo, + text=TestGameBase.text, + text_entities=TestGameBase.text_entities, + animation=TestGameBase.animation, ) game._unfreeze() return game -class TestGame: +class TestGameBase: title = "Python-telegram-bot Test Game" description = "description" photo = [PhotoSize("Blah", "ElseBlah", 640, 360, file_size=0)] @@ -47,6 +47,8 @@ class TestGame: text_entities = [MessageEntity(13, 17, MessageEntity.URL)] animation = Animation("blah", "unique_id", 320, 180, 1) + +class TestGameWithoutRequest(TestGameBase): def test_slot_behaviour(self, game, mro_slots): for attr in game.__slots__: assert getattr(game, attr, "err") != "err", f"got extra slot '{attr}'" @@ -95,20 +97,6 @@ class TestGame: assert game_dict["text_entities"] == [game.text_entities[0].to_dict()] assert game_dict["animation"] == game.animation.to_dict() - def test_parse_entity(self, game): - entity = MessageEntity(type=MessageEntity.URL, offset=13, length=17) - game.text_entities = [entity] - - assert game.parse_text_entity(entity) == "http://google.com" - - def test_parse_entities(self, game): - entity = MessageEntity(type=MessageEntity.URL, offset=13, length=17) - entity_2 = MessageEntity(type=MessageEntity.BOLD, offset=13, length=1) - game.text_entities = [entity_2, entity] - - assert game.parse_text_entities(MessageEntity.URL) == {entity: "http://google.com"} - assert game.parse_text_entities() == {entity: "http://google.com", entity_2: "h"} - def test_equality(self): a = Game("title", "description", [PhotoSize("Blah", "unique_id", 640, 360, file_size=0)]) b = Game( @@ -133,3 +121,17 @@ class TestGame: assert a != d assert hash(a) != hash(d) + + def test_parse_entity(self, game): + entity = MessageEntity(type=MessageEntity.URL, offset=13, length=17) + game.text_entities = [entity] + + assert game.parse_text_entity(entity) == "http://google.com" + + def test_parse_entities(self, game): + entity = MessageEntity(type=MessageEntity.URL, offset=13, length=17) + entity_2 = MessageEntity(type=MessageEntity.BOLD, offset=13, length=1) + game.text_entities = [entity_2, entity] + + assert game.parse_text_entities(MessageEntity.URL) == {entity: "http://google.com"} + assert game.parse_text_entities() == {entity: "http://google.com", entity_2: "h"} diff --git a/tests/test_gamehighscore.py b/tests/test_gamehighscore.py index 4d27698cb..1556a67d4 100644 --- a/tests/test_gamehighscore.py +++ b/tests/test_gamehighscore.py @@ -22,25 +22,31 @@ import pytest from telegram import GameHighScore, User -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def game_highscore(): return GameHighScore( - TestGameHighScore.position, TestGameHighScore.user, TestGameHighScore.score + TestGameHighScoreBase.position, TestGameHighScoreBase.user, TestGameHighScoreBase.score ) -class TestGameHighScore: +class TestGameHighScoreBase: position = 12 user = User(2, "test user", False) score = 42 + +class TestGameHighScoreWithoutRequest(TestGameHighScoreBase): def test_slot_behaviour(self, game_highscore, mro_slots): for attr in game_highscore.__slots__: assert getattr(game_highscore, attr, "err") != "err", f"got extra slot '{attr}'" assert len(mro_slots(game_highscore)) == len(set(mro_slots(game_highscore))), "same slot" def test_de_json(self, bot): - json_dict = {"position": self.position, "user": self.user.to_dict(), "score": self.score} + json_dict = { + "position": self.position, + "user": self.user.to_dict(), + "score": self.score, + } highscore = GameHighScore.de_json(json_dict, bot) assert highscore.api_kwargs == {} diff --git a/tests/test_inlinekeyboardbutton.py b/tests/test_inlinekeyboardbutton.py index e1478c9e9..d9783dcd0 100644 --- a/tests/test_inlinekeyboardbutton.py +++ b/tests/test_inlinekeyboardbutton.py @@ -22,22 +22,22 @@ import pytest from telegram import CallbackGame, InlineKeyboardButton, LoginUrl, WebAppInfo -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_keyboard_button(): return InlineKeyboardButton( - TestInlineKeyboardButton.text, - url=TestInlineKeyboardButton.url, - callback_data=TestInlineKeyboardButton.callback_data, - switch_inline_query=TestInlineKeyboardButton.switch_inline_query, - switch_inline_query_current_chat=TestInlineKeyboardButton.switch_inline_query_current_chat, - callback_game=TestInlineKeyboardButton.callback_game, - pay=TestInlineKeyboardButton.pay, - login_url=TestInlineKeyboardButton.login_url, - web_app=TestInlineKeyboardButton.web_app, + TestInlineKeyboardButtonBase.text, + url=TestInlineKeyboardButtonBase.url, + callback_data=TestInlineKeyboardButtonBase.callback_data, + switch_inline_query=TestInlineKeyboardButtonBase.switch_inline_query, + switch_inline_query_current_chat=TestInlineKeyboardButtonBase.switch_inline_query_current_chat, # noqa: E501 + callback_game=TestInlineKeyboardButtonBase.callback_game, + pay=TestInlineKeyboardButtonBase.pay, + login_url=TestInlineKeyboardButtonBase.login_url, + web_app=TestInlineKeyboardButtonBase.web_app, ) -class TestInlineKeyboardButton: +class TestInlineKeyboardButtonBase: text = "text" url = "url" callback_data = "callback data" @@ -48,6 +48,8 @@ class TestInlineKeyboardButton: login_url = LoginUrl("http://google.com") web_app = WebAppInfo(url="https://example.com") + +class TestInlineKeyboardButtonWithoutRequest(TestInlineKeyboardButtonBase): def test_slot_behaviour(self, inline_keyboard_button, mro_slots): inst = inline_keyboard_button for attr in inst.__slots__: diff --git a/tests/test_inlinekeyboardmarkup.py b/tests/test_inlinekeyboardmarkup.py index 7f0301d21..8e9a7e33c 100644 --- a/tests/test_inlinekeyboardmarkup.py +++ b/tests/test_inlinekeyboardmarkup.py @@ -28,12 +28,12 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_keyboard_markup(): - return InlineKeyboardMarkup(TestInlineKeyboardMarkup.inline_keyboard) + return InlineKeyboardMarkup(TestInlineKeyboardMarkupBase.inline_keyboard) -class TestInlineKeyboardMarkup: +class TestInlineKeyboardMarkupBase: inline_keyboard = [ [ InlineKeyboardButton(text="button1", callback_data="data1"), @@ -41,104 +41,14 @@ class TestInlineKeyboardMarkup: ] ] + +class TestInlineKeyboardMarkupWithoutRequest(TestInlineKeyboardMarkupBase): def test_slot_behaviour(self, inline_keyboard_markup, mro_slots): inst = inline_keyboard_markup 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" - @pytest.mark.flaky(3, 1) - async def test_send_message_with_inline_keyboard_markup( - self, bot, chat_id, inline_keyboard_markup - ): - message = await bot.send_message( - chat_id, "Testing InlineKeyboardMarkup", reply_markup=inline_keyboard_markup - ) - - assert message.text == "Testing InlineKeyboardMarkup" - - def test_from_button(self): - inline_keyboard_markup = InlineKeyboardMarkup.from_button( - InlineKeyboardButton(text="button1", callback_data="data1") - ).inline_keyboard - assert len(inline_keyboard_markup) == 1 - assert len(inline_keyboard_markup[0]) == 1 - - def test_from_row(self): - inline_keyboard_markup = InlineKeyboardMarkup.from_row( - [ - InlineKeyboardButton(text="button1", callback_data="data1"), - InlineKeyboardButton(text="button1", callback_data="data1"), - ] - ).inline_keyboard - assert len(inline_keyboard_markup) == 1 - assert len(inline_keyboard_markup[0]) == 2 - - def test_from_column(self): - inline_keyboard_markup = InlineKeyboardMarkup.from_column( - [ - InlineKeyboardButton(text="button1", callback_data="data1"), - InlineKeyboardButton(text="button1", callback_data="data1"), - ] - ).inline_keyboard - assert len(inline_keyboard_markup) == 2 - assert len(inline_keyboard_markup[0]) == 1 - assert len(inline_keyboard_markup[1]) == 1 - - def test_expected_values(self, inline_keyboard_markup): - assert inline_keyboard_markup.inline_keyboard == tuple( - tuple(row) for row in self.inline_keyboard - ) - - def test_wrong_keyboard_inputs(self): - with pytest.raises(ValueError): - InlineKeyboardMarkup( - [[InlineKeyboardButton("b1", "1")], InlineKeyboardButton("b2", "2")] - ) - with pytest.raises(ValueError): - InlineKeyboardMarkup("strings_are_not_allowed") - with pytest.raises(ValueError): - InlineKeyboardMarkup(["strings_are_not_allowed_in_the_rows_either"]) - with pytest.raises(ValueError): - InlineKeyboardMarkup(InlineKeyboardButton("b1", "1")) - with pytest.raises(ValueError): - InlineKeyboardMarkup([[[InlineKeyboardButton("only_2d_array_is_allowed", "1")]]]) - - async def test_expected_values_empty_switch(self, inline_keyboard_markup, bot, monkeypatch): - async def make_assertion( - url, - data, - reply_to_message_id=None, - disable_notification=None, - reply_markup=None, - timeout=None, - **kwargs, - ): - if reply_markup is not None: - markups = ( - InlineKeyboardMarkup, - ReplyKeyboardMarkup, - ForceReply, - ReplyKeyboardRemove, - ) - if isinstance(reply_markup, markups): - data["reply_markup"] = reply_markup.to_dict() - else: - data["reply_markup"] = reply_markup - - assert bool("'switch_inline_query': ''" in str(data["reply_markup"])) - assert bool("'switch_inline_query_current_chat': ''" in str(data["reply_markup"])) - - inline_keyboard_markup.inline_keyboard[0][0]._unfreeze() - inline_keyboard_markup.inline_keyboard[0][0].callback_data = None - inline_keyboard_markup.inline_keyboard[0][0].switch_inline_query = "" - inline_keyboard_markup.inline_keyboard[0][1]._unfreeze() - inline_keyboard_markup.inline_keyboard[0][1].callback_data = None - inline_keyboard_markup.inline_keyboard[0][1].switch_inline_query_current_chat = "" - - monkeypatch.setattr(bot, "_send_message", make_assertion) - await bot.send_message(123, "test", reply_markup=inline_keyboard_markup) - def test_to_dict(self, inline_keyboard_markup): inline_keyboard_markup_dict = inline_keyboard_markup.to_dict() @@ -233,3 +143,96 @@ class TestInlineKeyboardMarkup: assert a != g assert hash(a) != hash(g) + + def test_from_button(self): + inline_keyboard_markup = InlineKeyboardMarkup.from_button( + InlineKeyboardButton(text="button1", callback_data="data1") + ).inline_keyboard + assert len(inline_keyboard_markup) == 1 + assert len(inline_keyboard_markup[0]) == 1 + + def test_from_row(self): + inline_keyboard_markup = InlineKeyboardMarkup.from_row( + [ + InlineKeyboardButton(text="button1", callback_data="data1"), + InlineKeyboardButton(text="button1", callback_data="data1"), + ] + ).inline_keyboard + assert len(inline_keyboard_markup) == 1 + assert len(inline_keyboard_markup[0]) == 2 + + def test_from_column(self): + inline_keyboard_markup = InlineKeyboardMarkup.from_column( + [ + InlineKeyboardButton(text="button1", callback_data="data1"), + InlineKeyboardButton(text="button1", callback_data="data1"), + ] + ).inline_keyboard + assert len(inline_keyboard_markup) == 2 + assert len(inline_keyboard_markup[0]) == 1 + assert len(inline_keyboard_markup[1]) == 1 + + def test_expected_values(self, inline_keyboard_markup): + assert inline_keyboard_markup.inline_keyboard == tuple( + tuple(row) for row in self.inline_keyboard + ) + + def test_wrong_keyboard_inputs(self): + with pytest.raises(ValueError): + InlineKeyboardMarkup( + [[InlineKeyboardButton("b1", "1")], InlineKeyboardButton("b2", "2")] + ) + with pytest.raises(ValueError): + InlineKeyboardMarkup("strings_are_not_allowed") + with pytest.raises(ValueError): + InlineKeyboardMarkup(["strings_are_not_allowed_in_the_rows_either"]) + with pytest.raises(ValueError): + InlineKeyboardMarkup(InlineKeyboardButton("b1", "1")) + with pytest.raises(ValueError): + InlineKeyboardMarkup([[[InlineKeyboardButton("only_2d_array_is_allowed", "1")]]]) + + async def test_expected_values_empty_switch(self, inline_keyboard_markup, bot, monkeypatch): + async def make_assertion( + url, + data, + reply_to_message_id=None, + disable_notification=None, + reply_markup=None, + timeout=None, + **kwargs, + ): + if reply_markup is not None: + markups = ( + InlineKeyboardMarkup, + ReplyKeyboardMarkup, + ForceReply, + ReplyKeyboardRemove, + ) + if isinstance(reply_markup, markups): + data["reply_markup"] = reply_markup.to_dict() + else: + data["reply_markup"] = reply_markup + + assert bool("'switch_inline_query': ''" in str(data["reply_markup"])) + assert bool("'switch_inline_query_current_chat': ''" in str(data["reply_markup"])) + + inline_keyboard_markup.inline_keyboard[0][0]._unfreeze() + inline_keyboard_markup.inline_keyboard[0][0].callback_data = None + inline_keyboard_markup.inline_keyboard[0][0].switch_inline_query = "" + inline_keyboard_markup.inline_keyboard[0][1]._unfreeze() + inline_keyboard_markup.inline_keyboard[0][1].callback_data = None + inline_keyboard_markup.inline_keyboard[0][1].switch_inline_query_current_chat = "" + + monkeypatch.setattr(bot, "_send_message", make_assertion) + await bot.send_message(123, "test", reply_markup=inline_keyboard_markup) + + +class TestInlineKeyborardMarkupWithRequest(TestInlineKeyboardMarkupBase): + async def test_send_message_with_inline_keyboard_markup( + self, bot, chat_id, inline_keyboard_markup + ): + message = await bot.send_message( + chat_id, "Testing InlineKeyboardMarkup", reply_markup=inline_keyboard_markup + ) + + assert message.text == "Testing InlineKeyboardMarkup" diff --git a/tests/test_inlinequery.py b/tests/test_inlinequery.py index c4cd2d673..7b79a998f 100644 --- a/tests/test_inlinequery.py +++ b/tests/test_inlinequery.py @@ -27,26 +27,28 @@ from tests.auxil.bot_method_checks import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query(bot): ilq = InlineQuery( - TestInlineQuery.id_, - TestInlineQuery.from_user, - TestInlineQuery.query, - TestInlineQuery.offset, - location=TestInlineQuery.location, + TestInlineQueryBase.id_, + TestInlineQueryBase.from_user, + TestInlineQueryBase.query, + TestInlineQueryBase.offset, + location=TestInlineQueryBase.location, ) ilq.set_bot(bot) return ilq -class TestInlineQuery: +class TestInlineQueryBase: id_ = 1234 from_user = User(1, "First name", False) query = "query text" offset = "offset" location = Location(8.8, 53.1) + +class TestInlineQueryWithoutRequest(TestInlineQueryBase): def test_slot_behaviour(self, inline_query, mro_slots): for attr in inline_query.__slots__: assert getattr(inline_query, attr, "err") != "err", f"got extra slot '{attr}'" @@ -79,34 +81,6 @@ class TestInlineQuery: assert inline_query_dict["query"] == inline_query.query assert inline_query_dict["offset"] == inline_query.offset - async def test_answer(self, monkeypatch, inline_query): - async def make_assertion(*_, **kwargs): - return kwargs["inline_query_id"] == inline_query.id - - assert check_shortcut_signature( - InlineQuery.answer, Bot.answer_inline_query, ["inline_query_id"], ["auto_pagination"] - ) - assert await check_shortcut_call( - inline_query.answer, inline_query.get_bot(), "answer_inline_query" - ) - assert await check_defaults_handling(inline_query.answer, inline_query.get_bot()) - - monkeypatch.setattr(inline_query.get_bot(), "answer_inline_query", make_assertion) - assert await inline_query.answer(results=[]) - - async def test_answer_error(self, inline_query): - with pytest.raises(ValueError, match="mutually exclusive"): - await inline_query.answer(results=[], auto_pagination=True, current_offset="foobar") - - async def test_answer_auto_pagination(self, monkeypatch, inline_query): - async def make_assertion(*_, **kwargs): - inline_query_id_matches = kwargs["inline_query_id"] == inline_query.id - offset_matches = kwargs.get("current_offset") == inline_query.offset - return offset_matches and inline_query_id_matches - - monkeypatch.setattr(inline_query.get_bot(), "answer_inline_query", make_assertion) - assert await inline_query.answer(results=[], auto_pagination=True) - def test_equality(self): a = InlineQuery(self.id_, User(1, "", False), "", "") b = InlineQuery(self.id_, User(1, "", False), "", "") @@ -126,3 +100,31 @@ class TestInlineQuery: assert a != e assert hash(a) != hash(e) + + async def test_answer_error(self, inline_query): + with pytest.raises(ValueError, match="mutually exclusive"): + await inline_query.answer(results=[], auto_pagination=True, current_offset="foobar") + + async def test_answer(self, monkeypatch, inline_query): + async def make_assertion(*_, **kwargs): + return kwargs["inline_query_id"] == inline_query.id + + assert check_shortcut_signature( + InlineQuery.answer, Bot.answer_inline_query, ["inline_query_id"], ["auto_pagination"] + ) + assert await check_shortcut_call( + inline_query.answer, inline_query.get_bot(), "answer_inline_query" + ) + assert await check_defaults_handling(inline_query.answer, inline_query.get_bot()) + + monkeypatch.setattr(inline_query.get_bot(), "answer_inline_query", make_assertion) + assert await inline_query.answer(results=[]) + + async def test_answer_auto_pagination(self, monkeypatch, inline_query): + async def make_assertion(*_, **kwargs): + inline_query_id_matches = kwargs["inline_query_id"] == inline_query.id + offset_matches = kwargs.get("current_offset") == inline_query.offset + return offset_matches and inline_query_id_matches + + monkeypatch.setattr(inline_query.get_bot(), "answer_inline_query", make_assertion) + assert await inline_query.answer(results=[], auto_pagination=True) diff --git a/tests/test_inlinequeryresultarticle.py b/tests/test_inlinequeryresultarticle.py index de8698201..1d4ebce80 100644 --- a/tests/test_inlinequeryresultarticle.py +++ b/tests/test_inlinequeryresultarticle.py @@ -28,23 +28,23 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_article(): return InlineQueryResultArticle( - TestInlineQueryResultArticle.id_, - TestInlineQueryResultArticle.title, - input_message_content=TestInlineQueryResultArticle.input_message_content, - reply_markup=TestInlineQueryResultArticle.reply_markup, - url=TestInlineQueryResultArticle.url, - hide_url=TestInlineQueryResultArticle.hide_url, - description=TestInlineQueryResultArticle.description, - thumb_url=TestInlineQueryResultArticle.thumb_url, - thumb_height=TestInlineQueryResultArticle.thumb_height, - thumb_width=TestInlineQueryResultArticle.thumb_width, + TestInlineQueryResultArticleBase.id_, + TestInlineQueryResultArticleBase.title, + input_message_content=TestInlineQueryResultArticleBase.input_message_content, + reply_markup=TestInlineQueryResultArticleBase.reply_markup, + url=TestInlineQueryResultArticleBase.url, + hide_url=TestInlineQueryResultArticleBase.hide_url, + description=TestInlineQueryResultArticleBase.description, + thumb_url=TestInlineQueryResultArticleBase.thumb_url, + thumb_height=TestInlineQueryResultArticleBase.thumb_height, + thumb_width=TestInlineQueryResultArticleBase.thumb_width, ) -class TestInlineQueryResultArticle: +class TestInlineQueryResultArticleBase: id_ = "id" type_ = "article" title = "title" @@ -57,6 +57,8 @@ class TestInlineQueryResultArticle: thumb_height = 10 thumb_width = 15 + +class TestInlineQueryResultArticleWithoutRequest(TestInlineQueryResultArticleBase): def test_slot_behaviour(self, inline_query_result_article, mro_slots, recwarn): inst = inline_query_result_article for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultaudio.py b/tests/test_inlinequeryresultaudio.py index 0ae44c799..fd5fb1570 100644 --- a/tests/test_inlinequeryresultaudio.py +++ b/tests/test_inlinequeryresultaudio.py @@ -29,23 +29,23 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_audio(): return InlineQueryResultAudio( - TestInlineQueryResultAudio.id_, - TestInlineQueryResultAudio.audio_url, - TestInlineQueryResultAudio.title, - performer=TestInlineQueryResultAudio.performer, - audio_duration=TestInlineQueryResultAudio.audio_duration, - caption=TestInlineQueryResultAudio.caption, - parse_mode=TestInlineQueryResultAudio.parse_mode, - caption_entities=TestInlineQueryResultAudio.caption_entities, - input_message_content=TestInlineQueryResultAudio.input_message_content, - reply_markup=TestInlineQueryResultAudio.reply_markup, + TestInlineQueryResultAudioBase.id_, + TestInlineQueryResultAudioBase.audio_url, + TestInlineQueryResultAudioBase.title, + performer=TestInlineQueryResultAudioBase.performer, + audio_duration=TestInlineQueryResultAudioBase.audio_duration, + caption=TestInlineQueryResultAudioBase.caption, + parse_mode=TestInlineQueryResultAudioBase.parse_mode, + caption_entities=TestInlineQueryResultAudioBase.caption_entities, + input_message_content=TestInlineQueryResultAudioBase.input_message_content, + reply_markup=TestInlineQueryResultAudioBase.reply_markup, ) -class TestInlineQueryResultAudio: +class TestInlineQueryResultAudioBase: id_ = "id" type_ = "audio" audio_url = "audio url" @@ -58,6 +58,8 @@ class TestInlineQueryResultAudio: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultAudioWithoutRequest(TestInlineQueryResultAudioBase): def test_slot_behaviour(self, inline_query_result_audio, mro_slots): inst = inline_query_result_audio for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultcachedaudio.py b/tests/test_inlinequeryresultcachedaudio.py index 9ce63db5c..672346613 100644 --- a/tests/test_inlinequeryresultcachedaudio.py +++ b/tests/test_inlinequeryresultcachedaudio.py @@ -29,20 +29,20 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_cached_audio(): return InlineQueryResultCachedAudio( - TestInlineQueryResultCachedAudio.id_, - TestInlineQueryResultCachedAudio.audio_file_id, - caption=TestInlineQueryResultCachedAudio.caption, - parse_mode=TestInlineQueryResultCachedAudio.parse_mode, - caption_entities=TestInlineQueryResultCachedAudio.caption_entities, - input_message_content=TestInlineQueryResultCachedAudio.input_message_content, - reply_markup=TestInlineQueryResultCachedAudio.reply_markup, + TestInlineQueryResultCachedAudioBase.id_, + TestInlineQueryResultCachedAudioBase.audio_file_id, + caption=TestInlineQueryResultCachedAudioBase.caption, + parse_mode=TestInlineQueryResultCachedAudioBase.parse_mode, + caption_entities=TestInlineQueryResultCachedAudioBase.caption_entities, + input_message_content=TestInlineQueryResultCachedAudioBase.input_message_content, + reply_markup=TestInlineQueryResultCachedAudioBase.reply_markup, ) -class TestInlineQueryResultCachedAudio: +class TestInlineQueryResultCachedAudioBase: id_ = "id" type_ = "audio" audio_file_id = "audio file id" @@ -52,6 +52,8 @@ class TestInlineQueryResultCachedAudio: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultCachedAudioWithoutRequest(TestInlineQueryResultCachedAudioBase): def test_slot_behaviour(self, inline_query_result_cached_audio, mro_slots): inst = inline_query_result_cached_audio for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultcacheddocument.py b/tests/test_inlinequeryresultcacheddocument.py index e98c3dbe3..6b47eb7e7 100644 --- a/tests/test_inlinequeryresultcacheddocument.py +++ b/tests/test_inlinequeryresultcacheddocument.py @@ -29,22 +29,22 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_cached_document(): return InlineQueryResultCachedDocument( - TestInlineQueryResultCachedDocument.id_, - TestInlineQueryResultCachedDocument.title, - TestInlineQueryResultCachedDocument.document_file_id, - caption=TestInlineQueryResultCachedDocument.caption, - parse_mode=TestInlineQueryResultCachedDocument.parse_mode, - caption_entities=TestInlineQueryResultCachedDocument.caption_entities, - description=TestInlineQueryResultCachedDocument.description, - input_message_content=TestInlineQueryResultCachedDocument.input_message_content, - reply_markup=TestInlineQueryResultCachedDocument.reply_markup, + TestInlineQueryResultCachedDocumentBase.id_, + TestInlineQueryResultCachedDocumentBase.title, + TestInlineQueryResultCachedDocumentBase.document_file_id, + caption=TestInlineQueryResultCachedDocumentBase.caption, + parse_mode=TestInlineQueryResultCachedDocumentBase.parse_mode, + caption_entities=TestInlineQueryResultCachedDocumentBase.caption_entities, + description=TestInlineQueryResultCachedDocumentBase.description, + input_message_content=TestInlineQueryResultCachedDocumentBase.input_message_content, + reply_markup=TestInlineQueryResultCachedDocumentBase.reply_markup, ) -class TestInlineQueryResultCachedDocument: +class TestInlineQueryResultCachedDocumentBase: id_ = "id" type_ = "document" document_file_id = "document file id" @@ -56,6 +56,8 @@ class TestInlineQueryResultCachedDocument: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultCachedDocumentWithoutRequest(TestInlineQueryResultCachedDocumentBase): def test_slot_behaviour(self, inline_query_result_cached_document, mro_slots): inst = inline_query_result_cached_document for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultcachedgif.py b/tests/test_inlinequeryresultcachedgif.py index edc29abf4..34f26df73 100644 --- a/tests/test_inlinequeryresultcachedgif.py +++ b/tests/test_inlinequeryresultcachedgif.py @@ -28,21 +28,21 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_cached_gif(): return InlineQueryResultCachedGif( - TestInlineQueryResultCachedGif.id_, - TestInlineQueryResultCachedGif.gif_file_id, - title=TestInlineQueryResultCachedGif.title, - caption=TestInlineQueryResultCachedGif.caption, - parse_mode=TestInlineQueryResultCachedGif.parse_mode, - caption_entities=TestInlineQueryResultCachedGif.caption_entities, - input_message_content=TestInlineQueryResultCachedGif.input_message_content, - reply_markup=TestInlineQueryResultCachedGif.reply_markup, + TestInlineQueryResultCachedGifBase.id_, + TestInlineQueryResultCachedGifBase.gif_file_id, + title=TestInlineQueryResultCachedGifBase.title, + caption=TestInlineQueryResultCachedGifBase.caption, + parse_mode=TestInlineQueryResultCachedGifBase.parse_mode, + caption_entities=TestInlineQueryResultCachedGifBase.caption_entities, + input_message_content=TestInlineQueryResultCachedGifBase.input_message_content, + reply_markup=TestInlineQueryResultCachedGifBase.reply_markup, ) -class TestInlineQueryResultCachedGif: +class TestInlineQueryResultCachedGifBase: id_ = "id" type_ = "gif" gif_file_id = "gif file id" @@ -53,6 +53,8 @@ class TestInlineQueryResultCachedGif: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultCachedGifWithoutRequest(TestInlineQueryResultCachedGifBase): def test_slot_behaviour(self, inline_query_result_cached_gif, mro_slots): inst = inline_query_result_cached_gif for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultcachedmpeg4gif.py b/tests/test_inlinequeryresultcachedmpeg4gif.py index 12bb20934..37a47e11b 100644 --- a/tests/test_inlinequeryresultcachedmpeg4gif.py +++ b/tests/test_inlinequeryresultcachedmpeg4gif.py @@ -28,21 +28,21 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_cached_mpeg4_gif(): return InlineQueryResultCachedMpeg4Gif( - TestInlineQueryResultCachedMpeg4Gif.id_, - TestInlineQueryResultCachedMpeg4Gif.mpeg4_file_id, - title=TestInlineQueryResultCachedMpeg4Gif.title, - caption=TestInlineQueryResultCachedMpeg4Gif.caption, - parse_mode=TestInlineQueryResultCachedMpeg4Gif.parse_mode, - caption_entities=TestInlineQueryResultCachedMpeg4Gif.caption_entities, - input_message_content=TestInlineQueryResultCachedMpeg4Gif.input_message_content, - reply_markup=TestInlineQueryResultCachedMpeg4Gif.reply_markup, + TestInlineQueryResultCachedMpeg4GifBase.id_, + TestInlineQueryResultCachedMpeg4GifBase.mpeg4_file_id, + title=TestInlineQueryResultCachedMpeg4GifBase.title, + caption=TestInlineQueryResultCachedMpeg4GifBase.caption, + parse_mode=TestInlineQueryResultCachedMpeg4GifBase.parse_mode, + caption_entities=TestInlineQueryResultCachedMpeg4GifBase.caption_entities, + input_message_content=TestInlineQueryResultCachedMpeg4GifBase.input_message_content, + reply_markup=TestInlineQueryResultCachedMpeg4GifBase.reply_markup, ) -class TestInlineQueryResultCachedMpeg4Gif: +class TestInlineQueryResultCachedMpeg4GifBase: id_ = "id" type_ = "mpeg4_gif" mpeg4_file_id = "mpeg4 file id" @@ -53,6 +53,8 @@ class TestInlineQueryResultCachedMpeg4Gif: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultCachedMpeg4GifWithoutRequest(TestInlineQueryResultCachedMpeg4GifBase): def test_slot_behaviour(self, inline_query_result_cached_mpeg4_gif, mro_slots): inst = inline_query_result_cached_mpeg4_gif for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultcachedphoto.py b/tests/test_inlinequeryresultcachedphoto.py index 4bc11f640..209bc9061 100644 --- a/tests/test_inlinequeryresultcachedphoto.py +++ b/tests/test_inlinequeryresultcachedphoto.py @@ -28,22 +28,22 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_cached_photo(): return InlineQueryResultCachedPhoto( - TestInlineQueryResultCachedPhoto.id_, - TestInlineQueryResultCachedPhoto.photo_file_id, - title=TestInlineQueryResultCachedPhoto.title, - description=TestInlineQueryResultCachedPhoto.description, - caption=TestInlineQueryResultCachedPhoto.caption, - parse_mode=TestInlineQueryResultCachedPhoto.parse_mode, - caption_entities=TestInlineQueryResultCachedPhoto.caption_entities, - input_message_content=TestInlineQueryResultCachedPhoto.input_message_content, - reply_markup=TestInlineQueryResultCachedPhoto.reply_markup, + TestInlineQueryResultCachedPhotoBase.id_, + TestInlineQueryResultCachedPhotoBase.photo_file_id, + title=TestInlineQueryResultCachedPhotoBase.title, + description=TestInlineQueryResultCachedPhotoBase.description, + caption=TestInlineQueryResultCachedPhotoBase.caption, + parse_mode=TestInlineQueryResultCachedPhotoBase.parse_mode, + caption_entities=TestInlineQueryResultCachedPhotoBase.caption_entities, + input_message_content=TestInlineQueryResultCachedPhotoBase.input_message_content, + reply_markup=TestInlineQueryResultCachedPhotoBase.reply_markup, ) -class TestInlineQueryResultCachedPhoto: +class TestInlineQueryResultCachedPhotoBase: id_ = "id" type_ = "photo" photo_file_id = "photo file id" @@ -55,6 +55,8 @@ class TestInlineQueryResultCachedPhoto: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultCachedPhotoWithoutRequest(TestInlineQueryResultCachedPhotoBase): def test_slot_behaviour(self, inline_query_result_cached_photo, mro_slots): inst = inline_query_result_cached_photo for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultcachedsticker.py b/tests/test_inlinequeryresultcachedsticker.py index 866057df9..d41e0506b 100644 --- a/tests/test_inlinequeryresultcachedsticker.py +++ b/tests/test_inlinequeryresultcachedsticker.py @@ -27,23 +27,25 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_cached_sticker(): return InlineQueryResultCachedSticker( - TestInlineQueryResultCachedSticker.id_, - TestInlineQueryResultCachedSticker.sticker_file_id, - input_message_content=TestInlineQueryResultCachedSticker.input_message_content, - reply_markup=TestInlineQueryResultCachedSticker.reply_markup, + TestInlineQueryResultCachedStickerBase.id_, + TestInlineQueryResultCachedStickerBase.sticker_file_id, + input_message_content=TestInlineQueryResultCachedStickerBase.input_message_content, + reply_markup=TestInlineQueryResultCachedStickerBase.reply_markup, ) -class TestInlineQueryResultCachedSticker: +class TestInlineQueryResultCachedStickerBase: id_ = "id" type_ = "sticker" sticker_file_id = "sticker file id" input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultCachedStickerWithoutRequest(TestInlineQueryResultCachedStickerBase): def test_slot_behaviour(self, inline_query_result_cached_sticker, mro_slots): inst = inline_query_result_cached_sticker for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultcachedvideo.py b/tests/test_inlinequeryresultcachedvideo.py index bbf76087a..2ee368c40 100644 --- a/tests/test_inlinequeryresultcachedvideo.py +++ b/tests/test_inlinequeryresultcachedvideo.py @@ -28,22 +28,22 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_cached_video(): return InlineQueryResultCachedVideo( - TestInlineQueryResultCachedVideo.id_, - TestInlineQueryResultCachedVideo.video_file_id, - TestInlineQueryResultCachedVideo.title, - caption=TestInlineQueryResultCachedVideo.caption, - parse_mode=TestInlineQueryResultCachedVideo.parse_mode, - caption_entities=TestInlineQueryResultCachedVideo.caption_entities, - description=TestInlineQueryResultCachedVideo.description, - input_message_content=TestInlineQueryResultCachedVideo.input_message_content, - reply_markup=TestInlineQueryResultCachedVideo.reply_markup, + TestInlineQueryResultCachedVideoBase.id_, + TestInlineQueryResultCachedVideoBase.video_file_id, + TestInlineQueryResultCachedVideoBase.title, + caption=TestInlineQueryResultCachedVideoBase.caption, + parse_mode=TestInlineQueryResultCachedVideoBase.parse_mode, + caption_entities=TestInlineQueryResultCachedVideoBase.caption_entities, + description=TestInlineQueryResultCachedVideoBase.description, + input_message_content=TestInlineQueryResultCachedVideoBase.input_message_content, + reply_markup=TestInlineQueryResultCachedVideoBase.reply_markup, ) -class TestInlineQueryResultCachedVideo: +class TestInlineQueryResultCachedVideoBase: id_ = "id" type_ = "video" video_file_id = "video file id" @@ -55,6 +55,8 @@ class TestInlineQueryResultCachedVideo: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultCachedVideoWithoutRequest(TestInlineQueryResultCachedVideoBase): def test_slot_behaviour(self, inline_query_result_cached_video, mro_slots): inst = inline_query_result_cached_video for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultcachedvoice.py b/tests/test_inlinequeryresultcachedvoice.py index c12c73a81..c1625b2db 100644 --- a/tests/test_inlinequeryresultcachedvoice.py +++ b/tests/test_inlinequeryresultcachedvoice.py @@ -28,21 +28,21 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_cached_voice(): return InlineQueryResultCachedVoice( - TestInlineQueryResultCachedVoice.id_, - TestInlineQueryResultCachedVoice.voice_file_id, - TestInlineQueryResultCachedVoice.title, - caption=TestInlineQueryResultCachedVoice.caption, - parse_mode=TestInlineQueryResultCachedVoice.parse_mode, - caption_entities=TestInlineQueryResultCachedVoice.caption_entities, - input_message_content=TestInlineQueryResultCachedVoice.input_message_content, - reply_markup=TestInlineQueryResultCachedVoice.reply_markup, + TestInlineQueryResultCachedVoiceBase.id_, + TestInlineQueryResultCachedVoiceBase.voice_file_id, + TestInlineQueryResultCachedVoiceBase.title, + caption=TestInlineQueryResultCachedVoiceBase.caption, + parse_mode=TestInlineQueryResultCachedVoiceBase.parse_mode, + caption_entities=TestInlineQueryResultCachedVoiceBase.caption_entities, + input_message_content=TestInlineQueryResultCachedVoiceBase.input_message_content, + reply_markup=TestInlineQueryResultCachedVoiceBase.reply_markup, ) -class TestInlineQueryResultCachedVoice: +class TestInlineQueryResultCachedVoiceBase: id_ = "id" type_ = "voice" voice_file_id = "voice file id" @@ -53,6 +53,8 @@ class TestInlineQueryResultCachedVoice: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultCachedVoiceWithoutRequest(TestInlineQueryResultCachedVoiceBase): def test_slot_behaviour(self, inline_query_result_cached_voice, mro_slots): inst = inline_query_result_cached_voice for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultcontact.py b/tests/test_inlinequeryresultcontact.py index 4d61b8253..2b418e952 100644 --- a/tests/test_inlinequeryresultcontact.py +++ b/tests/test_inlinequeryresultcontact.py @@ -27,22 +27,22 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_contact(): return InlineQueryResultContact( - TestInlineQueryResultContact.id_, - TestInlineQueryResultContact.phone_number, - TestInlineQueryResultContact.first_name, - last_name=TestInlineQueryResultContact.last_name, - thumb_url=TestInlineQueryResultContact.thumb_url, - thumb_width=TestInlineQueryResultContact.thumb_width, - thumb_height=TestInlineQueryResultContact.thumb_height, - input_message_content=TestInlineQueryResultContact.input_message_content, - reply_markup=TestInlineQueryResultContact.reply_markup, + TestInlineQueryResultContactBase.id_, + TestInlineQueryResultContactBase.phone_number, + TestInlineQueryResultContactBase.first_name, + last_name=TestInlineQueryResultContactBase.last_name, + thumb_url=TestInlineQueryResultContactBase.thumb_url, + thumb_width=TestInlineQueryResultContactBase.thumb_width, + thumb_height=TestInlineQueryResultContactBase.thumb_height, + input_message_content=TestInlineQueryResultContactBase.input_message_content, + reply_markup=TestInlineQueryResultContactBase.reply_markup, ) -class TestInlineQueryResultContact: +class TestInlineQueryResultContactBase: id_ = "id" type_ = "contact" phone_number = "phone_number" @@ -54,6 +54,8 @@ class TestInlineQueryResultContact: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultContactWithoutRequest(TestInlineQueryResultContactBase): def test_slot_behaviour(self, inline_query_result_contact, mro_slots): inst = inline_query_result_contact for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultdocument.py b/tests/test_inlinequeryresultdocument.py index 1ca01ff4b..b1221788e 100644 --- a/tests/test_inlinequeryresultdocument.py +++ b/tests/test_inlinequeryresultdocument.py @@ -28,26 +28,26 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_document(): return InlineQueryResultDocument( - TestInlineQueryResultDocument.id_, - TestInlineQueryResultDocument.document_url, - TestInlineQueryResultDocument.title, - TestInlineQueryResultDocument.mime_type, - caption=TestInlineQueryResultDocument.caption, - parse_mode=TestInlineQueryResultDocument.parse_mode, - caption_entities=TestInlineQueryResultDocument.caption_entities, - description=TestInlineQueryResultDocument.description, - thumb_url=TestInlineQueryResultDocument.thumb_url, - thumb_width=TestInlineQueryResultDocument.thumb_width, - thumb_height=TestInlineQueryResultDocument.thumb_height, - input_message_content=TestInlineQueryResultDocument.input_message_content, - reply_markup=TestInlineQueryResultDocument.reply_markup, + TestInlineQueryResultDocumentBase.id_, + TestInlineQueryResultDocumentBase.document_url, + TestInlineQueryResultDocumentBase.title, + TestInlineQueryResultDocumentBase.mime_type, + caption=TestInlineQueryResultDocumentBase.caption, + parse_mode=TestInlineQueryResultDocumentBase.parse_mode, + caption_entities=TestInlineQueryResultDocumentBase.caption_entities, + description=TestInlineQueryResultDocumentBase.description, + thumb_url=TestInlineQueryResultDocumentBase.thumb_url, + thumb_width=TestInlineQueryResultDocumentBase.thumb_width, + thumb_height=TestInlineQueryResultDocumentBase.thumb_height, + input_message_content=TestInlineQueryResultDocumentBase.input_message_content, + reply_markup=TestInlineQueryResultDocumentBase.reply_markup, ) -class TestInlineQueryResultDocument: +class TestInlineQueryResultDocumentBase: id_ = "id" type_ = "document" document_url = "document url" @@ -63,6 +63,8 @@ class TestInlineQueryResultDocument: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultDocumentWithoutRequest(TestInlineQueryResultDocumentBase): def test_slot_behaviour(self, inline_query_result_document, mro_slots): inst = inline_query_result_document for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultgame.py b/tests/test_inlinequeryresultgame.py index 963b130ae..f7c6ae757 100644 --- a/tests/test_inlinequeryresultgame.py +++ b/tests/test_inlinequeryresultgame.py @@ -26,21 +26,23 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_game(): return InlineQueryResultGame( - TestInlineQueryResultGame.id_, - TestInlineQueryResultGame.game_short_name, - reply_markup=TestInlineQueryResultGame.reply_markup, + TestInlineQueryResultGameBase.id_, + TestInlineQueryResultGameBase.game_short_name, + reply_markup=TestInlineQueryResultGameBase.reply_markup, ) -class TestInlineQueryResultGame: +class TestInlineQueryResultGameBase: id_ = "id" type_ = "game" game_short_name = "game short name" reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultGameWithoutRequest(TestInlineQueryResultGameBase): def test_slot_behaviour(self, inline_query_result_game, mro_slots): inst = inline_query_result_game for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultgif.py b/tests/test_inlinequeryresultgif.py index c25b18aa1..c50fd6b2d 100644 --- a/tests/test_inlinequeryresultgif.py +++ b/tests/test_inlinequeryresultgif.py @@ -28,26 +28,26 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_gif(): return InlineQueryResultGif( - TestInlineQueryResultGif.id_, - TestInlineQueryResultGif.gif_url, - TestInlineQueryResultGif.thumb_url, - gif_width=TestInlineQueryResultGif.gif_width, - gif_height=TestInlineQueryResultGif.gif_height, - gif_duration=TestInlineQueryResultGif.gif_duration, - title=TestInlineQueryResultGif.title, - caption=TestInlineQueryResultGif.caption, - parse_mode=TestInlineQueryResultGif.parse_mode, - caption_entities=TestInlineQueryResultGif.caption_entities, - input_message_content=TestInlineQueryResultGif.input_message_content, - reply_markup=TestInlineQueryResultGif.reply_markup, - thumb_mime_type=TestInlineQueryResultGif.thumb_mime_type, + TestInlineQueryResultGifBase.id_, + TestInlineQueryResultGifBase.gif_url, + TestInlineQueryResultGifBase.thumb_url, + gif_width=TestInlineQueryResultGifBase.gif_width, + gif_height=TestInlineQueryResultGifBase.gif_height, + gif_duration=TestInlineQueryResultGifBase.gif_duration, + title=TestInlineQueryResultGifBase.title, + caption=TestInlineQueryResultGifBase.caption, + parse_mode=TestInlineQueryResultGifBase.parse_mode, + caption_entities=TestInlineQueryResultGifBase.caption_entities, + input_message_content=TestInlineQueryResultGifBase.input_message_content, + reply_markup=TestInlineQueryResultGifBase.reply_markup, + thumb_mime_type=TestInlineQueryResultGifBase.thumb_mime_type, ) -class TestInlineQueryResultGif: +class TestInlineQueryResultGifBase: id_ = "id" type_ = "gif" gif_url = "gif url" @@ -63,6 +63,8 @@ class TestInlineQueryResultGif: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultGifWithoutRequest(TestInlineQueryResultGifBase): def test_slot_behaviour(self, inline_query_result_gif, mro_slots): inst = inline_query_result_gif for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultlocation.py b/tests/test_inlinequeryresultlocation.py index ba8d4a34e..d15688e34 100644 --- a/tests/test_inlinequeryresultlocation.py +++ b/tests/test_inlinequeryresultlocation.py @@ -27,26 +27,26 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_location(): return InlineQueryResultLocation( - TestInlineQueryResultLocation.id_, - TestInlineQueryResultLocation.latitude, - TestInlineQueryResultLocation.longitude, - TestInlineQueryResultLocation.title, - live_period=TestInlineQueryResultLocation.live_period, - thumb_url=TestInlineQueryResultLocation.thumb_url, - thumb_width=TestInlineQueryResultLocation.thumb_width, - thumb_height=TestInlineQueryResultLocation.thumb_height, - input_message_content=TestInlineQueryResultLocation.input_message_content, - reply_markup=TestInlineQueryResultLocation.reply_markup, - horizontal_accuracy=TestInlineQueryResultLocation.horizontal_accuracy, - heading=TestInlineQueryResultLocation.heading, - proximity_alert_radius=TestInlineQueryResultLocation.proximity_alert_radius, + TestInlineQueryResultLocationBase.id_, + TestInlineQueryResultLocationBase.latitude, + TestInlineQueryResultLocationBase.longitude, + TestInlineQueryResultLocationBase.title, + live_period=TestInlineQueryResultLocationBase.live_period, + thumb_url=TestInlineQueryResultLocationBase.thumb_url, + thumb_width=TestInlineQueryResultLocationBase.thumb_width, + thumb_height=TestInlineQueryResultLocationBase.thumb_height, + input_message_content=TestInlineQueryResultLocationBase.input_message_content, + reply_markup=TestInlineQueryResultLocationBase.reply_markup, + horizontal_accuracy=TestInlineQueryResultLocationBase.horizontal_accuracy, + heading=TestInlineQueryResultLocationBase.heading, + proximity_alert_radius=TestInlineQueryResultLocationBase.proximity_alert_radius, ) -class TestInlineQueryResultLocation: +class TestInlineQueryResultLocationBase: id_ = "id" type_ = "location" latitude = 0.0 @@ -62,6 +62,8 @@ class TestInlineQueryResultLocation: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultLocationWithoutRequest(TestInlineQueryResultLocationBase): def test_slot_behaviour(self, inline_query_result_location, mro_slots): inst = inline_query_result_location for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultmpeg4gif.py b/tests/test_inlinequeryresultmpeg4gif.py index f40dea630..ae1210c89 100644 --- a/tests/test_inlinequeryresultmpeg4gif.py +++ b/tests/test_inlinequeryresultmpeg4gif.py @@ -28,26 +28,26 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_mpeg4_gif(): return InlineQueryResultMpeg4Gif( - TestInlineQueryResultMpeg4Gif.id_, - TestInlineQueryResultMpeg4Gif.mpeg4_url, - TestInlineQueryResultMpeg4Gif.thumb_url, - mpeg4_width=TestInlineQueryResultMpeg4Gif.mpeg4_width, - mpeg4_height=TestInlineQueryResultMpeg4Gif.mpeg4_height, - mpeg4_duration=TestInlineQueryResultMpeg4Gif.mpeg4_duration, - title=TestInlineQueryResultMpeg4Gif.title, - caption=TestInlineQueryResultMpeg4Gif.caption, - parse_mode=TestInlineQueryResultMpeg4Gif.parse_mode, - caption_entities=TestInlineQueryResultMpeg4Gif.caption_entities, - input_message_content=TestInlineQueryResultMpeg4Gif.input_message_content, - reply_markup=TestInlineQueryResultMpeg4Gif.reply_markup, - thumb_mime_type=TestInlineQueryResultMpeg4Gif.thumb_mime_type, + TestInlineQueryResultMpeg4GifBase.id_, + TestInlineQueryResultMpeg4GifBase.mpeg4_url, + TestInlineQueryResultMpeg4GifBase.thumb_url, + mpeg4_width=TestInlineQueryResultMpeg4GifBase.mpeg4_width, + mpeg4_height=TestInlineQueryResultMpeg4GifBase.mpeg4_height, + mpeg4_duration=TestInlineQueryResultMpeg4GifBase.mpeg4_duration, + title=TestInlineQueryResultMpeg4GifBase.title, + caption=TestInlineQueryResultMpeg4GifBase.caption, + parse_mode=TestInlineQueryResultMpeg4GifBase.parse_mode, + caption_entities=TestInlineQueryResultMpeg4GifBase.caption_entities, + input_message_content=TestInlineQueryResultMpeg4GifBase.input_message_content, + reply_markup=TestInlineQueryResultMpeg4GifBase.reply_markup, + thumb_mime_type=TestInlineQueryResultMpeg4GifBase.thumb_mime_type, ) -class TestInlineQueryResultMpeg4Gif: +class TestInlineQueryResultMpeg4GifBase: id_ = "id" type_ = "mpeg4_gif" mpeg4_url = "mpeg4 url" @@ -63,6 +63,8 @@ class TestInlineQueryResultMpeg4Gif: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultMpeg4GifWithoutRequest(TestInlineQueryResultMpeg4GifBase): def test_slot_behaviour(self, inline_query_result_mpeg4_gif, mro_slots): inst = inline_query_result_mpeg4_gif for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultphoto.py b/tests/test_inlinequeryresultphoto.py index 5edbccca2..e6944f3b3 100644 --- a/tests/test_inlinequeryresultphoto.py +++ b/tests/test_inlinequeryresultphoto.py @@ -28,25 +28,25 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_photo(): return InlineQueryResultPhoto( - TestInlineQueryResultPhoto.id_, - TestInlineQueryResultPhoto.photo_url, - TestInlineQueryResultPhoto.thumb_url, - photo_width=TestInlineQueryResultPhoto.photo_width, - photo_height=TestInlineQueryResultPhoto.photo_height, - title=TestInlineQueryResultPhoto.title, - description=TestInlineQueryResultPhoto.description, - caption=TestInlineQueryResultPhoto.caption, - parse_mode=TestInlineQueryResultPhoto.parse_mode, - caption_entities=TestInlineQueryResultPhoto.caption_entities, - input_message_content=TestInlineQueryResultPhoto.input_message_content, - reply_markup=TestInlineQueryResultPhoto.reply_markup, + TestInlineQueryResultPhotoBase.id_, + TestInlineQueryResultPhotoBase.photo_url, + TestInlineQueryResultPhotoBase.thumb_url, + photo_width=TestInlineQueryResultPhotoBase.photo_width, + photo_height=TestInlineQueryResultPhotoBase.photo_height, + title=TestInlineQueryResultPhotoBase.title, + description=TestInlineQueryResultPhotoBase.description, + caption=TestInlineQueryResultPhotoBase.caption, + parse_mode=TestInlineQueryResultPhotoBase.parse_mode, + caption_entities=TestInlineQueryResultPhotoBase.caption_entities, + input_message_content=TestInlineQueryResultPhotoBase.input_message_content, + reply_markup=TestInlineQueryResultPhotoBase.reply_markup, ) -class TestInlineQueryResultPhoto: +class TestInlineQueryResultPhotoBase: id_ = "id" type_ = "photo" photo_url = "photo url" @@ -62,6 +62,8 @@ class TestInlineQueryResultPhoto: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultPhotoWithoutRequest(TestInlineQueryResultPhotoBase): def test_slot_behaviour(self, inline_query_result_photo, mro_slots): inst = inline_query_result_photo for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultvenue.py b/tests/test_inlinequeryresultvenue.py index 5f02526c2..7592bfdf0 100644 --- a/tests/test_inlinequeryresultvenue.py +++ b/tests/test_inlinequeryresultvenue.py @@ -27,27 +27,27 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_venue(): return InlineQueryResultVenue( - TestInlineQueryResultVenue.id_, - TestInlineQueryResultVenue.latitude, - TestInlineQueryResultVenue.longitude, - TestInlineQueryResultVenue.title, - TestInlineQueryResultVenue.address, - foursquare_id=TestInlineQueryResultVenue.foursquare_id, - foursquare_type=TestInlineQueryResultVenue.foursquare_type, - thumb_url=TestInlineQueryResultVenue.thumb_url, - thumb_width=TestInlineQueryResultVenue.thumb_width, - thumb_height=TestInlineQueryResultVenue.thumb_height, - input_message_content=TestInlineQueryResultVenue.input_message_content, - reply_markup=TestInlineQueryResultVenue.reply_markup, - google_place_id=TestInlineQueryResultVenue.google_place_id, - google_place_type=TestInlineQueryResultVenue.google_place_type, + TestInlineQueryResultVenueBase.id_, + TestInlineQueryResultVenueBase.latitude, + TestInlineQueryResultVenueBase.longitude, + TestInlineQueryResultVenueBase.title, + TestInlineQueryResultVenueBase.address, + foursquare_id=TestInlineQueryResultVenueBase.foursquare_id, + foursquare_type=TestInlineQueryResultVenueBase.foursquare_type, + thumb_url=TestInlineQueryResultVenueBase.thumb_url, + thumb_width=TestInlineQueryResultVenueBase.thumb_width, + thumb_height=TestInlineQueryResultVenueBase.thumb_height, + input_message_content=TestInlineQueryResultVenueBase.input_message_content, + reply_markup=TestInlineQueryResultVenueBase.reply_markup, + google_place_id=TestInlineQueryResultVenueBase.google_place_id, + google_place_type=TestInlineQueryResultVenueBase.google_place_type, ) -class TestInlineQueryResultVenue: +class TestInlineQueryResultVenueBase: id_ = "id" type_ = "venue" latitude = "latitude" @@ -64,6 +64,8 @@ class TestInlineQueryResultVenue: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultVenueWithoutRequest(TestInlineQueryResultVenueBase): def test_slot_behaviour(self, inline_query_result_venue, mro_slots): inst = inline_query_result_venue for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultvideo.py b/tests/test_inlinequeryresultvideo.py index 79499381f..3e8edf76e 100644 --- a/tests/test_inlinequeryresultvideo.py +++ b/tests/test_inlinequeryresultvideo.py @@ -28,27 +28,27 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_video(): return InlineQueryResultVideo( - TestInlineQueryResultVideo.id_, - TestInlineQueryResultVideo.video_url, - TestInlineQueryResultVideo.mime_type, - TestInlineQueryResultVideo.thumb_url, - TestInlineQueryResultVideo.title, - video_width=TestInlineQueryResultVideo.video_width, - video_height=TestInlineQueryResultVideo.video_height, - video_duration=TestInlineQueryResultVideo.video_duration, - caption=TestInlineQueryResultVideo.caption, - parse_mode=TestInlineQueryResultVideo.parse_mode, - caption_entities=TestInlineQueryResultVideo.caption_entities, - description=TestInlineQueryResultVideo.description, - input_message_content=TestInlineQueryResultVideo.input_message_content, - reply_markup=TestInlineQueryResultVideo.reply_markup, + TestInlineQueryResultVideoBase.id_, + TestInlineQueryResultVideoBase.video_url, + TestInlineQueryResultVideoBase.mime_type, + TestInlineQueryResultVideoBase.thumb_url, + TestInlineQueryResultVideoBase.title, + video_width=TestInlineQueryResultVideoBase.video_width, + video_height=TestInlineQueryResultVideoBase.video_height, + video_duration=TestInlineQueryResultVideoBase.video_duration, + caption=TestInlineQueryResultVideoBase.caption, + parse_mode=TestInlineQueryResultVideoBase.parse_mode, + caption_entities=TestInlineQueryResultVideoBase.caption_entities, + description=TestInlineQueryResultVideoBase.description, + input_message_content=TestInlineQueryResultVideoBase.input_message_content, + reply_markup=TestInlineQueryResultVideoBase.reply_markup, ) -class TestInlineQueryResultVideo: +class TestInlineQueryResultVideoBase: id_ = "id" type_ = "video" video_url = "video url" @@ -65,6 +65,8 @@ class TestInlineQueryResultVideo: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultVideoWithoutRequest(TestInlineQueryResultVideoBase): def test_slot_behaviour(self, inline_query_result_video, mro_slots): inst = inline_query_result_video for attr in inst.__slots__: diff --git a/tests/test_inlinequeryresultvoice.py b/tests/test_inlinequeryresultvoice.py index 12316b74d..8ce2f42db 100644 --- a/tests/test_inlinequeryresultvoice.py +++ b/tests/test_inlinequeryresultvoice.py @@ -28,22 +28,22 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def inline_query_result_voice(): return InlineQueryResultVoice( - id=TestInlineQueryResultVoice.id_, - voice_url=TestInlineQueryResultVoice.voice_url, - title=TestInlineQueryResultVoice.title, - voice_duration=TestInlineQueryResultVoice.voice_duration, - caption=TestInlineQueryResultVoice.caption, - parse_mode=TestInlineQueryResultVoice.parse_mode, - caption_entities=TestInlineQueryResultVoice.caption_entities, - input_message_content=TestInlineQueryResultVoice.input_message_content, - reply_markup=TestInlineQueryResultVoice.reply_markup, + id=TestInlineQueryResultVoiceBase.id_, + voice_url=TestInlineQueryResultVoiceBase.voice_url, + title=TestInlineQueryResultVoiceBase.title, + voice_duration=TestInlineQueryResultVoiceBase.voice_duration, + caption=TestInlineQueryResultVoiceBase.caption, + parse_mode=TestInlineQueryResultVoiceBase.parse_mode, + caption_entities=TestInlineQueryResultVoiceBase.caption_entities, + input_message_content=TestInlineQueryResultVoiceBase.input_message_content, + reply_markup=TestInlineQueryResultVoiceBase.reply_markup, ) -class TestInlineQueryResultVoice: +class TestInlineQueryResultVoiceBase: id_ = "id" type_ = "voice" voice_url = "voice url" @@ -55,6 +55,8 @@ class TestInlineQueryResultVoice: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + +class TestInlineQueryResultVoiceWithoutRequest(TestInlineQueryResultVoiceBase): def test_slot_behaviour(self, inline_query_result_voice, mro_slots): inst = inline_query_result_voice for attr in inst.__slots__: diff --git a/tests/test_inputcontactmessagecontent.py b/tests/test_inputcontactmessagecontent.py index 107852c54..cd47a2b18 100644 --- a/tests/test_inputcontactmessagecontent.py +++ b/tests/test_inputcontactmessagecontent.py @@ -21,20 +21,22 @@ import pytest from telegram import InputContactMessageContent, User -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def input_contact_message_content(): return InputContactMessageContent( - TestInputContactMessageContent.phone_number, - TestInputContactMessageContent.first_name, - last_name=TestInputContactMessageContent.last_name, + TestInputContactMessageContentBase.phone_number, + TestInputContactMessageContentBase.first_name, + TestInputContactMessageContentBase.last_name, ) -class TestInputContactMessageContent: +class TestInputContactMessageContentBase: phone_number = "phone number" first_name = "first name" last_name = "last name" + +class TestInputContactMessageContentWithoutRequest(TestInputContactMessageContentBase): def test_slot_behaviour(self, input_contact_message_content, mro_slots): inst = input_contact_message_content for attr in inst.__slots__: diff --git a/tests/test_inputfile.py b/tests/test_inputfile.py index d9db96d51..b215c907f 100644 --- a/tests/test_inputfile.py +++ b/tests/test_inputfile.py @@ -26,12 +26,12 @@ from telegram import InputFile from tests.conftest import data_file -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def png_file(): return data_file("game.png") -class TestInputFile: +class TestInputFileWithoutRequest: def test_slot_behaviour(self, mro_slots): inst = InputFile(BytesIO(b"blah"), filename="tg.jpg") for attr in inst.__slots__: @@ -65,7 +65,7 @@ class TestInputFile: assert input_file.attach_name is None assert input_file.attach_uri is None - def test_mimetypes(self, caplog): + def test_mimetypes(self): # Only test a few to make sure logic works okay assert InputFile(data_file("telegram.jpg").open("rb")).mimetype == "image/jpeg" # For some reason python can guess the type on macOS @@ -139,6 +139,8 @@ class TestInputFile: == "blah.jpg" ) + +class TestInputFileWithRequest: async def test_send_bytes(self, bot, chat_id): # We test this here and not at the respective test modules because it's not worth # duplicating the test for the different methods diff --git a/tests/test_inputinvoicemessagecontent.py b/tests/test_inputinvoicemessagecontent.py index 045675512..d7d207575 100644 --- a/tests/test_inputinvoicemessagecontent.py +++ b/tests/test_inputinvoicemessagecontent.py @@ -22,33 +22,33 @@ import pytest from telegram import InputInvoiceMessageContent, InputTextMessageContent, LabeledPrice -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def input_invoice_message_content(): return InputInvoiceMessageContent( - title=TestInputInvoiceMessageContent.title, - description=TestInputInvoiceMessageContent.description, - payload=TestInputInvoiceMessageContent.payload, - provider_token=TestInputInvoiceMessageContent.provider_token, - currency=TestInputInvoiceMessageContent.currency, - prices=TestInputInvoiceMessageContent.prices, - max_tip_amount=TestInputInvoiceMessageContent.max_tip_amount, - suggested_tip_amounts=TestInputInvoiceMessageContent.suggested_tip_amounts, - provider_data=TestInputInvoiceMessageContent.provider_data, - photo_url=TestInputInvoiceMessageContent.photo_url, - photo_size=TestInputInvoiceMessageContent.photo_size, - photo_width=TestInputInvoiceMessageContent.photo_width, - photo_height=TestInputInvoiceMessageContent.photo_height, - need_name=TestInputInvoiceMessageContent.need_name, - need_phone_number=TestInputInvoiceMessageContent.need_phone_number, - need_email=TestInputInvoiceMessageContent.need_email, - need_shipping_address=TestInputInvoiceMessageContent.need_shipping_address, - send_phone_number_to_provider=TestInputInvoiceMessageContent.send_phone_number_to_provider, - send_email_to_provider=TestInputInvoiceMessageContent.send_email_to_provider, - is_flexible=TestInputInvoiceMessageContent.is_flexible, + title=TestInputInvoiceMessageContentBase.title, + description=TestInputInvoiceMessageContentBase.description, + payload=TestInputInvoiceMessageContentBase.payload, + provider_token=TestInputInvoiceMessageContentBase.provider_token, + currency=TestInputInvoiceMessageContentBase.currency, + prices=TestInputInvoiceMessageContentBase.prices, + max_tip_amount=TestInputInvoiceMessageContentBase.max_tip_amount, + suggested_tip_amounts=TestInputInvoiceMessageContentBase.suggested_tip_amounts, + provider_data=TestInputInvoiceMessageContentBase.provider_data, + photo_url=TestInputInvoiceMessageContentBase.photo_url, + photo_size=TestInputInvoiceMessageContentBase.photo_size, + photo_width=TestInputInvoiceMessageContentBase.photo_width, + photo_height=TestInputInvoiceMessageContentBase.photo_height, + need_name=TestInputInvoiceMessageContentBase.need_name, + need_phone_number=TestInputInvoiceMessageContentBase.need_phone_number, + need_email=TestInputInvoiceMessageContentBase.need_email, + need_shipping_address=TestInputInvoiceMessageContentBase.need_shipping_address, + send_phone_number_to_provider=TestInputInvoiceMessageContentBase.send_phone_number_to_provider, # noqa: E501 + send_email_to_provider=TestInputInvoiceMessageContentBase.send_email_to_provider, + is_flexible=TestInputInvoiceMessageContentBase.is_flexible, ) -class TestInputInvoiceMessageContent: +class TestInputInvoiceMessageContentBase: title = "invoice title" description = "invoice description" payload = "invoice payload" @@ -70,6 +70,8 @@ class TestInputInvoiceMessageContent: send_email_to_provider = True is_flexible = True + +class TestInputInvoiceMessageContentWithoutRequest(TestInputInvoiceMessageContentBase): def test_slot_behaviour(self, input_invoice_message_content, mro_slots): inst = input_invoice_message_content for attr in inst.__slots__: @@ -103,29 +105,7 @@ class TestInputInvoiceMessageContent: assert input_invoice_message_content.send_email_to_provider == self.send_email_to_provider assert input_invoice_message_content.is_flexible == self.is_flexible - def test_suggested_tip_amonuts_always_tuple(self): - input_invoice_message_content = InputInvoiceMessageContent( - title=self.title, - description=self.description, - payload=self.payload, - provider_token=self.provider_token, - currency=self.currency, - prices=self.prices, - max_tip_amount=self.max_tip_amount, - suggested_tip_amounts=self.suggested_tip_amounts, - provider_data=self.provider_data, - photo_url=self.photo_url, - photo_size=self.photo_size, - photo_width=self.photo_width, - photo_height=self.photo_height, - need_name=self.need_name, - need_phone_number=self.need_phone_number, - need_email=self.need_email, - need_shipping_address=self.need_shipping_address, - send_phone_number_to_provider=self.send_phone_number_to_provider, - send_email_to_provider=self.send_email_to_provider, - is_flexible=self.is_flexible, - ) + def test_suggested_tip_amonuts_always_tuple(self, input_invoice_message_content): assert isinstance(input_invoice_message_content.suggested_tip_amounts, tuple) assert input_invoice_message_content.suggested_tip_amounts == tuple( int(amount) for amount in self.suggested_tip_amounts diff --git a/tests/test_inputlocationmessagecontent.py b/tests/test_inputlocationmessagecontent.py index 003269c9f..a9e776cb6 100644 --- a/tests/test_inputlocationmessagecontent.py +++ b/tests/test_inputlocationmessagecontent.py @@ -21,19 +21,19 @@ import pytest from telegram import InputLocationMessageContent, Location -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def input_location_message_content(): return InputLocationMessageContent( - TestInputLocationMessageContent.latitude, - TestInputLocationMessageContent.longitude, - live_period=TestInputLocationMessageContent.live_period, - horizontal_accuracy=TestInputLocationMessageContent.horizontal_accuracy, - heading=TestInputLocationMessageContent.heading, - proximity_alert_radius=TestInputLocationMessageContent.proximity_alert_radius, + TestInputLocationMessageContentBase.latitude, + TestInputLocationMessageContentBase.longitude, + live_period=TestInputLocationMessageContentBase.live_period, + horizontal_accuracy=TestInputLocationMessageContentBase.horizontal_accuracy, + heading=TestInputLocationMessageContentBase.heading, + proximity_alert_radius=TestInputLocationMessageContentBase.proximity_alert_radius, ) -class TestInputLocationMessageContent: +class TestInputLocationMessageContentBase: latitude = -23.691288 longitude = -46.788279 live_period = 80 @@ -41,6 +41,8 @@ class TestInputLocationMessageContent: heading = 90 proximity_alert_radius = 999 + +class TestInputLocationMessageContentWithoutRequest(TestInputLocationMessageContentBase): def test_slot_behaviour(self, input_location_message_content, mro_slots): inst = input_location_message_content for attr in inst.__slots__: diff --git a/tests/test_inputmedia.py b/tests/test_inputmedia.py index a998dea60..585e3a581 100644 --- a/tests/test_inputmedia.py +++ b/tests/test_inputmedia.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio import copy from collections.abc import Sequence @@ -56,86 +57,75 @@ from .test_photo import _photo, photo, photo_file, thumb # noqa: F401 from .test_video import video, video_file # noqa: F401 -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def input_media_video(class_thumb_file): return InputMediaVideo( - media=TestInputMediaVideo.media, - caption=TestInputMediaVideo.caption, - width=TestInputMediaVideo.width, - height=TestInputMediaVideo.height, - duration=TestInputMediaVideo.duration, - parse_mode=TestInputMediaVideo.parse_mode, - caption_entities=TestInputMediaVideo.caption_entities, + media=TestInputMediaVideoBase.media, + caption=TestInputMediaVideoBase.caption, + width=TestInputMediaVideoBase.width, + height=TestInputMediaVideoBase.height, + duration=TestInputMediaVideoBase.duration, + parse_mode=TestInputMediaVideoBase.parse_mode, + caption_entities=TestInputMediaVideoBase.caption_entities, thumb=class_thumb_file, - supports_streaming=TestInputMediaVideo.supports_streaming, - has_spoiler=TestInputMediaVideo.has_spoiler, + supports_streaming=TestInputMediaVideoBase.supports_streaming, + has_spoiler=TestInputMediaVideoBase.has_spoiler, ) -@pytest.fixture(scope="class") -def input_media_photo(class_thumb_file): +@pytest.fixture(scope="module") +def input_media_photo(): return InputMediaPhoto( - media=TestInputMediaPhoto.media, - caption=TestInputMediaPhoto.caption, - parse_mode=TestInputMediaPhoto.parse_mode, - caption_entities=TestInputMediaPhoto.caption_entities, - has_spoiler=TestInputMediaPhoto.has_spoiler, + media=TestInputMediaPhotoBase.media, + caption=TestInputMediaPhotoBase.caption, + parse_mode=TestInputMediaPhotoBase.parse_mode, + caption_entities=TestInputMediaPhotoBase.caption_entities, + has_spoiler=TestInputMediaPhotoBase.has_spoiler, ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def input_media_animation(class_thumb_file): return InputMediaAnimation( - media=TestInputMediaAnimation.media, - caption=TestInputMediaAnimation.caption, - parse_mode=TestInputMediaAnimation.parse_mode, - caption_entities=TestInputMediaAnimation.caption_entities, - width=TestInputMediaAnimation.width, - height=TestInputMediaAnimation.height, + media=TestInputMediaAnimationBase.media, + caption=TestInputMediaAnimationBase.caption, + parse_mode=TestInputMediaAnimationBase.parse_mode, + caption_entities=TestInputMediaAnimationBase.caption_entities, + width=TestInputMediaAnimationBase.width, + height=TestInputMediaAnimationBase.height, thumb=class_thumb_file, - duration=TestInputMediaAnimation.duration, - has_spoiler=TestInputMediaAnimation.has_spoiler, + duration=TestInputMediaAnimationBase.duration, + has_spoiler=TestInputMediaAnimationBase.has_spoiler, ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def input_media_audio(class_thumb_file): return InputMediaAudio( - media=TestInputMediaAudio.media, - caption=TestInputMediaAudio.caption, - duration=TestInputMediaAudio.duration, - performer=TestInputMediaAudio.performer, - title=TestInputMediaAudio.title, + media=TestInputMediaAudioBase.media, + caption=TestInputMediaAudioBase.caption, + duration=TestInputMediaAudioBase.duration, + performer=TestInputMediaAudioBase.performer, + title=TestInputMediaAudioBase.title, thumb=class_thumb_file, - parse_mode=TestInputMediaAudio.parse_mode, - caption_entities=TestInputMediaAudio.caption_entities, + parse_mode=TestInputMediaAudioBase.parse_mode, + caption_entities=TestInputMediaAudioBase.caption_entities, ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def input_media_document(class_thumb_file): return InputMediaDocument( - media=TestInputMediaDocument.media, - caption=TestInputMediaDocument.caption, + media=TestInputMediaDocumentBase.media, + caption=TestInputMediaDocumentBase.caption, thumb=class_thumb_file, - parse_mode=TestInputMediaDocument.parse_mode, - caption_entities=TestInputMediaDocument.caption_entities, - disable_content_type_detection=TestInputMediaDocument.disable_content_type_detection, + parse_mode=TestInputMediaDocumentBase.parse_mode, + caption_entities=TestInputMediaDocumentBase.caption_entities, + disable_content_type_detection=TestInputMediaDocumentBase.disable_content_type_detection, ) -class CustomSequence(Sequence): - def __init__(self, items): - self.items = items - - def __getitem__(self, index): - return self.items[index] - - def __len__(self): - return len(self.items) - - -class TestInputMediaVideo: +class TestInputMediaVideoBase: type_ = "video" media = "NOTAREALFILEID" caption = "My Caption" @@ -147,6 +137,8 @@ class TestInputMediaVideo: caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)] has_spoiler = True + +class TestInputMediaVideoWithoutRequest(TestInputMediaVideoBase): def test_slot_behaviour(self, input_media_video, mro_slots): inst = input_media_video for attr in inst.__slots__: @@ -210,7 +202,7 @@ class TestInputMediaVideo: assert input_media_video.thumb == data_file("telegram.jpg").as_uri() -class TestInputMediaPhoto: +class TestInputMediaPhotoBase: type_ = "photo" media = "NOTAREALFILEID" caption = "My Caption" @@ -218,6 +210,8 @@ class TestInputMediaPhoto: caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)] has_spoiler = True + +class TestInputMediaPhotoWithoutRequest(TestInputMediaPhotoBase): def test_slot_behaviour(self, input_media_photo, mro_slots): inst = input_media_photo for attr in inst.__slots__: @@ -266,7 +260,7 @@ class TestInputMediaPhoto: assert input_media_photo.media == data_file("telegram.mp4").as_uri() -class TestInputMediaAnimation: +class TestInputMediaAnimationBase: type_ = "animation" media = "NOTAREALFILEID" caption = "My Caption" @@ -277,6 +271,8 @@ class TestInputMediaAnimation: duration = 1 has_spoiler = True + +class TestInputMediaAnimationWithoutRequest(TestInputMediaAnimationBase): def test_slot_behaviour(self, input_media_animation, mro_slots): inst = input_media_animation for attr in inst.__slots__: @@ -332,7 +328,7 @@ class TestInputMediaAnimation: assert input_media_animation.thumb == data_file("telegram.jpg").as_uri() -class TestInputMediaAudio: +class TestInputMediaAudioBase: type_ = "audio" media = "NOTAREALFILEID" caption = "My Caption" @@ -342,6 +338,8 @@ class TestInputMediaAudio: parse_mode = "HTML" caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)] + +class TestInputMediaAudioWithoutRequest(TestInputMediaAudioBase): def test_slot_behaviour(self, input_media_audio, mro_slots): inst = input_media_audio for attr in inst.__slots__: @@ -401,7 +399,7 @@ class TestInputMediaAudio: assert input_media_audio.thumb == data_file("telegram.jpg").as_uri() -class TestInputMediaDocument: +class TestInputMediaDocumentBase: type_ = "document" media = "NOTAREALFILEID" caption = "My Caption" @@ -409,6 +407,8 @@ class TestInputMediaDocument: caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)] disable_content_type_detection = True + +class TestInputMediaDocumentWithoutRequest(TestInputMediaDocumentBase): def test_slot_behaviour(self, input_media_document, mro_slots): inst = input_media_document for attr in inst.__slots__: @@ -467,7 +467,7 @@ class TestInputMediaDocument: assert input_media_document.thumb == data_file("telegram.jpg").as_uri() -@pytest.fixture(scope="function") # noqa: F811 +@pytest.fixture(scope="module") # noqa: F811 def media_group(photo, thumb): # noqa: F811 return [ InputMediaPhoto(photo, caption="*photo* 1", parse_mode="Markdown"), @@ -478,12 +478,12 @@ def media_group(photo, thumb): # noqa: F811 ] -@pytest.fixture(scope="function") # noqa: F811 +@pytest.fixture(scope="module") # noqa: F811 def media_group_no_caption_args(photo, thumb): # noqa: F811 return [InputMediaPhoto(photo), InputMediaPhoto(thumb), InputMediaPhoto(photo)] -@pytest.fixture(scope="function") # noqa: F811 +@pytest.fixture(scope="module") # noqa: F811 def media_group_no_caption_only_caption_entities(photo, thumb): # noqa: F811 return [ InputMediaPhoto(photo, caption_entities=[MessageEntity(MessageEntity.BOLD, 0, 5)]), @@ -491,7 +491,7 @@ def media_group_no_caption_only_caption_entities(photo, thumb): # noqa: F811 ] -@pytest.fixture(scope="function") # noqa: F811 +@pytest.fixture(scope="module") # noqa: F811 def media_group_no_caption_only_parse_mode(photo, thumb): # noqa: F811 return [ InputMediaPhoto(photo, parse_mode="Markdown"), @@ -499,32 +499,7 @@ def media_group_no_caption_only_parse_mode(photo, thumb): # noqa: F811 ] -class TestSendMediaGroup: - @pytest.mark.flaky(3, 1) - async def test_send_media_group_photo(self, bot, chat_id, media_group): - messages = await bot.send_media_group(chat_id, media_group) - assert isinstance(messages, tuple) - assert len(messages) == 3 - assert all(isinstance(mes, Message) for mes in messages) - assert all(mes.media_group_id == messages[0].media_group_id for mes in messages) - assert all(mes.caption == f"photo {idx+1}" for idx, mes in enumerate(messages)) - assert all( - mes.caption_entities == (MessageEntity(MessageEntity.BOLD, 0, 5),) 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 - ): - messages = await bot.send_media_group( - forum_group_id, - media_group, - message_thread_id=real_topic.message_thread_id, - ) - assert isinstance(messages, tuple) - assert len(messages) == 3 - assert all(isinstance(mes, Message) for mes in messages) - assert all(i.message_thread_id == real_topic.message_thread_id for i in messages) - +class TestSendMediaGroupWithoutRequest: async def test_send_media_group_throws_error_with_group_caption_and_individual_captions( self, bot, @@ -544,6 +519,143 @@ class TestSendMediaGroup: ): await bot.send_media_group(chat_id, group, caption="foo") + 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 + monkeypatch, + ): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + result = all( + field_tuple[0] == "custom_filename" + for field_tuple in request_data.multipart_data.values() + ) + if result is True: + raise Exception("Test was successful") + + monkeypatch.setattr(bot.request, "post", make_assertion) + + media = [ + InputMediaAnimation(animation_file, filename="custom_filename"), + InputMediaAudio(audio_file, filename="custom_filename"), + InputMediaPhoto(photo_file, filename="custom_filename"), + InputMediaVideo(video_file, filename="custom_filename"), + ] + + with pytest.raises(Exception, match="Test was successful"): + 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 + ): + async def make_assertion(method, url, request_data: RequestData, *args, **kwargs): + nonlocal input_video + files = request_data.multipart_data + video_check = files[input_video.media.attach_name] == input_video.media.field_tuple + thumb_check = files[input_video.thumb.attach_name] == input_video.thumb.field_tuple + result = video_check and thumb_check + raise Exception(f"Test was {'successful' if result else 'failing'}") + + monkeypatch.setattr(bot.request, "_request_wrapper", make_assertion) + input_video = InputMediaVideo(video_file, thumb=photo_file) + with pytest.raises(Exception, match="Test was successful"): + 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 + ): + async def make_assertion( + method: str, url: str, request_data: RequestData = None, *args, **kwargs + ): + files = request_data.multipart_data + video_check = files[input_video.media.attach_name] == input_video.media.field_tuple + thumb_check = files[input_video.thumb.attach_name] == input_video.thumb.field_tuple + result = video_check and thumb_check + raise Exception(f"Test was {'successful' if result else 'failing'}") + + monkeypatch.setattr(bot.request, "_request_wrapper", make_assertion) + input_video = InputMediaVideo(video_file, thumb=photo_file) + with pytest.raises(Exception, match="Test was successful"): + await bot.edit_message_media(chat_id=chat_id, message_id=123, media=input_video) + + +class CustomSequence(Sequence): + def __init__(self, items): + self.items = items + + def __getitem__(self, index): + return self.items[index] + + def __len__(self): + return len(self.items) + + +class TestSendMediaGroupWithRequest: + async def test_send_media_group_photo(self, bot, chat_id, media_group): + messages = await bot.send_media_group(chat_id, media_group) + assert isinstance(messages, tuple) + assert len(messages) == 3 + assert all(isinstance(mes, Message) for mes in messages) + assert all(mes.media_group_id == messages[0].media_group_id for mes in messages) + assert all(mes.caption == f"photo {idx+1}" for idx, mes in enumerate(messages)) + assert all( + 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 func(): + return await bot.send_media_group( + chat_id, + [ + InputMediaVideo(video_file), + InputMediaPhoto(photo_file), + InputMediaPhoto(data_file("telegram.jpg").read_bytes()), + ], + ) + + messages = await expect_bad_request( + func, "Type of file mismatch", "Telegram did not accept the file." + ) + + assert isinstance(messages, tuple) + assert len(messages) == 3 + assert all(isinstance(mes, Message) for mes in messages) + assert all(mes.media_group_id == messages[0].media_group_id for mes in messages) + + @pytest.mark.parametrize("sequence_type", [list, tuple, CustomSequence]) + @pytest.mark.parametrize("bot_class", ["raw_bot", "ext_bot"]) + async def test_send_media_group_different_sequences( + self, bot, chat_id, media_group, sequence_type, bot_class, raw_bot + ): + """Test that send_media_group accepts different sequence types. This test ensures that + Bot._insert_defaults works for arbitrary sequence types.""" + bot = bot if bot_class == "ext_bot" else raw_bot + + messages = await bot.send_media_group(chat_id, sequence_type(media_group)) + assert isinstance(messages, tuple) + assert len(messages) == 3 + assert all(isinstance(mes, Message) for mes in messages) + 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 + ): + messages = await bot.send_media_group( + forum_group_id, + media_group, + message_thread_id=real_topic.message_thread_id, + ) + assert isinstance(messages, tuple) + assert len(messages) == 3 + assert all(isinstance(mes, Message) for mes in messages) + assert all(i.message_thread_id == real_topic.message_thread_id for i in messages) + @pytest.mark.parametrize( "caption, parse_mode, caption_entities", [ @@ -553,7 +665,6 @@ class TestSendMediaGroup: ("photo 1", None, [MessageEntity(MessageEntity.BOLD, 0, 5)]), ], ) - @pytest.mark.flaky(3, 1) async def test_send_media_group_with_group_caption( self, bot, @@ -598,16 +709,15 @@ class TestSendMediaGroup: assert all(mes.caption is None for mes in other_messages) assert not any(mes.caption_entities for mes in other_messages) - @pytest.mark.flaky(3, 1) async def test_send_media_group_all_args(self, bot, raw_bot, chat_id, media_group): ext_bot = bot - for bot in (ext_bot, raw_bot): - # We need to test 1) below both the bot and raw_bot and setting this up with - # pytest.parametrize appears to be difficult ... - - m1 = await bot.send_message(chat_id, text="test") + # We need to test 1) below both the bot and raw_bot and setting this up with + # pytest.parametrize appears to be difficult ... + aws = {b.send_message(chat_id, text="test") for b in (ext_bot, raw_bot)} + for msg_task in asyncio.as_completed(aws): + m1 = await msg_task copied_media_group = copy.copy(media_group) - messages = await bot.send_media_group( + messages = await m1.get_bot().send_media_group( chat_id, media_group, disable_notification=True, @@ -633,7 +743,6 @@ class TestSendMediaGroup: ) assert all(mes.has_protected_content for mes in messages) - @pytest.mark.flaky(3, 1) async def test_send_media_group_with_spoiler( self, bot, chat_id, photo_file, video_file # noqa: F811 ): @@ -649,97 +758,36 @@ class TestSendMediaGroup: assert all(mes.media_group_id == messages[0].media_group_id for mes in messages) assert all(mes.has_media_spoiler for mes in messages) - @pytest.mark.flaky(3, 1) - 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 - monkeypatch, - ): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - result = all( - field_tuple[0] == "custom_filename" - for field_tuple in request_data.multipart_data.values() + async def test_edit_message_media(self, bot, raw_bot, chat_id, media_group): + ext_bot = bot + # We need to test 1) below both the bot and raw_bot and setting this up with + # pytest.parametrize appears to be difficult ... + aws = {b.send_media_group(chat_id, media_group) for b in (ext_bot, raw_bot)} + for msg_task in asyncio.as_completed(aws): + messages = await msg_task + cid = messages[-1].chat.id + mid = messages[-1].message_id + copied_media = copy.copy(media_group[0]) + new_message = ( + await messages[-1] + .get_bot() + .edit_message_media(chat_id=cid, message_id=mid, media=media_group[0]) ) - if result is True: - raise Exception("Test was successful") + assert isinstance(new_message, Message) - monkeypatch.setattr(bot.request, "post", make_assertion) + # 1) + # make sure that the media was not modified + assert media_group[0].parse_mode == copied_media.parse_mode - media = [ - InputMediaAnimation(animation_file, filename="custom_filename"), - InputMediaAudio(audio_file, filename="custom_filename"), - InputMediaPhoto(photo_file, filename="custom_filename"), - InputMediaVideo(video_file, filename="custom_filename"), - ] - - with pytest.raises(Exception, match="Test was successful"): - 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 - ): - async def make_assertion(method, url, request_data: RequestData, *args, **kwargs): - files = request_data.multipart_data - video_check = files[input_video.media.attach_name] == input_video.media.field_tuple - thumb_check = files[input_video.thumb.attach_name] == input_video.thumb.field_tuple - result = video_check and thumb_check - raise Exception(f"Test was {'successful' if result else 'failing'}") - - monkeypatch.setattr(bot.request, "_request_wrapper", make_assertion) - input_video = InputMediaVideo(video_file, thumb=photo_file) - with pytest.raises(Exception, match="Test was successful"): - await bot.send_media_group(chat_id, [input_video, input_video]) - - @pytest.mark.flaky(3, 1) # noqa: F811 - async def test_send_media_group_new_files( - self, - bot, - chat_id, - video_file, # noqa: F811 - photo_file, # noqa: F811 - animation_file, # noqa: F811 - ): - async def func(): - return await bot.send_media_group( - chat_id, - [ - InputMediaVideo(video_file), - InputMediaPhoto(photo_file), - InputMediaPhoto(data_file("telegram.jpg").read_bytes()), - ], - ) - - messages = await expect_bad_request( - func, "Type of file mismatch", "Telegram did not accept the file." + async def test_edit_message_media_new_file(self, bot, chat_id, media_group, thumb_file): + messages = await bot.send_media_group(chat_id, media_group) + cid = messages[-1].chat.id + mid = messages[-1].message_id + new_message = await bot.edit_message_media( + chat_id=cid, message_id=mid, media=InputMediaPhoto(thumb_file) ) + assert isinstance(new_message, Message) - assert isinstance(messages, tuple) - assert len(messages) == 3 - assert all(isinstance(mes, Message) for mes in messages) - assert all(mes.media_group_id == messages[0].media_group_id for mes in messages) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("sequence_type", [list, tuple, CustomSequence]) - @pytest.mark.parametrize("bot_class", ["raw_bot", "ext_bot"]) - async def test_send_media_group_different_sequences( - self, bot, chat_id, media_group, sequence_type, bot_class, raw_bot - ): - """Test that send_media_group accepts different sequence types. This test ensures that - Bot._insert_defaults works for arbitrary sequence types.""" - bot = bot if bot_class == "ext_bot" else raw_bot - - messages = await bot.send_media_group(chat_id, sequence_type(media_group)) - assert isinstance(messages, tuple) - assert len(messages) == 3 - assert all(isinstance(mes, Message) for mes in messages) - assert all(mes.media_group_id == messages[0].media_group_id for mes in messages) - - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize( "default_bot,custom", [ @@ -773,19 +821,18 @@ class TestSendMediaGroup: chat_id, media_group, reply_to_message_id=reply_to_message.message_id ) - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) async def test_send_media_group_default_protect_content( self, chat_id, media_group, default_bot ): - protected = await default_bot.send_media_group(chat_id, media_group) - assert all(msg.has_protected_content for msg in protected) - unprotected = await default_bot.send_media_group( - chat_id, media_group, protect_content=False + tasks = asyncio.gather( + default_bot.send_media_group(chat_id, media_group), + default_bot.send_media_group(chat_id, media_group, protect_content=False), ) + protected, unprotected = await tasks + assert all(msg.has_protected_content for msg in protected) assert not all(msg.has_protected_content for msg in unprotected) - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize("default_bot", [{"parse_mode": ParseMode.HTML}], indirect=True) async def test_send_media_group_default_parse_mode( self, chat_id, media_group_no_caption_args, default_bot @@ -797,19 +844,21 @@ class TestSendMediaGroup: # make sure no parse_mode was set as a side effect assert not any(item.parse_mode for item in media_group_no_caption_args) - overridden_markdown_v2 = await default_bot.send_media_group( - chat_id, - media_group_no_caption_args.copy(), - caption="*photo* 1", - parse_mode=ParseMode.MARKDOWN_V2, - ) - - overridden_none = await default_bot.send_media_group( - chat_id, - media_group_no_caption_args.copy(), - caption="photo 1", - parse_mode=None, + tasks = asyncio.gather( + default_bot.send_media_group( + chat_id, + media_group_no_caption_args.copy(), + caption="*photo* 1", + parse_mode=ParseMode.MARKDOWN_V2, + ), + default_bot.send_media_group( + chat_id, + media_group_no_caption_args.copy(), + caption="photo 1", + parse_mode=None, + ), ) + overridden_markdown_v2, overridden_none = await tasks # Make sure first message got the caption, which will lead to Telegram # displaying its caption as group caption @@ -830,53 +879,6 @@ class TestSendMediaGroup: assert all(mes.caption is None for mes in other_messages) assert not any(mes.caption_entities for mes in other_messages) - @pytest.mark.flaky(3, 1) - async def test_edit_message_media(self, bot, raw_bot, chat_id, media_group): - ext_bot = bot - for bot in (ext_bot, raw_bot): - # We need to test 1) below both the bot and raw_bot and setting this up with - # pytest.parametrize appears to be difficult ... - messages = await bot.send_media_group(chat_id, media_group) - cid = messages[-1].chat.id - mid = messages[-1].message_id - copied_media = copy.copy(media_group[0]) - new_message = await bot.edit_message_media( - chat_id=cid, message_id=mid, media=media_group[0] - ) - assert isinstance(new_message, Message) - - # 1) - # make sure that the media was not modified - assert media_group[0].parse_mode == copied_media.parse_mode - - @pytest.mark.flaky(3, 1) - async def test_edit_message_media_new_file(self, bot, chat_id, media_group, thumb_file): - messages = await bot.send_media_group(chat_id, media_group) - cid = messages[-1].chat.id - mid = messages[-1].message_id - new_message = await bot.edit_message_media( - chat_id=cid, message_id=mid, media=InputMediaPhoto(thumb_file) - ) - assert isinstance(new_message, Message) - - async def test_edit_message_media_with_thumb( - self, bot, chat_id, video_file, photo_file, monkeypatch # noqa: F811 - ): - async def make_assertion( - method: str, url: str, request_data: RequestData = None, *args, **kwargs - ): - files = request_data.multipart_data - video_check = files[input_video.media.attach_name] == input_video.media.field_tuple - thumb_check = files[input_video.thumb.attach_name] == input_video.thumb.field_tuple - result = video_check and thumb_check - raise Exception(f"Test was {'successful' if result else 'failing'}") - - monkeypatch.setattr(bot.request, "_request_wrapper", make_assertion) - input_video = InputMediaVideo(video_file, thumb=photo_file) - with pytest.raises(Exception, match="Test was successful"): - await bot.edit_message_media(chat_id=chat_id, message_id=123, media=input_video) - - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize( "default_bot", [{"parse_mode": ParseMode.HTML}], indirect=True, ids=["HTML-Bot"] ) diff --git a/tests/test_inputtextmessagecontent.py b/tests/test_inputtextmessagecontent.py index 97af5019d..13d53796f 100644 --- a/tests/test_inputtextmessagecontent.py +++ b/tests/test_inputtextmessagecontent.py @@ -22,22 +22,24 @@ from telegram import InputTextMessageContent, MessageEntity from telegram.constants import ParseMode -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def input_text_message_content(): return InputTextMessageContent( - TestInputTextMessageContent.message_text, - parse_mode=TestInputTextMessageContent.parse_mode, - entities=TestInputTextMessageContent.entities, - disable_web_page_preview=TestInputTextMessageContent.disable_web_page_preview, + TestInputTextMessageContentBase.message_text, + parse_mode=TestInputTextMessageContentBase.parse_mode, + entities=TestInputTextMessageContentBase.entities, + disable_web_page_preview=TestInputTextMessageContentBase.disable_web_page_preview, ) -class TestInputTextMessageContent: +class TestInputTextMessageContentBase: message_text = "*message text*" parse_mode = ParseMode.MARKDOWN entities = [MessageEntity(MessageEntity.ITALIC, 0, 7)] disable_web_page_preview = True + +class TestInputTextMessageContentWithoutRequest(TestInputTextMessageContentBase): def test_slot_behaviour(self, input_text_message_content, mro_slots): inst = input_text_message_content for attr in inst.__slots__: diff --git a/tests/test_inputvenuemessagecontent.py b/tests/test_inputvenuemessagecontent.py index cdad5f61b..2837249d1 100644 --- a/tests/test_inputvenuemessagecontent.py +++ b/tests/test_inputvenuemessagecontent.py @@ -21,21 +21,21 @@ import pytest from telegram import InputVenueMessageContent, Location -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def input_venue_message_content(): return InputVenueMessageContent( - TestInputVenueMessageContent.latitude, - TestInputVenueMessageContent.longitude, - TestInputVenueMessageContent.title, - TestInputVenueMessageContent.address, - foursquare_id=TestInputVenueMessageContent.foursquare_id, - foursquare_type=TestInputVenueMessageContent.foursquare_type, - google_place_id=TestInputVenueMessageContent.google_place_id, - google_place_type=TestInputVenueMessageContent.google_place_type, + TestInputVenueMessageContentBase.latitude, + TestInputVenueMessageContentBase.longitude, + TestInputVenueMessageContentBase.title, + TestInputVenueMessageContentBase.address, + foursquare_id=TestInputVenueMessageContentBase.foursquare_id, + foursquare_type=TestInputVenueMessageContentBase.foursquare_type, + google_place_id=TestInputVenueMessageContentBase.google_place_id, + google_place_type=TestInputVenueMessageContentBase.google_place_type, ) -class TestInputVenueMessageContent: +class TestInputVenueMessageContentBase: latitude = 1.0 longitude = 2.0 title = "title" @@ -45,6 +45,8 @@ class TestInputVenueMessageContent: google_place_id = "google place id" google_place_type = "google place type" + +class TestInputVenueMessageContentWithoutRequest(TestInputVenueMessageContentBase): def test_slot_behaviour(self, input_venue_message_content, mro_slots): inst = input_venue_message_content for attr in inst.__slots__: diff --git a/tests/test_invoice.py b/tests/test_invoice.py index a4476ce4c..cc2a0b2fa 100644 --- a/tests/test_invoice.py +++ b/tests/test_invoice.py @@ -16,6 +16,8 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio + import pytest from telegram import Invoice, LabeledPrice @@ -23,18 +25,18 @@ from telegram.error import BadRequest from telegram.request import RequestData -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def invoice(): return Invoice( - TestInvoice.title, - TestInvoice.description, - TestInvoice.start_parameter, - TestInvoice.currency, - TestInvoice.total_amount, + TestInvoiceBase.title, + TestInvoiceBase.description, + TestInvoiceBase.start_parameter, + TestInvoiceBase.currency, + TestInvoiceBase.total_amount, ) -class TestInvoice: +class TestInvoiceBase: payload = "payload" prices = [LabeledPrice("Fish", 100), LabeledPrice("Fish Tax", 1000)] provider_data = """{"test":"test"}""" @@ -46,6 +48,8 @@ class TestInvoice: max_tip_amount = 42 suggested_tip_amounts = [13, 42] + +class TestInvoiceWithoutRequest(TestInvoiceBase): def test_slot_behaviour(self, invoice, mro_slots): for attr in invoice.__slots__: assert getattr(invoice, attr, "err") != "err", f"got extra slot '{attr}'" @@ -54,11 +58,11 @@ class TestInvoice: def test_de_json(self, bot): invoice_json = Invoice.de_json( { - "title": TestInvoice.title, - "description": TestInvoice.description, - "start_parameter": TestInvoice.start_parameter, - "currency": TestInvoice.currency, - "total_amount": TestInvoice.total_amount, + "title": self.title, + "description": self.description, + "start_parameter": self.start_parameter, + "currency": self.currency, + "total_amount": self.total_amount, }, bot, ) @@ -80,100 +84,12 @@ class TestInvoice: assert invoice_dict["currency"] == invoice.currency assert invoice_dict["total_amount"] == invoice.total_amount - @pytest.mark.flaky(3, 1) - async def test_send_required_args_only(self, bot, chat_id, provider_token): - message = await bot.send_invoice( - chat_id=chat_id, - title=self.title, - description=self.description, - payload=self.payload, - provider_token=provider_token, - currency=self.currency, - prices=self.prices, - ) - - assert message.invoice.currency == self.currency - assert message.invoice.start_parameter == "" - assert message.invoice.description == self.description - assert message.invoice.title == self.title - assert message.invoice.total_amount == self.total_amount - - link = await bot.create_invoice_link( - title=self.title, - description=self.description, - payload=self.payload, - provider_token=provider_token, - currency=self.currency, - prices=self.prices, - ) - assert isinstance(link, str) - assert link != "" - - async def test_send_all_args_send_invoice(self, bot, chat_id, provider_token, monkeypatch): - message = await bot.send_invoice( - chat_id, - self.title, - self.description, - self.payload, - provider_token, - self.currency, - self.prices, - max_tip_amount=self.max_tip_amount, - suggested_tip_amounts=self.suggested_tip_amounts, - start_parameter=self.start_parameter, - provider_data=self.provider_data, - photo_url="https://raw.githubusercontent.com/" - "python-telegram-bot/logos/master/" - "logo/png/ptb-logo_240.png", - photo_size=240, - photo_width=240, - photo_height=240, - need_name=True, - need_phone_number=True, - need_email=True, - need_shipping_address=True, - send_phone_number_to_provider=True, - send_email_to_provider=True, - is_flexible=True, - disable_notification=True, - protect_content=True, - ) - - assert message.invoice.currency == self.currency - assert message.invoice.start_parameter == self.start_parameter - assert message.invoice.description == self.description - assert message.invoice.title == self.title - assert message.invoice.total_amount == self.total_amount - assert message.has_protected_content - - # We do this next one as safety guard to make sure that we pass all of the optional + async def test_send_invoice_all_args_mock(self, bot, monkeypatch): + # We do this one as safety guard to make sure that we pass all of the optional # parameters correctly because #2526 went unnoticed for 3 years … async def make_assertion(*args, **_): kwargs = args[1] - return ( - kwargs["chat_id"] == "chat_id" - and kwargs["title"] == "title" - and kwargs["description"] == "description" - and kwargs["payload"] == "payload" - and kwargs["provider_token"] == "provider_token" - and kwargs["currency"] == "currency" - and kwargs["prices"] == self.prices - and kwargs["max_tip_amount"] == "max_tip_amount" - and kwargs["suggested_tip_amounts"] == "suggested_tip_amounts" - and kwargs["start_parameter"] == "start_parameter" - and kwargs["provider_data"] == "provider_data" - and kwargs["photo_url"] == "photo_url" - and kwargs["photo_size"] == "photo_size" - and kwargs["photo_width"] == "photo_width" - and kwargs["photo_height"] == "photo_height" - and kwargs["need_name"] == "need_name" - and kwargs["need_phone_number"] == "need_phone_number" - and kwargs["need_email"] == "need_email" - and kwargs["need_shipping_address"] == "need_shipping_address" - and kwargs["send_phone_number_to_provider"] == "send_phone_number_to_provider" - and kwargs["send_email_to_provider"] == "send_email_to_provider" - and kwargs["is_flexible"] == "is_flexible" - ) + return all([kwargs[key] == key for key in kwargs]) monkeypatch.setattr(bot, "_send_message", make_assertion) assert await bot.send_invoice( @@ -183,7 +99,7 @@ class TestInvoice: payload="payload", provider_token="provider_token", currency="currency", - prices=self.prices, + prices="prices", max_tip_amount="max_tip_amount", suggested_tip_amounts="suggested_tip_amounts", start_parameter="start_parameter", @@ -203,33 +119,10 @@ class TestInvoice: protect_content=True, ) - async def test_send_all_args_create_invoice_link( - self, bot, chat_id, provider_token, monkeypatch - ): + async def test_send_all_args_create_invoice_link(self, bot, monkeypatch): async def make_assertion(*args, **_): kwargs = args[1] - return ( - kwargs["title"] == "title" - and kwargs["description"] == "description" - and kwargs["payload"] == "payload" - and kwargs["provider_token"] == "provider_token" - and kwargs["currency"] == "currency" - and kwargs["prices"] == self.prices - and kwargs["max_tip_amount"] == "max_tip_amount" - and kwargs["suggested_tip_amounts"] == "suggested_tip_amounts" - and kwargs["provider_data"] == "provider_data" - and kwargs["photo_url"] == "photo_url" - and kwargs["photo_size"] == "photo_size" - and kwargs["photo_width"] == "photo_width" - and kwargs["photo_height"] == "photo_height" - and kwargs["need_name"] == "need_name" - and kwargs["need_phone_number"] == "need_phone_number" - and kwargs["need_email"] == "need_email" - and kwargs["need_shipping_address"] == "need_shipping_address" - and kwargs["send_phone_number_to_provider"] == "send_phone_number_to_provider" - and kwargs["send_email_to_provider"] == "send_email_to_provider" - and kwargs["is_flexible"] == "is_flexible" - ) + return all([kwargs[i] == i for i in kwargs]) monkeypatch.setattr(bot, "_post", make_assertion) assert await bot.create_invoice_link( @@ -238,7 +131,7 @@ class TestInvoice: payload="payload", provider_token="provider_token", currency="currency", - prices=self.prices, + prices="prices", max_tip_amount="max_tip_amount", suggested_tip_amounts="suggested_tip_amounts", provider_data="provider_data", @@ -273,7 +166,75 @@ class TestInvoice: start_parameter=self.start_parameter, ) - @pytest.mark.flaky(3, 1) + def test_equality(self): + a = Invoice("invoice", "desc", "start", "EUR", 7) + b = Invoice("invoice", "desc", "start", "EUR", 7) + c = Invoice("invoices", "description", "stop", "USD", 8) + d = LabeledPrice("label", 5) + + assert a == b + assert hash(a) == hash(b) + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d) + + +class TestInvoiceWithRequest(TestInvoiceBase): + async def test_send_required_args_only(self, bot, chat_id, provider_token): + message = await bot.send_invoice( + chat_id=chat_id, + title=self.title, + description=self.description, + payload=self.payload, + provider_token=provider_token, + currency=self.currency, + prices=self.prices, + ) + + assert message.invoice.currency == self.currency + assert message.invoice.start_parameter == "" + assert message.invoice.description == self.description + assert message.invoice.title == self.title + assert message.invoice.total_amount == self.total_amount + + link = await bot.create_invoice_link( + title=self.title, + description=self.description, + payload=self.payload, + provider_token=provider_token, + currency=self.currency, + prices=self.prices, + ) + + assert isinstance(link, str) + assert link != "" + + @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) + async def test_send_invoice_default_protect_content( + self, chat_id, default_bot, provider_token + ): + tasks = asyncio.gather( + *( + default_bot.send_invoice( + chat_id, + self.title, + self.description, + self.payload, + provider_token, + self.currency, + self.prices, + **kwargs, + ) + for kwargs in ({}, {"protect_content": False}) + ) + ) + protected, unprotected = await tasks + assert protected.has_protected_content + assert not unprotected.has_protected_content + @pytest.mark.parametrize( "default_bot,custom", [ @@ -326,12 +287,8 @@ class TestInvoice: reply_to_message_id=reply_to_message.message_id, ) - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) - async def test_send_invoice_default_protect_content( - self, chat_id, default_bot, provider_token - ): - protected = await default_bot.send_invoice( + async def test_send_all_args_send_invoice(self, bot, chat_id, provider_token): + message = await bot.send_invoice( chat_id, self.title, self.description, @@ -339,31 +296,26 @@ class TestInvoice: provider_token, self.currency, self.prices, + max_tip_amount=self.max_tip_amount, + suggested_tip_amounts=self.suggested_tip_amounts, + start_parameter=self.start_parameter, + provider_data=self.provider_data, + photo_url="https://raw.githubusercontent.com/" + "python-telegram-bot/logos/master/logo/png/ptb-logo_240.png", + photo_size=240, + photo_width=240, + photo_height=240, + need_name=True, + need_phone_number=True, + need_email=True, + need_shipping_address=True, + send_phone_number_to_provider=True, + send_email_to_provider=True, + is_flexible=True, + disable_notification=True, + protect_content=True, ) - assert protected.has_protected_content - unprotected = await default_bot.send_invoice( - chat_id, - self.title, - self.description, - self.payload, - provider_token, - self.currency, - self.prices, - protect_content=False, - ) - assert not unprotected.has_protected_content - def test_equality(self): - a = Invoice("invoice", "desc", "start", "EUR", 7) - b = Invoice("invoice", "desc", "start", "EUR", 7) - c = Invoice("invoices", "description", "stop", "USD", 8) - d = LabeledPrice("label", 5) - - assert a == b - assert hash(a) == hash(b) - - assert a != c - assert hash(a) != hash(c) - - assert a != d - assert hash(a) != hash(d) + for attr in message.invoice.__slots__: + assert getattr(message.invoice, attr) == getattr(self, attr) + assert message.has_protected_content diff --git a/tests/test_jobqueue.py b/tests/test_jobqueue.py index 6bd4244e7..9376cbb12 100644 --- a/tests/test_jobqueue.py +++ b/tests/test_jobqueue.py @@ -27,9 +27,7 @@ import time import pytest from telegram.ext import ApplicationBuilder, CallbackContext, ContextTypes, Job, JobQueue -from tests.auxil.object_conversions import env_var_2_bool - -TEST_WITH_OPT_DEPS = env_var_2_bool(os.getenv("TEST_WITH_OPT_DEPS", True)) +from tests.conftest import TEST_WITH_OPT_DEPS, make_bot if TEST_WITH_OPT_DEPS: import pytz @@ -46,7 +44,7 @@ class CustomContext(CallbackContext): @pytest.fixture(scope="function") -async def job_queue(bot, app): +async def job_queue(app): jq = JobQueue() jq.set_application(app) await jq.start() @@ -179,9 +177,9 @@ class TestJobQueue: job_queue.run_repeating( self.job_run_once, 0.5, first=dtm.datetime.now(timezone) + dtm.timedelta(seconds=0.2) ) - await asyncio.sleep(0.15) + await asyncio.sleep(0.05) assert self.result == 0 - await asyncio.sleep(0.2) + await asyncio.sleep(0.25) assert self.result == 1 async def test_run_repeating_last(self, job_queue): @@ -192,7 +190,7 @@ class TestJobQueue: assert self.result == 1 async def test_run_repeating_last_timezone(self, job_queue, timezone): - """Test correct scheduling of job when passing a timezone-aware datetime as ``first``""" + """Test correct scheduling of job when passing a timezone-aware datetime as ``last``""" job_queue.run_repeating( self.job_run_once, 0.25, last=dtm.datetime.now(timezone) + dtm.timedelta(seconds=0.4) ) @@ -244,6 +242,7 @@ class TestJobQueue: j2 = job_queue.run_repeating(self.job_run_once, 0.2) await asyncio.sleep(0.25) + assert self.result == 1 j1.schedule_removal() j2.schedule_removal() @@ -273,8 +272,8 @@ class TestJobQueue: await asyncio.sleep(0.3) assert self.result == 1 - async def test_in_application(self, bot): - app = ApplicationBuilder().token(bot.token).build() + async def test_in_application(self, bot_info): + app = ApplicationBuilder().bot(make_bot(bot_info)).build() async with app: assert not app.job_queue.scheduler.running await app.start() @@ -311,7 +310,7 @@ class TestJobQueue: # Testing running at a specific datetime delta, now = dtm.timedelta(seconds=0.5), dtm.datetime.now(UTC) when = now + delta - expected_time = (now + delta).timestamp() + expected_time = when.timestamp() job_queue.run_once(self.job_datetime_tests, when) await asyncio.sleep(0.6) @@ -444,8 +443,11 @@ class TestJobQueue: callback = self.job_run_once job1 = job_queue.run_once(callback, 10, name="name1") + await asyncio.sleep(0.03) # To stablize tests on windows job2 = job_queue.run_once(callback, 10, name="name1") + await asyncio.sleep(0.03) job3 = job_queue.run_once(callback, 10, name="name2") + await asyncio.sleep(0.03) assert job_queue.jobs() == (job1, job2, job3) assert job_queue.get_jobs_by_name("name1") == (job1, job2) @@ -453,9 +455,9 @@ class TestJobQueue: async def test_job_run(self, app): job = app.job_queue.run_repeating(self.job_run_once, 0.02) - await asyncio.sleep(0.05) - assert self.result == 0 - await job.run(app) + await asyncio.sleep(0.05) # the job queue has not started yet + assert self.result == 0 # so the job will not run + await job.run(app) # but this will force it to run assert self.result == 1 async def test_enable_disable_job(self, job_queue): @@ -569,7 +571,7 @@ class TestJobQueue: ) job_queue.set_application(application) - def callback(context): + async def callback(context): self.result = ( type(context), context.user_data, @@ -603,8 +605,8 @@ class TestJobQueue: if wait: assert not task.done() ready_event.set() - await asyncio.sleep(0.1) + await asyncio.sleep(0.1) # no CancelledError (see source of JobQueue.stop for details) assert task.done() else: - await asyncio.sleep(0.1) + await asyncio.sleep(0.1) # unfortunately we will get a CancelledError here assert task.done() diff --git a/tests/test_keyboardbutton.py b/tests/test_keyboardbutton.py index 7d508973c..9e82aed1b 100644 --- a/tests/test_keyboardbutton.py +++ b/tests/test_keyboardbutton.py @@ -28,20 +28,20 @@ from telegram import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def keyboard_button(): return KeyboardButton( - TestKeyboardButton.text, - request_location=TestKeyboardButton.request_location, - request_contact=TestKeyboardButton.request_contact, - request_poll=TestKeyboardButton.request_poll, - web_app=TestKeyboardButton.web_app, - request_chat=TestKeyboardButton.request_chat, - request_user=TestKeyboardButton.request_user, + TestKeyboardButtonBase.text, + request_location=TestKeyboardButtonBase.request_location, + request_contact=TestKeyboardButtonBase.request_contact, + request_poll=TestKeyboardButtonBase.request_poll, + web_app=TestKeyboardButtonBase.web_app, + request_chat=TestKeyboardButtonBase.request_chat, + request_user=TestKeyboardButtonBase.request_user, ) -class TestKeyboardButton: +class TestKeyboardButtonBase: text = "text" request_location = True request_contact = True @@ -50,6 +50,8 @@ class TestKeyboardButton: request_chat = KeyboardButtonRequestChat(1, True) request_user = KeyboardButtonRequestUser(2) + +class TestKeyboardButtonWithoutRequest(TestKeyboardButtonBase): def test_slot_behaviour(self, keyboard_button, mro_slots): inst = keyboard_button for attr in inst.__slots__: diff --git a/tests/test_keyboardbuttonpolltype.py b/tests/test_keyboardbuttonpolltype.py index 85bc19e82..56404a213 100644 --- a/tests/test_keyboardbuttonpolltype.py +++ b/tests/test_keyboardbuttonpolltype.py @@ -21,14 +21,16 @@ import pytest from telegram import KeyboardButtonPollType, Poll -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def keyboard_button_poll_type(): - return KeyboardButtonPollType(TestKeyboardButtonPollType.type) + return KeyboardButtonPollType(TestKeyboardButtonPollTypeBase.type) -class TestKeyboardButtonPollType: +class TestKeyboardButtonPollTypeBase: type = Poll.QUIZ + +class TestKeyboardButtonPollTypeWithoutRequest(TestKeyboardButtonPollTypeBase): def test_slot_behaviour(self, keyboard_button_poll_type, mro_slots): inst = keyboard_button_poll_type for attr in inst.__slots__: diff --git a/tests/test_keyboardbuttonrequest.py b/tests/test_keyboardbuttonrequest.py index a59faba31..f298ba55f 100644 --- a/tests/test_keyboardbuttonrequest.py +++ b/tests/test_keyboardbuttonrequest.py @@ -25,17 +25,19 @@ from telegram import ChatAdministratorRights, KeyboardButtonRequestChat, Keyboar @pytest.fixture(scope="class") def request_user(): return KeyboardButtonRequestUser( - TestKeyboardButtonRequestUser.request_id, - TestKeyboardButtonRequestUser.user_is_bot, - TestKeyboardButtonRequestUser.user_is_premium, + TestKeyboardButtonRequestUserBase.request_id, + TestKeyboardButtonRequestUserBase.user_is_bot, + TestKeyboardButtonRequestUserBase.user_is_premium, ) -class TestKeyboardButtonRequestUser: +class TestKeyboardButtonRequestUserBase: request_id = 123 user_is_bot = True user_is_premium = False + +class TestKeyboardButtonRequestUserWithoutRequest(TestKeyboardButtonRequestUserBase): def test_slot_behaviour(self, request_user, mro_slots): for attr in request_user.__slots__: assert getattr(request_user, attr, "err") != "err", f"got extra slot '{attr}'" @@ -78,18 +80,18 @@ class TestKeyboardButtonRequestUser: @pytest.fixture(scope="class") def request_chat(): return KeyboardButtonRequestChat( - TestKeyboardButtonRequestChat.request_id, - TestKeyboardButtonRequestChat.chat_is_channel, - TestKeyboardButtonRequestChat.chat_is_forum, - TestKeyboardButtonRequestChat.chat_has_username, - TestKeyboardButtonRequestChat.chat_is_created, - TestKeyboardButtonRequestChat.user_administrator_rights, - TestKeyboardButtonRequestChat.bot_administrator_rights, - TestKeyboardButtonRequestChat.bot_is_member, + TestKeyboardButtonRequestChatBase.request_id, + TestKeyboardButtonRequestChatBase.chat_is_channel, + TestKeyboardButtonRequestChatBase.chat_is_forum, + TestKeyboardButtonRequestChatBase.chat_has_username, + TestKeyboardButtonRequestChatBase.chat_is_created, + TestKeyboardButtonRequestChatBase.user_administrator_rights, + TestKeyboardButtonRequestChatBase.bot_administrator_rights, + TestKeyboardButtonRequestChatBase.bot_is_member, ) -class TestKeyboardButtonRequestChat: +class TestKeyboardButtonRequestChatBase: request_id = 456 chat_is_channel = True chat_is_forum = False @@ -103,6 +105,8 @@ class TestKeyboardButtonRequestChat: ) bot_is_member = True + +class TestKeyboardButtonRequestChatWithoutRequest(TestKeyboardButtonRequestChatBase): def test_slot_behaviour(self, request_chat, mro_slots): for attr in request_chat.__slots__: assert getattr(request_chat, attr, "err") != "err", f"got extra slot '{attr}'" diff --git a/tests/test_labeledprice.py b/tests/test_labeledprice.py index 95b1c1332..e5c61c51b 100644 --- a/tests/test_labeledprice.py +++ b/tests/test_labeledprice.py @@ -21,15 +21,17 @@ import pytest from telegram import LabeledPrice, Location -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def labeled_price(): - return LabeledPrice(TestLabeledPrice.label, TestLabeledPrice.amount) + return LabeledPrice(TestLabeledPriceBase.label, TestLabeledPriceBase.amount) -class TestLabeledPrice: +class TestLabeledPriceBase: label = "label" amount = 100 + +class TestLabeledPriceWithoutRequest(TestLabeledPriceBase): def test_slot_behaviour(self, labeled_price, mro_slots): inst = labeled_price for attr in inst.__slots__: diff --git a/tests/test_location.py b/tests/test_location.py index 30045559a..06ac34c4d 100644 --- a/tests/test_location.py +++ b/tests/test_location.py @@ -16,6 +16,8 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio + import pytest from telegram import Location @@ -23,19 +25,19 @@ from telegram.error import BadRequest from telegram.request import RequestData -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def location(): return Location( - latitude=TestLocation.latitude, - longitude=TestLocation.longitude, - horizontal_accuracy=TestLocation.horizontal_accuracy, - live_period=TestLocation.live_period, - heading=TestLocation.live_period, - proximity_alert_radius=TestLocation.proximity_alert_radius, + latitude=TestLocationBase.latitude, + longitude=TestLocationBase.longitude, + horizontal_accuracy=TestLocationBase.horizontal_accuracy, + live_period=TestLocationBase.live_period, + heading=TestLocationBase.live_period, + proximity_alert_radius=TestLocationBase.proximity_alert_radius, ) -class TestLocation: +class TestLocationBase: latitude = -23.691288 longitude = -46.788279 horizontal_accuracy = 999 @@ -43,6 +45,8 @@ class TestLocation: heading = 90 proximity_alert_radius = 50 + +class TestLocationWithoutRequest(TestLocationBase): def test_slot_behaviour(self, location, mro_slots): for attr in location.__slots__: assert getattr(location, attr, "err") != "err", f"got extra slot '{attr}'" @@ -50,12 +54,12 @@ class TestLocation: def test_de_json(self, bot): json_dict = { - "latitude": TestLocation.latitude, - "longitude": TestLocation.longitude, - "horizontal_accuracy": TestLocation.horizontal_accuracy, - "live_period": TestLocation.live_period, - "heading": TestLocation.heading, - "proximity_alert_radius": TestLocation.proximity_alert_radius, + "latitude": self.latitude, + "longitude": self.longitude, + "horizontal_accuracy": self.horizontal_accuracy, + "live_period": self.live_period, + "heading": self.heading, + "proximity_alert_radius": self.proximity_alert_radius, } location = Location.de_json(json_dict, bot) assert location.api_kwargs == {} @@ -67,7 +71,139 @@ class TestLocation: assert location.heading == self.heading assert location.proximity_alert_radius == self.proximity_alert_radius - @pytest.mark.flaky(3, 1) + def test_to_dict(self, location): + location_dict = location.to_dict() + + assert location_dict["latitude"] == location.latitude + assert location_dict["longitude"] == location.longitude + assert location_dict["horizontal_accuracy"] == location.horizontal_accuracy + assert location_dict["live_period"] == location.live_period + assert location["heading"] == location.heading + assert location["proximity_alert_radius"] == location.proximity_alert_radius + + def test_equality(self): + a = Location(self.longitude, self.latitude) + b = Location(self.longitude, self.latitude) + d = Location(0, self.latitude) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a != d + assert hash(a) != hash(d) + + async def test_send_location_without_required(self, bot, chat_id): + with pytest.raises(ValueError, match="Either location or latitude and longitude"): + await bot.send_location(chat_id=chat_id) + + async def test_edit_location_without_required(self, bot): + with pytest.raises(ValueError, match="Either location or latitude and longitude"): + await bot.edit_message_live_location(chat_id=2, message_id=3) + + async def test_send_location_with_all_args(self, bot, location): + with pytest.raises(ValueError, match="Not both"): + await bot.send_location(chat_id=1, latitude=2.5, longitude=4.6, location=location) + + async def test_edit_location_with_all_args(self, bot, location): + with pytest.raises(ValueError, match="Not both"): + await bot.edit_message_live_location( + chat_id=1, message_id=7, latitude=2.5, longitude=4.6, location=location + ) + + # TODO: Needs improvement with in inline sent live location. + async def test_edit_live_inline_message(self, monkeypatch, bot, location): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + data = request_data.json_parameters + lat = data["latitude"] == str(location.latitude) + lon = data["longitude"] == str(location.longitude) + id_ = data["inline_message_id"] == "1234" + ha = data["horizontal_accuracy"] == "50" + heading = data["heading"] == "90" + prox_alert = data["proximity_alert_radius"] == "1000" + return lat and lon and id_ and ha and heading and prox_alert + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.edit_message_live_location( + inline_message_id=1234, + location=location, + horizontal_accuracy=50, + heading=90, + proximity_alert_radius=1000, + ) + + # TODO: Needs improvement with in inline sent live location. + async def test_stop_live_inline_message(self, monkeypatch, bot): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + id_ = request_data.json_parameters["inline_message_id"] == "1234" + return id_ + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.stop_message_live_location(inline_message_id=1234) + + async def test_send_with_location(self, monkeypatch, bot, chat_id, location): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + lat = request_data.json_parameters["latitude"] == str(location.latitude) + lon = request_data.json_parameters["longitude"] == str(location.longitude) + return lat and lon + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.send_location(location=location, chat_id=chat_id) + + async def test_edit_live_location_with_location(self, monkeypatch, bot, location): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + lat = request_data.json_parameters["latitude"] == str(location.latitude) + lon = request_data.json_parameters["longitude"] == str(location.longitude) + return lat and lon + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.edit_message_live_location(None, None, location=location) + + +class TestLocationWithRequest: + @pytest.mark.parametrize( + "default_bot,custom", + [ + ({"allow_sending_without_reply": True}, None), + ({"allow_sending_without_reply": False}, None), + ({"allow_sending_without_reply": False}, True), + ], + indirect=["default_bot"], + ) + async def test_send_location_default_allow_sending_without_reply( + self, default_bot, chat_id, location, custom + ): + reply_to_message = await default_bot.send_message(chat_id, "test") + await reply_to_message.delete() + if custom is not None: + message = await default_bot.send_location( + chat_id, + location=location, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = await default_bot.send_location( + chat_id, location=location, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match="message not found"): + await default_bot.send_location( + chat_id, location=location, reply_to_message_id=reply_to_message.message_id + ) + + @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) + async def test_send_location_default_protect_content(self, chat_id, default_bot, location): + tasks = asyncio.gather( + default_bot.send_location(chat_id, location=location), + default_bot.send_location(chat_id, location=location, protect_content=False), + ) + protected, unprotected = await tasks + assert protected.has_protected_content + assert not unprotected.has_protected_content + @pytest.mark.xfail async def test_send_live_location(self, bot, chat_id): message = await bot.send_location( @@ -110,135 +246,3 @@ class TestLocation: await bot.edit_message_live_location( message.chat_id, message.message_id, latitude=52.223880, longitude=5.164306 ) - - # TODO: Needs improvement with in inline sent live location. - async def test_edit_live_inline_message(self, monkeypatch, bot, location): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - data = request_data.json_parameters - lat = data["latitude"] == str(location.latitude) - lon = data["longitude"] == str(location.longitude) - id_ = data["inline_message_id"] == "1234" - ha = data["horizontal_accuracy"] == "50" - heading = data["heading"] == "90" - prox_alert = data["proximity_alert_radius"] == "1000" - return lat and lon and id_ and ha and heading and prox_alert - - monkeypatch.setattr(bot.request, "post", make_assertion) - assert await bot.edit_message_live_location( - inline_message_id=1234, - location=location, - horizontal_accuracy=50, - heading=90, - proximity_alert_radius=1000, - ) - - # TODO: Needs improvement with in inline sent live location. - async def test_stop_live_inline_message(self, monkeypatch, bot): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - id_ = request_data.json_parameters["inline_message_id"] == "1234" - return id_ - - monkeypatch.setattr(bot.request, "post", make_assertion) - assert await bot.stop_message_live_location(inline_message_id=1234) - - async def test_send_with_location(self, monkeypatch, bot, chat_id, location): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - lat = request_data.json_parameters["latitude"] == str(location.latitude) - lon = request_data.json_parameters["longitude"] == str(location.longitude) - return lat and lon - - monkeypatch.setattr(bot.request, "post", make_assertion) - assert await bot.send_location(location=location, chat_id=chat_id) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize( - "default_bot,custom", - [ - ({"allow_sending_without_reply": True}, None), - ({"allow_sending_without_reply": False}, None), - ({"allow_sending_without_reply": False}, True), - ], - indirect=["default_bot"], - ) - async def test_send_location_default_allow_sending_without_reply( - self, default_bot, chat_id, location, custom - ): - reply_to_message = await default_bot.send_message(chat_id, "test") - await reply_to_message.delete() - if custom is not None: - message = await default_bot.send_location( - chat_id, - location=location, - allow_sending_without_reply=custom, - reply_to_message_id=reply_to_message.message_id, - ) - assert message.reply_to_message is None - elif default_bot.defaults.allow_sending_without_reply: - message = await default_bot.send_location( - chat_id, location=location, reply_to_message_id=reply_to_message.message_id - ) - assert message.reply_to_message is None - else: - with pytest.raises(BadRequest, match="message not found"): - await default_bot.send_location( - chat_id, location=location, reply_to_message_id=reply_to_message.message_id - ) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) - async def test_send_location_default_protect_content(self, chat_id, default_bot, location): - protected = await default_bot.send_location(chat_id, location=location) - assert protected.has_protected_content - unprotected = await default_bot.send_location( - chat_id, location=location, protect_content=False - ) - assert not unprotected.has_protected_content - - async def test_edit_live_location_with_location(self, monkeypatch, bot, location): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - lat = request_data.json_parameters["latitude"] == str(location.latitude) - lon = request_data.json_parameters["longitude"] == str(location.longitude) - return lat and lon - - monkeypatch.setattr(bot.request, "post", make_assertion) - assert await bot.edit_message_live_location(None, None, location=location) - - async def test_send_location_without_required(self, bot, chat_id): - with pytest.raises(ValueError, match="Either location or latitude and longitude"): - await bot.send_location(chat_id=chat_id) - - async def test_edit_location_without_required(self, bot): - with pytest.raises(ValueError, match="Either location or latitude and longitude"): - await bot.edit_message_live_location(chat_id=2, message_id=3) - - async def test_send_location_with_all_args(self, bot, location): - with pytest.raises(ValueError, match="Not both"): - await bot.send_location(chat_id=1, latitude=2.5, longitude=4.6, location=location) - - async def test_edit_location_with_all_args(self, bot, location): - with pytest.raises(ValueError, match="Not both"): - await bot.edit_message_live_location( - chat_id=1, message_id=7, latitude=2.5, longitude=4.6, location=location - ) - - def test_to_dict(self, location): - location_dict = location.to_dict() - - assert location_dict["latitude"] == location.latitude - assert location_dict["longitude"] == location.longitude - assert location_dict["horizontal_accuracy"] == location.horizontal_accuracy - assert location_dict["live_period"] == location.live_period - assert location["heading"] == location.heading - assert location["proximity_alert_radius"] == location.proximity_alert_radius - - def test_equality(self): - a = Location(self.longitude, self.latitude) - b = Location(self.longitude, self.latitude) - d = Location(0, self.latitude) - - assert a == b - assert hash(a) == hash(b) - assert a is not b - - assert a != d - assert hash(a) != hash(d) diff --git a/tests/test_loginurl.py b/tests/test_loginurl.py index f33ff3b38..09fd40569 100644 --- a/tests/test_loginurl.py +++ b/tests/test_loginurl.py @@ -21,22 +21,24 @@ import pytest from telegram import LoginUrl -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def login_url(): return LoginUrl( - url=TestLoginUrl.url, - forward_text=TestLoginUrl.forward_text, - bot_username=TestLoginUrl.bot_username, - request_write_access=TestLoginUrl.request_write_access, + url=TestLoginUrlBase.url, + forward_text=TestLoginUrlBase.forward_text, + bot_username=TestLoginUrlBase.bot_username, + request_write_access=TestLoginUrlBase.request_write_access, ) -class TestLoginUrl: +class TestLoginUrlBase: url = "http://www.google.com" forward_text = "Send me forward!" bot_username = "botname" request_write_access = True + +class TestLoginUrlWithoutRequest(TestLoginUrlBase): def test_slot_behaviour(self, login_url, mro_slots): for attr in login_url.__slots__: assert getattr(login_url, attr, "err") != "err", f"got extra slot '{attr}'" diff --git a/tests/test_menubutton.py b/tests/test_menubutton.py index a72ea0bd7..091bbdb2d 100644 --- a/tests/test_menubutton.py +++ b/tests/test_menubutton.py @@ -31,7 +31,7 @@ from telegram import ( @pytest.fixture( - scope="class", + scope="module", params=[ MenuButton.DEFAULT, MenuButton.WEB_APP, @@ -43,7 +43,7 @@ def scope_type(request): @pytest.fixture( - scope="class", + scope="module", params=[ MenuButtonDefault, MenuButtonCommands, @@ -60,7 +60,7 @@ def scope_class(request): @pytest.fixture( - scope="class", + scope="module", params=[ (MenuButtonDefault, MenuButton.DEFAULT), (MenuButtonCommands, MenuButton.COMMANDS), @@ -76,24 +76,26 @@ def scope_class_and_type(request): return request.param -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def menu_button(scope_class_and_type): # We use de_json here so that we don't have to worry about which class gets which arguments return scope_class_and_type[0].de_json( dict( type=scope_class_and_type[1], - text=TestMenuButton.text, - web_app=TestMenuButton.web_app.to_dict(), + text=TestMenuButtonselfBase.text, + web_app=TestMenuButtonselfBase.web_app.to_dict(), ), bot=None, ) -# All the scope types are very similar, so we test everything via parametrization -class TestMenuButton: +class TestMenuButtonselfBase: text = "button_text" web_app = WebAppInfo(url="https://python-telegram-bot.org/web_app") + +# All the scope types are very similar, so we test everything via parametrization +class TestMenuButtonWithoutRequest(TestMenuButtonselfBase): def test_slot_behaviour(self, menu_button, mro_slots): for attr in menu_button.__slots__: assert getattr(menu_button, attr, "err") != "err", f"got extra slot '{attr}'" diff --git a/tests/test_message.py b/tests/test_message.py index a840560f2..ce8417358 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -66,13 +66,13 @@ from tests.auxil.bot_method_checks import ( from tests.test_passport import RAW_PASSPORT_DATA -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def message(bot): message = Message( - message_id=TestMessage.id_, - date=TestMessage.date, - chat=copy(TestMessage.chat), - from_user=copy(TestMessage.from_user), + message_id=TestMessageBase.id_, + date=TestMessageBase.date, + chat=copy(TestMessageBase.chat), + from_user=copy(TestMessageBase.from_user), ) message.set_bot(bot) message._unfreeze() @@ -271,17 +271,17 @@ def message(bot): ) def message_params(bot, request): message = Message( - message_id=TestMessage.id_, - from_user=TestMessage.from_user, - date=TestMessage.date, - chat=TestMessage.chat, + message_id=TestMessageBase.id_, + from_user=TestMessageBase.from_user, + date=TestMessageBase.date, + chat=TestMessageBase.chat, **request.param, ) message.set_bot(bot) return message -class TestMessage: +class TestMessageBase: id_ = 1 from_user = User(2, "testuser", False) date = datetime.utcnow() @@ -347,6 +347,13 @@ class TestMessage: caption_entities=[MessageEntity(**e) for e in test_entities_v2], ) + +class TestMessageWithoutRequest(TestMessageBase): + def test_slot_behaviour(self, message, mro_slots): + for attr in message.__slots__: + assert getattr(message, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(message)) == len(set(mro_slots(message))), "duplicate slot" + def test_all_possibilities_de_json_and_to_dict(self, bot, message_params): new = Message.de_json(message_params.to_dict(), bot) assert new.api_kwargs == {} @@ -358,10 +365,26 @@ class TestMessage: for slot in new.__slots__: assert not isinstance(new[slot], dict) - def test_slot_behaviour(self, message, mro_slots): - for attr in message.__slots__: - assert getattr(message, attr, "err") != "err", f"got extra slot '{attr}'" - assert len(mro_slots(message)) == len(set(mro_slots(message))), "duplicate slot" + def test_equality(self): + id_ = 1 + a = Message(id_, self.date, self.chat, from_user=self.from_user) + b = Message(id_, self.date, self.chat, from_user=self.from_user) + c = Message(id_, self.date, Chat(123, Chat.GROUP), from_user=User(0, "", False)) + d = Message(0, self.date, self.chat, from_user=self.from_user) + e = Update(id_) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) async def test_parse_entity(self): text = ( @@ -563,7 +586,7 @@ class TestMessage: expected = b"\\U0001f469\\u200d\\U0001f469\\u200d *ABC*".decode("unicode-escape") bold_entity = MessageEntity(type=MessageEntity.BOLD, offset=7, length=3) message = Message( - 1, self.from_user, self.date, self.chat, text=text, entities=[bold_entity] + 1, self.date, self.chat, self.from_user, text=text, entities=[bold_entity] ) assert expected == message.text_markdown @@ -1826,34 +1849,3 @@ class TestMessage: monkeypatch.setattr(message.get_bot(), "unpin_all_forum_topic_messages", make_assertion) assert await message.unpin_all_forum_topic_messages() - - def test_equality(self): - id_ = 1 - a = Message( - id_, - self.date, - self.chat, - from_user=self.from_user, - ) - b = Message( - id_, - self.date, - self.chat, - from_user=self.from_user, - ) - c = Message(id_, self.date, Chat(123, Chat.GROUP), from_user=User(0, "", False)) - d = Message(0, self.date, self.chat, from_user=self.from_user) - e = Update(id_) - - assert a == b - assert hash(a) == hash(b) - assert a is not b - - assert a != c - assert hash(a) != hash(c) - - assert a != d - assert hash(a) != hash(d) - - assert a != e - assert hash(a) != hash(e) diff --git a/tests/test_messageautodeletetimerchanged.py b/tests/test_messageautodeletetimerchanged.py index 40b48736f..31a3ecd67 100644 --- a/tests/test_messageautodeletetimerchanged.py +++ b/tests/test_messageautodeletetimerchanged.py @@ -19,7 +19,7 @@ from telegram import MessageAutoDeleteTimerChanged, VideoChatEnded -class TestMessageAutoDeleteTimerChanged: +class TestMessageAutoDeleteTimerChangedWithoutRequest: message_auto_delete_time = 100 def test_slot_behaviour(self, mro_slots): diff --git a/tests/test_messageentity.py b/tests/test_messageentity.py index 0e7b42bba..40faeff5b 100644 --- a/tests/test_messageentity.py +++ b/tests/test_messageentity.py @@ -22,7 +22,7 @@ from telegram import MessageEntity, User from telegram.constants import MessageEntityType -@pytest.fixture(scope="class", params=MessageEntity.ALL_TYPES) +@pytest.fixture(scope="module", params=MessageEntity.ALL_TYPES) def message_entity(request): type_ = request.param url = None @@ -37,12 +37,14 @@ def message_entity(request): return MessageEntity(type_, 1, 3, url=url, user=user, language=language) -class TestMessageEntity: +class TestMessageEntityBase: type_ = "url" offset = 1 length = 2 url = "url" + +class TestMessageEntityWithoutRequest(TestMessageEntityBase): def test_slot_behaviour(self, message_entity, mro_slots): inst = message_entity for attr in inst.__slots__: diff --git a/tests/test_messageid.py b/tests/test_messageid.py index f96a4afe8..f10250b3d 100644 --- a/tests/test_messageid.py +++ b/tests/test_messageid.py @@ -20,12 +20,12 @@ import pytest from telegram import MessageId, User -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def message_id(): - return MessageId(message_id=TestMessageId.m_id) + return MessageId(message_id=TestMessageIdWithoutRequest.m_id) -class TestMessageId: +class TestMessageIdWithoutRequest: m_id = 1234 def test_slot_behaviour(self, message_id, mro_slots): diff --git a/tests/test_no_passport.py b/tests/test_no_passport.py index 08c273ab6..9648422db 100644 --- a/tests/test_no_passport.py +++ b/tests/test_no_passport.py @@ -26,30 +26,26 @@ Because imports in pytest are intricate, we just run with the TEST_WITH_OPT_DEPS environment variable set to False in addition to the regular test suite """ -import os - import pytest from telegram import _bot as bot from telegram._passport import credentials as credentials -from tests.auxil.object_conversions import env_var_2_bool - -TEST_WITH_OPT_DEPS = env_var_2_bool(os.getenv("TEST_WITH_OPT_DEPS", True)) +from tests.conftest import TEST_WITH_OPT_DEPS @pytest.mark.skipif( TEST_WITH_OPT_DEPS, reason="Only relevant if the optional dependency is not installed" ) -class TestNoPassport: - def test_bot_init(self, bot_info, monkeypatch): +class TestNoPassportWithoutRequest: + def test_bot_init(self, bot_info): with pytest.raises(RuntimeError, match="passport"): bot.Bot(bot_info["token"], private_key=1, private_key_password=2) - def test_credentials_decrypt(self, monkeypatch): + def test_credentials_decrypt(self): with pytest.raises(RuntimeError, match="passport"): credentials.decrypt(1, 1, 1) - def test_encrypted_credentials_decrypted_secret(self, monkeypatch): + def test_encrypted_credentials_decrypted_secret(self): ec = credentials.EncryptedCredentials("data", "hash", "secret") with pytest.raises(RuntimeError, match="passport"): ec.decrypted_secret diff --git a/tests/test_orderinfo.py b/tests/test_orderinfo.py index 946512022..1700e2d2d 100644 --- a/tests/test_orderinfo.py +++ b/tests/test_orderinfo.py @@ -21,22 +21,24 @@ import pytest from telegram import OrderInfo, ShippingAddress -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def order_info(): return OrderInfo( - TestOrderInfo.name, - TestOrderInfo.phone_number, - TestOrderInfo.email, - TestOrderInfo.shipping_address, + TestOrderInfoBase.name, + TestOrderInfoBase.phone_number, + TestOrderInfoBase.email, + TestOrderInfoBase.shipping_address, ) -class TestOrderInfo: +class TestOrderInfoBase: name = "name" phone_number = "phone_number" email = "email" shipping_address = ShippingAddress("GB", "", "London", "12 Grimmauld Place", "", "WC1") + +class TestOrderInfoWithoutRequest(TestOrderInfoBase): def test_slot_behaviour(self, order_info, mro_slots): for attr in order_info.__slots__: assert getattr(order_info, attr, "err") != "err", f"got extra slot '{attr}'" @@ -44,10 +46,10 @@ class TestOrderInfo: def test_de_json(self, bot): json_dict = { - "name": TestOrderInfo.name, - "phone_number": TestOrderInfo.phone_number, - "email": TestOrderInfo.email, - "shipping_address": TestOrderInfo.shipping_address.to_dict(), + "name": self.name, + "phone_number": self.phone_number, + "email": self.email, + "shipping_address": self.shipping_address.to_dict(), } order_info = OrderInfo.de_json(json_dict, bot) assert order_info.api_kwargs == {} diff --git a/tests/test_passport.py b/tests/test_passport.py index 2dbc4d917..09e1b076e 100644 --- a/tests/test_passport.py +++ b/tests/test_passport.py @@ -128,7 +128,7 @@ RAW_PASSPORT_DATA = { } -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def all_passport_data(): return [ { @@ -214,12 +214,12 @@ def all_passport_data(): ] -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def passport_data(bot): return PassportData.de_json(RAW_PASSPORT_DATA, bot=bot) -class TestPassport: +class TestPassportBase: driver_license_selfie_file_id = "DgADBAADEQQAAkopgFNr6oi-wISRtAI" driver_license_selfie_file_unique_id = "d4e390cca57b4da5a65322b304762a12" driver_license_front_side_file_id = "DgADBAADxwMAApnQgVPK2-ckL2eXVAI" @@ -241,6 +241,8 @@ class TestPassport: driver_license_selfie_credentials_file_hash = "Cila/qLXSBH7DpZFbb5bRZIRxeFW2uv/ulL0u0JNsYI=" driver_license_selfie_credentials_secret = "tivdId6RNYNsvXYPppdzrbxOBuBOr9wXRPDcCvnXU7E=" + +class TestPassportWithoutRequest(TestPassportBase): def test_slot_behaviour(self, passport_data, mro_slots): inst = passport_data for attr in inst.__slots__: @@ -387,6 +389,37 @@ class TestPassport: assert email.type == "email" assert email.email == "fb3e3i47zt@dispostable.com" + def test_de_json_and_to_dict(self, bot): + passport_data = PassportData.de_json(RAW_PASSPORT_DATA, bot) + assert passport_data.api_kwargs == {} + assert passport_data.to_dict() == RAW_PASSPORT_DATA + + assert passport_data.decrypted_data + assert passport_data.to_dict() == RAW_PASSPORT_DATA + + def test_equality(self, passport_data): + a = PassportData(passport_data.data, passport_data.credentials) + b = PassportData(passport_data.data, passport_data.credentials) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + new_pp_data = deepcopy(passport_data) + new_pp_data.credentials._unfreeze() + new_pp_data.credentials.hash = "NOTAPROPERHASH" + c = PassportData(new_pp_data.data, new_pp_data.credentials) + + assert a != c + assert hash(a) != hash(c) + + def test_bot_init_invalid_key(self, bot): + with pytest.raises(TypeError): + Bot(bot.token, private_key="Invalid key!") + + with pytest.raises(ValueError): + Bot(bot.token, private_key=b"Invalid key!") + def test_all_types(self, passport_data, bot, all_passport_data): credentials = passport_data.decrypted_credentials.to_dict() @@ -422,13 +455,6 @@ class TestPassport: assert isinstance(new, PassportData) assert new.decrypted_data - def test_bot_init_invalid_key(self, bot): - with pytest.raises(TypeError): - Bot(bot.token, private_key="Invalid key!") - - with pytest.raises(ValueError): - Bot(bot.token, private_key=b"Invalid key!") - async def test_passport_data_okay_with_non_crypto_bot(self, bot): async with make_bot(token=bot.token) as b: assert PassportData.de_json(RAW_PASSPORT_DATA, bot=b) @@ -506,26 +532,3 @@ class TestPassport: ], ) assert message - - def test_de_json_and_to_dict(self, bot): - passport_data = PassportData.de_json(RAW_PASSPORT_DATA, bot) - assert passport_data.api_kwargs == {} - assert passport_data.to_dict() == RAW_PASSPORT_DATA - - assert passport_data.decrypted_data - assert passport_data.to_dict() == RAW_PASSPORT_DATA - - def test_equality(self, passport_data): - a = PassportData(passport_data.data, passport_data.credentials) - b = PassportData(passport_data.data, passport_data.credentials) - - assert a == b - assert hash(a) == hash(b) - assert a is not b - - passport_data.credentials._unfreeze() - passport_data.credentials.hash = "NOTAPROPERHASH" - c = PassportData(passport_data.data, passport_data.credentials) - - assert a != c - assert hash(a) != hash(c) diff --git a/tests/test_passportelementerrordatafield.py b/tests/test_passportelementerrordatafield.py index 16f7b5255..82b179395 100644 --- a/tests/test_passportelementerrordatafield.py +++ b/tests/test_passportelementerrordatafield.py @@ -21,23 +21,25 @@ import pytest from telegram import PassportElementErrorDataField, PassportElementErrorSelfie -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def passport_element_error_data_field(): return PassportElementErrorDataField( - TestPassportElementErrorDataField.type_, - TestPassportElementErrorDataField.field_name, - TestPassportElementErrorDataField.data_hash, - TestPassportElementErrorDataField.message, + TestPassportElementErrorDataFieldBase.type_, + TestPassportElementErrorDataFieldBase.field_name, + TestPassportElementErrorDataFieldBase.data_hash, + TestPassportElementErrorDataFieldBase.message, ) -class TestPassportElementErrorDataField: +class TestPassportElementErrorDataFieldBase: source = "data" type_ = "test_type" field_name = "test_field" data_hash = "data_hash" message = "Error message" + +class TestPassportElementErrorDataFieldWithoutRequest(TestPassportElementErrorDataFieldBase): def test_slot_behaviour(self, passport_element_error_data_field, mro_slots): inst = passport_element_error_data_field for attr in inst.__slots__: diff --git a/tests/test_passportelementerrorfile.py b/tests/test_passportelementerrorfile.py index 16642bd0b..c47ac4a37 100644 --- a/tests/test_passportelementerrorfile.py +++ b/tests/test_passportelementerrorfile.py @@ -21,21 +21,23 @@ import pytest from telegram import PassportElementErrorFile, PassportElementErrorSelfie -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def passport_element_error_file(): return PassportElementErrorFile( - TestPassportElementErrorFile.type_, - TestPassportElementErrorFile.file_hash, - TestPassportElementErrorFile.message, + TestPassportElementErrorFileBase.type_, + TestPassportElementErrorFileBase.file_hash, + TestPassportElementErrorFileBase.message, ) -class TestPassportElementErrorFile: +class TestPassportElementErrorFileBase: source = "file" type_ = "test_type" file_hash = "file_hash" message = "Error message" + +class TestPassportElementErrorFileWithoutRequest(TestPassportElementErrorFileBase): def test_slot_behaviour(self, passport_element_error_file, mro_slots): inst = passport_element_error_file for attr in inst.__slots__: diff --git a/tests/test_passportelementerrorfiles.py b/tests/test_passportelementerrorfiles.py index e2b00f603..7e6301b00 100644 --- a/tests/test_passportelementerrorfiles.py +++ b/tests/test_passportelementerrorfiles.py @@ -21,21 +21,23 @@ import pytest from telegram import PassportElementErrorFiles, PassportElementErrorSelfie -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def passport_element_error_files(): return PassportElementErrorFiles( - TestPassportElementErrorFiles.type_, - TestPassportElementErrorFiles.file_hashes, - TestPassportElementErrorFiles.message, + TestPassportElementErrorFilesBase.type_, + TestPassportElementErrorFilesBase.file_hashes, + TestPassportElementErrorFilesBase.message, ) -class TestPassportElementErrorFiles: +class TestPassportElementErrorFilesBase: source = "files" type_ = "test_type" file_hashes = ["hash1", "hash2"] message = "Error message" + +class TestPassportElementErrorFilesWithoutRequest(TestPassportElementErrorFilesBase): def test_slot_behaviour(self, passport_element_error_files, mro_slots): inst = passport_element_error_files for attr in inst.__slots__: diff --git a/tests/test_passportelementerrorfrontside.py b/tests/test_passportelementerrorfrontside.py index b624b5bdf..d45a4a603 100644 --- a/tests/test_passportelementerrorfrontside.py +++ b/tests/test_passportelementerrorfrontside.py @@ -21,21 +21,23 @@ import pytest from telegram import PassportElementErrorFrontSide, PassportElementErrorSelfie -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def passport_element_error_front_side(): return PassportElementErrorFrontSide( - TestPassportElementErrorFrontSide.type_, - TestPassportElementErrorFrontSide.file_hash, - TestPassportElementErrorFrontSide.message, + TestPassportElementErrorFrontSideBase.type_, + TestPassportElementErrorFrontSideBase.file_hash, + TestPassportElementErrorFrontSideBase.message, ) -class TestPassportElementErrorFrontSide: +class TestPassportElementErrorFrontSideBase: source = "front_side" type_ = "test_type" file_hash = "file_hash" message = "Error message" + +class TestPassportElementErrorFrontSideWithoutRequest(TestPassportElementErrorFrontSideBase): def test_slot_behaviour(self, passport_element_error_front_side, mro_slots): inst = passport_element_error_front_side for attr in inst.__slots__: diff --git a/tests/test_passportelementerrorreverseside.py b/tests/test_passportelementerrorreverseside.py index cd4918cb1..1808e63df 100644 --- a/tests/test_passportelementerrorreverseside.py +++ b/tests/test_passportelementerrorreverseside.py @@ -21,21 +21,23 @@ import pytest from telegram import PassportElementErrorReverseSide, PassportElementErrorSelfie -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def passport_element_error_reverse_side(): return PassportElementErrorReverseSide( - TestPassportElementErrorReverseSide.type_, - TestPassportElementErrorReverseSide.file_hash, - TestPassportElementErrorReverseSide.message, + TestPassportElementErrorReverseSideBase.type_, + TestPassportElementErrorReverseSideBase.file_hash, + TestPassportElementErrorReverseSideBase.message, ) -class TestPassportElementErrorReverseSide: +class TestPassportElementErrorReverseSideBase: source = "reverse_side" type_ = "test_type" file_hash = "file_hash" message = "Error message" + +class TestPassportElementErrorReverseSideWithoutRequest(TestPassportElementErrorReverseSideBase): def test_slot_behaviour(self, passport_element_error_reverse_side, mro_slots): inst = passport_element_error_reverse_side for attr in inst.__slots__: diff --git a/tests/test_passportelementerrorselfie.py b/tests/test_passportelementerrorselfie.py index 13495f736..892e5a769 100644 --- a/tests/test_passportelementerrorselfie.py +++ b/tests/test_passportelementerrorselfie.py @@ -21,21 +21,23 @@ import pytest from telegram import PassportElementErrorDataField, PassportElementErrorSelfie -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def passport_element_error_selfie(): return PassportElementErrorSelfie( - TestPassportElementErrorSelfie.type_, - TestPassportElementErrorSelfie.file_hash, - TestPassportElementErrorSelfie.message, + TestPassportElementErrorSelfieBase.type_, + TestPassportElementErrorSelfieBase.file_hash, + TestPassportElementErrorSelfieBase.message, ) -class TestPassportElementErrorSelfie: +class TestPassportElementErrorSelfieBase: source = "selfie" type_ = "test_type" file_hash = "file_hash" message = "Error message" + +class TestPassportElementErrorSelfieWithoutRequest(TestPassportElementErrorSelfieBase): def test_slot_behaviour(self, passport_element_error_selfie, mro_slots): inst = passport_element_error_selfie for attr in inst.__slots__: diff --git a/tests/test_passportelementerrortranslationfile.py b/tests/test_passportelementerrortranslationfile.py index 56fe393e2..71930bc0f 100644 --- a/tests/test_passportelementerrortranslationfile.py +++ b/tests/test_passportelementerrortranslationfile.py @@ -21,21 +21,25 @@ import pytest from telegram import PassportElementErrorDataField, PassportElementErrorTranslationFile -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def passport_element_error_translation_file(): return PassportElementErrorTranslationFile( - TestPassportElementErrorTranslationFile.type_, - TestPassportElementErrorTranslationFile.file_hash, - TestPassportElementErrorTranslationFile.message, + TestPassportElementErrorTranslationFileBase.type_, + TestPassportElementErrorTranslationFileBase.file_hash, + TestPassportElementErrorTranslationFileBase.message, ) -class TestPassportElementErrorTranslationFile: +class TestPassportElementErrorTranslationFileBase: source = "translation_file" type_ = "test_type" file_hash = "file_hash" message = "Error message" + +class TestPassportElementErrorTranslationFileWithoutRequest( + TestPassportElementErrorTranslationFileBase +): def test_slot_behaviour(self, passport_element_error_translation_file, mro_slots): inst = passport_element_error_translation_file for attr in inst.__slots__: diff --git a/tests/test_passportelementerrortranslationfiles.py b/tests/test_passportelementerrortranslationfiles.py index b965bfa68..09493c4ee 100644 --- a/tests/test_passportelementerrortranslationfiles.py +++ b/tests/test_passportelementerrortranslationfiles.py @@ -21,21 +21,25 @@ import pytest from telegram import PassportElementErrorSelfie, PassportElementErrorTranslationFiles -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def passport_element_error_translation_files(): return PassportElementErrorTranslationFiles( - TestPassportElementErrorTranslationFiles.type_, - TestPassportElementErrorTranslationFiles.file_hashes, - TestPassportElementErrorTranslationFiles.message, + TestPassportElementErrorTranslationFilesBase.type_, + TestPassportElementErrorTranslationFilesBase.file_hashes, + TestPassportElementErrorTranslationFilesBase.message, ) -class TestPassportElementErrorTranslationFiles: +class TestPassportElementErrorTranslationFilesBase: source = "translation_files" type_ = "test_type" file_hashes = ["hash1", "hash2"] message = "Error message" + +class TestPassportElementErrorTranslationFilesWithoutRequest( + TestPassportElementErrorTranslationFilesBase +): def test_slot_behaviour(self, passport_element_error_translation_files, mro_slots): inst = passport_element_error_translation_files for attr in inst.__slots__: diff --git a/tests/test_passportelementerrorunspecified.py b/tests/test_passportelementerrorunspecified.py index 949131754..4c80b3db3 100644 --- a/tests/test_passportelementerrorunspecified.py +++ b/tests/test_passportelementerrorunspecified.py @@ -21,21 +21,23 @@ import pytest from telegram import PassportElementErrorDataField, PassportElementErrorUnspecified -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def passport_element_error_unspecified(): return PassportElementErrorUnspecified( - TestPassportElementErrorUnspecified.type_, - TestPassportElementErrorUnspecified.element_hash, - TestPassportElementErrorUnspecified.message, + TestPassportElementErrorUnspecifiedBase.type_, + TestPassportElementErrorUnspecifiedBase.element_hash, + TestPassportElementErrorUnspecifiedBase.message, ) -class TestPassportElementErrorUnspecified: +class TestPassportElementErrorUnspecifiedBase: source = "unspecified" type_ = "test_type" element_hash = "element_hash" message = "Error message" + +class TestPassportElementErrorUnspecifiedWithoutRequest(TestPassportElementErrorUnspecifiedBase): def test_slot_behaviour(self, passport_element_error_unspecified, mro_slots): inst = passport_element_error_unspecified for attr in inst.__slots__: diff --git a/tests/test_passportfile.py b/tests/test_passportfile.py index 8e310a45e..4b5837fcd 100644 --- a/tests/test_passportfile.py +++ b/tests/test_passportfile.py @@ -29,21 +29,23 @@ from tests.auxil.bot_method_checks import ( @pytest.fixture(scope="class") def passport_file(bot): pf = PassportFile( - file_id=TestPassportFile.file_id, - file_unique_id=TestPassportFile.file_unique_id, - file_size=TestPassportFile.file_size, - file_date=TestPassportFile.file_date, + file_id=TestPassportFileBase.file_id, + file_unique_id=TestPassportFileBase.file_unique_id, + file_size=TestPassportFileBase.file_size, + file_date=TestPassportFileBase.file_date, ) pf.set_bot(bot) return pf -class TestPassportFile: +class TestPassportFileBase: file_id = "data" file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e" file_size = 50 file_date = 1532879128 + +class TestPassportFileWithoutRequest(TestPassportFileBase): def test_slot_behaviour(self, passport_file, mro_slots): inst = passport_file for attr in inst.__slots__: @@ -65,21 +67,6 @@ class TestPassportFile: assert passport_file_dict["file_size"] == passport_file.file_size assert passport_file_dict["file_date"] == passport_file.file_date - async def test_get_file_instance_method(self, monkeypatch, passport_file): - async def make_assertion(*_, **kwargs): - result = kwargs["file_id"] == passport_file.file_id - # we need to be a bit hacky here, b/c PF.get_file needs Bot.get_file to return a File - return File(file_id=result, file_unique_id=result) - - assert check_shortcut_signature(PassportFile.get_file, Bot.get_file, ["file_id"], []) - assert await check_shortcut_call( - passport_file.get_file, passport_file.get_bot(), "get_file" - ) - assert await check_defaults_handling(passport_file.get_file, passport_file.get_bot()) - - monkeypatch.setattr(passport_file.get_bot(), "get_file", make_assertion) - assert (await passport_file.get_file()).file_id == "True" - def test_equality(self): a = PassportFile(self.file_id, self.file_unique_id, self.file_size, self.file_date) b = PassportFile("", self.file_unique_id, self.file_size, self.file_date) @@ -99,3 +86,18 @@ class TestPassportFile: assert a != e assert hash(a) != hash(e) + + async def test_get_file_instance_method(self, monkeypatch, passport_file): + async def make_assertion(*_, **kwargs): + result = kwargs["file_id"] == passport_file.file_id + # we need to be a bit hacky here, b/c PF.get_file needs Bot.get_file to return a File + return File(file_id=result, file_unique_id=result) + + assert check_shortcut_signature(PassportFile.get_file, Bot.get_file, ["file_id"], []) + assert await check_shortcut_call( + passport_file.get_file, passport_file.get_bot(), "get_file" + ) + assert await check_defaults_handling(passport_file.get_file, passport_file.get_bot()) + + monkeypatch.setattr(passport_file.get_bot(), "get_file", make_assertion) + assert (await passport_file.get_file()).file_id == "True" diff --git a/tests/test_photo.py b/tests/test_photo.py index fc9acbdca..bed123f63 100644 --- a/tests/test_photo.py +++ b/tests/test_photo.py @@ -15,6 +15,7 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio import os from io import BytesIO from pathlib import Path @@ -35,34 +36,32 @@ from tests.conftest import data_file, expect_bad_request @pytest.fixture(scope="function") def photo_file(): - f = data_file("telegram.jpg").open("rb") - yield f - f.close() + with data_file("telegram.jpg").open("rb") as f: + yield f -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") async def _photo(bot, chat_id): async def func(): with data_file("telegram.jpg").open("rb") as f: - photo = (await bot.send_photo(chat_id, photo=f, read_timeout=50)).photo - return photo + 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="class") +@pytest.fixture(scope="module") def thumb(_photo): return _photo[0] -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def photo(_photo): return _photo[-1] -class TestPhoto: +class TestPhotoBase: width = 800 height = 800 caption = "PhotoTest - *Caption*" @@ -71,6 +70,8 @@ class TestPhoto: # so we accept three different sizes here. Shouldn't be too much file_size = [29176, 27662] + +class TestPhotoWithoutRequest(TestPhotoBase): def test_slot_behaviour(self, photo, mro_slots): for attr in photo.__slots__: assert getattr(photo, attr, "err") != "err", f"got extra slot '{attr}'" @@ -98,328 +99,6 @@ class TestPhoto: assert thumb.height == 90 assert thumb.file_size == 1477 - @pytest.mark.flaky(3, 1) - async def test_send_photo_all_args(self, bot, chat_id, photo_file, thumb, photo): - message = await bot.send_photo( - chat_id, - photo_file, - caption=self.caption, - disable_notification=False, - protect_content=True, - parse_mode="Markdown", - has_spoiler=True, - ) - - assert isinstance(message.photo[-2], PhotoSize) - assert isinstance(message.photo[-2].file_id, str) - assert isinstance(message.photo[-2].file_unique_id, str) - assert message.photo[-2].file_id != "" - assert message.photo[-2].file_unique_id != "" - - assert isinstance(message.photo[-1], PhotoSize) - assert isinstance(message.photo[-1].file_id, str) - assert isinstance(message.photo[-1].file_unique_id, str) - assert message.photo[-1].file_id != "" - assert message.photo[-1].file_unique_id != "" - - assert message.caption == TestPhoto.caption.replace("*", "") - assert message.has_protected_content - assert message.has_media_spoiler - - @pytest.mark.flaky(3, 1) - async def test_send_photo_custom_filename(self, bot, chat_id, photo_file, monkeypatch): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - return list(request_data.multipart_data.values())[0][0] == "custom_filename" - - monkeypatch.setattr(bot.request, "post", make_assertion) - - assert await bot.send_photo(chat_id, photo_file, filename="custom_filename") - - @pytest.mark.flaky(3, 1) - async def test_send_photo_parse_mode_markdown(self, bot, chat_id, photo_file, thumb, photo): - message = await bot.send_photo( - chat_id, photo_file, caption=self.caption, parse_mode="Markdown" - ) - assert isinstance(message.photo[-2], PhotoSize) - assert isinstance(message.photo[-2].file_id, str) - assert isinstance(message.photo[-2].file_unique_id, str) - assert message.photo[-2].file_id != "" - assert message.photo[-2].file_unique_id != "" - - assert isinstance(message.photo[-1], PhotoSize) - assert isinstance(message.photo[-1].file_id, str) - assert isinstance(message.photo[-1].file_unique_id, str) - assert message.photo[-1].file_id != "" - assert message.photo[-1].file_unique_id != "" - - assert message.caption == TestPhoto.caption.replace("*", "") - assert len(message.caption_entities) == 1 - - @pytest.mark.flaky(3, 1) - async def test_send_photo_parse_mode_html(self, bot, chat_id, photo_file, thumb, photo): - message = await bot.send_photo( - chat_id, photo_file, caption=self.caption, parse_mode="HTML" - ) - assert isinstance(message.photo[-2], PhotoSize) - assert isinstance(message.photo[-2].file_id, str) - assert isinstance(message.photo[-2].file_unique_id, str) - assert message.photo[-2].file_id != "" - assert message.photo[-2].file_unique_id != "" - - assert isinstance(message.photo[-1], PhotoSize) - assert isinstance(message.photo[-1].file_id, str) - assert isinstance(message.photo[-1].file_unique_id, str) - assert message.photo[-1].file_id != "" - assert message.photo[-1].file_unique_id != "" - - assert message.caption == TestPhoto.caption.replace("", "").replace("", "") - assert len(message.caption_entities) == 1 - - @pytest.mark.flaky(3, 1) - async def test_send_photo_caption_entities(self, bot, chat_id, photo_file, thumb, photo): - test_string = "Italic Bold Code" - entities = [ - MessageEntity(MessageEntity.ITALIC, 0, 6), - MessageEntity(MessageEntity.ITALIC, 7, 4), - MessageEntity(MessageEntity.ITALIC, 12, 4), - ] - message = await bot.send_photo( - chat_id, photo_file, caption=test_string, caption_entities=entities - ) - - assert message.caption == test_string - assert message.caption_entities == tuple(entities) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_photo_default_parse_mode_1( - self, default_bot, chat_id, photo_file, thumb, photo - ): - test_string = "Italic Bold Code" - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_photo(chat_id, photo_file, caption=test_markdown_string) - assert message.caption_markdown == test_markdown_string - assert message.caption == test_string - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_photo_default_parse_mode_2( - self, default_bot, chat_id, photo_file, thumb, photo - ): - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_photo( - chat_id, photo_file, caption=test_markdown_string, parse_mode=None - ) - assert message.caption == test_markdown_string - assert message.caption_markdown == escape_markdown(test_markdown_string) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_photo_default_parse_mode_3( - self, default_bot, chat_id, photo_file, thumb, photo - ): - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_photo( - chat_id, photo_file, caption=test_markdown_string, parse_mode="HTML" - ) - assert message.caption == test_markdown_string - assert message.caption_markdown == escape_markdown(test_markdown_string) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) - async def test_send_photo_default_protect_content(self, chat_id, default_bot, photo): - protected = await default_bot.send_photo(chat_id, photo) - assert protected.has_protected_content - unprotected = await default_bot.send_photo(chat_id, photo, protect_content=False) - assert not unprotected.has_protected_content - - @pytest.mark.parametrize("local_mode", [True, False]) - async def test_send_photo_local_files(self, monkeypatch, bot, chat_id, local_mode): - try: - bot._local_mode = local_mode - # For just test that the correct paths are passed as we have no local bot API set up - test_flag = False - file = data_file("telegram.jpg") - expected = file.as_uri() - - async def make_assertion(_, data, *args, **kwargs): - nonlocal test_flag - if local_mode: - test_flag = data.get("photo") == expected - else: - test_flag = isinstance(data.get("photo"), InputFile) - - monkeypatch.setattr(bot, "_post", make_assertion) - await bot.send_photo(chat_id, file) - assert test_flag - finally: - bot._local_mode = False - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize( - "default_bot,custom", - [ - ({"allow_sending_without_reply": True}, None), - ({"allow_sending_without_reply": False}, None), - ({"allow_sending_without_reply": False}, True), - ], - indirect=["default_bot"], - ) - async def test_send_photo_default_allow_sending_without_reply( - self, default_bot, chat_id, photo_file, thumb, photo, custom - ): - reply_to_message = await default_bot.send_message(chat_id, "test") - await reply_to_message.delete() - if custom is not None: - message = await default_bot.send_photo( - chat_id, - photo_file, - allow_sending_without_reply=custom, - reply_to_message_id=reply_to_message.message_id, - ) - assert message.reply_to_message is None - elif default_bot.defaults.allow_sending_without_reply: - message = await default_bot.send_photo( - chat_id, photo_file, reply_to_message_id=reply_to_message.message_id - ) - assert message.reply_to_message is None - else: - with pytest.raises(BadRequest, match="message not found"): - await default_bot.send_photo( - chat_id, photo_file, reply_to_message_id=reply_to_message.message_id - ) - - @pytest.mark.flaky(3, 1) - async def test_get_and_download(self, bot, photo): - path = Path("telegram.jpg") - if path.is_file(): - path.unlink() - - new_file = await bot.getFile(photo.file_id) - - assert new_file.file_size == photo.file_size - assert new_file.file_unique_id == photo.file_unique_id - assert new_file.file_path.startswith("https://") is True - - await new_file.download_to_drive("telegram.jpg") - - assert path.is_file() - - @pytest.mark.flaky(3, 1) - async def test_send_url_jpg_file(self, bot, chat_id, thumb, photo): - message = await bot.send_photo(chat_id, photo=self.photo_file_url) - - assert isinstance(message.photo[-2], PhotoSize) - assert isinstance(message.photo[-2].file_id, str) - assert isinstance(message.photo[-2].file_unique_id, str) - assert message.photo[-2].file_id != "" - assert message.photo[-2].file_unique_id != "" - - assert isinstance(message.photo[-1], PhotoSize) - assert isinstance(message.photo[-1].file_id, str) - assert isinstance(message.photo[-1].file_unique_id, str) - assert message.photo[-1].file_id != "" - assert message.photo[-1].file_unique_id != "" - - @pytest.mark.flaky(3, 1) - async def test_send_url_png_file(self, bot, chat_id): - message = await bot.send_photo( - photo="http://dummyimage.com/600x400/000/fff.png&text=telegram", chat_id=chat_id - ) - - photo = message.photo[-1] - - assert isinstance(photo, PhotoSize) - assert isinstance(photo.file_id, str) - assert isinstance(photo.file_unique_id, str) - assert photo.file_id != "" - assert photo.file_unique_id != "" - - @pytest.mark.flaky(3, 1) - async def test_send_url_gif_file(self, bot, chat_id): - message = await bot.send_photo( - photo="http://dummyimage.com/600x400/000/fff.png&text=telegram", chat_id=chat_id - ) - - photo = message.photo[-1] - - assert isinstance(photo, PhotoSize) - assert isinstance(photo.file_id, str) - assert isinstance(photo.file_unique_id, str) - assert photo.file_id != "" - assert photo.file_unique_id != "" - - @pytest.mark.flaky(3, 1) - async def test_send_file_unicode_filename(self, bot, chat_id): - """ - Regression test for https://github.com/python-telegram-bot/python-telegram-bot/issues/1202 - """ - with data_file("测试.png").open("rb") as f: - message = await bot.send_photo(photo=f, chat_id=chat_id) - - photo = message.photo[-1] - - assert isinstance(photo, PhotoSize) - assert isinstance(photo.file_id, str) - assert isinstance(photo.file_unique_id, str) - assert photo.file_id != "" - assert photo.file_unique_id != "" - - @pytest.mark.flaky(3, 1) - async def test_send_bytesio_jpg_file(self, bot, chat_id): - filepath = data_file("telegram_no_standard_header.jpg") - - # raw image bytes - raw_bytes = BytesIO(filepath.read_bytes()) - input_file = InputFile(raw_bytes) - assert input_file.mimetype == "application/octet-stream" - - # raw image bytes with name info - raw_bytes = BytesIO(filepath.read_bytes()) - raw_bytes.name = str(filepath) - input_file = InputFile(raw_bytes) - assert input_file.mimetype == "image/jpeg" - - # send raw photo - raw_bytes = BytesIO(filepath.read_bytes()) - message = await bot.send_photo(chat_id, photo=raw_bytes) - photo = message.photo[-1] - assert isinstance(photo.file_id, str) - assert isinstance(photo.file_unique_id, str) - assert photo.file_id != "" - assert photo.file_unique_id != "" - assert isinstance(photo, PhotoSize) - assert photo.width == 1280 - assert photo.height == 720 - assert photo.file_size == 33372 - - async def test_send_with_photosize(self, monkeypatch, bot, chat_id, photo): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - return request_data.json_parameters["photo"] == photo.file_id - - monkeypatch.setattr(bot.request, "post", make_assertion) - message = await bot.send_photo(photo=photo, chat_id=chat_id) - assert message - - @pytest.mark.flaky(3, 1) - async def test_resend(self, bot, chat_id, photo, thumb): - message = await bot.send_photo(chat_id=chat_id, photo=photo.file_id) - - assert isinstance(message.photo[-2], PhotoSize) - assert isinstance(message.photo[-2].file_id, str) - assert isinstance(message.photo[-2].file_unique_id, str) - assert message.photo[-2].file_id != "" - assert message.photo[-2].file_unique_id != "" - - assert isinstance(message.photo[-1], PhotoSize) - assert isinstance(message.photo[-1].file_id, str) - assert isinstance(message.photo[-1].file_unique_id, str) - assert message.photo[-1].file_id != "" - assert message.photo[-1].file_unique_id != "" - def test_de_json(self, bot, photo): json_dict = { "file_id": photo.file_id, @@ -447,31 +126,6 @@ class TestPhoto: assert photo_dict["height"] == photo.height assert photo_dict["file_size"] == photo.file_size - @pytest.mark.flaky(3, 1) - async def test_error_send_empty_file(self, bot, chat_id): - with pytest.raises(TelegramError): - await bot.send_photo(chat_id=chat_id, photo=open(os.devnull, "rb")) - - @pytest.mark.flaky(3, 1) - async def test_error_send_empty_file_id(self, bot, chat_id): - with pytest.raises(TelegramError): - await bot.send_photo(chat_id=chat_id, photo="") - - async def test_error_without_required_args(self, bot, chat_id): - with pytest.raises(TypeError): - await bot.send_photo(chat_id=chat_id) - - async def test_get_file_instance_method(self, monkeypatch, photo): - async def make_assertion(*_, **kwargs): - return kwargs["file_id"] == photo.file_id - - assert check_shortcut_signature(PhotoSize.get_file, Bot.get_file, ["file_id"], []) - assert await check_shortcut_call(photo.get_file, photo.get_bot(), "get_file") - assert await check_defaults_handling(photo.get_file, photo.get_bot()) - - monkeypatch.setattr(photo.get_bot(), "get_file", make_assertion) - assert await photo.get_file() - def test_equality(self, photo): a = PhotoSize(photo.file_id, photo.file_unique_id, self.width, self.height) b = PhotoSize("", photo.file_unique_id, self.width, self.height) @@ -499,3 +153,315 @@ class TestPhoto: assert a != e assert hash(a) != hash(e) + + async def test_error_without_required_args(self, bot, chat_id): + with pytest.raises(TypeError): + await bot.send_photo(chat_id=chat_id) + + async def test_send_photo_custom_filename(self, bot, chat_id, photo_file, monkeypatch): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return list(request_data.multipart_data.values())[0][0] == "custom_filename" + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.send_photo(chat_id, photo_file, filename="custom_filename") + + @pytest.mark.parametrize("local_mode", [True, False]) + async def test_send_photo_local_files(self, monkeypatch, bot, chat_id, local_mode): + try: + bot._local_mode = local_mode + # For just test that the correct paths are passed as we have no local bot API set up + test_flag = False + file = data_file("telegram.jpg") + expected = file.as_uri() + + async def make_assertion(_, data, *args, **kwargs): + nonlocal test_flag + if local_mode: + test_flag = data.get("photo") == expected + else: + test_flag = isinstance(data.get("photo"), InputFile) + + monkeypatch.setattr(bot, "_post", make_assertion) + await bot.send_photo(chat_id, file) + assert test_flag + finally: + bot._local_mode = False + + async def test_send_with_photosize(self, monkeypatch, bot, chat_id, photo): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return request_data.json_parameters["photo"] == photo.file_id + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.send_photo(photo=photo, chat_id=chat_id) + + async def test_get_file_instance_method(self, monkeypatch, photo): + async def make_assertion(*_, **kwargs): + return kwargs["file_id"] == photo.file_id + + assert check_shortcut_signature(PhotoSize.get_file, Bot.get_file, ["file_id"], []) + assert await check_shortcut_call(photo.get_file, photo.get_bot(), "get_file") + assert await check_defaults_handling(photo.get_file, photo.get_bot()) + + monkeypatch.setattr(photo.get_bot(), "get_file", make_assertion) + assert await photo.get_file() + + +class TestPhotoWithRequest(TestPhotoBase): + async def test_send_photo_all_args(self, bot, chat_id, photo_file): + message = await bot.send_photo( + chat_id, + photo_file, + caption=self.caption, + disable_notification=False, + protect_content=True, + parse_mode="Markdown", + has_spoiler=True, + ) + + assert isinstance(message.photo[-2], PhotoSize) + assert isinstance(message.photo[-2].file_id, str) + assert isinstance(message.photo[-2].file_unique_id, str) + assert message.photo[-2].file_id != "" + assert message.photo[-2].file_unique_id != "" + + assert isinstance(message.photo[-1], PhotoSize) + assert isinstance(message.photo[-1].file_id, str) + assert isinstance(message.photo[-1].file_unique_id, str) + assert message.photo[-1].file_id != "" + assert message.photo[-1].file_unique_id != "" + + assert message.caption == self.caption.replace("*", "") + assert message.has_protected_content + assert message.has_media_spoiler + + async def test_send_photo_parse_mode_markdown(self, bot, chat_id, photo_file): + message = await bot.send_photo( + chat_id, photo_file, caption=self.caption, parse_mode="Markdown" + ) + assert isinstance(message.photo[-2], PhotoSize) + assert isinstance(message.photo[-2].file_id, str) + assert isinstance(message.photo[-2].file_unique_id, str) + assert message.photo[-2].file_id != "" + assert message.photo[-2].file_unique_id != "" + + assert isinstance(message.photo[-1], PhotoSize) + assert isinstance(message.photo[-1].file_id, str) + assert isinstance(message.photo[-1].file_unique_id, str) + assert message.photo[-1].file_id != "" + assert message.photo[-1].file_unique_id != "" + + assert message.caption == self.caption.replace("*", "") + assert len(message.caption_entities) == 1 + + async def test_send_photo_parse_mode_html(self, bot, chat_id, photo_file): + message = await bot.send_photo( + chat_id, photo_file, caption=self.caption, parse_mode="HTML" + ) + assert isinstance(message.photo[-2], PhotoSize) + assert isinstance(message.photo[-2].file_id, str) + assert isinstance(message.photo[-2].file_unique_id, str) + assert message.photo[-2].file_id != "" + assert message.photo[-2].file_unique_id != "" + + assert isinstance(message.photo[-1], PhotoSize) + assert isinstance(message.photo[-1].file_id, str) + assert isinstance(message.photo[-1].file_unique_id, str) + assert message.photo[-1].file_id != "" + assert message.photo[-1].file_unique_id != "" + + assert message.caption == self.caption.replace("", "").replace("", "") + assert len(message.caption_entities) == 1 + + async def test_send_photo_caption_entities(self, bot, chat_id, photo_file): + test_string = "Italic Bold Code" + entities = [ + MessageEntity(MessageEntity.ITALIC, 0, 6), + MessageEntity(MessageEntity.ITALIC, 7, 4), + MessageEntity(MessageEntity.ITALIC, 12, 4), + ] + message = await bot.send_photo( + chat_id, photo_file, caption=test_string, caption_entities=entities + ) + + assert message.caption == test_string + assert message.caption_entities == tuple(entities) + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_photo_default_parse_mode_1(self, default_bot, chat_id, photo_file): + test_string = "Italic Bold Code" + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_photo(chat_id, photo_file, caption=test_markdown_string) + assert message.caption_markdown == test_markdown_string + assert message.caption == test_string + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_photo_default_parse_mode_2(self, default_bot, chat_id, photo_file): + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_photo( + chat_id, photo_file, caption=test_markdown_string, parse_mode=None + ) + assert message.caption == test_markdown_string + assert message.caption_markdown == escape_markdown(test_markdown_string) + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_photo_default_parse_mode_3(self, default_bot, chat_id, photo_file): + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_photo( + chat_id, photo_file, caption=test_markdown_string, parse_mode="HTML" + ) + assert message.caption == test_markdown_string + assert message.caption_markdown == escape_markdown(test_markdown_string) + + @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) + async def test_send_photo_default_protect_content(self, chat_id, default_bot, photo): + tasks = asyncio.gather( + default_bot.send_photo(chat_id, photo), + default_bot.send_photo(chat_id, photo, protect_content=False), + ) + protected, unprotected = await tasks + assert protected.has_protected_content + assert not unprotected.has_protected_content + + @pytest.mark.parametrize( + "default_bot,custom", + [ + ({"allow_sending_without_reply": True}, None), + ({"allow_sending_without_reply": False}, None), + ({"allow_sending_without_reply": False}, True), + ], + indirect=["default_bot"], + ) + async def test_send_photo_default_allow_sending_without_reply( + self, default_bot, chat_id, photo_file, custom + ): + reply_to_message = await default_bot.send_message(chat_id, "test") + await reply_to_message.delete() + if custom is not None: + message = await default_bot.send_photo( + chat_id, + photo_file, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = await default_bot.send_photo( + chat_id, photo_file, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match="message not found"): + await default_bot.send_photo( + chat_id, photo_file, reply_to_message_id=reply_to_message.message_id + ) + + async def test_get_and_download(self, bot, photo): + path = Path("telegram.jpg") + if path.is_file(): + path.unlink() + + new_file = await bot.getFile(photo.file_id) + + assert new_file.file_size == photo.file_size + assert new_file.file_unique_id == photo.file_unique_id + assert new_file.file_path.startswith("https://") is True + + await new_file.download_to_drive("telegram.jpg") + + assert path.is_file() + + async def test_send_url_jpg_file(self, bot, chat_id): + message = await bot.send_photo(chat_id, photo=self.photo_file_url) + + assert isinstance(message.photo[-2], PhotoSize) + assert isinstance(message.photo[-2].file_id, str) + assert isinstance(message.photo[-2].file_unique_id, str) + assert message.photo[-2].file_id != "" + assert message.photo[-2].file_unique_id != "" + + assert isinstance(message.photo[-1], PhotoSize) + assert isinstance(message.photo[-1].file_id, str) + assert isinstance(message.photo[-1].file_unique_id, str) + assert message.photo[-1].file_id != "" + assert message.photo[-1].file_unique_id != "" + + async def test_send_url_png_file(self, bot, chat_id): + message = await bot.send_photo( + photo="http://dummyimage.com/600x400/000/fff.png&text=telegram", chat_id=chat_id + ) + + photo = message.photo[-1] + + assert isinstance(photo, PhotoSize) + assert isinstance(photo.file_id, str) + assert isinstance(photo.file_unique_id, str) + assert photo.file_id != "" + assert photo.file_unique_id != "" + + async def test_send_file_unicode_filename(self, bot, chat_id): + """ + Regression test for https://github.com/python-telegram-bot/python-telegram-bot/issues/1202 + """ + with data_file("测试.png").open("rb") as f: + message = await bot.send_photo(photo=f, chat_id=chat_id) + + photo = message.photo[-1] + + assert isinstance(photo, PhotoSize) + assert isinstance(photo.file_id, str) + assert isinstance(photo.file_unique_id, str) + assert photo.file_id != "" + assert photo.file_unique_id != "" + + async def test_send_bytesio_jpg_file(self, bot, chat_id): + filepath = data_file("telegram_no_standard_header.jpg") + + # raw image bytes + raw_bytes = BytesIO(filepath.read_bytes()) + input_file = InputFile(raw_bytes) + assert input_file.mimetype == "application/octet-stream" + + # raw image bytes with name info + raw_bytes = BytesIO(filepath.read_bytes()) + raw_bytes.name = str(filepath) + input_file = InputFile(raw_bytes) + assert input_file.mimetype == "image/jpeg" + + # send raw photo + raw_bytes = BytesIO(filepath.read_bytes()) + message = await bot.send_photo(chat_id, photo=raw_bytes) + photo = message.photo[-1] + assert isinstance(photo.file_id, str) + assert isinstance(photo.file_unique_id, str) + assert photo.file_id != "" + assert photo.file_unique_id != "" + assert isinstance(photo, PhotoSize) + assert photo.width == 1280 + assert photo.height == 720 + assert photo.file_size == 33372 + + async def test_resend(self, bot, chat_id, photo): + message = await bot.send_photo(chat_id=chat_id, photo=photo.file_id) + + assert isinstance(message.photo[-2], PhotoSize) + assert isinstance(message.photo[-2].file_id, str) + assert isinstance(message.photo[-2].file_unique_id, str) + assert message.photo[-2].file_id != "" + assert message.photo[-2].file_unique_id != "" + + assert isinstance(message.photo[-1], PhotoSize) + assert isinstance(message.photo[-1].file_id, str) + assert isinstance(message.photo[-1].file_unique_id, str) + assert message.photo[-1].file_id != "" + assert message.photo[-1].file_unique_id != "" + + async def test_error_send_empty_file(self, bot, chat_id): + with pytest.raises(TelegramError): + await bot.send_photo(chat_id=chat_id, photo=open(os.devnull, "rb")) + + async def test_error_send_empty_file_id(self, bot, chat_id): + with pytest.raises(TelegramError): + await bot.send_photo(chat_id=chat_id, photo="") diff --git a/tests/test_poll.py b/tests/test_poll.py index 8a98a22a3..3fc49f46d 100644 --- a/tests/test_poll.py +++ b/tests/test_poll.py @@ -24,17 +24,19 @@ from telegram._utils.datetime import to_timestamp from telegram.constants import PollType -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def poll_option(): - out = PollOption(text=TestPollOption.text, voter_count=TestPollOption.voter_count) + out = PollOption(text=TestPollOptionBase.text, voter_count=TestPollOptionBase.voter_count) out._unfreeze() return out -class TestPollOption: +class TestPollOptionBase: text = "test option" voter_count = 3 + +class TestPollOptionWithoutRequest(TestPollOptionBase): def test_slot_behaviour(self, poll_option, mro_slots): for attr in poll_option.__slots__: assert getattr(poll_option, attr, "err") != "err", f"got extra slot '{attr}'" @@ -75,18 +77,20 @@ class TestPollOption: assert hash(a) != hash(e) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def poll_answer(): return PollAnswer( - poll_id=TestPollAnswer.poll_id, user=TestPollAnswer.user, option_ids=TestPollAnswer.poll_id + TestPollAnswerBase.poll_id, TestPollAnswerBase.user, TestPollAnswerBase.poll_id ) -class TestPollAnswer: +class TestPollAnswerBase: poll_id = "id" user = User(1, "", False) option_ids = [2] + +class TestPollAnswerWithoutRequest(TestPollAnswerBase): def test_de_json(self): json_dict = { "poll_id": self.poll_id, @@ -128,27 +132,27 @@ class TestPollAnswer: assert hash(a) != hash(e) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def poll(): poll = Poll( - TestPoll.id_, - TestPoll.question, - TestPoll.options, - TestPoll.total_voter_count, - TestPoll.is_closed, - TestPoll.is_anonymous, - TestPoll.type, - TestPoll.allows_multiple_answers, - explanation=TestPoll.explanation, - explanation_entities=TestPoll.explanation_entities, - open_period=TestPoll.open_period, - close_date=TestPoll.close_date, + TestPollBase.id_, + TestPollBase.question, + TestPollBase.options, + TestPollBase.total_voter_count, + TestPollBase.is_closed, + TestPollBase.is_anonymous, + TestPollBase.type, + TestPollBase.allows_multiple_answers, + explanation=TestPollBase.explanation, + explanation_entities=TestPollBase.explanation_entities, + open_period=TestPollBase.open_period, + close_date=TestPollBase.close_date, ) poll._unfreeze() return poll -class TestPoll: +class TestPollBase: id_ = "id" question = "Test?" options = [PollOption("test", 10), PollOption("test2", 11)] @@ -165,6 +169,8 @@ class TestPoll: open_period = 42 close_date = datetime.now(timezone.utc) + +class TestPollWithoutRequest(TestPollBase): def test_de_json(self, bot): json_dict = { "id": self.id_, @@ -218,6 +224,21 @@ class TestPoll: assert poll_dict["open_period"] == poll.open_period assert poll_dict["close_date"] == to_timestamp(poll.close_date) + def test_equality(self): + a = Poll(123, "question", ["O1", "O2"], 1, False, True, Poll.REGULAR, True) + b = Poll(123, "question", ["o1", "o2"], 1, True, False, Poll.REGULAR, True) + c = Poll(456, "question", ["o1", "o2"], 1, True, False, Poll.REGULAR, True) + d = PollOption("Text", 1) + + assert a == b + assert hash(a) == hash(b) + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d) + def test_enum_init(self): poll = Poll( type="foo", @@ -267,18 +288,3 @@ class TestPoll: assert poll.parse_explanation_entities(MessageEntity.URL) == {entity: "http://google.com"} assert poll.parse_explanation_entities() == {entity: "http://google.com", entity_2: "h"} - - def test_equality(self): - a = Poll(123, "question", ["O1", "O2"], 1, False, True, Poll.REGULAR, True) - b = Poll(123, "question", ["o1", "o2"], 1, True, False, Poll.REGULAR, True) - c = Poll(456, "question", ["o1", "o2"], 1, True, False, Poll.REGULAR, True) - d = PollOption("Text", 1) - - assert a == b - assert hash(a) == hash(b) - - assert a != c - assert hash(a) != hash(c) - - assert a != d - assert hash(a) != hash(d) diff --git a/tests/test_precheckoutquery.py b/tests/test_precheckoutquery.py index acff440b7..61cb8a3e2 100644 --- a/tests/test_precheckoutquery.py +++ b/tests/test_precheckoutquery.py @@ -27,22 +27,22 @@ from tests.auxil.bot_method_checks import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def pre_checkout_query(bot): pcq = PreCheckoutQuery( - TestPreCheckoutQuery.id_, - TestPreCheckoutQuery.from_user, - TestPreCheckoutQuery.currency, - TestPreCheckoutQuery.total_amount, - TestPreCheckoutQuery.invoice_payload, - shipping_option_id=TestPreCheckoutQuery.shipping_option_id, - order_info=TestPreCheckoutQuery.order_info, + TestPreCheckoutQueryBase.id_, + TestPreCheckoutQueryBase.from_user, + TestPreCheckoutQueryBase.currency, + TestPreCheckoutQueryBase.total_amount, + TestPreCheckoutQueryBase.invoice_payload, + shipping_option_id=TestPreCheckoutQueryBase.shipping_option_id, + order_info=TestPreCheckoutQueryBase.order_info, ) pcq.set_bot(bot) return pcq -class TestPreCheckoutQuery: +class TestPreCheckoutQueryBase: id_ = 5 invoice_payload = "invoice_payload" shipping_option_id = "shipping_option_id" @@ -51,6 +51,8 @@ class TestPreCheckoutQuery: from_user = User(0, "", False) order_info = OrderInfo() + +class TestPreCheckoutQueryWithoutRequest(TestPreCheckoutQueryBase): def test_slot_behaviour(self, pre_checkout_query, mro_slots): inst = pre_checkout_query for attr in inst.__slots__: @@ -91,27 +93,6 @@ class TestPreCheckoutQuery: assert pre_checkout_query_dict["from"] == pre_checkout_query.from_user.to_dict() assert pre_checkout_query_dict["order_info"] == pre_checkout_query.order_info.to_dict() - async def test_answer(self, monkeypatch, pre_checkout_query): - async def make_assertion(*_, **kwargs): - return kwargs["pre_checkout_query_id"] == pre_checkout_query.id - - assert check_shortcut_signature( - PreCheckoutQuery.answer, Bot.answer_pre_checkout_query, ["pre_checkout_query_id"], [] - ) - assert await check_shortcut_call( - pre_checkout_query.answer, - pre_checkout_query.get_bot(), - "answer_pre_checkout_query", - ) - assert await check_defaults_handling( - pre_checkout_query.answer, pre_checkout_query.get_bot() - ) - - monkeypatch.setattr( - pre_checkout_query.get_bot(), "answer_pre_checkout_query", make_assertion - ) - assert await pre_checkout_query.answer(ok=True) - def test_equality(self): a = PreCheckoutQuery( self.id_, self.from_user, self.currency, self.total_amount, self.invoice_payload @@ -137,3 +118,24 @@ class TestPreCheckoutQuery: assert a != e assert hash(a) != hash(e) + + async def test_answer(self, monkeypatch, pre_checkout_query): + async def make_assertion(*_, **kwargs): + return kwargs["pre_checkout_query_id"] == pre_checkout_query.id + + assert check_shortcut_signature( + PreCheckoutQuery.answer, Bot.answer_pre_checkout_query, ["pre_checkout_query_id"], [] + ) + assert await check_shortcut_call( + pre_checkout_query.answer, + pre_checkout_query.get_bot(), + "answer_pre_checkout_query", + ) + assert await check_defaults_handling( + pre_checkout_query.answer, pre_checkout_query.get_bot() + ) + + monkeypatch.setattr( + pre_checkout_query.get_bot(), "answer_pre_checkout_query", make_assertion + ) + assert await pre_checkout_query.answer(ok=True) diff --git a/tests/test_proximityalerttriggered.py b/tests/test_proximityalerttriggered.py index e47eb63a7..456c72b36 100644 --- a/tests/test_proximityalerttriggered.py +++ b/tests/test_proximityalerttriggered.py @@ -21,20 +21,22 @@ import pytest from telegram import BotCommand, ProximityAlertTriggered, User -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def proximity_alert_triggered(): return ProximityAlertTriggered( - traveler=TestProximityAlertTriggered.traveler, - watcher=TestProximityAlertTriggered.watcher, - distance=TestProximityAlertTriggered.distance, + TestProximityAlertTriggeredBase.traveler, + TestProximityAlertTriggeredBase.watcher, + TestProximityAlertTriggeredBase.distance, ) -class TestProximityAlertTriggered: +class TestProximityAlertTriggeredBase: traveler = User(1, "foo", False) watcher = User(2, "bar", False) distance = 42 + +class TestProximityAlertTriggeredWithoutRequest(TestProximityAlertTriggeredBase): def test_slot_behaviour(self, proximity_alert_triggered, mro_slots): inst = proximity_alert_triggered for attr in inst.__slots__: diff --git a/tests/test_ratelimiter.py b/tests/test_ratelimiter.py index 1f888f112..f610e0b6e 100644 --- a/tests/test_ratelimiter.py +++ b/tests/test_ratelimiter.py @@ -36,9 +36,7 @@ from telegram.constants import ParseMode from telegram.error import RetryAfter from telegram.ext import AIORateLimiter, BaseRateLimiter, Defaults, ExtBot from telegram.request import BaseRequest, RequestData -from tests.auxil.object_conversions import env_var_2_bool - -TEST_WITH_OPT_DEPS = env_var_2_bool(os.getenv("TEST_WITH_OPT_DEPS", True)) +from tests.conftest import TEST_WITH_OPT_DEPS @pytest.mark.skipif( diff --git a/tests/test_replykeyboardmarkup.py b/tests/test_replykeyboardmarkup.py index 0f3badfa1..5b97fda65 100644 --- a/tests/test_replykeyboardmarkup.py +++ b/tests/test_replykeyboardmarkup.py @@ -22,81 +22,32 @@ import pytest from telegram import InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def reply_keyboard_markup(): return ReplyKeyboardMarkup( - TestReplyKeyboardMarkup.keyboard, - resize_keyboard=TestReplyKeyboardMarkup.resize_keyboard, - one_time_keyboard=TestReplyKeyboardMarkup.one_time_keyboard, - selective=TestReplyKeyboardMarkup.selective, - is_persistent=TestReplyKeyboardMarkup.is_persistent, + TestReplyKeyboardMarkupBase.keyboard, + resize_keyboard=TestReplyKeyboardMarkupBase.resize_keyboard, + one_time_keyboard=TestReplyKeyboardMarkupBase.one_time_keyboard, + selective=TestReplyKeyboardMarkupBase.selective, + is_persistent=TestReplyKeyboardMarkupBase.is_persistent, ) -class TestReplyKeyboardMarkup: +class TestReplyKeyboardMarkupBase: keyboard = [[KeyboardButton("button1"), KeyboardButton("button2")]] resize_keyboard = True one_time_keyboard = True selective = True is_persistent = True + +class TestReplyKeyboardMarkupWithoutRequest(TestReplyKeyboardMarkupBase): def test_slot_behaviour(self, reply_keyboard_markup, mro_slots): inst = reply_keyboard_markup 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" - @pytest.mark.flaky(3, 1) - async def test_send_message_with_reply_keyboard_markup( - self, bot, chat_id, reply_keyboard_markup - ): - message = await bot.send_message(chat_id, "Text", reply_markup=reply_keyboard_markup) - - assert message.text == "Text" - - @pytest.mark.flaky(3, 1) - async def test_send_message_with_data_markup(self, bot, chat_id): - message = await bot.send_message( - chat_id, "text 2", reply_markup={"keyboard": [["1", "2"]]} - ) - - assert message.text == "text 2" - - def test_from_button(self): - reply_keyboard_markup = ReplyKeyboardMarkup.from_button( - KeyboardButton(text="button1") - ).keyboard - assert len(reply_keyboard_markup) == 1 - assert len(reply_keyboard_markup[0]) == 1 - - reply_keyboard_markup = ReplyKeyboardMarkup.from_button("button1").keyboard - assert len(reply_keyboard_markup) == 1 - assert len(reply_keyboard_markup[0]) == 1 - - def test_from_row(self): - reply_keyboard_markup = ReplyKeyboardMarkup.from_row( - [KeyboardButton(text="button1"), KeyboardButton(text="button2")] - ).keyboard - assert len(reply_keyboard_markup) == 1 - assert len(reply_keyboard_markup[0]) == 2 - - reply_keyboard_markup = ReplyKeyboardMarkup.from_row(["button1", "button2"]).keyboard - assert len(reply_keyboard_markup) == 1 - assert len(reply_keyboard_markup[0]) == 2 - - def test_from_column(self): - reply_keyboard_markup = ReplyKeyboardMarkup.from_column( - [KeyboardButton(text="button1"), KeyboardButton(text="button2")] - ).keyboard - assert len(reply_keyboard_markup) == 2 - assert len(reply_keyboard_markup[0]) == 1 - assert len(reply_keyboard_markup[1]) == 1 - - reply_keyboard_markup = ReplyKeyboardMarkup.from_column(["button1", "button2"]).keyboard - assert len(reply_keyboard_markup) == 2 - assert len(reply_keyboard_markup[0]) == 1 - assert len(reply_keyboard_markup[1]) == 1 - def test_expected_values(self, reply_keyboard_markup): assert isinstance(reply_keyboard_markup.keyboard, tuple) assert all(isinstance(row, tuple) for row in reply_keyboard_markup.keyboard) @@ -107,18 +58,6 @@ class TestReplyKeyboardMarkup: assert reply_keyboard_markup.selective == self.selective assert reply_keyboard_markup.is_persistent == self.is_persistent - def test_wrong_keyboard_inputs(self): - with pytest.raises(ValueError): - ReplyKeyboardMarkup([["button1"], 1]) - with pytest.raises(ValueError): - ReplyKeyboardMarkup("strings_are_not_allowed") - with pytest.raises(ValueError): - ReplyKeyboardMarkup(["strings_are_not_allowed_in_the_rows_either"]) - with pytest.raises(ValueError): - ReplyKeyboardMarkup(KeyboardButton("button1")) - with pytest.raises(ValueError): - ReplyKeyboardMarkup([[["button1"]]]) - def test_to_dict(self, reply_keyboard_markup): reply_keyboard_markup_dict = reply_keyboard_markup.to_dict() @@ -165,3 +104,66 @@ class TestReplyKeyboardMarkup: assert a != f assert hash(a) != hash(f) + + def test_wrong_keyboard_inputs(self): + with pytest.raises(ValueError): + ReplyKeyboardMarkup([["button1"], 1]) + with pytest.raises(ValueError): + ReplyKeyboardMarkup("strings_are_not_allowed") + with pytest.raises(ValueError): + ReplyKeyboardMarkup(["strings_are_not_allowed_in_the_rows_either"]) + with pytest.raises(ValueError): + ReplyKeyboardMarkup(KeyboardButton("button1")) + with pytest.raises(ValueError): + ReplyKeyboardMarkup([[["button1"]]]) + + def test_from_button(self): + reply_keyboard_markup = ReplyKeyboardMarkup.from_button( + KeyboardButton(text="button1") + ).keyboard + assert len(reply_keyboard_markup) == 1 + assert len(reply_keyboard_markup[0]) == 1 + + reply_keyboard_markup = ReplyKeyboardMarkup.from_button("button1").keyboard + assert len(reply_keyboard_markup) == 1 + assert len(reply_keyboard_markup[0]) == 1 + + def test_from_row(self): + reply_keyboard_markup = ReplyKeyboardMarkup.from_row( + [KeyboardButton(text="button1"), KeyboardButton(text="button2")] + ).keyboard + assert len(reply_keyboard_markup) == 1 + assert len(reply_keyboard_markup[0]) == 2 + + reply_keyboard_markup = ReplyKeyboardMarkup.from_row(["button1", "button2"]).keyboard + assert len(reply_keyboard_markup) == 1 + assert len(reply_keyboard_markup[0]) == 2 + + def test_from_column(self): + reply_keyboard_markup = ReplyKeyboardMarkup.from_column( + [KeyboardButton(text="button1"), KeyboardButton(text="button2")] + ).keyboard + assert len(reply_keyboard_markup) == 2 + assert len(reply_keyboard_markup[0]) == 1 + assert len(reply_keyboard_markup[1]) == 1 + + reply_keyboard_markup = ReplyKeyboardMarkup.from_column(["button1", "button2"]).keyboard + assert len(reply_keyboard_markup) == 2 + assert len(reply_keyboard_markup[0]) == 1 + assert len(reply_keyboard_markup[1]) == 1 + + +class TestReplyKeyboardMarkupWithRequest(TestReplyKeyboardMarkupBase): + async def test_send_message_with_reply_keyboard_markup( + self, bot, chat_id, reply_keyboard_markup + ): + message = await bot.send_message(chat_id, "Text", reply_markup=reply_keyboard_markup) + + assert message.text == "Text" + + async def test_send_message_with_data_markup(self, bot, chat_id): + message = await bot.send_message( + chat_id, "text 2", reply_markup={"keyboard": [["1", "2"]]} + ) + + assert message.text == "text 2" diff --git a/tests/test_replykeyboardremove.py b/tests/test_replykeyboardremove.py index fad6330b3..3845f5fc5 100644 --- a/tests/test_replykeyboardremove.py +++ b/tests/test_replykeyboardremove.py @@ -21,29 +21,23 @@ import pytest from telegram import ReplyKeyboardRemove -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def reply_keyboard_remove(): - return ReplyKeyboardRemove(selective=TestReplyKeyboardRemove.selective) + return ReplyKeyboardRemove(selective=TestReplyKeyboardRemoveBase.selective) -class TestReplyKeyboardRemove: +class TestReplyKeyboardRemoveBase: remove_keyboard = True selective = True + +class TestReplyKeyboardRemoveWithoutRequest(TestReplyKeyboardRemoveBase): def test_slot_behaviour(self, reply_keyboard_remove, mro_slots): inst = reply_keyboard_remove 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" - @pytest.mark.flaky(3, 1) - async def test_send_message_with_reply_keyboard_remove( - self, bot, chat_id, reply_keyboard_remove - ): - message = await bot.send_message(chat_id, "Text", reply_markup=reply_keyboard_remove) - - assert message.text == "Text" - def test_expected_values(self, reply_keyboard_remove): assert reply_keyboard_remove.remove_keyboard == self.remove_keyboard assert reply_keyboard_remove.selective == self.selective @@ -55,3 +49,11 @@ class TestReplyKeyboardRemove: reply_keyboard_remove_dict["remove_keyboard"] == reply_keyboard_remove.remove_keyboard ) assert reply_keyboard_remove_dict["selective"] == reply_keyboard_remove.selective + + +class TestReplyKeyboardRemoveWithRequest(TestReplyKeyboardRemoveBase): + async def test_send_message_with_reply_keyboard_remove( + self, bot, chat_id, reply_keyboard_remove + ): + message = await bot.send_message(chat_id, "Text", reply_markup=reply_keyboard_remove) + assert message.text == "Text" diff --git a/tests/test_request.py b/tests/test_request.py index 0b773d545..a81d3b1e4 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -20,7 +20,6 @@ implementations for BaseRequest and we want to test HTTPXRequest anyway.""" import asyncio import json -import os from collections import defaultdict from dataclasses import dataclass from http import HTTPStatus @@ -43,9 +42,9 @@ from telegram.error import ( ) from telegram.request._httpxrequest import HTTPXRequest -from .auxil.object_conversions import env_var_2_bool +from .conftest import TEST_WITH_OPT_DEPS -# We only need the first fixture, but it uses the others, so pytest needs us to import them as well +# We only need mixed_rqs fixture, but it uses the others, so pytest needs us to import them as well from .test_requestdata import ( # noqa: F401 file_params, input_media_photo, @@ -72,9 +71,6 @@ async def httpx_request(): yield rq -TEST_WITH_OPT_DEPS = env_var_2_bool(os.getenv("TEST_WITH_OPT_DEPS", True)) - - @pytest.mark.skipif( TEST_WITH_OPT_DEPS, reason="Only relevant if the optional dependency is not installed" ) @@ -84,14 +80,14 @@ class TestNoSocks: HTTPXRequest(proxy_url="socks5://foo") -class TestRequest: +class TestRequestWithoutRequest: test_flag = None @pytest.fixture(autouse=True) def reset(self): self.test_flag = None - async def test_init_import_errors(self, bot, monkeypatch): + async def test_init_import_errors(self, monkeypatch): """Makes sure that import errors are forwarded - related to TestNoSocks above""" def __init__(self, *args, **kwargs): @@ -325,7 +321,7 @@ class TestRequest: assert self.test_flag == (1, 2, 3, 4) -class TestHTTPXRequest: +class TestHTTPXRequestWithoutRequest: test_flag = None @pytest.fixture(autouse=True) @@ -595,8 +591,9 @@ class TestHTTPXRequest: httpx_request.do_request(method="GET", url="URL"), ) - @pytest.mark.flaky(3, 1) - async def test_do_request_wait_for_pool(self, monkeypatch, httpx_request): + +class TestHTTPXRequestWithRequest: + async def test_do_request_wait_for_pool(self, httpx_request): """The pool logic is buried rather deeply in httpxcore, so we make actual requests here instead of mocking""" task_1 = asyncio.create_task( diff --git a/tests/test_requestdata.py b/tests/test_requestdata.py index 7a92cbec9..ad45e063e 100644 --- a/tests/test_requestdata.py +++ b/tests/test_requestdata.py @@ -108,28 +108,28 @@ def file_rqs(file_params) -> RequestData: ) -@pytest.fixture() +@pytest.fixture(scope="module") def mixed_params(file_params, simple_params) -> Dict[str, Any]: both = file_params.copy() both.update(simple_params) return both -@pytest.fixture() +@pytest.fixture(scope="module") def mixed_jsons(file_jsons, simple_jsons) -> Dict[str, Any]: both = file_jsons.copy() both.update(simple_jsons) return both -@pytest.fixture() +@pytest.fixture(scope="module") def mixed_rqs(mixed_params) -> RequestData: return RequestData( [RequestParameter.from_input(key, value) for key, value in mixed_params.items()] ) -class TestRequestData: +class TestRequestDataWithoutRequest: def test_slot_behaviour(self, simple_rqs, mro_slots): for attr in simple_rqs.__slots__: assert getattr(simple_rqs, attr, "err") != "err", f"got extra slot '{attr}'" @@ -201,7 +201,7 @@ class TestRequestData: assert file_rqs.multipart_data == expected assert mixed_rqs.multipart_data == expected - def test_url_encoding(self, monkeypatch): + def test_url_encoding(self): data = RequestData( [ RequestParameter.from_input("chat_id", 123), diff --git a/tests/test_requestparameter.py b/tests/test_requestparameter.py index f7b7e4b8a..12c55c007 100644 --- a/tests/test_requestparameter.py +++ b/tests/test_requestparameter.py @@ -27,7 +27,13 @@ from telegram.request._requestparameter import RequestParameter from tests.conftest import data_file -class TestRequestParameter: +class TestRequestParameterWithoutRequest: + def test_slot_behaviour(self, mro_slots): + inst = RequestParameter("name", "value", [1, 2]) + 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_init(self): request_parameter = RequestParameter("name", "value", [1, 2]) assert request_parameter.name == "name" @@ -39,12 +45,6 @@ class TestRequestParameter: assert request_parameter.value == "value" assert request_parameter.input_files is None - def test_slot_behaviour(self, mro_slots): - inst = RequestParameter("name", "value", [1, 2]) - 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" - @pytest.mark.parametrize( "value, expected", [ diff --git a/tests/test_sentwebappmessage.py b/tests/test_sentwebappmessage.py index a1a3761f2..4304ab6bf 100644 --- a/tests/test_sentwebappmessage.py +++ b/tests/test_sentwebappmessage.py @@ -22,16 +22,16 @@ import pytest from telegram import SentWebAppMessage -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def sent_web_app_message(): - return SentWebAppMessage( - inline_message_id=TestSentWebAppMessage.inline_message_id, - ) + return SentWebAppMessage(inline_message_id=TestSentWebAppMessageBase.inline_message_id) -class TestSentWebAppMessage: +class TestSentWebAppMessageBase: inline_message_id = "123" + +class TestSentWebAppMessageWithoutRequest(TestSentWebAppMessageBase): def test_slot_behaviour(self, sent_web_app_message, mro_slots): inst = sent_web_app_message for attr in inst.__slots__: diff --git a/tests/test_shared.py b/tests/test_shared.py index 621d6753e..0d7da3b53 100644 --- a/tests/test_shared.py +++ b/tests/test_shared.py @@ -25,15 +25,17 @@ from telegram import ChatShared, UserShared @pytest.fixture(scope="class") def user_shared(): return UserShared( - TestUserShared.request_id, - TestUserShared.user_id, + TestUserSharedBase.request_id, + TestUserSharedBase.user_id, ) -class TestUserShared: +class TestUserSharedBase: request_id = 789 user_id = 101112 + +class TestUserSharedWithoutRequest(TestUserSharedBase): def test_slot_behaviour(self, user_shared, mro_slots): for attr in user_shared.__slots__: assert getattr(user_shared, attr, "err") != "err", f"got extra slot '{attr}'" @@ -77,15 +79,17 @@ class TestUserShared: @pytest.fixture(scope="class") def chat_shared(): return ChatShared( - TestChatShared.request_id, - TestChatShared.chat_id, + TestChatSharedBase.request_id, + TestChatSharedBase.chat_id, ) -class TestChatShared: +class TestChatSharedBase: request_id = 131415 chat_id = 161718 + +class TestChatSharedWithoutRequest(TestChatSharedBase): def test_slot_behaviour(self, chat_shared, mro_slots): for attr in chat_shared.__slots__: assert getattr(chat_shared, attr, "err") != "err", f"got extra slot '{attr}'" diff --git a/tests/test_shippingaddress.py b/tests/test_shippingaddress.py index 6b3fa53f1..439af4625 100644 --- a/tests/test_shippingaddress.py +++ b/tests/test_shippingaddress.py @@ -21,19 +21,19 @@ import pytest from telegram import ShippingAddress -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def shipping_address(): return ShippingAddress( - TestShippingAddress.country_code, - TestShippingAddress.state, - TestShippingAddress.city, - TestShippingAddress.street_line1, - TestShippingAddress.street_line2, - TestShippingAddress.post_code, + TestShippingAddressBase.country_code, + TestShippingAddressBase.state, + TestShippingAddressBase.city, + TestShippingAddressBase.street_line1, + TestShippingAddressBase.street_line2, + TestShippingAddressBase.post_code, ) -class TestShippingAddress: +class TestShippingAddressBase: country_code = "GB" state = "state" city = "London" @@ -41,6 +41,8 @@ class TestShippingAddress: street_line2 = "street_line2" post_code = "WC1" + +class TestShippingAddressWithoutRequest(TestShippingAddressBase): def test_slot_behaviour(self, shipping_address, mro_slots): inst = shipping_address for attr in inst.__slots__: @@ -98,10 +100,20 @@ class TestShippingAddress: "", self.state, self.city, self.street_line1, self.street_line2, self.post_code ) d2 = ShippingAddress( - self.country_code, "", self.city, self.street_line1, self.street_line2, self.post_code + self.country_code, + "", + self.city, + self.street_line1, + self.street_line2, + self.post_code, ) d3 = ShippingAddress( - self.country_code, self.state, "", self.street_line1, self.street_line2, self.post_code + self.country_code, + self.state, + "", + self.street_line1, + self.street_line2, + self.post_code, ) d4 = ShippingAddress( self.country_code, self.state, self.city, "", self.street_line2, self.post_code diff --git a/tests/test_shippingoption.py b/tests/test_shippingoption.py index e9241ce37..05832e931 100644 --- a/tests/test_shippingoption.py +++ b/tests/test_shippingoption.py @@ -21,18 +21,20 @@ import pytest from telegram import LabeledPrice, ShippingOption, Voice -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def shipping_option(): return ShippingOption( - TestShippingOption.id_, TestShippingOption.title, TestShippingOption.prices + TestShippingOptionBase.id_, TestShippingOptionBase.title, TestShippingOptionBase.prices ) -class TestShippingOption: +class TestShippingOptionBase: id_ = "id" title = "title" prices = [LabeledPrice("Fish Container", 100), LabeledPrice("Premium Fish Container", 1000)] + +class TestShippingOptionWithoutRequest(TestShippingOptionBase): def test_slot_behaviour(self, shipping_option, mro_slots): inst = shipping_option for attr in inst.__slots__: diff --git a/tests/test_shippingquery.py b/tests/test_shippingquery.py index 061c0f6e8..485511918 100644 --- a/tests/test_shippingquery.py +++ b/tests/test_shippingquery.py @@ -27,24 +27,26 @@ from tests.auxil.bot_method_checks import ( ) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def shipping_query(bot): sq = ShippingQuery( - TestShippingQuery.id_, - TestShippingQuery.from_user, - TestShippingQuery.invoice_payload, - TestShippingQuery.shipping_address, + TestShippingQueryBase.id_, + TestShippingQueryBase.from_user, + TestShippingQueryBase.invoice_payload, + TestShippingQueryBase.shipping_address, ) sq.set_bot(bot) return sq -class TestShippingQuery: +class TestShippingQueryBase: id_ = "5" invoice_payload = "invoice_payload" from_user = User(0, "", False) shipping_address = ShippingAddress("GB", "", "London", "12 Grimmauld Place", "", "WC1") + +class TestShippingQueryWithoutRequest(TestShippingQueryBase): def test_slot_behaviour(self, shipping_query, mro_slots): inst = shipping_query for attr in inst.__slots__: @@ -53,10 +55,10 @@ class TestShippingQuery: def test_de_json(self, bot): json_dict = { - "id": TestShippingQuery.id_, - "invoice_payload": TestShippingQuery.invoice_payload, - "from": TestShippingQuery.from_user.to_dict(), - "shipping_address": TestShippingQuery.shipping_address.to_dict(), + "id": self.id_, + "invoice_payload": self.invoice_payload, + "from": self.from_user.to_dict(), + "shipping_address": self.shipping_address.to_dict(), } shipping_query = ShippingQuery.de_json(json_dict, bot) assert shipping_query.api_kwargs == {} @@ -76,21 +78,6 @@ class TestShippingQuery: assert shipping_query_dict["from"] == shipping_query.from_user.to_dict() assert shipping_query_dict["shipping_address"] == shipping_query.shipping_address.to_dict() - async def test_answer(self, monkeypatch, shipping_query): - async def make_assertion(*_, **kwargs): - return kwargs["shipping_query_id"] == shipping_query.id - - assert check_shortcut_signature( - ShippingQuery.answer, Bot.answer_shipping_query, ["shipping_query_id"], [] - ) - assert await check_shortcut_call( - shipping_query.answer, shipping_query._bot, "answer_shipping_query" - ) - assert await check_defaults_handling(shipping_query.answer, shipping_query._bot) - - monkeypatch.setattr(shipping_query._bot, "answer_shipping_query", make_assertion) - assert await shipping_query.answer(ok=True) - def test_equality(self): a = ShippingQuery(self.id_, self.from_user, self.invoice_payload, self.shipping_address) b = ShippingQuery(self.id_, self.from_user, self.invoice_payload, self.shipping_address) @@ -110,3 +97,18 @@ class TestShippingQuery: assert a != e assert hash(a) != hash(e) + + async def test_answer(self, monkeypatch, shipping_query): + async def make_assertion(*_, **kwargs): + return kwargs["shipping_query_id"] == shipping_query.id + + assert check_shortcut_signature( + ShippingQuery.answer, Bot.answer_shipping_query, ["shipping_query_id"], [] + ) + assert await check_shortcut_call( + shipping_query.answer, shipping_query._bot, "answer_shipping_query" + ) + assert await check_defaults_handling(shipping_query.answer, shipping_query._bot) + + monkeypatch.setattr(shipping_query._bot, "answer_shipping_query", make_assertion) + assert await shipping_query.answer(ok=True) diff --git a/tests/test_sticker.py b/tests/test_sticker.py index cc29c87eb..b4c1c2ecd 100644 --- a/tests/test_sticker.py +++ b/tests/test_sticker.py @@ -39,7 +39,7 @@ def sticker_file(): yield file -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") async def sticker(bot, chat_id): with data_file("telegram.webp").open("rb") as f: return (await bot.send_sticker(chat_id, sticker=f, read_timeout=50)).sticker @@ -51,7 +51,7 @@ def animated_sticker_file(): yield f -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") async def animated_sticker(bot, chat_id): with data_file("telegram_animated_sticker.tgs").open("rb") as f: return (await bot.send_sticker(chat_id, sticker=f, read_timeout=50)).sticker @@ -63,13 +63,13 @@ def video_sticker_file(): yield f -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def video_sticker(bot, chat_id): with data_file("telegram_video_sticker.webm").open("rb") as f: return bot.send_sticker(chat_id, sticker=f, timeout=50).sticker -class TestSticker: +class TestStickerBase: # sticker_file_url = 'https://python-telegram-bot.org/static/testfiles/telegram.webp' # Serving sticker from gh since our server sends wrong content_type sticker_file_url = ( @@ -94,7 +94,9 @@ class TestSticker: premium_animation = File("this_is_an_id", "this_is_an_unique_id") - def test_slot_behaviour(self, sticker, mro_slots, recwarn): + +class TestStickerWithoutRequest(TestStickerBase): + def test_slot_behaviour(self, sticker, mro_slots): for attr in sticker.__slots__: assert getattr(sticker, attr, "err") != "err", f"got extra slot '{attr}'" assert len(mro_slots(sticker)) == len(set(mro_slots(sticker))), "duplicate slot" @@ -125,91 +127,19 @@ class TestSticker: # we need to be a premium TG user to send a premium sticker, so the below is not tested # assert sticker.premium_animation == self.premium_animation - @pytest.mark.flaky(3, 1) - async def test_send_all_args(self, bot, chat_id, sticker_file, sticker): - message = await bot.send_sticker( - chat_id, sticker=sticker_file, disable_notification=False, protect_content=True - ) + def test_to_dict(self, sticker): + sticker_dict = sticker.to_dict() - assert isinstance(message.sticker, Sticker) - assert isinstance(message.sticker.file_id, str) - assert isinstance(message.sticker.file_unique_id, str) - assert message.sticker.file_id != "" - assert message.sticker.file_unique_id != "" - assert message.sticker.width == sticker.width - assert message.sticker.height == sticker.height - assert message.sticker.is_animated == sticker.is_animated - assert message.sticker.is_video == sticker.is_video - assert message.sticker.file_size == sticker.file_size - assert message.sticker.type == sticker.type - assert message.has_protected_content - # we need to be a premium TG user to send a premium sticker, so the below is not tested - # assert message.sticker.premium_animation == sticker.premium_animation - - assert isinstance(message.sticker.thumb, PhotoSize) - assert isinstance(message.sticker.thumb.file_id, str) - assert isinstance(message.sticker.thumb.file_unique_id, str) - assert message.sticker.thumb.file_id != "" - assert message.sticker.thumb.file_unique_id != "" - assert message.sticker.thumb.width == sticker.thumb.width - assert message.sticker.thumb.height == sticker.thumb.height - assert message.sticker.thumb.file_size == sticker.thumb.file_size - - @pytest.mark.flaky(3, 1) - async def test_get_and_download(self, bot, sticker): - path = Path("telegram.webp") - if path.is_file(): - path.unlink() - - new_file = await bot.get_file(sticker.file_id) - - assert new_file.file_size == sticker.file_size - assert new_file.file_id == sticker.file_id - assert new_file.file_unique_id == sticker.file_unique_id - assert new_file.file_path.startswith("https://") - - await new_file.download_to_drive("telegram.webp") - - assert path.is_file() - - @pytest.mark.flaky(3, 1) - async def test_resend(self, bot, chat_id, sticker): - message = await bot.send_sticker(chat_id=chat_id, sticker=sticker.file_id) - - assert message.sticker == sticker - - @pytest.mark.flaky(3, 1) - async def test_send_on_server_emoji(self, bot, chat_id): - server_file_id = "CAADAQADHAADyIsGAAFZfq1bphjqlgI" - message = await bot.send_sticker(chat_id=chat_id, sticker=server_file_id) - sticker = message.sticker - assert sticker.emoji == self.emoji - - @pytest.mark.flaky(3, 1) - async def test_send_from_url(self, bot, chat_id): - message = await bot.send_sticker(chat_id=chat_id, sticker=self.sticker_file_url) - sticker = message.sticker - - assert isinstance(message.sticker, Sticker) - assert isinstance(message.sticker.file_id, str) - assert isinstance(message.sticker.file_unique_id, str) - assert message.sticker.file_id != "" - assert message.sticker.file_unique_id != "" - assert message.sticker.width == sticker.width - assert message.sticker.height == sticker.height - assert message.sticker.is_animated == sticker.is_animated - assert message.sticker.is_video == sticker.is_video - assert message.sticker.file_size == sticker.file_size - assert message.sticker.type == sticker.type - - assert isinstance(message.sticker.thumb, PhotoSize) - assert isinstance(message.sticker.thumb.file_id, str) - assert isinstance(message.sticker.thumb.file_unique_id, str) - assert message.sticker.thumb.file_id != "" - assert message.sticker.thumb.file_unique_id != "" - assert message.sticker.thumb.width == sticker.thumb.width - assert message.sticker.thumb.height == sticker.thumb.height - assert message.sticker.thumb.file_size == sticker.thumb.file_size + assert isinstance(sticker_dict, dict) + assert sticker_dict["file_id"] == sticker.file_id + assert sticker_dict["file_unique_id"] == sticker.file_unique_id + assert sticker_dict["width"] == sticker.width + assert sticker_dict["height"] == sticker.height + assert sticker_dict["is_animated"] == sticker.is_animated + assert sticker_dict["is_video"] == sticker.is_video + assert sticker_dict["file_size"] == sticker.file_size + assert sticker_dict["thumb"] == sticker.thumb.to_dict() + assert sticker_dict["type"] == sticker.type def test_de_json(self, bot, sticker): json_dict = { @@ -242,135 +172,6 @@ class TestSticker: assert json_sticker.type == self.type assert json_sticker.custom_emoji_id == self.custom_emoji_id - async def test_send_with_sticker(self, monkeypatch, bot, chat_id, sticker): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - return request_data.json_parameters["sticker"] == sticker.file_id - - monkeypatch.setattr(bot.request, "post", make_assertion) - message = await bot.send_sticker(sticker=sticker, chat_id=chat_id) - assert message - - @pytest.mark.parametrize("local_mode", [True, False]) - async def test_send_sticker_local_files(self, monkeypatch, bot, chat_id, local_mode): - try: - bot._local_mode = local_mode - # For just test that the correct paths are passed as we have no local bot API set up - test_flag = False - file = data_file("telegram.jpg") - expected = file.as_uri() - - async def make_assertion(_, data, *args, **kwargs): - nonlocal test_flag - if local_mode: - test_flag = data.get("sticker") == expected - else: - test_flag = isinstance(data.get("sticker"), InputFile) - - monkeypatch.setattr(bot, "_post", make_assertion) - await bot.send_sticker(chat_id, file) - assert test_flag - monkeypatch.delattr(bot, "_post") - finally: - bot._local_mode = False - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize( - "default_bot,custom", - [ - ({"allow_sending_without_reply": True}, None), - ({"allow_sending_without_reply": False}, None), - ({"allow_sending_without_reply": False}, True), - ], - indirect=["default_bot"], - ) - async def test_send_sticker_default_allow_sending_without_reply( - self, default_bot, chat_id, sticker, custom - ): - reply_to_message = await default_bot.send_message(chat_id, "test") - await reply_to_message.delete() - if custom is not None: - message = await default_bot.send_sticker( - chat_id, - sticker, - allow_sending_without_reply=custom, - reply_to_message_id=reply_to_message.message_id, - ) - assert message.reply_to_message is None - elif default_bot.defaults.allow_sending_without_reply: - message = await default_bot.send_sticker( - chat_id, sticker, reply_to_message_id=reply_to_message.message_id - ) - assert message.reply_to_message is None - else: - with pytest.raises(BadRequest, match="message not found"): - await default_bot.send_sticker( - chat_id, sticker, reply_to_message_id=reply_to_message.message_id - ) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) - async def test_send_sticker_default_protect_content(self, chat_id, sticker, default_bot): - protected = await default_bot.send_sticker(chat_id, sticker) - assert protected.has_protected_content - unprotected = await default_bot.send_sticker(chat_id, sticker, protect_content=False) - assert not unprotected.has_protected_content - - def test_to_dict(self, sticker): - sticker_dict = sticker.to_dict() - - assert isinstance(sticker_dict, dict) - assert sticker_dict["file_id"] == sticker.file_id - assert sticker_dict["file_unique_id"] == sticker.file_unique_id - assert sticker_dict["width"] == sticker.width - assert sticker_dict["height"] == sticker.height - assert sticker_dict["is_animated"] == sticker.is_animated - assert sticker_dict["is_video"] == sticker.is_video - assert sticker_dict["file_size"] == sticker.file_size - assert sticker_dict["thumb"] == sticker.thumb.to_dict() - assert sticker_dict["type"] == sticker.type - - @pytest.mark.flaky(3, 1) - async def test_error_send_empty_file(self, bot, chat_id): - with pytest.raises(TelegramError): - await bot.send_sticker(chat_id, open(os.devnull, "rb")) - - @pytest.mark.flaky(3, 1) - async def test_error_send_empty_file_id(self, bot, chat_id): - with pytest.raises(TelegramError): - await bot.send_sticker(chat_id, "") - - async def test_error_without_required_args(self, bot, chat_id): - with pytest.raises(TypeError): - await bot.send_sticker(chat_id) - - @pytest.mark.flaky(3, 1) - async def test_premium_animation(self, bot): - # testing animation sucks a bit since we can't create a premium sticker. What we can do is - # get a sticker set which includes a premium sticker and check that specific one. - premium_sticker_set = await bot.get_sticker_set("Flame") - # the first one to appear here is a sticker with unique file id of AQADOBwAAifPOElr - # this could change in the future ofc. - premium_sticker = premium_sticker_set.stickers[20] - assert premium_sticker.premium_animation.file_unique_id == "AQADOBwAAifPOElr" - assert isinstance(premium_sticker.premium_animation.file_id, str) - assert premium_sticker.premium_animation.file_id != "" - premium_sticker_dict = { - "file_unique_id": "AQADOBwAAifPOElr", - "file_id": premium_sticker.premium_animation.file_id, - "file_size": premium_sticker.premium_animation.file_size, - } - assert premium_sticker.premium_animation.to_dict() == premium_sticker_dict - - @pytest.mark.flaky(3, 1) - async def test_custom_emoji(self, bot): - # testing custom emoji stickers is as much of an annoyance as the premium animation, see - # in test_premium_animation - custom_emoji_set = await bot.get_sticker_set("PTBStaticEmojiTestPack") - # the first one to appear here is a sticker with unique file id of AQADjBsAAkKD0Uty - # this could change in the future ofc. - custom_emoji_sticker = custom_emoji_set.stickers[0] - assert custom_emoji_sticker.custom_emoji_id == "6046140249875156202" - def test_equality(self, sticker): a = Sticker( sticker.file_id, @@ -429,6 +230,216 @@ class TestSticker: assert a != e assert hash(a) != hash(e) + async def test_error_without_required_args(self, bot, chat_id): + with pytest.raises(TypeError): + await bot.send_sticker(chat_id) + + async def test_send_with_sticker(self, monkeypatch, bot, chat_id, sticker): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return request_data.json_parameters["sticker"] == sticker.file_id + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.send_sticker(sticker=sticker, chat_id=chat_id) + + @pytest.mark.parametrize("local_mode", [True, False]) + async def test_send_sticker_local_files(self, monkeypatch, bot, chat_id, local_mode): + try: + bot._local_mode = local_mode + # For just test that the correct paths are passed as we have no local bot API set up + test_flag = False + file = data_file("telegram.jpg") + expected = file.as_uri() + + async def make_assertion(_, data, *args, **kwargs): + nonlocal test_flag + if local_mode: + test_flag = data.get("sticker") == expected + else: + test_flag = isinstance(data.get("sticker"), InputFile) + + monkeypatch.setattr(bot, "_post", make_assertion) + await bot.send_sticker(chat_id, file) + assert test_flag + finally: + bot._local_mode = False + + +class TestStickerWithRequest(TestStickerBase): + async def test_send_all_args(self, bot, chat_id, sticker_file, sticker): + message = await bot.send_sticker( + chat_id, sticker=sticker_file, disable_notification=False, protect_content=True + ) + + assert isinstance(message.sticker, Sticker) + assert isinstance(message.sticker.file_id, str) + assert isinstance(message.sticker.file_unique_id, str) + assert message.sticker.file_id != "" + assert message.sticker.file_unique_id != "" + assert message.sticker.width == sticker.width + assert message.sticker.height == sticker.height + assert message.sticker.is_animated == sticker.is_animated + assert message.sticker.is_video == sticker.is_video + assert message.sticker.file_size == sticker.file_size + assert message.sticker.type == sticker.type + assert message.has_protected_content + # we need to be a premium TG user to send a premium sticker, so the below is not tested + # assert message.sticker.premium_animation == sticker.premium_animation + + assert isinstance(message.sticker.thumb, PhotoSize) + assert isinstance(message.sticker.thumb.file_id, str) + assert isinstance(message.sticker.thumb.file_unique_id, str) + assert message.sticker.thumb.file_id != "" + assert message.sticker.thumb.file_unique_id != "" + assert message.sticker.thumb.width == sticker.thumb.width + assert message.sticker.thumb.height == sticker.thumb.height + assert message.sticker.thumb.file_size == sticker.thumb.file_size + + async def test_get_and_download(self, bot, sticker, chat_id): + path = Path("telegram.webp") + if path.is_file(): + path.unlink() + + new_file = await bot.get_file(sticker.file_id) + + assert new_file.file_size == sticker.file_size + assert new_file.file_unique_id == sticker.file_unique_id + assert new_file.file_path.startswith("https://") + + await new_file.download_to_drive("telegram.webp") + + assert path.is_file() + + async def test_resend(self, bot, chat_id, sticker): + message = await bot.send_sticker(chat_id=chat_id, sticker=sticker.file_id) + + assert message.sticker == sticker + + async def test_send_on_server_emoji(self, bot, chat_id): + server_file_id = "CAADAQADHAADyIsGAAFZfq1bphjqlgI" + message = await bot.send_sticker(chat_id=chat_id, sticker=server_file_id) + sticker = message.sticker + assert sticker.emoji == self.emoji + + async def test_send_from_url(self, bot, chat_id): + message = await bot.send_sticker(chat_id=chat_id, sticker=self.sticker_file_url) + sticker = message.sticker + + assert isinstance(message.sticker, Sticker) + assert isinstance(message.sticker.file_id, str) + assert isinstance(message.sticker.file_unique_id, str) + assert message.sticker.file_id != "" + assert message.sticker.file_unique_id != "" + assert message.sticker.width == sticker.width + assert message.sticker.height == sticker.height + assert message.sticker.is_animated == sticker.is_animated + assert message.sticker.is_video == sticker.is_video + assert message.sticker.file_size == sticker.file_size + assert message.sticker.type == sticker.type + + assert isinstance(message.sticker.thumb, PhotoSize) + assert isinstance(message.sticker.thumb.file_id, str) + assert isinstance(message.sticker.thumb.file_unique_id, str) + assert message.sticker.thumb.file_id != "" + assert message.sticker.thumb.file_unique_id != "" + assert message.sticker.thumb.width == sticker.thumb.width + assert message.sticker.thumb.height == sticker.thumb.height + assert message.sticker.thumb.file_size == sticker.thumb.file_size + + @pytest.mark.parametrize( + "default_bot,custom", + [ + ({"allow_sending_without_reply": True}, None), + ({"allow_sending_without_reply": False}, None), + ({"allow_sending_without_reply": False}, True), + ], + indirect=["default_bot"], + ) + async def test_send_sticker_default_allow_sending_without_reply( + self, default_bot, chat_id, sticker, custom + ): + reply_to_message = await default_bot.send_message(chat_id, "test") + await reply_to_message.delete() + if custom is not None: + message = await default_bot.send_sticker( + chat_id, + sticker, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = await default_bot.send_sticker( + chat_id, sticker, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match="message not found"): + await default_bot.send_sticker( + chat_id, sticker, reply_to_message_id=reply_to_message.message_id + ) + + @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) + async def test_send_sticker_default_protect_content(self, chat_id, sticker, default_bot): + tasks = asyncio.gather( + default_bot.send_sticker(chat_id, sticker), + default_bot.send_sticker(chat_id, sticker, protect_content=False), + ) + protected, unprotected = await tasks + assert protected.has_protected_content + assert not unprotected.has_protected_content + + async def test_premium_animation(self, bot): + # testing animation sucks a bit since we can't create a premium sticker. What we can do is + # get a sticker set which includes a premium sticker and check that specific one. + premium_sticker_set = await bot.get_sticker_set("Flame") + # the first one to appear here is a sticker with unique file id of AQADOBwAAifPOElr + # this could change in the future ofc. + premium_sticker = premium_sticker_set.stickers[20] + assert premium_sticker.premium_animation.file_unique_id == "AQADOBwAAifPOElr" + assert isinstance(premium_sticker.premium_animation.file_id, str) + assert premium_sticker.premium_animation.file_id != "" + premium_sticker_dict = { + "file_unique_id": "AQADOBwAAifPOElr", + "file_id": premium_sticker.premium_animation.file_id, + "file_size": premium_sticker.premium_animation.file_size, + } + assert premium_sticker.premium_animation.to_dict() == premium_sticker_dict + + async def test_custom_emoji(self, bot): + # testing custom emoji stickers is as much of an annoyance as the premium animation, see + # in test_premium_animation + custom_emoji_set = await bot.get_sticker_set("PTBStaticEmojiTestPack") + # the first one to appear here is a sticker with unique file id of AQADjBsAAkKD0Uty + # this could change in the future ofc. + custom_emoji_sticker = custom_emoji_set.stickers[0] + assert custom_emoji_sticker.custom_emoji_id == "6046140249875156202" + + async def test_custom_emoji_sticker(self, bot): + # we use the same ID as in test_custom_emoji + emoji_sticker_list = await bot.get_custom_emoji_stickers(["6046140249875156202"]) + assert emoji_sticker_list[0].emoji == "😎" + assert emoji_sticker_list[0].height == 100 + assert emoji_sticker_list[0].width == 100 + assert not emoji_sticker_list[0].is_animated + assert not emoji_sticker_list[0].is_video + assert emoji_sticker_list[0].set_name == "PTBStaticEmojiTestPack" + assert emoji_sticker_list[0].type == Sticker.CUSTOM_EMOJI + assert emoji_sticker_list[0].custom_emoji_id == "6046140249875156202" + assert emoji_sticker_list[0].thumb.width == 100 + assert emoji_sticker_list[0].thumb.height == 100 + assert emoji_sticker_list[0].thumb.file_size == 3614 + assert emoji_sticker_list[0].thumb.file_unique_id == "AQAD6gwAAoY06FNy" + assert emoji_sticker_list[0].file_size == 3678 + assert emoji_sticker_list[0].file_unique_id == "AgAD6gwAAoY06FM" + + async def test_error_send_empty_file(self, bot, chat_id): + with pytest.raises(TelegramError): + await bot.send_sticker(chat_id, open(os.devnull, "rb")) + + async def test_error_send_empty_file_id(self, bot, chat_id): + with pytest.raises(TelegramError): + await bot.send_sticker(chat_id, "") + @pytest.fixture(scope="function") async def sticker_set(bot): @@ -478,7 +489,7 @@ def sticker_set_thumb_file(): yield file -class TestStickerSet: +class TestStickerSetBase: title = "Test stickers" is_animated = True is_video = True @@ -487,6 +498,14 @@ class TestStickerSet: sticker_type = Sticker.REGULAR contains_masks = True + +class TestStickerSetWithoutRequest(TestStickerSetBase): + def test_slot_behaviour(self, mro_slots): + inst = StickerSet("this", "is", True, self.stickers, True, "not") + 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_de_json(self, bot, sticker): name = f"test_by_{bot.username}" json_dict = { @@ -510,82 +529,6 @@ class TestStickerSet: assert sticker_set.sticker_type == self.sticker_type assert sticker_set.api_kwargs == {"contains_masks": self.contains_masks} - async def test_create_sticker_set( - self, bot, chat_id, sticker_file, animated_sticker_file, video_sticker_file - ): - """Creates the sticker set (if needed) which is required for tests. Make sure that this - test comes before the tests that actually use the sticker sets! - """ - test_by = f"test_by_{bot.username}" - for sticker_set in [test_by, f"animated_{test_by}", f"video_{test_by}"]: - try: - await bot.get_sticker_set(sticker_set) - except BadRequest as e: - if not e.message == "Stickerset_invalid": - raise e - - if sticker_set.startswith(test_by): - s = await bot.create_new_sticker_set( - chat_id, - name=sticker_set, - title="Sticker Test", - png_sticker=sticker_file, - emojis="😄", - ) - assert s - elif sticker_set.startswith("animated"): - a = await bot.create_new_sticker_set( - chat_id, - name=sticker_set, - title="Animated Test", - tgs_sticker=animated_sticker_file, - emojis="😄", - ) - assert a - elif sticker_set.startswith("video"): - v = await bot.create_new_sticker_set( - chat_id, - name=sticker_set, - title="Video Test", - webm_sticker=video_sticker_file, - emojis="🤔", - ) - assert v - - @pytest.mark.flaky(3, 1) - async def test_bot_methods_1_png(self, bot, chat_id, sticker_file): - with data_file("telegram_sticker.png").open("rb") as f: - # chat_id was hardcoded as 95205500 but it stopped working for some reason - file = await bot.upload_sticker_file(chat_id, f) - assert file - assert await bot.add_sticker_to_set( - chat_id, f"test_by_{bot.username}", png_sticker=file.file_id, emojis="😄" - ) - # Also test with file input and mask - assert await bot.add_sticker_to_set( - chat_id, - f"test_by_{bot.username}", - png_sticker=sticker_file, - emojis="😄", - mask_position=MaskPosition(MaskPosition.EYES, -1, 1, 2), - ) - - @pytest.mark.flaky(3, 1) - async def test_bot_methods_1_tgs(self, bot, chat_id): - assert await bot.add_sticker_to_set( - chat_id, - f"animated_test_by_{bot.username}", - tgs_sticker=data_file("telegram_animated_sticker.tgs").open("rb"), - emojis="😄", - ) - - @pytest.mark.flaky(3, 1) - async def test_bot_methods_1_webm(self, bot, chat_id): - with data_file("telegram_video_sticker.webm").open("rb") as f: - assert await bot.add_sticker_to_set( - chat_id, f"video_test_by_{bot.username}", webm_sticker=f, emojis="🤔" - ) - def test_sticker_set_to_dict(self, sticker_set): sticker_set_dict = sticker_set.to_dict() @@ -598,215 +541,6 @@ class TestStickerSet: assert sticker_set_dict["thumb"] == sticker_set.thumb.to_dict() assert sticker_set_dict["sticker_type"] == sticker_set.sticker_type - @pytest.mark.flaky(3, 1) - async def test_bot_methods_2_png(self, bot, sticker_set): - file_id = sticker_set.stickers[0].file_id - assert await bot.set_sticker_position_in_set(file_id, 1) - - @pytest.mark.flaky(3, 1) - async def test_bot_methods_2_tgs(self, bot, animated_sticker_set): - file_id = animated_sticker_set.stickers[0].file_id - assert await bot.set_sticker_position_in_set(file_id, 1) - - @pytest.mark.flaky(3, 1) - async def test_bot_methods_2_webm(self, bot, video_sticker_set): - file_id = video_sticker_set.stickers[0].file_id - assert await bot.set_sticker_position_in_set(file_id, 1) - - @pytest.mark.flaky(3, 1) - async def test_bot_methods_3_png(self, bot, chat_id, sticker_set_thumb_file): - assert await bot.set_sticker_set_thumb( - f"test_by_{bot.username}", chat_id, sticker_set_thumb_file - ) - - @pytest.mark.flaky(10, 1) - async def test_bot_methods_3_tgs( - self, bot, chat_id, animated_sticker_file, animated_sticker_set - ): - await asyncio.sleep(1) - animated_test = f"animated_test_by_{bot.username}" - assert await bot.set_sticker_set_thumb(animated_test, chat_id, animated_sticker_file) - file_id = animated_sticker_set.stickers[-1].file_id - # also test with file input and mask - assert await bot.set_sticker_set_thumb(animated_test, chat_id, file_id) - - # TODO: Try the below by creating a custom .webm and not by downloading another pack's thumb - @pytest.mark.skip( - "Skipped for now since Telegram throws a 'File is too big' error " - "regardless of the .webm file size." - ) - def test_bot_methods_3_webm(self, bot, chat_id, video_sticker_file, video_sticker_set): - pass - - @pytest.mark.flaky(10, 1) - async def test_bot_methods_4_png(self, bot, sticker_set): - await asyncio.sleep(1) - file_id = sticker_set.stickers[-1].file_id - assert await bot.delete_sticker_from_set(file_id) - - @pytest.mark.flaky(10, 1) - async def test_bot_methods_4_tgs(self, bot, animated_sticker_set): - await asyncio.sleep(1) - file_id = animated_sticker_set.stickers[-1].file_id - assert await bot.delete_sticker_from_set(file_id) - - @pytest.mark.flaky(10, 1) - async def test_bot_methods_4_webm(self, bot, video_sticker_set): - await asyncio.sleep(1) - file_id = video_sticker_set.stickers[-1].file_id - assert await bot.delete_sticker_from_set(file_id) - - @pytest.mark.parametrize("local_mode", [True, False]) - async def test_upload_sticker_file_local_files(self, monkeypatch, bot, chat_id, local_mode): - try: - bot._local_mode = local_mode - # For just test that the correct paths are passed as we have no local bot API set up - test_flag = False - file = data_file("telegram.jpg") - expected = file.as_uri() - - async def make_assertion(_, data, *args, **kwargs): - nonlocal test_flag - if local_mode: - test_flag = data.get("png_sticker") == expected - else: - test_flag = isinstance(data.get("png_sticker"), InputFile) - - monkeypatch.setattr(bot, "_post", make_assertion) - await bot.upload_sticker_file(chat_id, file) - assert test_flag - monkeypatch.delattr(bot, "_post") - finally: - bot._local_mode = False - - @pytest.mark.parametrize("local_mode", [True, False]) - async def test_create_new_sticker_set_local_files(self, monkeypatch, bot, chat_id, local_mode): - try: - bot._local_mode = local_mode - # For just test that the correct paths are passed as we have no local bot API set up - test_flag = False - file = data_file("telegram.jpg") - expected = file.as_uri() - - async def make_assertion(_, data, *args, **kwargs): - nonlocal test_flag - if local_mode: - test_flag = ( - data.get("png_sticker") == expected - and data.get("tgs_sticker") == expected - and data.get("webm_sticker") == expected - ) - else: - test_flag = ( - isinstance(data.get("png_sticker"), InputFile) - and isinstance(data.get("tgs_sticker"), InputFile) - and isinstance(data.get("webm_sticker"), InputFile) - ) - - monkeypatch.setattr(bot, "_post", make_assertion) - await bot.create_new_sticker_set( - chat_id, - "name", - "title", - "emoji", - png_sticker=file, - tgs_sticker=file, - webm_sticker=file, - ) - assert test_flag - monkeypatch.delattr(bot, "_post") - finally: - bot._local_mode = False - - async def test_create_new_sticker_all_params(self, monkeypatch, bot, chat_id, mask_position): - async def make_assertion(_, data, *args, **kwargs): - assert data["user_id"] == chat_id - assert data["name"] == "name" - assert data["title"] == "title" - assert data["emojis"] == "emoji" - assert data["mask_position"] == mask_position - assert data["png_sticker"] == "wow.png" - assert data["tgs_sticker"] == "wow.tgs" - assert data["webm_sticker"] == "wow.webm" - assert data["sticker_type"] == Sticker.MASK - - monkeypatch.setattr(bot, "_post", make_assertion) - await bot.create_new_sticker_set( - chat_id, - "name", - "title", - "emoji", - mask_position=mask_position, - png_sticker="wow.png", - tgs_sticker="wow.tgs", - webm_sticker="wow.webm", - sticker_type=Sticker.MASK, - ) - monkeypatch.delattr(bot, "_post") - - @pytest.mark.parametrize("local_mode", [True, False]) - async def test_add_sticker_to_set_local_files(self, monkeypatch, bot, chat_id, local_mode): - try: - bot._local_mode = local_mode - # For just test that the correct paths are passed as we have no local bot API set up - test_flag = False - file = data_file("telegram.jpg") - expected = file.as_uri() - - async def make_assertion(_, data, *args, **kwargs): - nonlocal test_flag - if local_mode: - test_flag = ( - data.get("png_sticker") == expected and data.get("tgs_sticker") == expected - ) - else: - test_flag = isinstance(data.get("png_sticker"), InputFile) and isinstance( - data.get("tgs_sticker"), InputFile - ) - - monkeypatch.setattr(bot, "_post", make_assertion) - await bot.add_sticker_to_set( - chat_id, "name", "emoji", png_sticker=file, tgs_sticker=file - ) - assert test_flag - monkeypatch.delattr(bot, "_post") - finally: - bot._local_mode = False - - @pytest.mark.parametrize("local_mode", [True, False]) - async def test_set_sticker_set_thumb_local_files(self, monkeypatch, bot, chat_id, local_mode): - try: - bot._local_mode = local_mode - # For just test that the correct paths are passed as we have no local bot API set up - test_flag = False - file = data_file("telegram.jpg") - expected = file.as_uri() - - async def make_assertion(_, data, *args, **kwargs): - nonlocal test_flag - if local_mode: - test_flag = data.get("thumb") == expected - else: - test_flag = isinstance(data.get("thumb"), InputFile) - - monkeypatch.setattr(bot, "_post", make_assertion) - await bot.set_sticker_set_thumb("name", chat_id, thumb=file) - assert test_flag - monkeypatch.delattr(bot, "_post") - finally: - bot._local_mode = False - - async def test_get_file_instance_method(self, monkeypatch, sticker): - async def make_assertion(*_, **kwargs): - return kwargs["file_id"] == sticker.file_id - - assert check_shortcut_signature(Sticker.get_file, Bot.get_file, ["file_id"], []) - assert await check_shortcut_call(sticker.get_file, sticker.get_bot(), "get_file") - assert await check_defaults_handling(sticker.get_file, sticker.get_bot()) - - monkeypatch.setattr(sticker.get_bot(), "get_file", make_assertion) - assert await sticker.get_file() - def test_equality(self): a = StickerSet( self.name, @@ -848,23 +582,316 @@ class TestStickerSet: assert a != e assert hash(a) != hash(e) + @pytest.mark.parametrize("local_mode", [True, False]) + async def test_upload_sticker_file_local_files(self, monkeypatch, bot, chat_id, local_mode): + try: + bot._local_mode = local_mode + # For just test that the correct paths are passed as we have no local bot API set up + test_flag = False + file = data_file("telegram.jpg") + expected = file.as_uri() -@pytest.fixture(scope="class") + async def make_assertion(_, data, *args, **kwargs): + nonlocal test_flag + if local_mode: + test_flag = data.get("png_sticker") == expected + else: + test_flag = isinstance(data.get("png_sticker"), InputFile) + + monkeypatch.setattr(bot, "_post", make_assertion) + await bot.upload_sticker_file(chat_id, file) + assert test_flag + finally: + bot._local_mode = False + + @pytest.mark.parametrize("local_mode", [True, False]) + async def test_create_new_sticker_set_local_files(self, monkeypatch, bot, chat_id, local_mode): + try: + bot._local_mode = local_mode + # For just test that the correct paths are passed as we have no local bot API set up + test_flag = False + file = data_file("telegram.jpg") + expected = file.as_uri() + + async def make_assertion(_, data, *args, **kwargs): + nonlocal test_flag + if local_mode: + test_flag = ( + data.get("png_sticker") == expected + and data.get("tgs_sticker") == expected + and data.get("webm_sticker") == expected + ) + else: + test_flag = ( + isinstance(data.get("png_sticker"), InputFile) + and isinstance(data.get("tgs_sticker"), InputFile) + and isinstance(data.get("webm_sticker"), InputFile) + ) + + monkeypatch.setattr(bot, "_post", make_assertion) + await bot.create_new_sticker_set( + chat_id, + "name", + "title", + "emoji", + png_sticker=file, + tgs_sticker=file, + webm_sticker=file, + ) + assert test_flag + finally: + bot._local_mode = False + + async def test_create_new_sticker_all_params(self, monkeypatch, bot, chat_id, mask_position): + async def make_assertion(_, data, *args, **kwargs): + assert data["user_id"] == chat_id + assert data["name"] == "name" + assert data["title"] == "title" + assert data["emojis"] == "emoji" + assert data["mask_position"] == mask_position + assert data["png_sticker"] == "wow.png" + assert data["tgs_sticker"] == "wow.tgs" + assert data["webm_sticker"] == "wow.webm" + assert data["sticker_type"] == Sticker.MASK + + monkeypatch.setattr(bot, "_post", make_assertion) + await bot.create_new_sticker_set( + chat_id, + "name", + "title", + "emoji", + mask_position=mask_position, + png_sticker="wow.png", + tgs_sticker="wow.tgs", + webm_sticker="wow.webm", + sticker_type=Sticker.MASK, + ) + + @pytest.mark.parametrize("local_mode", [True, False]) + async def test_add_sticker_to_set_local_files(self, monkeypatch, bot, chat_id, local_mode): + try: + bot._local_mode = local_mode + # For just test that the correct paths are passed as we have no local bot API set up + test_flag = False + file = data_file("telegram.jpg") + expected = file.as_uri() + + async def make_assertion(_, data, *args, **kwargs): + nonlocal test_flag + if local_mode: + test_flag = ( + data.get("png_sticker") == expected and data.get("tgs_sticker") == expected + ) + else: + test_flag = isinstance(data.get("png_sticker"), InputFile) and isinstance( + data.get("tgs_sticker"), InputFile + ) + + monkeypatch.setattr(bot, "_post", make_assertion) + await bot.add_sticker_to_set( + chat_id, "name", "emoji", png_sticker=file, tgs_sticker=file + ) + assert test_flag + finally: + bot._local_mode = False + + @pytest.mark.parametrize("local_mode", [True, False]) + async def test_set_sticker_set_thumb_local_files(self, monkeypatch, bot, chat_id, local_mode): + try: + bot._local_mode = local_mode + # For just test that the correct paths are passed as we have no local bot API set up + test_flag = False + file = data_file("telegram.jpg") + expected = file.as_uri() + + async def make_assertion(_, data, *args, **kwargs): + nonlocal test_flag + if local_mode: + test_flag = data.get("thumb") == expected + else: + test_flag = isinstance(data.get("thumb"), InputFile) + + monkeypatch.setattr(bot, "_post", make_assertion) + await bot.set_sticker_set_thumb("name", chat_id, thumb=file) + assert test_flag + finally: + bot._local_mode = False + + async def test_get_file_instance_method(self, monkeypatch, sticker): + async def make_assertion(*_, **kwargs): + return kwargs["file_id"] == sticker.file_id + + assert check_shortcut_signature(Sticker.get_file, Bot.get_file, ["file_id"], []) + assert await check_shortcut_call(sticker.get_file, sticker.get_bot(), "get_file") + assert await check_defaults_handling(sticker.get_file, sticker.get_bot()) + + monkeypatch.setattr(sticker.get_bot(), "get_file", make_assertion) + assert await sticker.get_file() + + +@pytest.mark.xdist_group("stickerset") +class TestStickerSetWithRequest: + async def test_create_sticker_set( + self, bot, chat_id, sticker_file, animated_sticker_file, video_sticker_file + ): + """Creates the sticker set (if needed) which is required for tests. Make sure that this + test comes before the tests that actually use the sticker sets! + """ + test_by = f"test_by_{bot.username}" + for sticker_set in [test_by, f"animated_{test_by}", f"video_{test_by}"]: + try: + ss = await bot.get_sticker_set(sticker_set) + assert isinstance(ss, StickerSet) + except BadRequest as e: + if not e.message == "Stickerset_invalid": + raise e + + if sticker_set.startswith(test_by): + s = await bot.create_new_sticker_set( + chat_id, + name=sticker_set, + title="Sticker Test", + png_sticker=sticker_file, + emojis="😄", + ) + assert s + elif sticker_set.startswith("animated"): + a = await bot.create_new_sticker_set( + chat_id, + name=sticker_set, + title="Animated Test", + tgs_sticker=animated_sticker_file, + emojis="😄", + ) + assert a + elif sticker_set.startswith("video"): + v = await bot.create_new_sticker_set( + chat_id, + name=sticker_set, + title="Video Test", + webm_sticker=video_sticker_file, + emojis="🤔", + ) + assert v + + async def test_bot_methods_1_png(self, bot, chat_id, sticker_file): + with data_file("telegram_sticker.png").open("rb") as f: + # chat_id was hardcoded as 95205500 but it stopped working for some reason + file = await bot.upload_sticker_file(chat_id, f) + assert file + + await asyncio.sleep(1) + tasks = asyncio.gather( + bot.add_sticker_to_set( + chat_id, f"test_by_{bot.username}", png_sticker=file.file_id, emojis="😄" + ), + bot.add_sticker_to_set( # Also test with file input and mask + chat_id, + f"test_by_{bot.username}", + png_sticker=sticker_file, + emojis="😄", + mask_position=MaskPosition(MaskPosition.EYES, -1, 1, 2), + ), + ) + assert all(await tasks) + + async def test_bot_methods_1_tgs(self, bot, chat_id): + await asyncio.sleep(1) + assert await bot.add_sticker_to_set( + chat_id, + f"animated_test_by_{bot.username}", + tgs_sticker=data_file("telegram_animated_sticker.tgs").open("rb"), + emojis="😄", + ) + + async def test_bot_methods_1_webm(self, bot, chat_id): + await asyncio.sleep(1) + with data_file("telegram_video_sticker.webm").open("rb") as f: + assert await bot.add_sticker_to_set( + chat_id, f"video_test_by_{bot.username}", webm_sticker=f, emojis="🤔" + ) + + async def test_bot_methods_2_png(self, bot, sticker_set): + await asyncio.sleep(1) + file_id = sticker_set.stickers[0].file_id + assert await bot.set_sticker_position_in_set(file_id, 1) + + async def test_bot_methods_2_tgs(self, bot, animated_sticker_set): + await asyncio.sleep(1) + file_id = animated_sticker_set.stickers[0].file_id + assert await bot.set_sticker_position_in_set(file_id, 1) + + async def test_bot_methods_2_webm(self, bot, video_sticker_set): + await asyncio.sleep(1) + file_id = video_sticker_set.stickers[0].file_id + assert await bot.set_sticker_position_in_set(file_id, 1) + + async def test_bot_methods_3_png(self, bot, chat_id, sticker_set_thumb_file): + await asyncio.sleep(1) + assert await bot.set_sticker_set_thumb( + f"test_by_{bot.username}", chat_id, sticker_set_thumb_file + ) + + async def test_bot_methods_3_tgs( + self, bot, chat_id, animated_sticker_file, animated_sticker_set + ): + await asyncio.sleep(1) + animated_test = f"animated_test_by_{bot.username}" + file_id = animated_sticker_set.stickers[-1].file_id + tasks = asyncio.gather( + bot.set_sticker_set_thumb(animated_test, chat_id, animated_sticker_file), + bot.set_sticker_set_thumb(animated_test, chat_id, file_id), + ) + assert all(await tasks) + + # TODO: Try the below by creating a custom .webm and not by downloading another pack's thumb + @pytest.mark.skip( + "Skipped for now since Telegram throws a 'File is too big' error " + "regardless of the .webm file size." + ) + def test_bot_methods_3_webm(self, bot, chat_id, video_sticker_file, video_sticker_set): + pass + + async def test_bot_methods_4_png(self, bot, sticker_set): + await asyncio.sleep(1) + file_id = sticker_set.stickers[-1].file_id + assert await bot.delete_sticker_from_set(file_id) + + async def test_bot_methods_4_tgs(self, bot, animated_sticker_set): + await asyncio.sleep(1) + file_id = animated_sticker_set.stickers[-1].file_id + assert await bot.delete_sticker_from_set(file_id) + + async def test_bot_methods_4_webm(self, bot, video_sticker_set): + await asyncio.sleep(1) + file_id = video_sticker_set.stickers[-1].file_id + assert await bot.delete_sticker_from_set(file_id) + + +@pytest.fixture(scope="module") def mask_position(): return MaskPosition( - TestMaskPosition.point, - TestMaskPosition.x_shift, - TestMaskPosition.y_shift, - TestMaskPosition.scale, + TestMaskPositionBase.point, + TestMaskPositionBase.x_shift, + TestMaskPositionBase.y_shift, + TestMaskPositionBase.scale, ) -class TestMaskPosition: +class TestMaskPositionBase: point = MaskPosition.EYES x_shift = -1 y_shift = 1 scale = 2 + +class TestMaskPositionWithoutRequest(TestMaskPositionBase): + def test_slot_behaviour(self, mask_position, mro_slots): + inst = mask_position + 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_mask_position_de_json(self, bot): json_dict = { "point": self.point, @@ -908,23 +935,3 @@ class TestMaskPosition: assert a != e assert hash(a) != hash(e) - - -class TestGetCustomEmojiSticker: - async def test_custom_emoji_sticker(self, bot): - # we use the same ID as in test_custom_emoji - emoji_sticker_list = await bot.get_custom_emoji_stickers(["6046140249875156202"]) - assert emoji_sticker_list[0].emoji == "😎" - assert emoji_sticker_list[0].height == 100 - assert emoji_sticker_list[0].width == 100 - assert not emoji_sticker_list[0].is_animated - assert not emoji_sticker_list[0].is_video - assert emoji_sticker_list[0].set_name == "PTBStaticEmojiTestPack" - assert emoji_sticker_list[0].type == Sticker.CUSTOM_EMOJI - assert emoji_sticker_list[0].custom_emoji_id == "6046140249875156202" - assert emoji_sticker_list[0].thumb.width == 100 - assert emoji_sticker_list[0].thumb.height == 100 - assert emoji_sticker_list[0].thumb.file_size == 3614 - assert emoji_sticker_list[0].thumb.file_unique_id == "AQAD6gwAAoY06FNy" - assert emoji_sticker_list[0].file_size == 3678 - assert emoji_sticker_list[0].file_unique_id == "AgAD6gwAAoY06FM" diff --git a/tests/test_successfulpayment.py b/tests/test_successfulpayment.py index b3e927aed..80dce68fa 100644 --- a/tests/test_successfulpayment.py +++ b/tests/test_successfulpayment.py @@ -21,20 +21,20 @@ import pytest from telegram import OrderInfo, SuccessfulPayment -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def successful_payment(): return SuccessfulPayment( - TestSuccessfulPayment.currency, - TestSuccessfulPayment.total_amount, - TestSuccessfulPayment.invoice_payload, - TestSuccessfulPayment.telegram_payment_charge_id, - TestSuccessfulPayment.provider_payment_charge_id, - shipping_option_id=TestSuccessfulPayment.shipping_option_id, - order_info=TestSuccessfulPayment.order_info, + TestSuccessfulPaymentBase.currency, + TestSuccessfulPaymentBase.total_amount, + TestSuccessfulPaymentBase.invoice_payload, + TestSuccessfulPaymentBase.telegram_payment_charge_id, + TestSuccessfulPaymentBase.provider_payment_charge_id, + shipping_option_id=TestSuccessfulPaymentBase.shipping_option_id, + order_info=TestSuccessfulPaymentBase.order_info, ) -class TestSuccessfulPayment: +class TestSuccessfulPaymentBase: invoice_payload = "invoice_payload" shipping_option_id = "shipping_option_id" currency = "EUR" @@ -43,6 +43,8 @@ class TestSuccessfulPayment: telegram_payment_charge_id = "telegram_payment_charge_id" provider_payment_charge_id = "provider_payment_charge_id" + +class TestSuccessfulPaymentWithoutRequest(TestSuccessfulPaymentBase): def test_slot_behaviour(self, successful_payment, mro_slots): inst = successful_payment for attr in inst.__slots__: diff --git a/tests/test_update.py b/tests/test_update.py index b8429be41..5fcc7db1b 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -97,22 +97,25 @@ all_types = ( ids = all_types + ("callback_query_without_message",) -@pytest.fixture(params=params, ids=ids) +@pytest.fixture(scope="module", params=params, ids=ids) def update(request): - return Update(update_id=TestUpdate.update_id, **request.param) + return Update(update_id=TestUpdateBase.update_id, **request.param) -class TestUpdate: +class TestUpdateBase: update_id = 868573637 - def test_slot_behaviour(self, update, mro_slots): + +class TestUpdateWithoutRequest(TestUpdateBase): + def test_slot_behaviour(self, mro_slots): + update = Update(self.update_id) for attr in update.__slots__: assert getattr(update, attr, "err") != "err", f"got extra slot '{attr}'" assert len(mro_slots(update)) == len(set(mro_slots(update))), "duplicate slot" @pytest.mark.parametrize("paramdict", argvalues=params, ids=ids) def test_de_json(self, bot, paramdict): - json_dict = {"update_id": TestUpdate.update_id} + json_dict = {"update_id": self.update_id} # Convert the single update 'item' to a dict of that item and apply it to the json_dict json_dict.update({k: v.to_dict() for k, v in paramdict.items()}) update = Update.de_json(json_dict, bot) @@ -142,6 +145,26 @@ class TestUpdate: if getattr(update, _type) is not None: assert update_dict[_type] == getattr(update, _type).to_dict() + def test_equality(self): + a = Update(self.update_id, message=message) + b = Update(self.update_id, message=message) + c = Update(self.update_id) + d = Update(0, message=message) + e = User(self.update_id, "", False) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a == c + assert hash(a) == hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) + def test_effective_chat(self, update): # Test that it's sometimes None per docstring chat = update.effective_chat @@ -188,23 +211,3 @@ class TestUpdate: assert eff_message.message_id == message.message_id else: assert eff_message is None - - def test_equality(self): - a = Update(self.update_id, message=message) - b = Update(self.update_id, message=message) - c = Update(self.update_id) - d = Update(0, message=message) - e = User(self.update_id, "", False) - - assert a == b - assert hash(a) == hash(b) - assert a is not b - - assert a == c - assert hash(a) == hash(c) - - assert a != d - assert hash(a) != hash(d) - - assert a != e - assert hash(a) != hash(e) diff --git a/tests/test_updater.py b/tests/test_updater.py index 9b70ac161..35fbd8665 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -18,7 +18,6 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. import asyncio import logging -import os from collections import defaultdict from http import HTTPStatus from pathlib import Path @@ -31,8 +30,8 @@ from telegram._utils.defaultvalue import DEFAULT_NONE from telegram.error import InvalidToken, RetryAfter, TelegramError, TimedOut from telegram.ext import ExtBot, InvalidCallbackData, Updater from telegram.request import HTTPXRequest -from tests.auxil.object_conversions import env_var_2_bool from tests.conftest import ( + TEST_WITH_OPT_DEPS, DictBot, data_file, make_bot, @@ -41,8 +40,6 @@ from tests.conftest import ( send_webhook_message, ) -TEST_WITH_OPT_DEPS = env_var_2_bool(os.getenv("TEST_WITH_OPT_DEPS", True)) - if TEST_WITH_OPT_DEPS: from telegram.ext._utils.webhookhandler import WebhookServer @@ -532,7 +529,7 @@ class TestUpdater: # that depends on this distinction works if ext_bot and not isinstance(updater.bot, ExtBot): updater.bot = ExtBot(updater.bot.token) - if not ext_bot and not type(updater.bot) is Bot: + if not ext_bot and type(updater.bot) is not Bot: updater.bot = DictBot(updater.bot.token) async def delete_webhook(*args, **kwargs): diff --git a/tests/test_user.py b/tests/test_user.py index 64b3aa8de..6ea946b9d 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -27,44 +27,44 @@ from tests.auxil.bot_method_checks import ( ) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def json_dict(): return { - "id": TestUser.id_, - "is_bot": TestUser.is_bot, - "first_name": TestUser.first_name, - "last_name": TestUser.last_name, - "username": TestUser.username, - "language_code": TestUser.language_code, - "can_join_groups": TestUser.can_join_groups, - "can_read_all_group_messages": TestUser.can_read_all_group_messages, - "supports_inline_queries": TestUser.supports_inline_queries, - "is_premium": TestUser.is_premium, - "added_to_attachment_menu": TestUser.added_to_attachment_menu, + "id": TestUserBase.id_, + "is_bot": TestUserBase.is_bot, + "first_name": TestUserBase.first_name, + "last_name": TestUserBase.last_name, + "username": TestUserBase.username, + "language_code": TestUserBase.language_code, + "can_join_groups": TestUserBase.can_join_groups, + "can_read_all_group_messages": TestUserBase.can_read_all_group_messages, + "supports_inline_queries": TestUserBase.supports_inline_queries, + "is_premium": TestUserBase.is_premium, + "added_to_attachment_menu": TestUserBase.added_to_attachment_menu, } @pytest.fixture(scope="function") def user(bot): user = User( - id=TestUser.id_, - first_name=TestUser.first_name, - is_bot=TestUser.is_bot, - last_name=TestUser.last_name, - username=TestUser.username, - language_code=TestUser.language_code, - can_join_groups=TestUser.can_join_groups, - can_read_all_group_messages=TestUser.can_read_all_group_messages, - supports_inline_queries=TestUser.supports_inline_queries, - is_premium=TestUser.is_premium, - added_to_attachment_menu=TestUser.added_to_attachment_menu, + id=TestUserBase.id_, + first_name=TestUserBase.first_name, + is_bot=TestUserBase.is_bot, + last_name=TestUserBase.last_name, + username=TestUserBase.username, + language_code=TestUserBase.language_code, + can_join_groups=TestUserBase.can_join_groups, + can_read_all_group_messages=TestUserBase.can_read_all_group_messages, + supports_inline_queries=TestUserBase.supports_inline_queries, + is_premium=TestUserBase.is_premium, + added_to_attachment_menu=TestUserBase.added_to_attachment_menu, ) user.set_bot(bot) user._unfreeze() return user -class TestUser: +class TestUserBase: id_ = 1 is_bot = True first_name = "first\u2022name" @@ -77,6 +77,8 @@ class TestUser: is_premium = True added_to_attachment_menu = False + +class TestUserWithoutRequest(TestUserBase): def test_slot_behaviour(self, user, mro_slots): for attr in user.__slots__: assert getattr(user, attr, "err") != "err", f"got extra slot '{attr}'" @@ -98,42 +100,41 @@ class TestUser: assert user.is_premium == self.is_premium assert user.added_to_attachment_menu == self.added_to_attachment_menu - def test_de_json_without_username(self, json_dict, bot): - del json_dict["username"] + def test_to_dict(self, user): + user_dict = user.to_dict() - user = User.de_json(json_dict, bot) - assert user.api_kwargs == {} + assert isinstance(user_dict, dict) + assert user_dict["id"] == user.id + assert user_dict["is_bot"] == user.is_bot + assert user_dict["first_name"] == user.first_name + assert user_dict["last_name"] == user.last_name + assert user_dict["username"] == user.username + assert user_dict["language_code"] == user.language_code + assert user_dict["can_join_groups"] == user.can_join_groups + assert user_dict["can_read_all_group_messages"] == user.can_read_all_group_messages + assert user_dict["supports_inline_queries"] == user.supports_inline_queries + assert user_dict["is_premium"] == user.is_premium + assert user_dict["added_to_attachment_menu"] == user.added_to_attachment_menu - assert user.id == self.id_ - assert user.is_bot == self.is_bot - assert user.first_name == self.first_name - assert user.last_name == self.last_name - assert user.username is None - assert user.language_code == self.language_code - assert user.can_join_groups == self.can_join_groups - assert user.can_read_all_group_messages == self.can_read_all_group_messages - assert user.supports_inline_queries == self.supports_inline_queries - assert user.is_premium == self.is_premium - assert user.added_to_attachment_menu == self.added_to_attachment_menu + def test_equality(self): + a = User(self.id_, self.first_name, self.is_bot, self.last_name) + b = User(self.id_, self.first_name, self.is_bot, self.last_name) + c = User(self.id_, self.first_name, self.is_bot) + d = User(0, self.first_name, self.is_bot, self.last_name) + e = Update(self.id_) - def test_de_json_without_username_and_last_name(self, json_dict, bot): - del json_dict["username"] - del json_dict["last_name"] + assert a == b + assert hash(a) == hash(b) + assert a is not b - user = User.de_json(json_dict, bot) - assert user.api_kwargs == {} + assert a == c + assert hash(a) == hash(c) - assert user.id == self.id_ - assert user.is_bot == self.is_bot - assert user.first_name == self.first_name - assert user.last_name is None - assert user.username is None - assert user.language_code == self.language_code - assert user.can_join_groups == self.can_join_groups - assert user.can_read_all_group_messages == self.can_read_all_group_messages - assert user.supports_inline_queries == self.supports_inline_queries - assert user.is_premium == self.is_premium - assert user.added_to_attachment_menu == self.added_to_attachment_menu + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) def test_name(self, user): assert user.name == "@username" @@ -560,23 +561,3 @@ class TestUser: "the\\{name\\>\u2022", user.id ) assert user.mention_markdown_v2(user.username) == expected.format(user.username, user.id) - - def test_equality(self): - a = User(self.id_, self.first_name, self.is_bot, self.last_name) - b = User(self.id_, self.first_name, self.is_bot, self.last_name) - c = User(self.id_, self.first_name, self.is_bot) - d = User(0, self.first_name, self.is_bot, self.last_name) - e = Update(self.id_) - - assert a == b - assert hash(a) == hash(b) - assert a is not b - - assert a == c - assert hash(a) == hash(c) - - assert a != d - assert hash(a) != hash(d) - - assert a != e - assert hash(a) != hash(e) diff --git a/tests/test_userprofilephotos.py b/tests/test_userprofilephotos.py index 7fb7c593c..da179e53e 100644 --- a/tests/test_userprofilephotos.py +++ b/tests/test_userprofilephotos.py @@ -19,7 +19,7 @@ from telegram import PhotoSize, UserProfilePhotos -class TestUserProfilePhotos: +class TestUserProfilePhotosBase: total_count = 2 photos = [ [ @@ -32,6 +32,8 @@ class TestUserProfilePhotos: ], ] + +class TestUserProfilePhotosWithoutRequest(TestUserProfilePhotosBase): def test_slot_behaviour(self, mro_slots): inst = UserProfilePhotos(self.total_count, self.photos) for attr in inst.__slots__: diff --git a/tests/test_venue.py b/tests/test_venue.py index d0360ef9f..55944c7b0 100644 --- a/tests/test_venue.py +++ b/tests/test_venue.py @@ -16,6 +16,8 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio + import pytest from telegram import Location, Venue @@ -23,20 +25,20 @@ from telegram.error import BadRequest from telegram.request import RequestData -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def venue(): return Venue( - TestVenue.location, - TestVenue.title, - TestVenue.address, - foursquare_id=TestVenue.foursquare_id, - foursquare_type=TestVenue.foursquare_type, - google_place_id=TestVenue.google_place_id, - google_place_type=TestVenue.google_place_type, + TestVenueBase.location, + TestVenueBase.title, + TestVenueBase.address, + foursquare_id=TestVenueBase.foursquare_id, + foursquare_type=TestVenueBase.foursquare_type, + google_place_id=TestVenueBase.google_place_id, + google_place_type=TestVenueBase.google_place_type, ) -class TestVenue: +class TestVenueBase: location = Location(longitude=-46.788279, latitude=-23.691288) title = "title" address = "address" @@ -45,6 +47,8 @@ class TestVenue: google_place_id = "google place id" google_place_type = "google place type" + +class TestVenueWithoutRequest(TestVenueBase): def test_slot_behaviour(self, venue, mro_slots): for attr in venue.__slots__: assert getattr(venue, attr, "err") != "err", f"got extra slot '{attr}'" @@ -52,13 +56,13 @@ class TestVenue: def test_de_json(self, bot): json_dict = { - "location": TestVenue.location.to_dict(), - "title": TestVenue.title, - "address": TestVenue.address, - "foursquare_id": TestVenue.foursquare_id, - "foursquare_type": TestVenue.foursquare_type, - "google_place_id": TestVenue.google_place_id, - "google_place_type": TestVenue.google_place_type, + "location": self.location.to_dict(), + "title": self.title, + "address": self.address, + "foursquare_id": self.foursquare_id, + "foursquare_type": self.foursquare_type, + "google_place_id": self.google_place_id, + "google_place_type": self.google_place_type, } venue = Venue.de_json(json_dict, bot) assert venue.api_kwargs == {} @@ -71,6 +75,53 @@ class TestVenue: assert venue.google_place_id == self.google_place_id assert venue.google_place_type == self.google_place_type + def test_to_dict(self, venue): + venue_dict = venue.to_dict() + + assert isinstance(venue_dict, dict) + assert venue_dict["location"] == venue.location.to_dict() + assert venue_dict["title"] == venue.title + assert venue_dict["address"] == venue.address + assert venue_dict["foursquare_id"] == venue.foursquare_id + assert venue_dict["foursquare_type"] == venue.foursquare_type + assert venue_dict["google_place_id"] == venue.google_place_id + assert venue_dict["google_place_type"] == venue.google_place_type + + def test_equality(self): + a = Venue(Location(0, 0), self.title, self.address) + b = Venue(Location(0, 0), self.title, self.address) + c = Venue(Location(0, 0), self.title, "") + d = Venue(Location(0, 1), self.title, self.address) + d2 = Venue(Location(0, 0), "", self.address) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a == c + assert hash(a) == hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != d2 + assert hash(a) != hash(d2) + + async def test_send_venue_without_required(self, bot, chat_id): + with pytest.raises(ValueError, match="Either venue or latitude, longitude, address and"): + await bot.send_venue(chat_id=chat_id) + + async def test_send_venue_mutually_exclusive(self, bot, chat_id, venue): + with pytest.raises(ValueError, match="Not both"): + await bot.send_venue( + chat_id=chat_id, + latitude=1, + longitude=1, + address="address", + title="title", + venue=venue, + ) + async def test_send_with_venue(self, monkeypatch, bot, chat_id, venue): async def make_assertion(url, request_data: RequestData, *args, **kwargs): data = request_data.json_parameters @@ -89,7 +140,8 @@ class TestVenue: message = await bot.send_venue(chat_id, venue=venue) assert message - @pytest.mark.flaky(3, 1) + +class TestVenueWithRequest(TestVenueBase): @pytest.mark.parametrize( "default_bot,custom", [ @@ -123,57 +175,12 @@ class TestVenue: chat_id, venue=venue, reply_to_message_id=reply_to_message.message_id ) - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) async def test_send_venue_default_protect_content(self, default_bot, chat_id, venue): - protected = await default_bot.send_venue(chat_id, venue=venue) + tasks = asyncio.gather( + default_bot.send_venue(chat_id, venue=venue), + default_bot.send_venue(chat_id, venue=venue, protect_content=False), + ) + protected, unprotected = await tasks assert protected.has_protected_content - unprotected = await default_bot.send_venue(chat_id, venue=venue, protect_content=False) assert not unprotected.has_protected_content - - async def test_send_venue_without_required(self, bot, chat_id): - with pytest.raises(ValueError, match="Either venue or latitude, longitude, address and"): - await bot.send_venue(chat_id=chat_id) - - async def test_send_venue_mutually_exclusive(self, bot, chat_id, venue): - with pytest.raises(ValueError, match="Not both"): - await bot.send_venue( - chat_id=chat_id, - latitude=1, - longitude=1, - address="address", - title="title", - venue=venue, - ) - - def test_to_dict(self, venue): - venue_dict = venue.to_dict() - - assert isinstance(venue_dict, dict) - assert venue_dict["location"] == venue.location.to_dict() - assert venue_dict["title"] == venue.title - assert venue_dict["address"] == venue.address - assert venue_dict["foursquare_id"] == venue.foursquare_id - assert venue_dict["foursquare_type"] == venue.foursquare_type - assert venue_dict["google_place_id"] == venue.google_place_id - assert venue_dict["google_place_type"] == venue.google_place_type - - def test_equality(self): - a = Venue(Location(0, 0), self.title, self.address) - b = Venue(Location(0, 0), self.title, self.address) - c = Venue(Location(0, 0), self.title, "") - d = Venue(Location(0, 1), self.title, self.address) - d2 = Venue(Location(0, 0), "", self.address) - - assert a == b - assert hash(a) == hash(b) - assert a is not b - - assert a == c - assert hash(a) == hash(c) - - assert a != d - assert hash(a) != hash(d) - - assert a != d2 - assert hash(a) != hash(d2) diff --git a/tests/test_video.py b/tests/test_video.py index 3a3d524aa..e88cb831c 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio import os from pathlib import Path @@ -35,18 +36,17 @@ from tests.conftest import data_file @pytest.fixture(scope="function") def video_file(): - f = data_file("telegram.mp4").open("rb") - yield f - f.close() + with data_file("telegram.mp4").open("rb") as f: + yield f -@pytest.fixture(scope="class") +@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 TestVideo: +class TestVideoBase: width = 360 height = 640 duration = 5 @@ -54,17 +54,16 @@ class TestVideo: mime_type = "video/mp4" supports_streaming = True file_name = "telegram.mp4" - thumb_width = 180 thumb_height = 320 thumb_file_size = 1767 - caption = "VideoTest - *Caption*" video_file_url = "https://python-telegram-bot.org/static/testfiles/telegram.mp4" - video_file_id = "5a3128a4d2a04750b5b58397f3b5e812" video_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e" + +class TestVideoWithoutRequest(TestVideoBase): def test_slot_behaviour(self, video, mro_slots): for attr in video.__slots__: assert getattr(video, attr, "err") != "err", f"got extra slot '{attr}'" @@ -91,221 +90,6 @@ class TestVideo: assert video.file_size == self.file_size assert video.mime_type == self.mime_type - @pytest.mark.flaky(3, 1) - async def test_send_all_args(self, bot, chat_id, video_file, video, thumb_file): - message = await bot.send_video( - chat_id, - video_file, - duration=self.duration, - caption=self.caption, - supports_streaming=self.supports_streaming, - disable_notification=False, - protect_content=True, - width=video.width, - height=video.height, - parse_mode="Markdown", - thumb=thumb_file, - has_spoiler=True, - ) - - assert isinstance(message.video, Video) - assert isinstance(message.video.file_id, str) - assert isinstance(message.video.file_unique_id, str) - assert message.video.file_id != "" - assert message.video.file_unique_id != "" - assert message.video.width == video.width - assert message.video.height == video.height - assert message.video.duration == video.duration - assert message.video.file_size == video.file_size - - assert message.caption == self.caption.replace("*", "") - - assert message.video.thumb.file_size == self.thumb_file_size - assert message.video.thumb.width == self.thumb_width - assert message.video.thumb.height == self.thumb_height - - assert message.video.file_name == self.file_name - assert message.has_protected_content - assert message.has_media_spoiler - - @pytest.mark.flaky(3, 1) - async def test_send_video_custom_filename(self, bot, chat_id, video_file, monkeypatch): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - return list(request_data.multipart_data.values())[0][0] == "custom_filename" - - monkeypatch.setattr(bot.request, "post", make_assertion) - - assert await bot.send_video(chat_id, video_file, filename="custom_filename") - - @pytest.mark.flaky(3, 1) - async def test_get_and_download(self, bot, video): - path = Path("telegram.mp4") - if path.is_file(): - path.unlink() - - new_file = await bot.get_file(video.file_id) - - assert new_file.file_size == self.file_size - assert new_file.file_id == video.file_id - assert new_file.file_unique_id == video.file_unique_id - assert new_file.file_path.startswith("https://") - - await new_file.download_to_drive("telegram.mp4") - - assert path.is_file() - - @pytest.mark.flaky(3, 1) - async def test_send_mp4_file_url(self, bot, chat_id, video): - message = await bot.send_video(chat_id, self.video_file_url, caption=self.caption) - - assert isinstance(message.video, Video) - assert isinstance(message.video.file_id, str) - assert isinstance(message.video.file_unique_id, str) - assert message.video.file_id != "" - assert message.video.file_unique_id != "" - assert message.video.width == video.width - assert message.video.height == video.height - assert message.video.duration == video.duration - assert message.video.file_size == video.file_size - - assert isinstance(message.video.thumb, PhotoSize) - assert isinstance(message.video.thumb.file_id, str) - assert isinstance(message.video.thumb.file_unique_id, str) - assert message.video.thumb.file_id != "" - assert message.video.thumb.file_unique_id != "" - assert message.video.thumb.width == 51 # This seems odd that it's not self.thumb_width - assert message.video.thumb.height == 90 # Ditto - assert message.video.thumb.file_size == 645 # same - - assert message.caption == self.caption - - @pytest.mark.flaky(3, 1) - async def test_send_video_caption_entities(self, bot, chat_id, video): - test_string = "Italic Bold Code" - entities = [ - MessageEntity(MessageEntity.ITALIC, 0, 6), - MessageEntity(MessageEntity.ITALIC, 7, 4), - MessageEntity(MessageEntity.ITALIC, 12, 4), - ] - message = await bot.send_video( - chat_id, video, caption=test_string, caption_entities=entities - ) - - assert message.caption == test_string - assert message.caption_entities == tuple(entities) - - @pytest.mark.flaky(3, 1) - async def test_resend(self, bot, chat_id, video): - message = await bot.send_video(chat_id, video.file_id) - - assert message.video == video - - async def test_send_with_video(self, monkeypatch, bot, chat_id, video): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - return request_data.json_parameters["video"] == video.file_id - - monkeypatch.setattr(bot.request, "post", make_assertion) - message = await bot.send_video(chat_id, video=video) - assert message - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_video_default_parse_mode_1(self, default_bot, chat_id, video): - test_string = "Italic Bold Code" - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_video(chat_id, video, caption=test_markdown_string) - assert message.caption_markdown == test_markdown_string - assert message.caption == test_string - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_video_default_parse_mode_2(self, default_bot, chat_id, video): - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_video( - chat_id, video, caption=test_markdown_string, parse_mode=None - ) - assert message.caption == test_markdown_string - assert message.caption_markdown == escape_markdown(test_markdown_string) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_video_default_parse_mode_3(self, default_bot, chat_id, video): - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_video( - chat_id, video, caption=test_markdown_string, parse_mode="HTML" - ) - assert message.caption == test_markdown_string - assert message.caption_markdown == escape_markdown(test_markdown_string) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) - async def test_send_video_default_protect_content(self, chat_id, default_bot, video): - protected = await default_bot.send_video(chat_id, video) - assert protected.has_protected_content - unprotected = await default_bot.send_video(chat_id, video, protect_content=False) - assert not unprotected.has_protected_content - - @pytest.mark.parametrize("local_mode", [True, False]) - async def test_send_video_local_files(self, monkeypatch, bot, chat_id, local_mode): - try: - bot._local_mode = local_mode - # For just test that the correct paths are passed as we have no local bot API set up - test_flag = False - file = data_file("telegram.jpg") - expected = file.as_uri() - - async def make_assertion(_, data, *args, **kwargs): - nonlocal test_flag - if local_mode: - test_flag = data.get("video") == expected and data.get("thumb") == expected - else: - test_flag = isinstance(data.get("video"), InputFile) and isinstance( - data.get("thumb"), InputFile - ) - - monkeypatch.setattr(bot, "_post", make_assertion) - await bot.send_video(chat_id, file, thumb=file) - assert test_flag - finally: - bot._local_mode = False - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize( - "default_bot,custom", - [ - ({"allow_sending_without_reply": True}, None), - ({"allow_sending_without_reply": False}, None), - ({"allow_sending_without_reply": False}, True), - ], - indirect=["default_bot"], - ) - async def test_send_video_default_allow_sending_without_reply( - self, default_bot, chat_id, video, custom - ): - reply_to_message = await default_bot.send_message(chat_id, "test") - await reply_to_message.delete() - if custom is not None: - message = await default_bot.send_video( - chat_id, - video, - allow_sending_without_reply=custom, - reply_to_message_id=reply_to_message.message_id, - ) - assert message.reply_to_message is None - elif default_bot.defaults.allow_sending_without_reply: - message = await default_bot.send_video( - chat_id, video, reply_to_message_id=reply_to_message.message_id - ) - assert message.reply_to_message is None - else: - with pytest.raises(BadRequest, match="message not found"): - await default_bot.send_video( - chat_id, video, reply_to_message_id=reply_to_message.message_id - ) - def test_de_json(self, bot): json_dict = { "file_id": self.video_file_id, @@ -342,31 +126,6 @@ class TestVideo: assert video_dict["file_size"] == video.file_size assert video_dict["file_name"] == video.file_name - @pytest.mark.flaky(3, 1) - async def test_error_send_empty_file(self, bot, chat_id): - with pytest.raises(TelegramError): - await bot.send_video(chat_id, open(os.devnull, "rb")) - - @pytest.mark.flaky(3, 1) - async def test_error_send_empty_file_id(self, bot, chat_id): - with pytest.raises(TelegramError): - await bot.send_video(chat_id, "") - - async def test_error_without_required_args(self, bot, chat_id): - with pytest.raises(TypeError): - await bot.send_video(chat_id=chat_id) - - async def test_get_file_instance_method(self, monkeypatch, video): - async def make_assertion(*_, **kwargs): - return kwargs["file_id"] == video.file_id - - assert check_shortcut_signature(Video.get_file, Bot.get_file, ["file_id"], []) - assert await check_shortcut_call(video.get_file, video.get_bot(), "get_file") - assert await check_defaults_handling(video.get_file, video.get_bot()) - - monkeypatch.setattr(video.get_bot(), "get_file", make_assertion) - assert await video.get_file() - def test_equality(self, video): a = Video(video.file_id, video.file_unique_id, self.width, self.height, self.duration) b = Video("", video.file_unique_id, self.width, self.height, self.duration) @@ -386,3 +145,232 @@ class TestVideo: assert a != e assert hash(a) != hash(e) + + async def test_error_without_required_args(self, bot, chat_id): + with pytest.raises(TypeError): + await bot.send_video(chat_id=chat_id) + + async def test_send_with_video(self, monkeypatch, bot, chat_id, video): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return request_data.json_parameters["video"] == video.file_id + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.send_video(chat_id, video=video) + + async def test_send_video_custom_filename(self, bot, chat_id, video_file, monkeypatch): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return list(request_data.multipart_data.values())[0][0] == "custom_filename" + + monkeypatch.setattr(bot.request, "post", make_assertion) + + assert await bot.send_video(chat_id, video_file, filename="custom_filename") + + @pytest.mark.parametrize("local_mode", [True, False]) + async def test_send_video_local_files(self, monkeypatch, bot, chat_id, local_mode): + try: + bot._local_mode = local_mode + # For just test that the correct paths are passed as we have no local bot API set up + test_flag = False + file = data_file("telegram.jpg") + expected = file.as_uri() + + async def make_assertion(_, data, *args, **kwargs): + nonlocal test_flag + if local_mode: + test_flag = data.get("video") == expected and data.get("thumb") == expected + else: + test_flag = isinstance(data.get("video"), InputFile) and isinstance( + data.get("thumb"), InputFile + ) + + monkeypatch.setattr(bot, "_post", make_assertion) + await bot.send_video(chat_id, file, thumb=file) + assert test_flag + finally: + bot._local_mode = False + + async def test_get_file_instance_method(self, monkeypatch, video): + async def make_assertion(*_, **kwargs): + return kwargs["file_id"] == video.file_id + + assert check_shortcut_signature(Video.get_file, Bot.get_file, ["file_id"], []) + assert await check_shortcut_call(video.get_file, video.get_bot(), "get_file") + assert await check_defaults_handling(video.get_file, video.get_bot()) + + monkeypatch.setattr(video.get_bot(), "get_file", make_assertion) + assert await video.get_file() + + +class TestVideoWithRequest(TestVideoBase): + async def test_send_all_args(self, bot, chat_id, video_file, video, thumb_file): + message = await bot.send_video( + chat_id, + video_file, + duration=self.duration, + caption=self.caption, + supports_streaming=self.supports_streaming, + disable_notification=False, + protect_content=True, + width=video.width, + height=video.height, + parse_mode="Markdown", + thumb=thumb_file, + has_spoiler=True, + ) + + assert isinstance(message.video, Video) + assert isinstance(message.video.file_id, str) + assert isinstance(message.video.file_unique_id, str) + assert message.video.file_id != "" + assert message.video.file_unique_id != "" + assert message.video.width == video.width + assert message.video.height == video.height + assert message.video.duration == video.duration + assert message.video.file_size == video.file_size + + assert message.caption == self.caption.replace("*", "") + + assert message.video.thumb.file_size == self.thumb_file_size + assert message.video.thumb.width == self.thumb_width + assert message.video.thumb.height == self.thumb_height + + assert message.video.file_name == self.file_name + assert message.has_protected_content + assert message.has_media_spoiler + + async def test_get_and_download(self, bot, video, chat_id): + path = Path("telegram.mp4") + if path.is_file(): + path.unlink() + + new_file = await bot.get_file(video.file_id) + + assert new_file.file_size == self.file_size + assert new_file.file_unique_id == video.file_unique_id + assert new_file.file_path.startswith("https://") + + await new_file.download_to_drive("telegram.mp4") + + assert path.is_file() + + async def test_send_mp4_file_url(self, bot, chat_id, video): + message = await bot.send_video(chat_id, self.video_file_url, caption=self.caption) + + assert isinstance(message.video, Video) + assert isinstance(message.video.file_id, str) + assert isinstance(message.video.file_unique_id, str) + assert message.video.file_id != "" + assert message.video.file_unique_id != "" + assert message.video.width == video.width + assert message.video.height == video.height + assert message.video.duration == video.duration + assert message.video.file_size == video.file_size + + assert isinstance(message.video.thumb, PhotoSize) + assert isinstance(message.video.thumb.file_id, str) + assert isinstance(message.video.thumb.file_unique_id, str) + assert message.video.thumb.file_id != "" + assert message.video.thumb.file_unique_id != "" + assert message.video.thumb.width == 51 # This seems odd that it's not self.thumb_width + assert message.video.thumb.height == 90 # Ditto + assert message.video.thumb.file_size == 645 # same + + assert message.caption == self.caption + + async def test_send_video_caption_entities(self, bot, chat_id, video): + test_string = "Italic Bold Code" + entities = [ + MessageEntity(MessageEntity.ITALIC, 0, 6), + MessageEntity(MessageEntity.ITALIC, 7, 4), + MessageEntity(MessageEntity.ITALIC, 12, 4), + ] + message = await bot.send_video( + chat_id, video, caption=test_string, caption_entities=entities + ) + + assert message.caption == test_string + assert message.caption_entities == tuple(entities) + + async def test_resend(self, bot, chat_id, video): + message = await bot.send_video(chat_id, video.file_id) + assert message.video == video + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_video_default_parse_mode_1(self, default_bot, chat_id, video): + test_string = "Italic Bold Code" + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_video(chat_id, video, caption=test_markdown_string) + assert message.caption_markdown == test_markdown_string + assert message.caption == test_string + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_video_default_parse_mode_2(self, default_bot, chat_id, video): + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_video( + chat_id, video, caption=test_markdown_string, parse_mode=None + ) + assert message.caption == test_markdown_string + assert message.caption_markdown == escape_markdown(test_markdown_string) + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_video_default_parse_mode_3(self, default_bot, chat_id, video): + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_video( + chat_id, video, caption=test_markdown_string, parse_mode="HTML" + ) + assert message.caption == test_markdown_string + assert message.caption_markdown == escape_markdown(test_markdown_string) + + @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) + async def test_send_video_default_protect_content(self, chat_id, default_bot, video): + tasks = asyncio.gather( + default_bot.send_video(chat_id, video), + default_bot.send_video(chat_id, video, protect_content=False), + ) + protected, unprotected = await tasks + assert protected.has_protected_content + assert not unprotected.has_protected_content + + @pytest.mark.parametrize( + "default_bot,custom", + [ + ({"allow_sending_without_reply": True}, None), + ({"allow_sending_without_reply": False}, None), + ({"allow_sending_without_reply": False}, True), + ], + indirect=["default_bot"], + ) + async def test_send_video_default_allow_sending_without_reply( + self, default_bot, chat_id, video, custom + ): + reply_to_message = await default_bot.send_message(chat_id, "test") + await reply_to_message.delete() + if custom is not None: + message = await default_bot.send_video( + chat_id, + video, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = await default_bot.send_video( + chat_id, video, reply_to_message_id=reply_to_message.message_id + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match="message not found"): + await default_bot.send_video( + chat_id, video, reply_to_message_id=reply_to_message.message_id + ) + + async def test_error_send_empty_file(self, bot, chat_id): + with pytest.raises(TelegramError): + await bot.send_video(chat_id, open(os.devnull, "rb")) + + async def test_error_send_empty_file_id(self, bot, chat_id): + with pytest.raises(TelegramError): + await bot.send_video(chat_id, "") diff --git a/tests/test_videochat.py b/tests/test_videochat.py index e2609296e..8ebd2136e 100644 --- a/tests/test_videochat.py +++ b/tests/test_videochat.py @@ -30,17 +30,17 @@ from telegram import ( from telegram._utils.datetime import to_timestamp -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def user1(): return User(first_name="Misses Test", id=123, is_bot=False) -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def user2(): return User(first_name="Mister Test", id=124, is_bot=False) -class TestVideoChatStarted: +class TestVideoChatStartedWithoutRequest: def test_slot_behaviour(self, mro_slots): action = VideoChatStarted() for attr in action.__slots__: @@ -58,7 +58,7 @@ class TestVideoChatStarted: assert video_chat_dict == {} -class TestVideoChatEnded: +class TestVideoChatEndedWithoutRequest: duration = 100 def test_slot_behaviour(self, mro_slots): @@ -97,7 +97,7 @@ class TestVideoChatEnded: assert hash(a) != hash(d) -class TestVideoChatParticipantsInvited: +class TestVideoChatParticipantsInvitedWithoutRequest: def test_slot_behaviour(self, mro_slots, user1): action = VideoChatParticipantsInvited([user1]) for attr in action.__slots__: @@ -148,7 +148,7 @@ class TestVideoChatParticipantsInvited: assert hash(a) != hash(e) -class TestVideoChatScheduled: +class TestVideoChatScheduledWithoutRequest: start_date = dtm.datetime.now(dtm.timezone.utc) def test_slot_behaviour(self, mro_slots): diff --git a/tests/test_videonote.py b/tests/test_videonote.py index d5a0b69fe..9a857b0c3 100644 --- a/tests/test_videonote.py +++ b/tests/test_videonote.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio import os from pathlib import Path @@ -34,30 +35,29 @@ from tests.conftest import data_file @pytest.fixture(scope="function") def video_note_file(): - f = data_file("telegram2.mp4").open("rb") - yield f - f.close() + with data_file("telegram2.mp4").open("rb") as f: + yield f -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") async def video_note(bot, chat_id): with data_file("telegram2.mp4").open("rb") as f: return (await bot.send_video_note(chat_id, video_note=f, read_timeout=50)).video_note -class TestVideoNote: +class TestVideoNoteBase: length = 240 duration = 3 file_size = 132084 - thumb_width = 240 thumb_height = 240 thumb_file_size = 11547 - caption = "VideoNoteTest - Caption" videonote_file_id = "5a3128a4d2a04750b5b58397f3b5e812" videonote_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e" + +class TestVideoNoteWithoutRequest(TestVideoNoteBase): def test_slot_behaviour(self, video_note, mro_slots): for attr in video_note.__slots__: assert getattr(video_note, attr, "err") != "err", f"got extra slot '{attr}'" @@ -82,74 +82,6 @@ class TestVideoNote: assert video_note.duration == self.duration assert video_note.file_size == self.file_size - @pytest.mark.flaky(3, 1) - async def test_send_all_args(self, bot, chat_id, video_note_file, video_note, thumb_file): - message = await bot.send_video_note( - chat_id, - video_note_file, - duration=self.duration, - length=self.length, - disable_notification=False, - protect_content=True, - thumb=thumb_file, - ) - - assert isinstance(message.video_note, VideoNote) - assert isinstance(message.video_note.file_id, str) - assert isinstance(message.video_note.file_unique_id, str) - assert message.video_note.file_id != "" - assert message.video_note.file_unique_id != "" - assert message.video_note.length == video_note.length - assert message.video_note.duration == video_note.duration - assert message.video_note.file_size == video_note.file_size - - assert message.video_note.thumb.file_size == self.thumb_file_size - assert message.video_note.thumb.width == self.thumb_width - assert message.video_note.thumb.height == self.thumb_height - assert message.has_protected_content - - @pytest.mark.flaky(3, 1) - async def test_send_video_note_custom_filename( - self, bot, chat_id, video_note_file, monkeypatch - ): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - return list(request_data.multipart_data.values())[0][0] == "custom_filename" - - monkeypatch.setattr(bot.request, "post", make_assertion) - - assert await bot.send_video_note(chat_id, video_note_file, filename="custom_filename") - - @pytest.mark.flaky(3, 1) - async def test_get_and_download(self, bot, video_note): - path = Path("telegram2.mp4") - if path.is_file(): - path.unlink() - - new_file = await bot.get_file(video_note.file_id) - - assert new_file.file_size == self.file_size - assert new_file.file_id == video_note.file_id - assert new_file.file_unique_id == video_note.file_unique_id - assert new_file.file_path.startswith("https://") - - await new_file.download_to_drive("telegram2.mp4") - - assert path.is_file() - - @pytest.mark.flaky(3, 1) - async def test_resend(self, bot, chat_id, video_note): - message = await bot.send_video_note(chat_id, video_note.file_id) - - assert message.video_note == video_note - - async def test_send_with_video_note(self, monkeypatch, bot, chat_id, video_note): - async def make_assertion(url, request_data: RequestData, *args, **kwargs): - return request_data.json_parameters["video_note"] == video_note.file_id - - monkeypatch.setattr(bot.request, "post", make_assertion) - message = await bot.send_video_note(chat_id, video_note=video_note) - assert message - def test_de_json(self, bot): json_dict = { "file_id": self.videonote_file_id, @@ -177,6 +109,47 @@ class TestVideoNote: assert video_note_dict["duration"] == video_note.duration assert video_note_dict["file_size"] == video_note.file_size + def test_equality(self, video_note): + a = VideoNote(video_note.file_id, video_note.file_unique_id, self.length, self.duration) + b = VideoNote("", video_note.file_unique_id, self.length, self.duration) + c = VideoNote(video_note.file_id, video_note.file_unique_id, 0, 0) + d = VideoNote("", "", self.length, self.duration) + e = Voice(video_note.file_id, video_note.file_unique_id, self.duration) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a == c + assert hash(a) == hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) + + async def test_error_without_required_args(self, bot, chat_id): + with pytest.raises(TypeError): + await bot.send_video_note(chat_id=chat_id) + + async def test_send_with_video_note(self, monkeypatch, bot, chat_id, video_note): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return request_data.json_parameters["video_note"] == video_note.file_id + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.send_video_note(chat_id, video_note=video_note) + + async def test_send_video_note_custom_filename( + self, bot, chat_id, video_note_file, monkeypatch + ): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return list(request_data.multipart_data.values())[0][0] == "custom_filename" + + monkeypatch.setattr(bot.request, "post", make_assertion) + + assert await bot.send_video_note(chat_id, video_note_file, filename="custom_filename") + @pytest.mark.parametrize("local_mode", [True, False]) async def test_send_video_note_local_files(self, monkeypatch, bot, chat_id, local_mode): try: @@ -203,7 +176,63 @@ class TestVideoNote: finally: bot._local_mode = False - @pytest.mark.flaky(3, 1) + async def test_get_file_instance_method(self, monkeypatch, video_note): + async def make_assertion(*_, **kwargs): + return kwargs["file_id"] == video_note.file_id + + assert check_shortcut_signature(VideoNote.get_file, Bot.get_file, ["file_id"], []) + assert await check_shortcut_call(video_note.get_file, video_note.get_bot(), "get_file") + assert await check_defaults_handling(video_note.get_file, video_note.get_bot()) + + monkeypatch.setattr(video_note.get_bot(), "get_file", make_assertion) + assert await video_note.get_file() + + +class TestVideoNoteWithRequest(TestVideoNoteBase): + async def test_send_all_args(self, bot, chat_id, video_note_file, video_note, thumb_file): + message = await bot.send_video_note( + chat_id, + video_note_file, + duration=self.duration, + length=self.length, + disable_notification=False, + protect_content=True, + thumb=thumb_file, + ) + + assert isinstance(message.video_note, VideoNote) + assert isinstance(message.video_note.file_id, str) + assert isinstance(message.video_note.file_unique_id, str) + assert message.video_note.file_id != "" + assert message.video_note.file_unique_id != "" + assert message.video_note.length == video_note.length + assert message.video_note.duration == video_note.duration + assert message.video_note.file_size == video_note.file_size + + assert message.video_note.thumb.file_size == self.thumb_file_size + assert message.video_note.thumb.width == self.thumb_width + assert message.video_note.thumb.height == self.thumb_height + assert message.has_protected_content + + async def test_get_and_download(self, bot, video_note, chat_id): + path = Path("telegram2.mp4") + if path.is_file(): + path.unlink() + + new_file = await bot.get_file(video_note.file_id) + + assert new_file.file_size == self.file_size + assert new_file.file_unique_id == video_note.file_unique_id + assert new_file.file_path.startswith("https://") + + await new_file.download_to_drive("telegram2.mp4") + + assert path.is_file() + + async def test_resend(self, bot, chat_id, video_note): + message = await bot.send_video_note(chat_id, video_note.file_id) + assert message.video_note == video_note + @pytest.mark.parametrize( "default_bot,custom", [ @@ -237,55 +266,20 @@ class TestVideoNote: chat_id, video_note, reply_to_message_id=reply_to_message.message_id ) - @pytest.mark.flaky(3, 1) @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) async def test_send_video_note_default_protect_content(self, chat_id, default_bot, video_note): - protected = await default_bot.send_video_note(chat_id, video_note) + tasks = asyncio.gather( + default_bot.send_video_note(chat_id, video_note), + default_bot.send_video_note(chat_id, video_note, protect_content=False), + ) + protected, unprotected = await tasks assert protected.has_protected_content - unprotected = await default_bot.send_video_note(chat_id, video_note, protect_content=False) assert not unprotected.has_protected_content - @pytest.mark.flaky(3, 1) async def test_error_send_empty_file(self, bot, chat_id): with pytest.raises(TelegramError): await bot.send_video_note(chat_id, open(os.devnull, "rb")) - @pytest.mark.flaky(3, 1) async def test_error_send_empty_file_id(self, bot, chat_id): with pytest.raises(TelegramError): await bot.send_video_note(chat_id, "") - - async def test_error_without_required_args(self, bot, chat_id): - with pytest.raises(TypeError): - await bot.send_video_note(chat_id=chat_id) - - async def test_get_file_instance_method(self, monkeypatch, video_note): - async def make_assertion(*_, **kwargs): - return kwargs["file_id"] == video_note.file_id - - assert check_shortcut_signature(VideoNote.get_file, Bot.get_file, ["file_id"], []) - assert await check_shortcut_call(video_note.get_file, video_note.get_bot(), "get_file") - assert await check_defaults_handling(video_note.get_file, video_note.get_bot()) - - monkeypatch.setattr(video_note.get_bot(), "get_file", make_assertion) - assert await video_note.get_file() - - def test_equality(self, video_note): - a = VideoNote(video_note.file_id, video_note.file_unique_id, self.length, self.duration) - b = VideoNote("", video_note.file_unique_id, self.length, self.duration) - c = VideoNote(video_note.file_id, video_note.file_unique_id, 0, 0) - d = VideoNote("", "", self.length, self.duration) - e = Voice(video_note.file_id, video_note.file_unique_id, self.duration) - - assert a == b - assert hash(a) == hash(b) - assert a is not b - - assert a == c - assert hash(a) == hash(c) - - assert a != d - assert hash(a) != hash(d) - - assert a != e - assert hash(a) != hash(e) diff --git a/tests/test_voice.py b/tests/test_voice.py index 64152de21..67557661a 100644 --- a/tests/test_voice.py +++ b/tests/test_voice.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio import os from pathlib import Path @@ -35,28 +36,27 @@ from tests.conftest import data_file @pytest.fixture(scope="function") def voice_file(): - f = data_file("telegram.ogg").open("rb") - yield f - f.close() + with data_file("telegram.ogg").open("rb") as f: + yield f -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") async def voice(bot, chat_id): with data_file("telegram.ogg").open("rb") as f: return (await bot.send_voice(chat_id, voice=f, read_timeout=50)).voice -class TestVoice: +class TestVoiceBase: duration = 3 mime_type = "audio/ogg" file_size = 9199 - caption = "Test *voice*" voice_file_url = "https://python-telegram-bot.org/static/testfiles/telegram.ogg" - voice_file_id = "5a3128a4d2a04750b5b58397f3b5e812" voice_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e" + +class TestVoiceWithoutRequest(TestVoiceBase): def test_slot_behaviour(self, voice, mro_slots): for attr in voice.__slots__: assert getattr(voice, attr, "err") != "err", f"got extra slot '{attr}'" @@ -75,30 +75,57 @@ class TestVoice: assert voice.mime_type == self.mime_type assert voice.file_size == self.file_size - @pytest.mark.flaky(3, 1) - async def test_send_all_args(self, bot, chat_id, voice_file, voice): - message = await bot.send_voice( - chat_id, - voice_file, - duration=self.duration, - caption=self.caption, - disable_notification=False, - protect_content=True, - parse_mode="Markdown", - ) + def test_de_json(self, bot): + json_dict = { + "file_id": self.voice_file_id, + "file_unique_id": self.voice_file_unique_id, + "duration": self.duration, + "mime_type": self.mime_type, + "file_size": self.file_size, + } + json_voice = Voice.de_json(json_dict, bot) + assert json_voice.api_kwargs == {} - assert isinstance(message.voice, Voice) - assert isinstance(message.voice.file_id, str) - assert isinstance(message.voice.file_unique_id, str) - assert message.voice.file_id != "" - assert message.voice.file_unique_id != "" - assert message.voice.duration == voice.duration - assert message.voice.mime_type == voice.mime_type - assert message.voice.file_size == voice.file_size - assert message.caption == self.caption.replace("*", "") - assert message.has_protected_content + assert json_voice.file_id == self.voice_file_id + assert json_voice.file_unique_id == self.voice_file_unique_id + assert json_voice.duration == self.duration + assert json_voice.mime_type == self.mime_type + assert json_voice.file_size == self.file_size + + def test_to_dict(self, voice): + voice_dict = voice.to_dict() + + assert isinstance(voice_dict, dict) + assert voice_dict["file_id"] == voice.file_id + assert voice_dict["file_unique_id"] == voice.file_unique_id + assert voice_dict["duration"] == voice.duration + assert voice_dict["mime_type"] == voice.mime_type + assert voice_dict["file_size"] == voice.file_size + + def test_equality(self, voice): + a = Voice(voice.file_id, voice.file_unique_id, self.duration) + b = Voice("", voice.file_unique_id, self.duration) + c = Voice(voice.file_id, voice.file_unique_id, 0) + d = Voice("", "", self.duration) + e = Audio(voice.file_id, voice.file_unique_id, self.duration) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a == c + assert hash(a) == hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) + + async def test_error_without_required_args(self, bot, chat_id): + with pytest.raises(TypeError): + await bot.sendVoice(chat_id) - @pytest.mark.flaky(3, 1) async def test_send_voice_custom_filename(self, bot, chat_id, voice_file, monkeypatch): async def make_assertion(url, request_data: RequestData, *args, **kwargs): return list(request_data.multipart_data.values())[0][0] == "custom_filename" @@ -107,104 +134,12 @@ class TestVoice: assert await bot.send_voice(chat_id, voice_file, filename="custom_filename") - @pytest.mark.flaky(3, 1) - async def test_get_and_download(self, bot, voice): - path = Path("telegram.ogg") - if path.is_file(): - path.unlink() - - new_file = await bot.get_file(voice.file_id) - - assert new_file.file_size == voice.file_size - assert new_file.file_id == voice.file_id - assert new_file.file_unique_id == voice.file_unique_id - assert new_file.file_path.startswith("https://") - - await new_file.download_to_drive("telegram.ogg") - - assert path.is_file() - - @pytest.mark.flaky(3, 1) - async def test_send_ogg_url_file(self, bot, chat_id, voice): - message = await bot.sendVoice(chat_id, self.voice_file_url, duration=self.duration) - - assert isinstance(message.voice, Voice) - assert isinstance(message.voice.file_id, str) - assert isinstance(message.voice.file_unique_id, str) - assert message.voice.file_id != "" - assert message.voice.file_unique_id != "" - assert message.voice.duration == voice.duration - assert message.voice.mime_type == voice.mime_type - assert message.voice.file_size == voice.file_size - - @pytest.mark.flaky(3, 1) - async def test_resend(self, bot, chat_id, voice): - message = await bot.sendVoice(chat_id, voice.file_id) - - assert message.voice == voice - async def test_send_with_voice(self, monkeypatch, bot, chat_id, voice): async def make_assertion(url, request_data: RequestData, *args, **kwargs): return request_data.json_parameters["voice"] == voice.file_id monkeypatch.setattr(bot.request, "post", make_assertion) - message = await bot.send_voice(chat_id, voice=voice) - assert message - - @pytest.mark.flaky(3, 1) - async def test_send_voice_caption_entities(self, bot, chat_id, voice_file): - test_string = "Italic Bold Code" - entities = [ - MessageEntity(MessageEntity.ITALIC, 0, 6), - MessageEntity(MessageEntity.ITALIC, 7, 4), - MessageEntity(MessageEntity.ITALIC, 12, 4), - ] - message = await bot.send_voice( - chat_id, voice_file, caption=test_string, caption_entities=entities - ) - - assert message.caption == test_string - assert message.caption_entities == tuple(entities) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_voice_default_parse_mode_1(self, default_bot, chat_id, voice): - test_string = "Italic Bold Code" - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_voice(chat_id, voice, caption=test_markdown_string) - assert message.caption_markdown == test_markdown_string - assert message.caption == test_string - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_voice_default_parse_mode_2(self, default_bot, chat_id, voice): - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_voice( - chat_id, voice, caption=test_markdown_string, parse_mode=None - ) - assert message.caption == test_markdown_string - assert message.caption_markdown == escape_markdown(test_markdown_string) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) - async def test_send_voice_default_parse_mode_3(self, default_bot, chat_id, voice): - test_markdown_string = "_Italic_ *Bold* `Code`" - - message = await default_bot.send_voice( - chat_id, voice, caption=test_markdown_string, parse_mode="HTML" - ) - assert message.caption == test_markdown_string - assert message.caption_markdown == escape_markdown(test_markdown_string) - - @pytest.mark.flaky(3, 1) - @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) - async def test_send_voice_default_protect_content(self, chat_id, default_bot, voice): - protected = await default_bot.send_voice(chat_id, voice) - assert protected.has_protected_content - unprotected = await default_bot.send_voice(chat_id, voice, protect_content=False) - assert not unprotected.has_protected_content + assert await bot.send_voice(chat_id, voice=voice) @pytest.mark.parametrize("local_mode", [True, False]) async def test_send_voice_local_files(self, monkeypatch, bot, chat_id, local_mode): @@ -228,7 +163,126 @@ class TestVoice: finally: bot._local_mode = False - @pytest.mark.flaky(3, 1) + async def test_get_file_instance_method(self, monkeypatch, voice): + async def make_assertion(*_, **kwargs): + return kwargs["file_id"] == voice.file_id + + assert check_shortcut_signature(Voice.get_file, Bot.get_file, ["file_id"], []) + assert await check_shortcut_call(voice.get_file, voice.get_bot(), "get_file") + assert await check_defaults_handling(voice.get_file, voice.get_bot()) + + monkeypatch.setattr(voice.get_bot(), "get_file", make_assertion) + assert await voice.get_file() + + +class TestVoiceWithRequest(TestVoiceBase): + async def test_send_all_args(self, bot, chat_id, voice_file, voice): + message = await bot.send_voice( + chat_id, + voice_file, + duration=self.duration, + caption=self.caption, + disable_notification=False, + protect_content=True, + parse_mode="Markdown", + ) + + assert isinstance(message.voice, Voice) + assert isinstance(message.voice.file_id, str) + assert isinstance(message.voice.file_unique_id, str) + assert message.voice.file_id != "" + assert message.voice.file_unique_id != "" + assert message.voice.duration == voice.duration + assert message.voice.mime_type == voice.mime_type + assert message.voice.file_size == voice.file_size + assert message.caption == self.caption.replace("*", "") + assert message.has_protected_content + + async def test_get_and_download(self, bot, voice, chat_id): + path = Path("telegram.ogg") + if path.is_file(): + path.unlink() + + new_file = await bot.get_file(voice.file_id) + + assert new_file.file_size == voice.file_size + assert new_file.file_unique_id == voice.file_unique_id + assert new_file.file_path.startswith("https://") + + await new_file.download_to_drive("telegram.ogg") + + assert path.is_file() + + async def test_send_ogg_url_file(self, bot, chat_id, voice): + message = await bot.sendVoice(chat_id, self.voice_file_url, duration=self.duration) + + assert isinstance(message.voice, Voice) + assert isinstance(message.voice.file_id, str) + assert isinstance(message.voice.file_unique_id, str) + assert message.voice.file_id != "" + assert message.voice.file_unique_id != "" + assert message.voice.duration == voice.duration + assert message.voice.mime_type == voice.mime_type + assert message.voice.file_size == voice.file_size + + async def test_resend(self, bot, chat_id, voice): + message = await bot.sendVoice(chat_id, voice.file_id) + + assert message.voice == voice + + async def test_send_voice_caption_entities(self, bot, chat_id, voice_file): + test_string = "Italic Bold Code" + entities = [ + MessageEntity(MessageEntity.ITALIC, 0, 6), + MessageEntity(MessageEntity.ITALIC, 7, 4), + MessageEntity(MessageEntity.ITALIC, 12, 4), + ] + message = await bot.send_voice( + chat_id, voice_file, caption=test_string, caption_entities=entities + ) + + assert message.caption == test_string + assert message.caption_entities == tuple(entities) + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_voice_default_parse_mode_1(self, default_bot, chat_id, voice): + test_string = "Italic Bold Code" + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_voice(chat_id, voice, caption=test_markdown_string) + assert message.caption_markdown == test_markdown_string + assert message.caption == test_string + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_voice_default_parse_mode_2(self, default_bot, chat_id, voice): + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_voice( + chat_id, voice, caption=test_markdown_string, parse_mode=None + ) + assert message.caption == test_markdown_string + assert message.caption_markdown == escape_markdown(test_markdown_string) + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_voice_default_parse_mode_3(self, default_bot, chat_id, voice): + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_voice( + chat_id, voice, caption=test_markdown_string, parse_mode="HTML" + ) + assert message.caption == test_markdown_string + assert message.caption_markdown == escape_markdown(test_markdown_string) + + @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) + async def test_send_voice_default_protect_content(self, chat_id, default_bot, voice): + tasks = asyncio.gather( + default_bot.send_voice(chat_id, voice), + default_bot.send_voice(chat_id, voice, protect_content=False), + ) + protected, unprotected = await tasks + assert protected.has_protected_content + assert not unprotected.has_protected_content + @pytest.mark.parametrize( "default_bot,custom", [ @@ -262,74 +316,10 @@ class TestVoice: chat_id, voice, reply_to_message_id=reply_to_message.message_id ) - def test_de_json(self, bot): - json_dict = { - "file_id": self.voice_file_id, - "file_unique_id": self.voice_file_unique_id, - "duration": self.duration, - "mime_type": self.mime_type, - "file_size": self.file_size, - } - json_voice = Voice.de_json(json_dict, bot) - assert json_voice.api_kwargs == {} - - assert json_voice.file_id == self.voice_file_id - assert json_voice.file_unique_id == self.voice_file_unique_id - assert json_voice.duration == self.duration - assert json_voice.mime_type == self.mime_type - assert json_voice.file_size == self.file_size - - def test_to_dict(self, voice): - voice_dict = voice.to_dict() - - assert isinstance(voice_dict, dict) - assert voice_dict["file_id"] == voice.file_id - assert voice_dict["file_unique_id"] == voice.file_unique_id - assert voice_dict["duration"] == voice.duration - assert voice_dict["mime_type"] == voice.mime_type - assert voice_dict["file_size"] == voice.file_size - - @pytest.mark.flaky(3, 1) async def test_error_send_empty_file(self, bot, chat_id): with pytest.raises(TelegramError): await bot.sendVoice(chat_id, open(os.devnull, "rb")) - @pytest.mark.flaky(3, 1) async def test_error_send_empty_file_id(self, bot, chat_id): with pytest.raises(TelegramError): await bot.sendVoice(chat_id, "") - - async def test_error_without_required_args(self, bot, chat_id): - with pytest.raises(TypeError): - await bot.sendVoice(chat_id) - - async def test_get_file_instance_method(self, monkeypatch, voice): - async def make_assertion(*_, **kwargs): - return kwargs["file_id"] == voice.file_id - - assert check_shortcut_signature(Voice.get_file, Bot.get_file, ["file_id"], []) - assert await check_shortcut_call(voice.get_file, voice.get_bot(), "get_file") - assert await check_defaults_handling(voice.get_file, voice.get_bot()) - - monkeypatch.setattr(voice.get_bot(), "get_file", make_assertion) - assert await voice.get_file() - - def test_equality(self, voice): - a = Voice(voice.file_id, voice.file_unique_id, self.duration) - b = Voice("", voice.file_unique_id, self.duration) - c = Voice(voice.file_id, voice.file_unique_id, 0) - d = Voice("", "", self.duration) - e = Audio(voice.file_id, voice.file_unique_id, self.duration) - - assert a == b - assert hash(a) == hash(b) - assert a is not b - - assert a == c - assert hash(a) == hash(c) - - assert a != d - assert hash(a) != hash(d) - - assert a != e - assert hash(a) != hash(e) diff --git a/tests/test_webappdata.py b/tests/test_webappdata.py index 4d2e0b556..c64f3894d 100644 --- a/tests/test_webappdata.py +++ b/tests/test_webappdata.py @@ -22,18 +22,17 @@ import pytest from telegram import WebAppData -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def web_app_data(): - return WebAppData( - data=TestWebAppData.data, - button_text=TestWebAppData.button_text, - ) + return WebAppData(data=TestWebAppDataBase.data, button_text=TestWebAppDataBase.button_text) -class TestWebAppData: +class TestWebAppDataBase: data = "data" button_text = "button_text" + +class TestWebAppDataWithoutRequest(TestWebAppDataBase): def test_slot_behaviour(self, web_app_data, mro_slots): for attr in web_app_data.__slots__: assert getattr(web_app_data, attr, "err") != "err", f"got extra slot '{attr}'" diff --git a/tests/test_webappinfo.py b/tests/test_webappinfo.py index 4c41ae497..cd1b07233 100644 --- a/tests/test_webappinfo.py +++ b/tests/test_webappinfo.py @@ -22,14 +22,16 @@ import pytest from telegram import WebAppInfo -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def web_app_info(): - return WebAppInfo(url=TestWebAppInfo.url) + return WebAppInfo(url=TestWebAppInfoBase.url) -class TestWebAppInfo: +class TestWebAppInfoBase: url = "https://www.example.com" + +class TestWebAppInfoWithoutRequest(TestWebAppInfoBase): def test_slot_behaviour(self, web_app_info, mro_slots): for attr in web_app_info.__slots__: assert getattr(web_app_info, attr, "err") != "err", f"got extra slot '{attr}'" diff --git a/tests/test_webhookinfo.py b/tests/test_webhookinfo.py index 41907094a..554db3992 100644 --- a/tests/test_webhookinfo.py +++ b/tests/test_webhookinfo.py @@ -25,21 +25,21 @@ from telegram import LoginUrl, WebhookInfo from telegram._utils.datetime import from_timestamp -@pytest.fixture(scope="class") +@pytest.fixture(scope="module") def webhook_info(): return WebhookInfo( - url=TestWebhookInfo.url, - has_custom_certificate=TestWebhookInfo.has_custom_certificate, - pending_update_count=TestWebhookInfo.pending_update_count, - ip_address=TestWebhookInfo.ip_address, - last_error_date=TestWebhookInfo.last_error_date, - max_connections=TestWebhookInfo.max_connections, - allowed_updates=TestWebhookInfo.allowed_updates, - last_synchronization_error_date=TestWebhookInfo.last_synchronization_error_date, + url=TestWebhookInfoBase.url, + has_custom_certificate=TestWebhookInfoBase.has_custom_certificate, + pending_update_count=TestWebhookInfoBase.pending_update_count, + ip_address=TestWebhookInfoBase.ip_address, + last_error_date=TestWebhookInfoBase.last_error_date, + max_connections=TestWebhookInfoBase.max_connections, + allowed_updates=TestWebhookInfoBase.allowed_updates, + last_synchronization_error_date=TestWebhookInfoBase.last_synchronization_error_date, ) -class TestWebhookInfo: +class TestWebhookInfoBase: url = "http://www.google.com" has_custom_certificate = False pending_update_count = 5 @@ -49,6 +49,8 @@ class TestWebhookInfo: allowed_updates = ["type1", "type2"] last_synchronization_error_date = time.time() + +class TestWebhookInfoWithoutRequest(TestWebhookInfoBase): def test_slot_behaviour(self, webhook_info, mro_slots): for attr in webhook_info.__slots__: assert getattr(webhook_info, attr, "err") != "err", f"got extra slot '{attr}'"