├── .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 |
--------------------------------------------------------------------------------