diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/client.py b/deltachat-rpc-client/src/deltachat_rpc_client/client.py index 6abcc8a59..f04e0d1e1 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/client.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/client.py @@ -1,6 +1,17 @@ """Event loop implementations offering high level event handling/hooking.""" +import inspect import logging -from typing import Callable, Dict, Iterable, Optional, Set, Tuple, Type, Union +from typing import ( + Callable, + Coroutine, + Dict, + Iterable, + Optional, + Set, + Tuple, + Type, + Union, +) from deltachat_rpc_client.account import Account @@ -56,6 +67,18 @@ class Client: self.logger.debug("Account configured") async def run_forever(self) -> None: + """Process events forever.""" + await self.run_until(lambda _: False) + + async def run_until( + self, func: Callable[[AttrDict], Union[bool, Coroutine]] + ) -> AttrDict: + """Process events until the given callable evaluates to True. + + The callable should accept an AttrDict object representing the + last processed event. The event is returned when the callable + evaluates to True. + """ self.logger.debug("Listening to incoming events...") if await self.is_configured(): await self.account.start_io() @@ -68,6 +91,12 @@ class Client: if event.type == EventType.INCOMING_MSG: await self._process_messages() + stop = func(event) + if inspect.isawaitable(stop): + stop = await stop + if stop: + return event + async def _on_event( self, event: AttrDict, filter_type: Type[EventFilter] = RawEvent ) -> None: diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py b/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py index e8cce5f4c..0d8aca5d0 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py @@ -1,13 +1,11 @@ import json import os -from typing import AsyncGenerator, List +from typing import AsyncGenerator, List, Optional import aiohttp import pytest_asyncio -from .account import Account -from .client import Bot -from .deltachat import DeltaChat +from . import Account, AttrDict, Bot, Client, DeltaChat, EventType, Message from .rpc import Rpc @@ -51,6 +49,46 @@ class ACFactory: await account.start_io() return accounts + async def send_message( + self, + to_account: Account, + from_account: Optional[Account] = None, + text: Optional[str] = None, + file: Optional[str] = None, + group: Optional[str] = None, + ) -> Message: + if not from_account: + from_account = (await self.get_online_accounts(1))[0] + to_contact = await from_account.create_contact( + await to_account.get_config("addr") + ) + if group: + to_chat = await from_account.create_group(group) + await to_chat.add_contact(to_contact) + else: + to_chat = await to_contact.create_chat() + return await to_chat.send_message(text=text, file=file) + + async def process_message( + self, + to_client: Client, + from_account: Optional[Account] = None, + text: Optional[str] = None, + file: Optional[str] = None, + group: Optional[str] = None, + ) -> AttrDict: + await self.send_message( + to_account=to_client.account, + from_account=from_account, + text=text, + file=file, + group=group, + ) + + event = await to_client.run_until(lambda e: e.type == EventType.INCOMING_MSG) + msg = await to_client.account.get_message_by_id(event.msg_id) + return await msg.get_snapshot() + @pytest_asyncio.fixture async def rpc(tmp_path) -> AsyncGenerator: diff --git a/deltachat-rpc-client/tests/test_something.py b/deltachat-rpc-client/tests/test_something.py index 7c265f1ec..1b2b3625e 100644 --- a/deltachat-rpc-client/tests/test_something.py +++ b/deltachat-rpc-client/tests/test_something.py @@ -1,6 +1,8 @@ +from unittest.mock import MagicMock + import pytest -from deltachat_rpc_client import AttrDict, EventType, events +from deltachat_rpc_client import EventType, events from deltachat_rpc_client.rpc import JsonRpcError @@ -216,40 +218,46 @@ async def test_message(acfactory) -> None: @pytest.mark.asyncio async def test_bot(acfactory) -> None: - async def callback(e): - res.append(e) + def track(key): + async def wrapper(e): + mock.hook(e[key]) - res = [] + return wrapper + + mock = MagicMock() + user = (await acfactory.get_online_accounts(1))[0] bot = await acfactory.new_configured_bot() + assert await bot.is_configured() assert await bot.account.get_config("bot") == "1" - bot.add_hook(callback, events.RawEvent(EventType.INFO)) - info_event = AttrDict(account=bot.account, type=EventType.INFO, msg="info") - warn_event = AttrDict(account=bot.account, type=EventType.WARNING, msg="warning") - await bot._on_event(info_event) - await bot._on_event(warn_event) - assert info_event in res - assert warn_event not in res - assert len(res) == 1 + hook = track("msg_id"), events.RawEvent(EventType.INCOMING_MSG) + bot.add_hook(*hook) + event = await acfactory.process_message( + from_account=user, to_client=bot, text="Hello!" + ) + mock.hook.assert_called_once_with(event.id) + bot.remove_hook(*hook) - res = [] - bot.add_hook(callback, events.NewMessage(r"hello")) - bot.add_hook(callback, events.NewMessage(command="/help")) - snapshot1 = AttrDict(text="hello", command=None) - snapshot2 = AttrDict(text="hello, world", command=None) - snapshot3 = AttrDict(text="hey!", command=None) - for snapshot in [snapshot1, snapshot2, snapshot3]: - await bot._on_event(snapshot, events.NewMessage) - assert len(res) == 2 - assert snapshot1 in res - assert snapshot2 in res - assert snapshot3 not in res + mock.hook.reset_mock() + hook = track("id"), events.NewMessage(r"hello") + bot.add_hook(*hook) + bot.add_hook(track("id"), events.NewMessage(command="/help")) + event = await acfactory.process_message( + from_account=user, to_client=bot, text="hello" + ) + mock.hook.assert_called_with(event.id) + event = await acfactory.process_message( + from_account=user, to_client=bot, text="hello!" + ) + mock.hook.assert_called_with(event.id) + await acfactory.process_message(from_account=user, to_client=bot, text="hey!") + assert len(mock.hook.mock_calls) == 2 + bot.remove_hook(*hook) - res = [] - bot.remove_hook(callback, events.NewMessage(r"hello")) - snapshot4 = AttrDict(command="/help") - await bot._on_event(snapshot, events.NewMessage) - await bot._on_event(snapshot4, events.NewMessage) - assert len(res) == 1 - assert snapshot4 in res + mock.hook.reset_mock() + await acfactory.process_message(from_account=user, to_client=bot, text="hello") + event = await acfactory.process_message( + from_account=user, to_client=bot, text="/help" + ) + mock.hook.assert_called_once_with(event.id)