Skip to content

Commit

Permalink
init: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
OCCASS committed Oct 24, 2022
0 parents commit 3be755b
Show file tree
Hide file tree
Showing 26 changed files with 2,208 additions and 0 deletions.
65 changes: 65 additions & 0 deletions .gitignore
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
42 changes: 42 additions & 0 deletions .pre-commit-config.yaml
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 ]

14 changes: 14 additions & 0 deletions Pipfile
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"
443 changes: 443 additions & 0 deletions Pipfile.lock

Large diffs are not rendered by default.

53 changes: 53 additions & 0 deletions README.md
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

5 changes: 5 additions & 0 deletions aiogram_pytest/__init__.py
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"
2 changes: 2 additions & 0 deletions aiogram_pytest/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class MethodIsNotCalledError(Exception):
pass
145 changes: 145 additions & 0 deletions aiogram_pytest/handler.py
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))
Loading

0 comments on commit 3be755b

Please sign in to comment.