├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .pre-commit-config.yaml ├── Pipfile ├── Pipfile.lock ├── README.md ├── aiogram_tests ├── __init__.py ├── exceptions.py ├── handler │ ├── __init__.py │ ├── base.py │ └── handler.py ├── mocked_bot.py ├── requester.py ├── types │ ├── __init__.py │ └── dataset │ │ ├── __init__.py │ │ └── base.py └── utils.py ├── dev_requirements.txt ├── examples ├── example_tests.py └── test_bot.py ├── pyproject.toml ├── pyrightconfig.json ├── requirements.txt ├── tests ├── __init__.py ├── __main__.py ├── bot.py ├── middleware.py ├── test_bot.py ├── test_dataset_item.py ├── test_handler.py ├── test_handlers.py ├── test_requester.py └── test_utils.py └── tox.ini /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-python@v4 15 | with: 16 | python-version: '3.9' 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | pip install -r dev_requirements.txt 21 | - name: Install pipenv 22 | run: | 23 | python3 -m pip install --upgrade pipenv wheel 24 | - name: Install dependencies 25 | run: | 26 | python3 -m pip install -r dev_requirements.txt 27 | - name: Run test suite 28 | run: | 29 | pipenv run test -v -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python template 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # Installer logs 30 | pip-log.txt 31 | pip-delete-this-directory.txt 32 | 33 | # Unit test / coverage reports 34 | htmlcov/ 35 | .tox/ 36 | .pytest_cache/ 37 | .coverage 38 | .coverage.* 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | *,cover 43 | 44 | # Sphinx documentation 45 | docs/_build/ 46 | 47 | # virtualenv 48 | .venv 49 | venv/ 50 | ENV/ 51 | 52 | # JetBrains 53 | .idea/ 54 | 55 | # Current project 56 | experiment.py 57 | 58 | # Doc's 59 | docs/html 60 | 61 | # i18n/l10n 62 | *.mo 63 | 64 | # pynev 65 | .python-version 66 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | # For reformatting code 5 | - id: black 6 | name: black 7 | entry: black 8 | language: python 9 | types: [ python ] 10 | args: [ --line-length=120, --target-version=py38 ] 11 | 12 | # For upgrade python code syntax for newer versions of the language 13 | - id: pyupgrade 14 | name: pyupgrade 15 | entry: pyupgrade 16 | language: python 17 | types: [ python ] 18 | 19 | # Tool for automatically reordering python imports 20 | - id: reorder-python-imports 21 | name: reorder-python-imports 22 | entry: reorder-python-imports 23 | language: python 24 | types: [ python ] 25 | 26 | # Removes unused imports and unused variables from Python code 27 | - id: autoflake 28 | name: autoflake 29 | entry: autoflake 30 | language: python 31 | types: [ python ] 32 | args: [ --in-place, --remove-all-unused-imports, --remove-duplicate-keys ] 33 | 34 | # Run python unittest 35 | - id: tests 36 | name: pytest 37 | entry: pipenv run pytest 38 | language: python 39 | 'types': [ python ] 40 | pass_filenames: false 41 | stages: [ commit ] 42 | 43 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | aiogram = ">=3.0.0" 8 | pytest = "*" 9 | pytest-asyncio = "*" 10 | 11 | [requires] 12 | python_version = "3.9" 13 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "b05e0ae166303c0d00aa79aef0abf3e7ef6212e8b36a77a8d72487d4bd2bd4c7" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.10" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "aiogram": { 20 | "hashes": [ 21 | "sha256:02e4120122af423575d48519cb469d15189f5da00a3237e715d492fe6d798bfd", 22 | "sha256:d2c5068cc89fc4012038268e111ca9249f66decce817522ec4d215dd829bc507" 23 | ], 24 | "index": "pypi", 25 | "version": "==2.22.2" 26 | }, 27 | "aiohttp": { 28 | "hashes": [ 29 | "sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8", 30 | "sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142", 31 | "sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18", 32 | "sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34", 33 | "sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a", 34 | "sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033", 35 | "sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06", 36 | "sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4", 37 | "sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d", 38 | "sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b", 39 | "sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc", 40 | "sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091", 41 | "sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d", 42 | "sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85", 43 | "sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb", 44 | "sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937", 45 | "sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf", 46 | "sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1", 47 | "sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b", 48 | "sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d", 49 | "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269", 50 | "sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da", 51 | "sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346", 52 | "sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494", 53 | "sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697", 54 | "sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4", 55 | "sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585", 56 | "sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c", 57 | "sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da", 58 | "sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad", 59 | "sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2", 60 | "sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6", 61 | "sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c", 62 | "sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849", 63 | "sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa", 64 | "sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b", 65 | "sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb", 66 | "sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7", 67 | "sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715", 68 | "sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76", 69 | "sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d", 70 | "sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276", 71 | "sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6", 72 | "sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37", 73 | "sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb", 74 | "sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d", 75 | "sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c", 76 | "sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446", 77 | "sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008", 78 | "sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342", 79 | "sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d", 80 | "sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7", 81 | "sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061", 82 | "sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba", 83 | "sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7", 84 | "sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290", 85 | "sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0", 86 | "sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d", 87 | "sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8", 88 | "sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f", 89 | "sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48", 90 | "sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502", 91 | "sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62", 92 | "sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9", 93 | "sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403", 94 | "sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77", 95 | "sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476", 96 | "sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e", 97 | "sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96", 98 | "sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5", 99 | "sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784", 100 | "sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091", 101 | "sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b", 102 | "sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97", 103 | "sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a", 104 | "sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2", 105 | "sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9", 106 | "sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d", 107 | "sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73", 108 | "sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017", 109 | "sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363", 110 | "sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c", 111 | "sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d", 112 | "sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618", 113 | "sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491", 114 | "sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b", 115 | "sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca" 116 | ], 117 | "markers": "python_version >= '3.6'", 118 | "version": "==3.8.3" 119 | }, 120 | "aiosignal": { 121 | "hashes": [ 122 | "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a", 123 | "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2" 124 | ], 125 | "markers": "python_version >= '3.6'", 126 | "version": "==1.2.0" 127 | }, 128 | "async-timeout": { 129 | "hashes": [ 130 | "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15", 131 | "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c" 132 | ], 133 | "markers": "python_version >= '3.6'", 134 | "version": "==4.0.2" 135 | }, 136 | "attrs": { 137 | "hashes": [ 138 | "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6", 139 | "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c" 140 | ], 141 | "markers": "python_version >= '3.5'", 142 | "version": "==22.1.0" 143 | }, 144 | "babel": { 145 | "hashes": [ 146 | "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9", 147 | "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0" 148 | ], 149 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 150 | "version": "==2.9.1" 151 | }, 152 | "certifi": { 153 | "hashes": [ 154 | "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14", 155 | "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382" 156 | ], 157 | "markers": "python_version >= '3.6'", 158 | "version": "==2022.9.24" 159 | }, 160 | "charset-normalizer": { 161 | "hashes": [ 162 | "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", 163 | "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" 164 | ], 165 | "markers": "python_full_version >= '3.6.0'", 166 | "version": "==2.1.1" 167 | }, 168 | "frozenlist": { 169 | "hashes": [ 170 | "sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e", 171 | "sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04", 172 | "sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944", 173 | "sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845", 174 | "sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f", 175 | "sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f", 176 | "sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb", 177 | "sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2", 178 | "sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3", 179 | "sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f", 180 | "sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a", 181 | "sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131", 182 | "sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1", 183 | "sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa", 184 | "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8", 185 | "sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b", 186 | "sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2", 187 | "sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e", 188 | "sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b", 189 | "sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b", 190 | "sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10", 191 | "sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170", 192 | "sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8", 193 | "sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b", 194 | "sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989", 195 | "sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd", 196 | "sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03", 197 | "sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519", 198 | "sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189", 199 | "sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b", 200 | "sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca", 201 | "sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff", 202 | "sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96", 203 | "sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d", 204 | "sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5", 205 | "sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2", 206 | "sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204", 207 | "sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a", 208 | "sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8", 209 | "sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141", 210 | "sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792", 211 | "sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9", 212 | "sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c", 213 | "sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c", 214 | "sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221", 215 | "sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d", 216 | "sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc", 217 | "sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f", 218 | "sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9", 219 | "sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef", 220 | "sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3", 221 | "sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab", 222 | "sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b", 223 | "sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db", 224 | "sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988", 225 | "sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6", 226 | "sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc", 227 | "sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83", 228 | "sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be" 229 | ], 230 | "markers": "python_version >= '3.7'", 231 | "version": "==1.3.1" 232 | }, 233 | "idna": { 234 | "hashes": [ 235 | "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", 236 | "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" 237 | ], 238 | "markers": "python_version >= '3.5'", 239 | "version": "==3.4" 240 | }, 241 | "iniconfig": { 242 | "hashes": [ 243 | "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", 244 | "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" 245 | ], 246 | "version": "==1.1.1" 247 | }, 248 | "multidict": { 249 | "hashes": [ 250 | "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60", 251 | "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c", 252 | "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672", 253 | "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51", 254 | "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032", 255 | "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2", 256 | "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b", 257 | "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80", 258 | "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88", 259 | "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a", 260 | "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d", 261 | "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389", 262 | "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c", 263 | "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9", 264 | "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c", 265 | "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516", 266 | "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b", 267 | "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43", 268 | "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee", 269 | "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227", 270 | "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d", 271 | "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae", 272 | "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7", 273 | "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4", 274 | "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9", 275 | "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f", 276 | "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013", 277 | "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9", 278 | "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e", 279 | "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693", 280 | "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a", 281 | "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15", 282 | "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb", 283 | "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96", 284 | "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87", 285 | "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376", 286 | "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658", 287 | "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0", 288 | "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071", 289 | "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360", 290 | "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc", 291 | "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3", 292 | "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba", 293 | "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8", 294 | "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9", 295 | "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2", 296 | "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3", 297 | "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68", 298 | "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8", 299 | "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d", 300 | "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49", 301 | "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608", 302 | "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57", 303 | "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86", 304 | "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20", 305 | "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293", 306 | "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849", 307 | "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937", 308 | "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d" 309 | ], 310 | "markers": "python_version >= '3.7'", 311 | "version": "==6.0.2" 312 | }, 313 | "packaging": { 314 | "hashes": [ 315 | "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", 316 | "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" 317 | ], 318 | "markers": "python_version >= '3.6'", 319 | "version": "==21.3" 320 | }, 321 | "pluggy": { 322 | "hashes": [ 323 | "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", 324 | "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" 325 | ], 326 | "markers": "python_version >= '3.6'", 327 | "version": "==1.0.0" 328 | }, 329 | "py": { 330 | "hashes": [ 331 | "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", 332 | "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" 333 | ], 334 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 335 | "version": "==1.11.0" 336 | }, 337 | "pyparsing": { 338 | "hashes": [ 339 | "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", 340 | "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" 341 | ], 342 | "markers": "python_full_version >= '3.6.8'", 343 | "version": "==3.0.9" 344 | }, 345 | "pytest": { 346 | "hashes": [ 347 | "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7", 348 | "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39" 349 | ], 350 | "index": "pypi", 351 | "version": "==7.1.3" 352 | }, 353 | "pytest-asyncio": { 354 | "hashes": [ 355 | "sha256:2c85a835df33fda40fe3973b451e0c194ca11bc2c007eabff90bb3d156fc172b", 356 | "sha256:626699de2a747611f3eeb64168b3575f70439b06c3d0206e6ceaeeb956e65519" 357 | ], 358 | "index": "pypi", 359 | "version": "==0.20.1" 360 | }, 361 | "pytz": { 362 | "hashes": [ 363 | "sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22", 364 | "sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914" 365 | ], 366 | "version": "==2022.5" 367 | }, 368 | "tomli": { 369 | "hashes": [ 370 | "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", 371 | "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" 372 | ], 373 | "markers": "python_version >= '3.7'", 374 | "version": "==2.0.1" 375 | }, 376 | "yarl": { 377 | "hashes": [ 378 | "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb", 379 | "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3", 380 | "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035", 381 | "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453", 382 | "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d", 383 | "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a", 384 | "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231", 385 | "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f", 386 | "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae", 387 | "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b", 388 | "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3", 389 | "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507", 390 | "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd", 391 | "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae", 392 | "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe", 393 | "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c", 394 | "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4", 395 | "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64", 396 | "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357", 397 | "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54", 398 | "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461", 399 | "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4", 400 | "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497", 401 | "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0", 402 | "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1", 403 | "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957", 404 | "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350", 405 | "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780", 406 | "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843", 407 | "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548", 408 | "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6", 409 | "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40", 410 | "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee", 411 | "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b", 412 | "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6", 413 | "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0", 414 | "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e", 415 | "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880", 416 | "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc", 417 | "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e", 418 | "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead", 419 | "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28", 420 | "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf", 421 | "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd", 422 | "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae", 423 | "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0", 424 | "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0", 425 | "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae", 426 | "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda", 427 | "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546", 428 | "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802", 429 | "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be", 430 | "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07", 431 | "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936", 432 | "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272", 433 | "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc", 434 | "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a", 435 | "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28", 436 | "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b" 437 | ], 438 | "markers": "python_version >= '3.7'", 439 | "version": "==1.8.1" 440 | } 441 | }, 442 | "develop": {} 443 | } 444 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aiogram Tests 2 | 3 | ***aiogram_tests*** is a testing library for bots written on aiogram 4 | 5 | ## 📚 Simple examples 6 | 7 | ### Simple handler test 8 | 9 | #### Simple bot: 10 | 11 | ```python 12 | from aiogram import Bot, Dispatcher, types 13 | from aiogram.fsm.context import FSMContext 14 | 15 | # Please, keep your bot tokens on environments, this code only example 16 | bot = Bot('123456789:AABBCCDDEEFFaabbccddeeff-1234567890') 17 | dp = Dispatcher() 18 | 19 | 20 | @dp.message() 21 | async def echo(message: types.Message, state: FSMContext) -> None: 22 | await message.answer(message.text) 23 | 24 | 25 | if __name__ == '__main__': 26 | dp.run_polling(bot) 27 | 28 | 29 | ``` 30 | 31 | #### Test cases: 32 | 33 | ```python 34 | import pytest 35 | 36 | from bot import echo 37 | 38 | from aiogram_tests import MockedBot 39 | from aiogram_tests.handler import MessageHandler 40 | from aiogram_tests.types.dataset import MESSAGE 41 | 42 | 43 | @pytest.mark.asyncio 44 | async def test_echo(): 45 | request = MockedBot(MessageHandler(echo)) 46 | calls = await request.query(message=MESSAGE.as_object(text="Hello, Bot!")) 47 | answer_message = calls.send_messsage.fetchone() 48 | assert answer_message.text == "Hello, Bot!" 49 | 50 | ``` 51 | 52 | ### ▶️ More examples 53 | 54 | -------------------------------------------------------------------------------- /aiogram_tests/__init__.py: -------------------------------------------------------------------------------- 1 | from .requester import MockedBot 2 | 3 | __all__ = ["MockedBot"] 4 | __version__ = "1.0.1" 5 | -------------------------------------------------------------------------------- /aiogram_tests/exceptions.py: -------------------------------------------------------------------------------- 1 | class MethodIsNotCalledError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /aiogram_tests/handler/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import RequestHandler 2 | from .handler import CallbackQueryHandler 3 | from .handler import MessageHandler 4 | from .handler import TelegramEventObserverHandler 5 | 6 | __all__ = ["MessageHandler", "CallbackQueryHandler", "TelegramEventObserverHandler", "RequestHandler"] 7 | -------------------------------------------------------------------------------- /aiogram_tests/handler/base.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable 2 | from typing import List 3 | from typing import Optional 4 | from typing import Type 5 | 6 | from aiogram import BaseMiddleware 7 | from aiogram import Bot 8 | from aiogram import Dispatcher 9 | from aiogram.dispatcher.event.telegram import TelegramEventObserver 10 | from aiogram.fsm.storage.memory import MemoryStorage 11 | from aiogram.methods import TelegramMethod 12 | from aiogram.methods.base import Response 13 | from aiogram.methods.base import TelegramType 14 | from aiogram.types import Chat 15 | from aiogram.types import User 16 | 17 | from aiogram_tests.mocked_bot import MockedBot 18 | from aiogram_tests.types.dataset import CHAT 19 | from aiogram_tests.types.dataset import USER 20 | 21 | 22 | class RequestHandler: 23 | def __init__( 24 | self, 25 | dp_middlewares: Iterable[BaseMiddleware] = None, 26 | exclude_observer_methods: Iterable[str] = None, 27 | auto_mock_success: bool = False, 28 | dp: Optional[Dispatcher] = None, 29 | **kwargs, 30 | ): 31 | self.bot = MockedBot(auto_mock_success=auto_mock_success) 32 | if dp is None: 33 | dp = Dispatcher(storage=MemoryStorage()) 34 | self.dp = dp 35 | 36 | if dp_middlewares is None: 37 | dp_middlewares = () 38 | 39 | if exclude_observer_methods is None: 40 | exclude_observer_methods = [] 41 | 42 | dispatcher_methods = self._get_dispatcher_event_observers() 43 | available_methods = tuple(set(dispatcher_methods) - set(exclude_observer_methods)) 44 | self._register_middlewares(available_methods, tuple(dp_middlewares)) 45 | 46 | Bot.set_current(self.bot) 47 | User.set_current(USER.as_object()) 48 | Chat.set_current(CHAT.as_object()) 49 | 50 | def _get_dispatcher_event_observers(self) -> List[str]: 51 | """ 52 | Returns a names for bot event observers, like message, callback_query etc. 53 | """ 54 | 55 | result = [] 56 | for name in dir(self.dp): 57 | if isinstance(getattr(self.dp, name), TelegramEventObserver): 58 | result.append(name) 59 | 60 | return result 61 | 62 | def _register_middlewares(self, event_observer: Iterable, middlewares: Iterable) -> None: 63 | for eo_name in event_observer: 64 | for m in middlewares: 65 | eo_obj = getattr(self.dp, eo_name) 66 | eo_obj.middleware.register(m) 67 | 68 | async def __call__(self, *args, **kwargs): 69 | raise NotImplementedError 70 | 71 | def add_result_for( 72 | self, 73 | method: Type[TelegramMethod[TelegramType]], 74 | ok: bool, 75 | result: TelegramType = None, 76 | description: Optional[str] = None, 77 | error_code: int = 200, 78 | migrate_to_chat_id: Optional[int] = None, 79 | retry_after: Optional[int] = None, 80 | ) -> Response[TelegramType]: 81 | response = self.bot.add_result_for( 82 | method=method, 83 | ok=ok, 84 | result=result, 85 | description=description, 86 | error_code=error_code, 87 | migrate_to_chat_id=migrate_to_chat_id, 88 | retry_after=retry_after, 89 | ) 90 | return response 91 | -------------------------------------------------------------------------------- /aiogram_tests/handler/handler.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | from typing import Dict 3 | from typing import Iterable 4 | from typing import List 5 | from typing import Union 6 | 7 | from aiogram import types 8 | from aiogram.filters import Filter 9 | from aiogram.filters import StateFilter 10 | from aiogram.fsm.state import State 11 | 12 | from .base import RequestHandler 13 | 14 | 15 | class TelegramEventObserverHandler(RequestHandler): 16 | def __init__( 17 | self, 18 | callback: Callable, 19 | *filters: Filter, 20 | state: Union[State, str, None] = None, 21 | state_data: Dict = None, 22 | dp_middlewares: Iterable = None, 23 | exclude_observer_methods: Iterable = None, 24 | **kwargs, 25 | ): 26 | super().__init__(dp_middlewares, exclude_observer_methods, **kwargs) 27 | 28 | self._callback = callback 29 | self._filters: List = list(filters) 30 | self._state: Union[State, str, None] = state 31 | self._state_data: Dict = state_data 32 | 33 | if self._state_data is None: 34 | self._state_data = {} 35 | 36 | if self._filters is None: 37 | self._filters = [] 38 | 39 | if not isinstance(self._state_data, dict): 40 | raise ValueError("state_data is not a dict") 41 | 42 | async def __call__(self, *args, **kwargs): 43 | if self._state: 44 | self._filters.append(StateFilter(self._state)) 45 | 46 | self.register_handler() 47 | 48 | if self._state: 49 | state = self.dp.fsm.get_context(self.bot, user_id=12345678, chat_id=12345678) 50 | await state.set_state(self._state) 51 | await state.update_data(**self._state_data) 52 | 53 | await self.feed_update(*args, **kwargs) 54 | 55 | def register_handler(self) -> None: 56 | """ 57 | Register TelegramEventObserver in dispatcher 58 | """ 59 | 60 | raise NotImplementedError 61 | 62 | async def feed_update(self, *args, **kwargs) -> None: 63 | """ 64 | Feed dispatcher updates 65 | """ 66 | 67 | raise NotImplementedError 68 | 69 | 70 | class MessageHandler(TelegramEventObserverHandler): 71 | def __init__( 72 | self, 73 | callback: Callable, 74 | *filters: Filter, 75 | state: Union[State, str, None] = None, 76 | state_data: Dict = None, 77 | dp_middlewares: Iterable = None, 78 | exclude_observer_methods: Iterable = None, 79 | **kwargs, 80 | ): 81 | super().__init__( 82 | callback, 83 | *filters, 84 | state=state, 85 | state_data=state_data, 86 | dp_middlewares=dp_middlewares, 87 | exclude_observer_methods=exclude_observer_methods, 88 | **kwargs, 89 | ) 90 | 91 | def register_handler(self) -> None: 92 | self.dp.message.register(self._callback, *self._filters) 93 | 94 | async def feed_update(self, message: types.Message, *args, **kwargs) -> None: 95 | await self.dp.feed_update(self.bot, types.Update(update_id=12345678, message=message)) 96 | 97 | 98 | class CallbackQueryHandler(TelegramEventObserverHandler): 99 | def __init__( 100 | self, 101 | callback: Callable, 102 | *filters: Filter, 103 | state: Union[State, str, None] = None, 104 | state_data: Dict = None, 105 | dp_middlewares: Iterable = None, 106 | exclude_observer_methods: Iterable = None, 107 | **kwargs, 108 | ): 109 | super().__init__( 110 | callback, 111 | *filters, 112 | state=state, 113 | state_data=state_data, 114 | dp_middlewares=dp_middlewares, 115 | exclude_observer_methods=exclude_observer_methods, 116 | **kwargs, 117 | ) 118 | 119 | def register_handler(self) -> None: 120 | self.dp.callback_query.register(self._callback, *self._filters) 121 | 122 | async def feed_update(self, callback_query: types.CallbackQuery, *args, **kwargs) -> None: 123 | await self.dp.feed_update(self.bot, types.Update(update_id=12345678, callback_query=callback_query)) 124 | -------------------------------------------------------------------------------- /aiogram_tests/mocked_bot.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from typing import AsyncGenerator 3 | from typing import Deque 4 | from typing import Optional 5 | from typing import Type 6 | from typing import Union 7 | 8 | from aiogram import Bot 9 | from aiogram.client.session.base import BaseSession 10 | from aiogram.methods import TelegramMethod 11 | from aiogram.methods.base import Request 12 | from aiogram.methods.base import Response 13 | from aiogram.methods.base import TelegramType 14 | from aiogram.types import ResponseParameters 15 | from aiogram.types import UNSET 16 | from aiogram.types import User 17 | 18 | 19 | class MockedSession(BaseSession): 20 | def __init__(self): 21 | super().__init__() 22 | self.responses: Deque[Response[TelegramType]] = deque() 23 | self.requests: Deque[Request] = deque() 24 | self.closed = True 25 | 26 | def add_result(self, response: Response[TelegramType]) -> Response[TelegramType]: 27 | self.responses.appendleft(response) 28 | return response 29 | 30 | def get_request(self) -> Union[Request, None]: 31 | if self.requests: 32 | return self.requests[-1] 33 | 34 | return None 35 | 36 | async def close(self): 37 | self.closed = True 38 | 39 | async def make_request( 40 | self, bot: Bot, method: TelegramMethod[TelegramType], timeout: Optional[int] = UNSET 41 | ) -> TelegramType: 42 | self.closed = False 43 | self.requests.append(method.build_request(bot)) 44 | response: Response[TelegramType] = self.responses.pop() 45 | self.check_response(method=method, status_code=response.error_code, content=response.json()) 46 | return response.result # type: ignore 47 | 48 | async def stream_content( 49 | self, url: str, timeout: int, chunk_size: int 50 | ) -> AsyncGenerator[bytes, None]: # pragma: no cover 51 | yield b"" 52 | 53 | 54 | class MockedBot(Bot): 55 | def __init__(self, auto_mock_success=False, **kwargs): 56 | super().__init__(kwargs.pop("token", "42:TEST"), session=MockedSession(), **kwargs) 57 | self.session = MockedSession() 58 | self._me = User( 59 | id=self.id, 60 | is_bot=True, 61 | first_name="FirstName", 62 | last_name="LastName", 63 | username="username", 64 | language_code="ru", 65 | ) 66 | self.auto_mock_success = auto_mock_success 67 | 68 | def add_result_for( 69 | self, 70 | method: Type[TelegramMethod[TelegramType]], 71 | ok: bool, 72 | result: TelegramType = None, 73 | description: Optional[str] = None, 74 | error_code: int = 200, 75 | migrate_to_chat_id: Optional[int] = None, 76 | retry_after: Optional[int] = None, 77 | ) -> Response[TelegramType]: 78 | response = Response[method.__returning__]( # type: ignore 79 | ok=ok, 80 | result=result, 81 | description=description, 82 | error_code=error_code, 83 | parameters=ResponseParameters( 84 | migrate_to_chat_id=migrate_to_chat_id, 85 | retry_after=retry_after, 86 | ), 87 | ) 88 | self.session.add_result(response) 89 | return response 90 | 91 | async def __call__(self, method: Type[TelegramMethod[TelegramType]], request_timeout: Optional[int] = None): 92 | if self.auto_mock_success: 93 | self.add_result_for(method, ok=True) 94 | return await super().__call__(method, request_timeout) 95 | 96 | def get_request(self) -> Request: 97 | return self.session.get_request() 98 | -------------------------------------------------------------------------------- /aiogram_tests/requester.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from typing import Type 3 | 4 | from aiogram.methods import TelegramMethod 5 | from aiogram.methods.base import Response 6 | from aiogram.methods.base import TelegramType 7 | 8 | from .exceptions import MethodIsNotCalledError 9 | from .handler.base import RequestHandler 10 | from .utils import camel_case2snake_case 11 | 12 | 13 | class CallsList(list): 14 | def fetchone(self): 15 | if len(self) > 0: 16 | return self[-1] 17 | else: 18 | return None 19 | 20 | def fetchall(self): 21 | return self 22 | 23 | 24 | class Calls: 25 | def _get_attributes(self): 26 | res = [] 27 | for item in dir(self): 28 | if item.startswith("_") or item.endswith("_"): 29 | continue 30 | 31 | if not callable(getattr(self, item)): 32 | res.append(item) 33 | 34 | return tuple(res) 35 | 36 | def __getattr__(self, item): 37 | if item in dir(self): 38 | return getattr(self, item) 39 | else: 40 | raise MethodIsNotCalledError( 41 | "method '%s' is not called by bot, so you cant to get this attribute. Called methods: %s" 42 | % (item, self._get_attributes()) 43 | ) 44 | 45 | 46 | class MockedBot: 47 | def __init__(self, request_handler: RequestHandler): 48 | self._handler: RequestHandler = request_handler 49 | 50 | async def query(self, *args, **kwargs) -> Calls: 51 | try: 52 | await self._handler(*args, **kwargs) 53 | except TypeError as e: 54 | raise AttributeError("incorrect argument name. %s" % e) 55 | 56 | requests = self._handler.bot.session.requests 57 | result = {} 58 | for r in requests: 59 | method_name = camel_case2snake_case(r.method) 60 | 61 | if method_name not in result: 62 | result[method_name] = CallsList() 63 | 64 | result[method_name].append(self._dict_to_obj(r.data)) 65 | 66 | return self._generate_result_obj(result) 67 | 68 | def add_result_for( 69 | self, 70 | method: Type[TelegramMethod[TelegramType]], 71 | ok: bool, 72 | result: TelegramType = None, 73 | description: Optional[str] = None, 74 | error_code: int = 200, 75 | migrate_to_chat_id: Optional[int] = None, 76 | retry_after: Optional[int] = None, 77 | ) -> Response[TelegramType]: 78 | response = self._handler.add_result_for( 79 | method=method, 80 | ok=ok, 81 | result=result, 82 | description=description, 83 | error_code=error_code, 84 | migrate_to_chat_id=migrate_to_chat_id, 85 | retry_after=retry_after, 86 | ) 87 | return response 88 | 89 | @staticmethod 90 | def _dict_to_obj(data: dict): 91 | GeneratedResponse = type("GeneratedResponse", (), data) 92 | return GeneratedResponse() 93 | 94 | @staticmethod 95 | def _generate_result_obj(data: dict): 96 | GeneratedCalls = type("GeneratedCalls", (Calls,), data) 97 | return GeneratedCalls() 98 | -------------------------------------------------------------------------------- /aiogram_tests/types/__init__.py: -------------------------------------------------------------------------------- 1 | from . import dataset 2 | 3 | __all__ = ("dataset",) 4 | -------------------------------------------------------------------------------- /aiogram_tests/types/dataset/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram import types 2 | 3 | from .base import DatasetItem 4 | 5 | USER = DatasetItem( 6 | { 7 | "id": 12345678, 8 | "is_bot": False, 9 | "first_name": "FirstName", 10 | "last_name": "LastName", 11 | "username": "username", 12 | "language_code": "ru", 13 | }, 14 | model=types.User, 15 | ) 16 | 17 | CHAT = DatasetItem( 18 | { 19 | "id": 12345678, 20 | "first_name": "FirstName", 21 | "last_name": "LastName", 22 | "username": "username", 23 | "type": "private", 24 | }, 25 | model=types.Chat, 26 | ) 27 | 28 | CHAT_PHOTO = DatasetItem( 29 | { 30 | "small_file_id": "small_file_id", 31 | "small_file_unique_id": "small_file_unique_id", 32 | "big_file_id": "big_file_id", 33 | "big_file_unique_id": "big_file_unique_id", 34 | }, 35 | model=types.ChatPhoto, 36 | ) 37 | 38 | PHOTO = DatasetItem( 39 | { 40 | "file_id": "AgADBAADFak0G88YZAf8OAug7bHyS9x2ZxkABHVfpJywcloRAAGAAQABAg", 41 | "file_unique_id": "file_unique_id", 42 | "file_size": 1101, 43 | "width": 90, 44 | "height": 51, 45 | }, 46 | model=types.PhotoSize, 47 | ) 48 | 49 | AUDIO = DatasetItem( 50 | { 51 | "duration": 236, 52 | "mime_type": "audio/mpeg3", 53 | "title": "The Best Song", 54 | "performer": "The Best Singer", 55 | "file_id": "CQADAgADbQEAAsnrIUpNoRRNsH7_hAI", 56 | "file_size": 9507774, 57 | "file_unique_id": "file_unique_id", 58 | }, 59 | model=types.Audio, 60 | ) 61 | 62 | BOT_COMMAND = DatasetItem( 63 | { 64 | "command": "start", 65 | "description": "Start bot", 66 | }, 67 | model=types.BotCommand, 68 | ) 69 | 70 | CHAT_MEMBER = DatasetItem( 71 | { 72 | "user": USER, 73 | "status": "administrator", 74 | "can_be_edited": False, 75 | "can_manage_chat": True, 76 | "can_change_info": True, 77 | "can_delete_messages": True, 78 | "can_invite_users": True, 79 | "can_restrict_members": True, 80 | "can_pin_messages": True, 81 | "can_promote_members": False, 82 | "can_manage_voice_chats": True, # Deprecated 83 | "can_manage_video_chats": True, 84 | "is_anonymous": False, 85 | }, 86 | model=types.ChatMember, 87 | ) 88 | 89 | CHAT_MEMBER_OWNER = DatasetItem( 90 | { 91 | "user": USER, 92 | "status": "creator", 93 | "is_anonymous": False, 94 | }, 95 | model=types.ChatMemberOwner, 96 | ) 97 | 98 | CONTACT = DatasetItem( 99 | { 100 | "phone_number": "88005553535", 101 | "first_name": "John", 102 | "last_name": "Smith", 103 | }, 104 | model=types.Contact, 105 | ) 106 | 107 | DICE = DatasetItem({"value": 6, "emoji": "🎲"}, model=types.Dice) 108 | 109 | DOCUMENT = DatasetItem( 110 | { 111 | "file_name": "test.docx", 112 | "mime_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 113 | "file_id": "BQADAgADpgADy_JxS66XQTBRHFleAg", 114 | "file_unique_id": "file_unique_id", 115 | "file_size": 21331, 116 | }, 117 | model=types.Document, 118 | ) 119 | 120 | ANIMATION = DatasetItem( 121 | {"file_id": "file_id", "file_unique_id": "file_unique_id", "width": 50, "height": 50, "duration": 50}, 122 | model=types.Animation, 123 | ) 124 | 125 | ENTITY_BOLD = DatasetItem( 126 | { 127 | "offset": 5, 128 | "length": 2, 129 | "type": "bold", 130 | } 131 | ) 132 | 133 | ENTITY_ITALIC = DatasetItem( 134 | { 135 | "offset": 8, 136 | "length": 1, 137 | "type": "italic", 138 | } 139 | ) 140 | 141 | ENTITY_LINK = DatasetItem( 142 | { 143 | "offset": 10, 144 | "length": 6, 145 | "type": "text_link", 146 | "url": "https://google.com/", 147 | } 148 | ) 149 | 150 | ENTITY_CODE = DatasetItem( 151 | { 152 | "offset": 17, 153 | "length": 7, 154 | "type": "code", 155 | } 156 | ) 157 | 158 | ENTITY_PRE = DatasetItem( 159 | { 160 | "offset": 30, 161 | "length": 4, 162 | "type": "pre", 163 | } 164 | ) 165 | 166 | ENTITY_MENTION = DatasetItem( 167 | { 168 | "offset": 47, 169 | "length": 9, 170 | "type": "mention", 171 | } 172 | ) 173 | 174 | GAME = DatasetItem( 175 | { 176 | "title": "Karate Kido", 177 | "description": "No trees were harmed in the making of this game :)", 178 | "photo": [PHOTO, PHOTO, PHOTO], 179 | "animation": ANIMATION, 180 | }, 181 | model=types.Game, 182 | ) 183 | 184 | INVOICE = DatasetItem( 185 | { 186 | "title": "Working Time Machine", 187 | "description": "Want to visit your great-great-great-grandparents? " 188 | "Make a fortune at the races? " 189 | "Shake hands with Hammurabi and take a stroll in the Hanging Gardens? " 190 | "Order our Working Time Machine today!", 191 | "start_parameter": "time-machine-example", 192 | "currency": "USD", 193 | "total_amount": 6250, 194 | }, 195 | model=types.Invoice, 196 | ) 197 | 198 | LOCATION = DatasetItem( 199 | { 200 | "latitude": 50.693416, 201 | "longitude": 30.624605, 202 | }, 203 | model=types.Location, 204 | ) 205 | 206 | VENUE = DatasetItem( 207 | { 208 | "location": LOCATION, 209 | "title": "Venue Name", 210 | "address": "Venue Address", 211 | "foursquare_id": "4e6f2cec483bad563d150f98", 212 | }, 213 | model=types.Venue, 214 | ) 215 | 216 | SHIPPING_ADDRESS = DatasetItem( 217 | { 218 | "country_code": "US", 219 | "state": "State", 220 | "city": "DefaultCity", 221 | "street_line1": "Central", 222 | "street_line2": "Middle", 223 | "post_code": "424242", 224 | }, 225 | model=types.ShippingAddress, 226 | ) 227 | 228 | STICKER = DatasetItem( 229 | { 230 | "width": 512, 231 | "height": 512, 232 | "emoji": "🛠", 233 | "set_name": "StickerSet", 234 | "thumb": PHOTO, 235 | "file_id": "AAbbCCddEEffGGhh1234567890", 236 | "file_size": 12345, 237 | "file_unique_id": "file_unique_id", 238 | "type": "type", 239 | "is_animated": False, 240 | "is_video": False, 241 | }, 242 | model=types.Sticker, 243 | ) 244 | 245 | SUCCESSFUL_PAYMENT = DatasetItem( 246 | { 247 | "currency": "USD", 248 | "total_amount": 6250, 249 | "invoice_payload": "HAPPY FRIDAYS COUPON", 250 | "telegram_payment_charge_id": "_", 251 | "provider_payment_charge_id": "12345678901234_test", 252 | }, 253 | model=types.SuccessfulPayment, 254 | ) 255 | 256 | VIDEO = DatasetItem( 257 | { 258 | "duration": 52, 259 | "width": 853, 260 | "height": 480, 261 | "mime_type": "video/quicktime", 262 | "thumb": PHOTO, 263 | "file_id": "BAADAgpAADdawy_JxS72kRvV3cortAg", 264 | "file_unique_id": "file_unique_id", 265 | "file_size": 10099782, 266 | }, 267 | model=types.Video, 268 | ) 269 | 270 | VIDEO_NOTE = DatasetItem( 271 | { 272 | "duration": 4, 273 | "length": 240, 274 | "thumb": PHOTO, 275 | "file_id": "AbCdEfGhIjKlMnOpQrStUvWxYz", 276 | "file_unique_id": "file_unique_id", 277 | "file_size": 186562, 278 | }, 279 | model=types.VideoNote, 280 | ) 281 | 282 | VOICE = DatasetItem( 283 | { 284 | "duration": 1, 285 | "mime_type": "audio/ogg", 286 | "file_id": "AwADawAgADADy_JxS2gopIVIIxlhAg", 287 | "file_unique_id": "file_unique_id", 288 | "file_size": 4321, 289 | }, 290 | model=types.Voice, 291 | ) 292 | 293 | CALLBACK_QUERY = DatasetItem( 294 | {"id": 12345678, "chat_instance": "AABBCC", "from": USER, "chat": CHAT, "data": "data"}, model=types.CallbackQuery 295 | ) 296 | 297 | CHANNEL = DatasetItem( 298 | { 299 | "type": "channel", 300 | "username": "best_channel_ever", 301 | "id": -1001065170817, 302 | }, 303 | model=types.Chat, 304 | ) 305 | 306 | CHANNEL_POST = DatasetItem( 307 | {"message_id": 12345, "sender_chat": CHANNEL, "chat": CHANNEL, "date": 1508825372, "text": "Hi, channel!"}, 308 | model=types.Message, 309 | ) 310 | 311 | EDITED_CHANNEL_POST = DatasetItem( 312 | { 313 | "message_id": 12345, 314 | "sender_chat": CHANNEL, 315 | "chat": CHANNEL, 316 | "date": 1508825372, 317 | "edit_date": 1508825379, 318 | "text": "Hi, channel! (edited)", 319 | }, 320 | model=types.Message, 321 | ) 322 | 323 | EDITED_MESSAGE = DatasetItem( 324 | { 325 | "message_id": 12345, 326 | "from": USER, 327 | "chat": CHAT, 328 | "date": 1508825372, 329 | "edit_date": 1508825379, 330 | "text": "hi there (edited)", 331 | }, 332 | model=types.Message, 333 | ) 334 | 335 | FORWARDED_MESSAGE = DatasetItem( 336 | { 337 | "message_id": 12345, 338 | "from": USER, 339 | "chat": CHAT, 340 | "date": 1522828529, 341 | "forward_from_chat": CHAT, 342 | "forward_from_message_id": 123, 343 | "forward_date": 1522749037, 344 | "text": "Forwarded text with entities from public channel ", 345 | "entities": [ENTITY_BOLD, ENTITY_CODE, ENTITY_ITALIC, ENTITY_LINK, ENTITY_LINK, ENTITY_MENTION, ENTITY_PRE], 346 | }, 347 | model=types.Message, 348 | ) 349 | 350 | MESSAGE = DatasetItem( 351 | {"message_id": 11223, "from": USER, "chat": CHAT, "date": 1508709711, "text": "Hi, world!"}, 352 | model=types.Message, 353 | ) 354 | 355 | MESSAGE_WITH_AUDIO = DatasetItem( 356 | { 357 | "message_id": 12345, 358 | "from": USER, 359 | "chat": CHAT, 360 | "date": 1508739776, 361 | "audio": AUDIO, 362 | "caption": "This is my favourite song", 363 | }, 364 | model=types.Message, 365 | ) 366 | 367 | MESSAGE_WITH_CONTACT = DatasetItem( 368 | { 369 | "message_id": 56006, 370 | "from": USER, 371 | "chat": CHAT, 372 | "date": 1522850298, 373 | "contact": CONTACT, 374 | }, 375 | model=types.Message, 376 | ) 377 | 378 | MESSAGE_WITH_DICE = DatasetItem( 379 | {"message_id": 12345, "from": USER, "chat": CHAT, "date": 1508768012, "dice": DICE}, model=types.Message 380 | ) 381 | 382 | MESSAGE_WITH_DOCUMENT = DatasetItem( 383 | { 384 | "message_id": 12345, 385 | "from": USER, 386 | "chat": CHAT, 387 | "date": 1508768012, 388 | "document": DOCUMENT, 389 | "caption": "Read my document", 390 | }, 391 | model=types.Message, 392 | ) 393 | 394 | MESSAGE_WITH_GAME = DatasetItem( 395 | { 396 | "message_id": 12345, 397 | "from": USER, 398 | "chat": CHAT, 399 | "date": 1508824810, 400 | "game": GAME, 401 | }, 402 | model=types.Message, 403 | ) 404 | 405 | MESSAGE_WITH_INVOICE = DatasetItem( 406 | { 407 | "message_id": 9772, 408 | "from": USER, 409 | "chat": CHAT, 410 | "date": 1508761719, 411 | "invoice": INVOICE, 412 | }, 413 | model=types.Message, 414 | ) 415 | 416 | MESSAGE_WITH_LOCATION = DatasetItem( 417 | { 418 | "message_id": 12345, 419 | "from": USER, 420 | "chat": CHAT, 421 | "date": 1508755473, 422 | "location": LOCATION, 423 | }, 424 | model=types.Message, 425 | ) 426 | 427 | MESSAGE_WITH_MIGRATE_TO_CHAT_ID = DatasetItem( 428 | { 429 | "message_id": 12345, 430 | "from": USER, 431 | "chat": CHAT, 432 | "date": 1526943253, 433 | "migrate_to_chat_id": -1234567890987, 434 | }, 435 | model=types.Message, 436 | ) 437 | 438 | MESSAGE_WITH_MIGRATE_FROM_CHAT_ID = DatasetItem( 439 | { 440 | "message_id": 12345, 441 | "from": USER, 442 | "chat": CHAT, 443 | "date": 1526943253, 444 | "migrate_from_chat_id": -123456789, 445 | }, 446 | model=types.Message, 447 | ) 448 | 449 | MESSAGE_WITH_PHOTO = DatasetItem( 450 | { 451 | "message_id": 12345, 452 | "from": USER, 453 | "chat": CHAT, 454 | "date": 1508825154, 455 | "photo": [PHOTO, PHOTO, PHOTO, PHOTO], 456 | "caption": "photo description", 457 | }, 458 | model=types.Message, 459 | ) 460 | 461 | MESSAGE_WITH_MEDIA_GROUP = DatasetItem( 462 | { 463 | "message_id": 55966, 464 | "from": USER, 465 | "chat": CHAT, 466 | "date": 1522843665, 467 | "media_group_id": "12182749320567362", 468 | "photo": [PHOTO, PHOTO, PHOTO, PHOTO], 469 | }, 470 | model=types.Message, 471 | ) 472 | 473 | MESSAGE_WITH_STICKER = DatasetItem( 474 | { 475 | "message_id": 12345, 476 | "from": USER, 477 | "chat": CHAT, 478 | "date": 1508771450, 479 | "sticker": STICKER, 480 | }, 481 | model=types.Message, 482 | ) 483 | 484 | MESSAGE_WITH_SUCCESSFUL_PAYMENT = DatasetItem( 485 | { 486 | "message_id": 9768, 487 | "from": USER, 488 | "chat": CHAT, 489 | "date": 1508761169, 490 | "successful_payment": SUCCESSFUL_PAYMENT, 491 | }, 492 | model=types.Message, 493 | ) 494 | 495 | MESSAGE_WITH_VENUE = DatasetItem( 496 | { 497 | "message_id": 56004, 498 | "from": USER, 499 | "chat": CHAT, 500 | "date": 1522849819, 501 | "location": LOCATION, 502 | "venue": VENUE, 503 | }, 504 | model=types.Message, 505 | ) 506 | 507 | MESSAGE_WITH_VIDEO = DatasetItem( 508 | { 509 | "message_id": 12345, 510 | "from": USER, 511 | "chat": CHAT, 512 | "date": 1508756494, 513 | "video": VIDEO, 514 | "caption": "description", 515 | }, 516 | model=types.Message, 517 | ) 518 | 519 | MESSAGE_WITH_VIDEO_NOTE = DatasetItem( 520 | { 521 | "message_id": 55934, 522 | "from": USER, 523 | "chat": CHAT, 524 | "date": 1522835890, 525 | "video_note": VIDEO_NOTE, 526 | }, 527 | model=types.Message, 528 | ) 529 | 530 | MESSAGE_WITH_VOICE = DatasetItem( 531 | { 532 | "message_id": 12345, 533 | "from": USER, 534 | "chat": CHAT, 535 | "date": 1508768403, 536 | "voice": VOICE, 537 | }, 538 | model=types.Message, 539 | ) 540 | 541 | MESSAGE_FROM_CHANNEL = DatasetItem( 542 | { 543 | "message_id": 123432, 544 | "from": None, 545 | "chat": CHANNEL, 546 | "date": 1508768405, 547 | "text": "Hi, world!", 548 | }, 549 | model=types.Message, 550 | ) 551 | 552 | PRE_CHECKOUT_QUERY = DatasetItem( 553 | { 554 | "id": "262181558630368727", 555 | "from": USER, 556 | "currency": "USD", 557 | "total_amount": 6250, 558 | "invoice_payload": "HAPPY FRIDAYS COUPON", 559 | }, 560 | model=types.PreCheckoutQuery, 561 | ) 562 | 563 | REPLY_MESSAGE = DatasetItem( 564 | { 565 | "message_id": 12345, 566 | "from": USER, 567 | "chat": CHAT, 568 | "date": 1508751866, 569 | "reply_to_message": MESSAGE, 570 | "text": "Reply to quoted message", 571 | }, 572 | model=types.Message, 573 | ) 574 | 575 | SHIPPING_QUERY = DatasetItem( 576 | { 577 | "id": "262181558684397422", 578 | "from": USER, 579 | "invoice_payload": "HAPPY FRIDAYS COUPON", 580 | "shipping_address": SHIPPING_ADDRESS, 581 | }, 582 | model=types.ShippingQuery, 583 | ) 584 | 585 | USER_PROFILE_PHOTOS = DatasetItem( 586 | { 587 | "total_count": 1, 588 | "photos": [ 589 | [PHOTO, PHOTO, PHOTO], 590 | ], 591 | }, 592 | model=types.UserProfilePhotos, 593 | ) 594 | 595 | FILE = DatasetItem( 596 | {"file_id": "XXXYYYZZZ", "file_size": 5254, "file_path": "voice/file_8", "file_unique_id": "file_unique_id"}, 597 | model=types.File, 598 | ) 599 | 600 | UPDATE = DatasetItem( 601 | { 602 | "update_id": 123456789, 603 | "message": MESSAGE, 604 | }, 605 | model=types.Update, 606 | ) 607 | 608 | WEBHOOK_INFO = DatasetItem( 609 | { 610 | "url": "", 611 | "has_custom_certificate": False, 612 | "pending_update_count": 0, 613 | }, 614 | model=types.WebhookInfo, 615 | ) 616 | 617 | REPLY_KEYBOARD_MARKUP = DatasetItem( 618 | { 619 | "keyboard": [[{"text": "something here"}]], 620 | "resize_keyboard": True, 621 | }, 622 | model=types.ReplyKeyboardMarkup, 623 | ) 624 | 625 | CHAT_PERMISSIONS = DatasetItem( 626 | { 627 | "can_send_messages": True, 628 | "can_send_media_messages": True, 629 | "can_send_polls": True, 630 | "can_send_other_messages": True, 631 | "can_add_web_page_previews": True, 632 | "can_change_info": True, 633 | "can_invite_users": True, 634 | "can_pin_messages": True, 635 | }, 636 | model=types.ChatPermissions, 637 | ) 638 | 639 | CHAT_LOCATION = DatasetItem( 640 | { 641 | "location": LOCATION, 642 | "address": "address", 643 | }, 644 | model=types.ChatLocation, 645 | ) 646 | 647 | FULL_CHAT = DatasetItem( 648 | { 649 | **CHAT, 650 | "photo": CHAT_PHOTO, 651 | "bio": "bio", 652 | "has_private_forwards": False, 653 | "description": "description", 654 | "invite_link": "invite_link", 655 | "pinned_message": MESSAGE, 656 | "permissions": CHAT_PERMISSIONS, 657 | "slow_mode_delay": 10, 658 | "message_auto_delete_time": 60, 659 | "has_protected_content": True, 660 | "sticker_set_name": "sticker_set_name", 661 | "can_set_sticker_set": True, 662 | "linked_chat_id": -1234567890, 663 | "location": CHAT_LOCATION, 664 | }, 665 | model=types.Chat, 666 | ) 667 | -------------------------------------------------------------------------------- /aiogram_tests/types/dataset/base.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Mapping 2 | from typing import Any 3 | from typing import Union 4 | 5 | 6 | class DatasetItem(Mapping): 7 | def __init__(self, data: dict, *, model=None, name=None): 8 | self._data = data 9 | self._name = name 10 | self._model = model 11 | 12 | @property 13 | def data(self) -> dict: 14 | return self._data 15 | 16 | @property 17 | def name(self) -> str: 18 | return self._name 19 | 20 | @property 21 | def model(self) -> Any: 22 | return self._model 23 | 24 | def as_object(self, **replace_args) -> Union[Any, None]: 25 | """ 26 | Return an object from dict 27 | 28 | :return: Any | None 29 | """ 30 | try: 31 | data = self._data.copy() 32 | data.update(**replace_args) 33 | if self._model and isinstance(self._data, dict): 34 | return self._recursive_as_object(data, self._model) 35 | else: 36 | return data 37 | except (AttributeError, TypeError): 38 | return None 39 | 40 | def _recursive_as_object(self, data: dict, model: Any): 41 | """ 42 | This method is converting dict data to object, if one of the params is the DatasetItem method will be 43 | recursive convert it; 44 | 45 | :param data: the dict that should be as object 46 | :param model: the object that will be returned 47 | :return: 48 | """ 49 | result_data = data.copy() 50 | for key, value in data.items(): 51 | if isinstance(value, DatasetItem): 52 | result_data[key] = self._recursive_as_object(value.data, value.model) 53 | elif isinstance(value, list): 54 | for index, item in enumerate(value): 55 | if not isinstance(item, (DatasetItem, list)): 56 | continue 57 | 58 | result_data[key][index] = self._recursive_as_object(item.data, item.model) 59 | 60 | return model(**result_data) 61 | 62 | def __iter__(self): 63 | return iter(self._data.keys()) 64 | 65 | def __getitem__(self, item): 66 | return self._data[item] 67 | 68 | def __len__(self): 69 | return len(self._data) 70 | -------------------------------------------------------------------------------- /aiogram_tests/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def camel_case2snake_case(var: str) -> str: 5 | return re.sub(r"(? None: 26 | await message.answer(message.text) 27 | 28 | 29 | @dp.message(Command(commands=["start"])) 30 | async def command_handler(message: types.Message, state: FSMContext) -> None: 31 | await message.answer("Hello, new user!") 32 | 33 | 34 | @dp.message(States.state) 35 | async def message_handler_with_state(message: types.Message, state: FSMContext) -> None: 36 | await message.reply("Hello, from state!") 37 | 38 | 39 | @dp.message(States.state_1) 40 | async def message_handler_with_state_data(message: types.Message, state: FSMContext) -> None: 41 | data = await state.get_data() 42 | await message.answer(f'Info from state data: {data["info"]}') 43 | 44 | 45 | @dp.callback_query(TestCallbackData.filter()) 46 | async def callback_query_handler( 47 | callback_query: types.CallbackQuery, callback_data: TestCallbackData, state: FSMContext 48 | ) -> None: 49 | name = callback_data.name 50 | await callback_query.message.answer(f"Hello, {name}") 51 | 52 | 53 | @dp.callback_query(States.state, TestCallbackData.filter()) 54 | async def callback_query_handler_with_state( 55 | callback_query: types.CallbackQuery, callback_data: dict, state: FSMContext 56 | ) -> None: 57 | await callback_query.answer("Hello, from state!") 58 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["aiogram ~= 3.0.0b7", "setuptools"] 3 | 4 | [project] 5 | name = "aiogram_tests" 6 | version = "1.0.2" 7 | authors = [ 8 | { name = "Timur", email = "pavlov.timur556@yandex.ru" } 9 | ] 10 | description = "A library for testing your bots on aiogram" 11 | readme = "README.md" 12 | classifiers = [ 13 | "Programming Language :: Python :: 3", 14 | "License :: OSI Approved :: MIT License", 15 | "Operating System :: OS Independent", 16 | ] 17 | 18 | [project.urls] 19 | "Homepage" = "https://github.com/aiogram-tests/aiogram_tests" 20 | -------------------------------------------------------------------------------- /pyrightconfig.json: -------------------------------------------------------------------------------- 1 | {"venv": "venv", "venvPath": "."} -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiogram==3.0.0b7 2 | pytest 3 | pytest-asyncio 4 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OCCASS/aiogram_tests/331a15ce49c25ad7a70a3e58f253fbf828ba36ee/tests/__init__.py -------------------------------------------------------------------------------- /tests/__main__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OCCASS/aiogram_tests/331a15ce49c25ad7a70a3e58f253fbf828ba36ee/tests/__main__.py -------------------------------------------------------------------------------- /tests/bot.py: -------------------------------------------------------------------------------- 1 | from aiogram import Dispatcher 2 | from aiogram import types 3 | from aiogram.exceptions import TelegramAPIError 4 | from aiogram.filters import Command 5 | from aiogram.filters import CommandObject 6 | from aiogram.filters.callback_data import CallbackData 7 | from aiogram.fsm.context import FSMContext 8 | from aiogram.fsm.state import State 9 | from aiogram.fsm.state import StatesGroup 10 | from aiogram.fsm.storage.memory import MemoryStorage 11 | 12 | 13 | class TestCallbackData(CallbackData, prefix="test_callback_data"): 14 | id: int 15 | name: str 16 | 17 | 18 | dp = Dispatcher(storage=MemoryStorage()) 19 | 20 | 21 | class States(StatesGroup): 22 | state = State() 23 | state_1 = State() 24 | 25 | 26 | @dp.message(Command(commands=["start"])) 27 | async def command_handler(message: types.Message, state: FSMContext) -> None: 28 | await message.answer("Hello, new user!") 29 | 30 | 31 | @dp.message(States.state) 32 | async def message_handler_with_state(message: types.Message, state: FSMContext) -> None: 33 | await message.reply("Hello, from state!") 34 | 35 | 36 | @dp.message(States.state_1) 37 | async def message_handler_with_state_data(message: types.Message, state: FSMContext) -> None: 38 | data = await state.get_data() 39 | await message.answer(f'Info from state data: {data["info"]}') 40 | 41 | 42 | @dp.callback_query(TestCallbackData.filter()) 43 | async def callback_query_handler( 44 | callback_query: types.CallbackQuery, callback_data: TestCallbackData, state: FSMContext 45 | ) -> None: 46 | name = callback_data.name 47 | await callback_query.message.answer(f"Hello, {name}") 48 | 49 | 50 | @dp.callback_query(States.state, TestCallbackData.filter()) 51 | async def callback_query_handler_with_state( 52 | callback_query: types.CallbackQuery, callback_data: dict, state: FSMContext 53 | ) -> None: 54 | await callback_query.answer("Hello, from state!") 55 | 56 | 57 | @dp.message(Command(commands="foo")) 58 | async def foo_command_handler(m: types.Message, command: CommandObject): 59 | if command.args == "fail": 60 | try: 61 | return await m.answer("try to don't failed") 62 | except TelegramAPIError: 63 | return await m.answer("sorry, i'm failed") 64 | else: 65 | await m.answer("success") 66 | 67 | 68 | @dp.message() 69 | async def message_handler(message: types.Message, state: FSMContext) -> None: 70 | await message.answer(message.text) 71 | -------------------------------------------------------------------------------- /tests/middleware.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from typing import Awaitable 3 | from typing import Callable 4 | from typing import Dict 5 | 6 | from aiogram import BaseMiddleware 7 | from aiogram.types import Message 8 | 9 | 10 | class TestMiddleware(BaseMiddleware): 11 | async def __call__( 12 | self, handler: Callable[[Message, Dict[str, Any]], Awaitable[Any]], event: Message, data: Dict[str, Any] 13 | ): 14 | return await handler(event, data) 15 | -------------------------------------------------------------------------------- /tests/test_bot.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from aiogram.filters import Command 3 | from aiogram.methods import AnswerCallbackQuery 4 | from aiogram.methods import SendMessage 5 | 6 | from .bot import callback_query_handler 7 | from .bot import callback_query_handler_with_state 8 | from .bot import command_handler 9 | from .bot import dp 10 | from .bot import foo_command_handler 11 | from .bot import message_handler 12 | from .bot import message_handler_with_state 13 | from .bot import message_handler_with_state_data 14 | from .bot import States 15 | from .bot import TestCallbackData 16 | from aiogram_tests import MockedBot 17 | from aiogram_tests.handler import CallbackQueryHandler 18 | from aiogram_tests.handler import MessageHandler 19 | from aiogram_tests.types.dataset import CALLBACK_QUERY 20 | from aiogram_tests.types.dataset import MESSAGE 21 | 22 | 23 | @pytest.mark.asyncio 24 | async def test_message_handler(): 25 | requester = MockedBot(request_handler=MessageHandler(message_handler, auto_mock_success=True)) 26 | calls = await requester.query(MESSAGE.as_object(text="Hello!")) 27 | answer_message = calls.send_message.fetchone().text 28 | assert answer_message == "Hello!" 29 | 30 | 31 | @pytest.mark.asyncio 32 | async def test_command_handler(): 33 | requester = MockedBot(request_handler=MessageHandler(command_handler, Command(commands=["start"]))) 34 | requester.add_result_for(SendMessage, ok=True) 35 | calls = await requester.query(MESSAGE.as_object(text="/start")) 36 | answer_message = calls.send_message.fetchone().text 37 | assert answer_message == "Hello, new user!" 38 | 39 | 40 | @pytest.mark.asyncio 41 | async def test_message_handler_with_state(): 42 | requester = MockedBot(request_handler=MessageHandler(message_handler_with_state, state=States.state)) 43 | requester.add_result_for(SendMessage, ok=True) 44 | calls = await requester.query(MESSAGE.as_object(text="Hello, bot!")) 45 | answer_message = calls.send_message.fetchone().text 46 | assert answer_message == "Hello, from state!" 47 | 48 | 49 | @pytest.mark.asyncio 50 | async def test_callback_query_handler(): 51 | requester = MockedBot(request_handler=CallbackQueryHandler(callback_query_handler, TestCallbackData.filter())) 52 | requester.add_result_for(AnswerCallbackQuery, ok=True) 53 | requester.add_result_for(SendMessage, ok=True) 54 | 55 | callback_query = CALLBACK_QUERY.as_object( 56 | data=TestCallbackData(id=1, name="John").pack(), message=MESSAGE.as_object(text="Hello world!") 57 | ) 58 | calls = await requester.query(callback_query) 59 | 60 | answer_text = calls.send_message.fetchone().text 61 | assert answer_text == "Hello, John" 62 | 63 | requester.add_result_for(AnswerCallbackQuery, ok=True) 64 | requester.add_result_for(SendMessage, ok=True) 65 | callback_query = CALLBACK_QUERY.as_object( 66 | data=TestCallbackData(id=1, name="Mike").pack(), message=MESSAGE.as_object(text="Hello world!") 67 | ) 68 | calls = await requester.query(callback_query) 69 | 70 | answer_text = calls.send_message.fetchone().text 71 | assert answer_text == "Hello, Mike" 72 | 73 | 74 | @pytest.mark.asyncio 75 | async def test_callback_query_handler_with_state(): 76 | requester = MockedBot( 77 | request_handler=CallbackQueryHandler(callback_query_handler_with_state, TestCallbackData.filter()) 78 | ) 79 | 80 | requester.add_result_for(AnswerCallbackQuery, ok=True) 81 | requester.add_result_for(SendMessage, ok=True) 82 | 83 | callback_query = CALLBACK_QUERY.as_object(data=TestCallbackData(id=1, name="John").pack()) 84 | calls = await requester.query(callback_query) 85 | 86 | answer_text = calls.answer_callback_query.fetchone().text 87 | assert answer_text == "Hello, from state!" 88 | 89 | 90 | @pytest.mark.asyncio 91 | async def test_handler_with_state_data(): 92 | requester = MockedBot( 93 | request_handler=MessageHandler( 94 | message_handler_with_state_data, state=States.state_1, state_data={"info": "this is message handler"} 95 | ) 96 | ) 97 | 98 | requester.add_result_for(SendMessage, ok=True) 99 | calls = await requester.query(MESSAGE.as_object()) 100 | answer_message = calls.send_message.fetchone() 101 | assert answer_message.text == "Info from state data: this is message handler" 102 | 103 | 104 | @pytest.mark.asyncio 105 | async def test_handler_with_fail(): 106 | requester = MockedBot(request_handler=MessageHandler(foo_command_handler, dp=dp)) 107 | 108 | requester.add_result_for(SendMessage, ok=False, description="Have no rights to send a message", error_code=401) 109 | requester.add_result_for(SendMessage, ok=True) 110 | calls = await requester.query(MESSAGE.as_object(text="/foo fail")) 111 | answer_message = calls.send_message.pop() 112 | assert answer_message.text == "sorry, i'm failed" 113 | answer_message = calls.send_message.pop() 114 | assert answer_message.text == "try to don't failed" 115 | assert calls.send_message.fetchone() is None 116 | 117 | requester.add_result_for(SendMessage, ok=True) 118 | calls = await requester.query(MESSAGE.as_object(text="/foo")) 119 | answer_message = calls.send_message.pop() 120 | assert answer_message.text == "success" 121 | -------------------------------------------------------------------------------- /tests/test_dataset_item.py: -------------------------------------------------------------------------------- 1 | from aiogram import types 2 | 3 | import aiogram_tests.types.dataset as dataset 4 | from aiogram_tests.types.dataset import DatasetItem 5 | 6 | 7 | def test_as_object(): 8 | dataset_item = DatasetItem({"firstArg": 1, "secondArg": 2}) 9 | assert dataset_item.as_object() == {"firstArg": 1, "secondArg": 2} 10 | assert dataset_item.as_object(firstArg=3) == {"firstArg": 3, "secondArg": 2} 11 | assert dataset_item.as_object(thirdArg=3) == {"firstArg": 1, "secondArg": 2, "thirdArg": 3} 12 | 13 | 14 | def test_as_object_converting(): 15 | dataset_item = DatasetItem( 16 | { 17 | "id": 12345678, 18 | "is_bot": False, 19 | "first_name": "FirstName", 20 | "last_name": "LastName", 21 | "username": "username", 22 | }, 23 | model=types.User, 24 | ) 25 | assert dataset_item.as_object() == types.User( 26 | id=12345678, is_bot=False, first_name="FirstName", last_name="LastName", username="username" 27 | ) 28 | assert dataset_item.as_object(first_name="EditedFirstName") == types.User( 29 | id=12345678, is_bot=False, first_name="EditedFirstName", last_name="LastName", username="username" 30 | ) 31 | assert dataset_item.as_object(language_code="ru") == types.User( 32 | id=12345678, 33 | is_bot=False, 34 | first_name="FirstName", 35 | last_name="LastName", 36 | username="username", 37 | language_code="ru", 38 | ) 39 | 40 | 41 | def test_as_object_converting_with_nesting(): 42 | dataset_item = DatasetItem( 43 | { 44 | "message_id": 11223, 45 | "from": { 46 | "id": 12345678, 47 | "is_bot": False, 48 | "first_name": "FirstName", 49 | "last_name": "LastName", 50 | "username": "username", 51 | }, 52 | "chat": { 53 | "id": 12345678, 54 | "first_name": "FirstName", 55 | "last_name": "LastName", 56 | "username": "username", 57 | "type": "private", 58 | }, 59 | "date": 1508709711, 60 | "text": "Hi, world!", 61 | }, 62 | model=types.Message, 63 | ) 64 | assert dataset_item.as_object() == types.Message( 65 | message_id=11223, 66 | from_user=types.User( 67 | id=12345678, is_bot=False, first_name="FirstName", last_name="LastName", username="username" 68 | ), 69 | chat=types.Chat(id=12345678, first_name="FirstName", last_name="LastName", username="username", type="private"), 70 | date=1508709711, 71 | text="Hi, world!", 72 | ) 73 | 74 | 75 | def test_converting_all_dataset_items_to_model(): 76 | all_items = (getattr(dataset, name) for name in dir(dataset)) 77 | for item in all_items: 78 | if not isinstance(item, DatasetItem): 79 | continue 80 | 81 | item.as_object() 82 | -------------------------------------------------------------------------------- /tests/test_handler.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from aiogram.filters import StateFilter 3 | 4 | from .middleware import TestMiddleware 5 | from aiogram_tests.handler import MessageHandler 6 | from aiogram_tests.handler import RequestHandler 7 | from aiogram_tests.handler import TelegramEventObserverHandler 8 | 9 | 10 | def test_request_handler_initialization(): 11 | RequestHandler((), ()) 12 | 13 | 14 | def test_request_handler_dp_middlewares(): 15 | r_h = RequestHandler(dp_middlewares=(TestMiddleware(),)) 16 | middlewares_count = len(r_h.dp.message.middleware) 17 | assert middlewares_count == 1 18 | 19 | r_h = RequestHandler(dp_middlewares=(TestMiddleware(), TestMiddleware())) 20 | middlewares_count = len(r_h.dp.message.middleware) 21 | assert middlewares_count == 2 22 | 23 | r_h = RequestHandler(dp_middlewares=(TestMiddleware(), TestMiddleware()), exclude_observer_methods=["message"]) 24 | middlewares_count = len(r_h.dp.message.middleware) 25 | assert middlewares_count == 0 26 | 27 | 28 | def test_telegram_observ_methods_handler_init(): 29 | async def callback(*args, **kwargs): 30 | pass 31 | 32 | with pytest.raises(ValueError): 33 | _ = TelegramEventObserverHandler(callback, state_data=[]) 34 | 35 | 36 | @pytest.mark.asyncio 37 | async def test_telegram_observ_handler(): 38 | async def callback(*args, **kwargs): 39 | pass 40 | 41 | t_h = MessageHandler(callback) 42 | await t_h(None) 43 | handlers_count = len(t_h.dp.message.handlers) 44 | assert handlers_count == 1 45 | 46 | t_h = MessageHandler(callback, StateFilter(None)) 47 | await t_h(None) 48 | handlers_count = len(t_h.dp.message.handlers) 49 | assert handlers_count == 1 50 | 51 | 52 | @pytest.mark.asyncio 53 | async def test_state_telegram_observ_handler(): 54 | async def callback(*args, **kwargs): 55 | pass 56 | 57 | t_h = MessageHandler(callback, state="state", state_data={"name": "Mike"}) 58 | await t_h(None) 59 | 60 | context = t_h.dp.fsm.get_context(t_h.bot, 12345678, 12345678) 61 | state = await context.get_state() 62 | data = await context.get_data() 63 | 64 | assert state == "state" 65 | assert data == {"name": "Mike"} 66 | -------------------------------------------------------------------------------- /tests/test_handlers.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OCCASS/aiogram_tests/331a15ce49c25ad7a70a3e58f253fbf828ba36ee/tests/test_handlers.py -------------------------------------------------------------------------------- /tests/test_requester.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from aiogram_tests.exceptions import MethodIsNotCalledError 4 | from aiogram_tests.handler import MessageHandler 5 | from aiogram_tests.requester import Calls 6 | from aiogram_tests.requester import CallsList 7 | from aiogram_tests.requester import MockedBot 8 | from aiogram_tests.types.dataset import MESSAGE 9 | 10 | 11 | def test_calls_list(): 12 | calls_list = CallsList([1, 2, 3, 4, 5, 6, 7, 8]) 13 | assert calls_list.fetchone() == 8 14 | assert calls_list.fetchall() == [1, 2, 3, 4, 5, 6, 7, 8] 15 | 16 | 17 | def test_calls(): 18 | GeneratedCalls = type("GeneratedCalls", (Calls,), {"send_message": "Message"}) 19 | generated_calls = GeneratedCalls() 20 | assert generated_calls.send_message == "Message" 21 | with pytest.raises(MethodIsNotCalledError): 22 | _ = generated_calls.callback_query 23 | 24 | 25 | @pytest.mark.asyncio 26 | async def test_requester(): 27 | async def callback(*args, **kwargs): 28 | pass 29 | 30 | request_handler = MessageHandler(callback) 31 | requester = MockedBot(request_handler=request_handler) 32 | calls = await requester.query(message=MESSAGE.as_object(text="Hello world!")) 33 | assert isinstance(calls, Calls) 34 | with pytest.raises(MethodIsNotCalledError): 35 | _ = calls.send_message 36 | 37 | with pytest.raises(AttributeError): 38 | await requester.query(callback_query=MESSAGE.as_object(text="Hello world!")) 39 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from aiogram_tests.utils import camel_case2snake_case 2 | 3 | 4 | def test_camle_case_convertor(): 5 | snake_case = camel_case2snake_case("camelCase") 6 | assert snake_case == "camel_case" 7 | snake_case = camel_case2snake_case("CamelCase") 8 | assert snake_case == "camel_case" 9 | snake_case = camel_case2snake_case("CamelCaseCase") 10 | assert snake_case == "camel_case_case" 11 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38 3 | 4 | [testenv] 5 | deps = -r dev_requirements.txt 6 | changedir = tests 7 | commands = pytest 8 | skip_install = true 9 | --------------------------------------------------------------------------------