-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 3be755b
Showing
26 changed files
with
2,208 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
### Python template | ||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
|
||
# C extensions | ||
*.so | ||
|
||
# Distribution / packaging | ||
.Python | ||
env/ | ||
build/ | ||
develop-eggs/ | ||
dist/ | ||
downloads/ | ||
eggs/ | ||
.eggs/ | ||
lib/ | ||
lib64/ | ||
parts/ | ||
sdist/ | ||
var/ | ||
wheels/ | ||
*.egg-info/ | ||
.installed.cfg | ||
*.egg | ||
|
||
# Installer logs | ||
pip-log.txt | ||
pip-delete-this-directory.txt | ||
|
||
# Unit test / coverage reports | ||
htmlcov/ | ||
.tox/ | ||
.pytest_cache/ | ||
.coverage | ||
.coverage.* | ||
.cache | ||
nosetests.xml | ||
coverage.xml | ||
*,cover | ||
|
||
# Sphinx documentation | ||
docs/_build/ | ||
|
||
# virtualenv | ||
.venv | ||
venv/ | ||
ENV/ | ||
|
||
# JetBrains | ||
.idea/ | ||
|
||
# Current project | ||
experiment.py | ||
|
||
# Doc's | ||
docs/html | ||
|
||
# i18n/l10n | ||
*.mo | ||
|
||
# pynev | ||
.python-version |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
repos: | ||
- repo: local | ||
hooks: | ||
# For reformatting code | ||
- id: black | ||
name: black | ||
entry: black | ||
language: python | ||
types: [ python ] | ||
args: [ --line-length=120, --target-version=py38 ] | ||
|
||
# For upgrade python code syntax for newer versions of the language | ||
- id: pyupgrade | ||
name: pyupgrade | ||
entry: pyupgrade | ||
language: python | ||
types: [ python ] | ||
|
||
# Tool for automatically reordering python imports | ||
- id: reorder-python-imports | ||
name: reorder-python-imports | ||
entry: reorder-python-imports | ||
language: python | ||
types: [ python ] | ||
|
||
# Removes unused imports and unused variables from Python code | ||
- id: autoflake | ||
name: autoflake | ||
entry: autoflake | ||
language: python | ||
types: [ python ] | ||
args: [ --in-place, --remove-all-unused-imports, --remove-duplicate-keys ] | ||
|
||
# Run python unittest | ||
- id: tests | ||
name: unittest | ||
entry: python3 -m unittest tests/__main__.py | ||
language: python | ||
'types': [ python ] | ||
pass_filenames: false | ||
stages: [ commit ] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[[source]] | ||
url = "https://pypi.org/simple" | ||
verify_ssl = true | ||
name = "pypi" | ||
|
||
[packages] | ||
aiogram = "*" | ||
pytest = "*" | ||
pytest-asyncio = "*" | ||
|
||
[dev-packages] | ||
|
||
[requires] | ||
python_version = "3.10" |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# aiogram_unittest | ||
|
||
***aiogram_unittest*** is a testing library for bots written on <a href="https://github.com/aiogram/aiogram">aiogram</a> | ||
|
||
## 📚 Simple examples | ||
|
||
### Simple handler test | ||
|
||
#### Simple bot: | ||
|
||
```python | ||
from aiogram import Bot, Dispatcher, types | ||
from aiogram.fsm.context import FSMContext | ||
|
||
# Please, keep your bot tokens on environments, this code only example | ||
bot = Bot('123456789:AABBCCDDEEFFaabbccddeeff-1234567890') | ||
dp = Dispatcher() | ||
|
||
|
||
@dp.message() | ||
async def echo(message: types.Message, state: FSMContext) -> None: | ||
await message.answer(message.text) | ||
|
||
|
||
if __name__ == '__main__': | ||
dp.run_polling(bot) | ||
|
||
|
||
``` | ||
|
||
#### Test cases: | ||
|
||
```python | ||
import pytest | ||
|
||
from bot import echo | ||
|
||
from aiogram_pytest import Requester | ||
from aiogram_pytest.handler import MessageHandler | ||
from aiogram_pytest.types.dataset import MESSAGE | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_echo(): | ||
request = Requester(request_handler=MessageHandler(echo)) | ||
calls = await request.query(message=MESSAGE.as_object(text="Hello, Bot!")) | ||
answer_message = calls.send_messsage.fetchone() | ||
assert answer_message.text == "Hello, Bot!" | ||
|
||
``` | ||
|
||
### ▶️ <a href='https://github.com/OCCCAS/aiogram_unittest/tree/master/examples'>More</a> examples | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from .requester import Requester | ||
from .requester import RequestType | ||
|
||
__all__ = ["Requester", "RequestType"] | ||
__version__ = "0.0.1" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
class MethodIsNotCalledError(Exception): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
from typing import Any | ||
from typing import Dict | ||
from typing import Iterable | ||
from typing import List | ||
from typing import Tuple | ||
from typing import Union | ||
|
||
from aiogram import Bot | ||
from aiogram import Dispatcher | ||
from aiogram import types | ||
from aiogram.dispatcher.event.telegram import TelegramEventObserver | ||
from aiogram.filters import StateFilter | ||
from aiogram.fsm.state import State | ||
from aiogram.fsm.storage.memory import MemoryStorage | ||
from aiogram.types import Update | ||
|
||
from .mocked_bot import MockedBot | ||
from .types.dataset import CHAT | ||
from .types.dataset import USER | ||
|
||
|
||
class RequestHandler: | ||
def __init__( | ||
self, dp_middlewares: Iterable = None, dp_filters: Iterable = None, exclude_observer_methods: Iterable = None | ||
): | ||
self.bot = MockedBot() | ||
self.dp = Dispatcher(storage=MemoryStorage()) | ||
|
||
if dp_middlewares is None: | ||
dp_middlewares = () | ||
|
||
if dp_filters is None: | ||
dp_filters = () | ||
|
||
if exclude_observer_methods is None: | ||
exclude_observer_methods = [] | ||
|
||
dispatcher_methods = self._get_dispatcher_event_observers() | ||
available_methods = tuple(set(dispatcher_methods) - set(exclude_observer_methods)) | ||
self._register_middlewares(available_methods, tuple(dp_middlewares)) | ||
self._register_filters(available_methods, tuple(dp_filters)) | ||
|
||
Bot.set_current(self.bot) | ||
types.User.set_current(USER.as_object()) | ||
types.Chat.set_current(CHAT.as_object()) | ||
|
||
def _get_dispatcher_event_observers(self): | ||
result = [] | ||
for name in dir(self.bot): | ||
if isinstance(getattr(self.bot, name), TelegramEventObserver): | ||
result.append(name) | ||
|
||
return result | ||
|
||
def _register_middlewares(self, event_observer: Tuple, middlewares: Tuple): | ||
for eo_name in event_observer: | ||
for m in middlewares: | ||
eo_obj = getattr(self.bot, eo_name) | ||
eo_obj.middleware.register(m) | ||
|
||
def _register_filters(self, event_observer: Tuple, filters: Tuple): | ||
for eo_name in event_observer: | ||
for f in filters: | ||
eo_obj = getattr(self.bot, eo_name) | ||
eo_obj.middleware.register(f) | ||
|
||
async def __call__(self, *args, **kwargs): | ||
raise NotImplementedError | ||
|
||
|
||
class MessageHandler(RequestHandler): | ||
def __init__( | ||
self, | ||
callback, | ||
*filters: Any, | ||
state: Union[State, str, None] = None, | ||
state_data: dict = None, | ||
dp_middlewares: Iterable = None, | ||
exclude_observer_methods: Iterable = None, | ||
**kwargs, | ||
): | ||
super().__init__(dp_middlewares, (), exclude_observer_methods) | ||
self._callback = callback | ||
self._filters: List = list(filters) | ||
self._state: Union[State, str, None] = state | ||
self._state_data: Dict = state_data | ||
|
||
if self._state_data is None: | ||
self._state_data = {} | ||
|
||
if self._filters is None: | ||
self._filters = [] | ||
|
||
if not isinstance(self._state_data, dict): | ||
raise ValueError("state_data is not a dict") | ||
|
||
async def __call__(self, message: types.Message): | ||
if self._state: | ||
self._filters.append(StateFilter(self._state)) | ||
|
||
self.dp.message.register(self._callback, *self._filters) | ||
|
||
if self._state: | ||
state = self.dp.fsm.get_context(self.bot, user_id=12345678, chat_id=12345678) | ||
await state.set_state(self._state) | ||
await state.update_data(**self._state_data) | ||
|
||
await self.dp.feed_update(self.bot, Update(update_id=12345678, message=message)) | ||
|
||
|
||
class CallbackQueryHandler(RequestHandler): | ||
def __init__( | ||
self, | ||
callback, | ||
*filters, | ||
state: Union[State, str, None] = None, | ||
state_data: dict = None, | ||
dp_middlewares: Iterable = None, | ||
exclude_observer_methods: Iterable = None, | ||
**kwargs, | ||
): | ||
super().__init__(dp_middlewares, (), exclude_observer_methods) | ||
self._callback = callback | ||
self._filters: List = list(filters) | ||
self._state: Union[State, str, None] = state | ||
self._state_data: Dict = state_data | ||
|
||
if self._state_data is None: | ||
self._state_data = {} | ||
|
||
if self._filters is None: | ||
self._filters = [] | ||
|
||
async def __call__(self, callback_query: types.CallbackQuery): | ||
if self._state: | ||
self._filters.append(StateFilter(self._state)) | ||
|
||
self.dp.callback_query.register(self._callback, *self._filters) | ||
|
||
if self._state: | ||
state = self.dp.fsm.get_context(self.bot, user_id=12345678, chat_id=12345678) | ||
await state.set_state(self._state) | ||
await state.update_data(**self._state_data) | ||
|
||
await self.dp.feed_update(self.bot, types.Update(update_id=12345678, callback_query=callback_query)) |
Oops, something went wrong.