diff --git a/aiogram/api/session/base.py b/aiogram/api/session/base.py index f0e6eefe..113af58e 100644 --- a/aiogram/api/session/base.py +++ b/aiogram/api/session/base.py @@ -2,7 +2,7 @@ import abc import asyncio import datetime import json -from typing import Any, Callable, Dict, List, Optional, TypeVar, Union +from typing import Any, Callable, Optional, TypeVar, Union from pydantic.dataclasses import dataclass @@ -76,13 +76,13 @@ class BaseSession(abc.ABC): return self.json_dumps(self.clean_json(value)) if isinstance(value, datetime.timedelta): now = datetime.datetime.now() - return int((now + value).timestamp()) + return str(round((now + value).timestamp())) if isinstance(value, datetime.datetime): - return round(value.timestamp()) + return str(round(value.timestamp())) else: return str(value) - def clean_json(self, value: Union[List, Dict]): + def clean_json(self, value: Any): if isinstance(value, list): return [self.clean_json(v) for v in value if v is not None] elif isinstance(value, dict): diff --git a/poetry.lock b/poetry.lock index 07661b9c..a04eb8c9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -49,6 +49,14 @@ optional = false python-versions = ">=3.5.3" version = "3.0.1" +[[package]] +category = "dev" +description = "Enhance the standard unittest package with features for testing asyncio libraries" +name = "asynctest" +optional = false +python-versions = ">=3.5" +version = "0.13.0" + [[package]] category = "main" description = "Atomic file writes." @@ -109,7 +117,7 @@ version = "7.0" [[package]] category = "main" description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\"" +marker = "python_version >= \"3.5\" and sys_platform == \"win32\" or sys_platform == \"win32\"" name = "colorama" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -164,7 +172,7 @@ version = "2.8" [[package]] category = "main" description = "Read metadata from Python packages" -marker = "python_version < \"3.8\"" +marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_version < \"3.8\"" name = "importlib-metadata" optional = false python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3" @@ -457,6 +465,38 @@ version = "2.8.1" coverage = ">=4.4" pytest = ">=3.6" +[[package]] +category = "main" +description = "Thin-wrapper around the mock package for easier use with py.test" +name = "pytest-mock" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.11.2" + +[package.dependencies] +pytest = ">=2.7" + +[[package]] +category = "dev" +description = "Mypy static type checker plugin for Pytest" +name = "pytest-mypy" +optional = false +python-versions = "~=3.4" +version = "0.4.2" + +[package.dependencies] +[[package.dependencies.mypy]] +python = ">=3.5,<3.8" +version = ">=0.500" + +[[package.dependencies.mypy]] +python = ">=3.8" +version = ">=0.700" + +[package.dependencies.pytest] +python = ">=3.5" +version = ">=2.8" + [[package]] category = "main" description = "World timezone definitions, modern and historical" @@ -544,7 +584,7 @@ multidict = ">=4.0" [[package]] category = "main" description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.8\"" +marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_version < \"3.8\"" name = "zipp" optional = false python-versions = ">=2.7" @@ -554,7 +594,7 @@ version = "0.6.0" more-itertools = "*" [metadata] -content-hash = "5252c850bdd8bc1c88cfb7aedc2ce27896fc06310a0ecf6674548702051cadcc" +content-hash = "36e7679dedd3bcc50c24575b35da90a64bd33ef54d7862db94159dfc7ecaf28a" python-versions = "^3.7" [metadata.hashes] @@ -563,6 +603,7 @@ aiohttp = ["1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e", " appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"] aresponses = ["d1d6ef52b9a97142d106688cf9b112602ef3dc66f6368de8f91f47241d8cfc9c", "f62bcdd739612b6254dd552467b5897a81dcf785e4bb48463bf71e40df398580"] async-timeout = ["0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", "4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"] +asynctest = ["5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676", "c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"] atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"] attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"] babel = ["af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", "e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"] @@ -603,6 +644,8 @@ pyparsing = ["20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", pytest = ["15837d2880cb94821087bc07476892ea740696b20e90288fd6c19e44b435abdb", "b6cf7ad9064049ee486586b3a0ddd70dc5136c40e1147e7d286efd77ba66c5eb"] pytest-asyncio = ["9fac5100fd716cbecf6ef89233e8590a4ad61d729d1732e0a96b84182df1daaf", "d734718e25cfc32d2bf78d346e99d33724deeba774cc4afdf491530c6184b63b"] pytest-cov = ["cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b", "cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"] +pytest-mock = ["b3514caac35fe3f05555923eabd9546abce11571cc2ddf7d8615959d04f2c89e", "ea502c3891599c26243a3a847ccf0b1d20556678c528f86c98e3cd6d40c5cf11"] +pytest-mypy = ["3b7b56912d55439d5f447cc609f91caac7f74f0f1c89f1379d04f06bac777c32", "5a5338cecff17f005b181546a13e282761754b481225df37f33d37f86ac5b304"] pytz = ["1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", "b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"] pyyaml = ["0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", "01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", "5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", "5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", "7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", "7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", "87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", "9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", "a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", "b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", "b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", "bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", "f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"] six = ["1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", "30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"] diff --git a/pyproject.toml b/pyproject.toml index 647b76b1..16ae3445 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ pydantic = "^1.1" Babel = "^2.7" pytest-cov = "^2.8" aiofiles = "^0.4.0" +pytest-mock = "^1.11" [tool.poetry.dev-dependencies] black = {version = "^18.3-alpha.0", allows-prereleases = true} @@ -30,6 +31,8 @@ mkdocs = "^1.0" mkdocs-material = "^4.4" pygments = "^2.4" pymdown-extensions = "^6.1" +pytest-mypy = "^0.4.2" +asynctest = "^0.13.0" [tool.black] line-length = 99 diff --git a/tests/test_api/__init__.py b/tests/test_api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_api/test_client/__init__.py b/tests/test_api/test_client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_api/test_methods/__init__.py b/tests/test_api/test_methods/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_api/test_session/__init__.py b/tests/test_api/test_session/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_api/test_session/test_api_server.py b/tests/test_api/test_session/test_api_server.py new file mode 100644 index 00000000..2ff6fc26 --- /dev/null +++ b/tests/test_api/test_session/test_api_server.py @@ -0,0 +1,11 @@ +from aiogram.api.session.base import PRODUCTION + + +class TestAPIServer: + def test_method_url(self): + method_url = PRODUCTION.api_url(token="TOKEN", method="apiMethod") + assert method_url == "https://api.telegram.org/botTOKEN/apiMethod" + + def test_file_url(self): + file_url = PRODUCTION.file_url(token="TOKEN", path="path") + assert file_url == "https://api.telegram.org/file/botTOKEN/path" diff --git a/tests/test_api/test_session/test_base_session.py b/tests/test_api/test_session/test_base_session.py new file mode 100644 index 00000000..2d345059 --- /dev/null +++ b/tests/test_api/test_session/test_base_session.py @@ -0,0 +1,89 @@ +import datetime +from unittest.mock import patch + +import pytest +from asynctest import CoroutineMock + +from aiogram.api.session.base import BaseSession +from aiogram.utils.mixins import DataMixin + + +class TestBaseSession(DataMixin): + def setup(self): + self["__abstractmethods__"] = BaseSession.__abstractmethods__ + BaseSession.__abstractmethods__ = set() + + def teardown(self): + BaseSession.__abstractmethods__ = self["__abstractmethods__"] + + def test_sync_close(self): + session = BaseSession() + + with patch( + "aiogram.api.session.base.BaseSession.close", new=CoroutineMock() + ) as mocked_close: + session.__del__() + mocked_close.assert_called_once_with() + + @pytest.mark.asyncio + async def test_async_close(self): + session = BaseSession() + + with patch( + "aiogram.api.session.base.BaseSession.close", new=CoroutineMock() + ) as mocked_close: + session.__del__() + mocked_close.assert_called_once_with() + + def test_prepare_value(self): + session = BaseSession() + + now = datetime.datetime( + year=2019, month=11, day=15, hour=12, minute=42, second=15, microsecond=0 + ) + + assert session.prepare_value("text") == "text" + assert session.prepare_value(["test"]) == '["test"]' + assert session.prepare_value({"test": "ok"}) == '{"test": "ok"}' + assert session.prepare_value(now) == "1573814535" + assert isinstance(session.prepare_value(datetime.timedelta(minutes=2)), str) + assert session.prepare_value(42) == "42" + + def test_clean_json(self): + session = BaseSession() + + cleaned_dict = session.clean_json({"key": "value", "null": None}) + assert "key" in cleaned_dict + assert "null" not in cleaned_dict + + cleaned_list = session.clean_json(["kaboom", 42, None]) + assert len(cleaned_list) == 2 + assert 42 in cleaned_list + assert None not in cleaned_list + assert cleaned_list[0] == "kaboom" + + def test_clean_json_with_nested_json(self): + session = BaseSession() + + cleaned = session.clean_json( + { + "key": "value", + "null": None, + "nested_list": ["kaboom", 42, None], + "nested_dict": {"key": "value", "null": None}, + } + ) + + assert len(cleaned) == 3 + assert "null" not in cleaned + + assert isinstance(cleaned["nested_list"], list) + assert cleaned["nested_list"] == ["kaboom", 42] + + assert isinstance(cleaned["nested_dict"], dict) + assert cleaned["nested_dict"] == {"key": "value"} + + def test_clean_json_not_json(self): + session = BaseSession() + + assert session.clean_json(42) == 42 diff --git a/tests/test_api/test_types/__init__.py b/tests/test_api/test_types/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_utils/test_helper.py b/tests/test_utils/test_helper.py index 5477917e..ae07ce97 100644 --- a/tests/test_utils/test_helper.py +++ b/tests/test_utils/test_helper.py @@ -1,4 +1,5 @@ import pytest + from aiogram.utils.helper import Helper, HelperMode, Item, ListItem, OrderedHelper diff --git a/tests/test_utils/test_mixins.py b/tests/test_utils/test_mixins.py index 720bd38e..606d6aaa 100644 --- a/tests/test_utils/test_mixins.py +++ b/tests/test_utils/test_mixins.py @@ -1,4 +1,5 @@ import pytest + from aiogram.utils.mixins import ContextInstanceMixin, DataMixin @@ -47,5 +48,7 @@ class TestContextInstanceMixin: def test_set_wrong_type(self): obj = ContextObject() - with pytest.raises(TypeError, match=r"Value should be instance of 'ContextObject' not '.+'"): + with pytest.raises( + TypeError, match=r"Value should be instance of 'ContextObject' not '.+'" + ): obj.set_current(42)