Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions matrix/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from typing import Optional, Any

from nio import AsyncClient, Event, MatrixRoom
from nio import AsyncClient, Event, MatrixRoom, SyncResponse

from .room import Room, make_room
from .space import Space
Expand Down Expand Up @@ -297,6 +297,7 @@ def _load_config(self, config: Config | str) -> None:
self.register_command(self.help)

self.client.add_event_callback(self._on_matrix_event, Event)
self.client.add_response_callback(self._on_sync, SyncResponse)
self._auto_register_events()

def start(self, *, config: Config | str) -> None:
Expand Down Expand Up @@ -351,15 +352,16 @@ async def run(self) -> None:
async def _wait_until_synced(self) -> None:
await self._synced.wait()

async def _on_sync(self, _response: SyncResponse) -> None:
"""Mark the bot as synced once a sync with the homeserver completes."""
self._synced.set()

# MATRIX EVENTS

async def on_message(self, room: Room, event: Event) -> None:
await self._process_commands(room, event)

async def _on_matrix_event(self, matrix_room: MatrixRoom, event: Event) -> None:
if not self._synced.is_set():
self._synced.set()

# ignore bot events
if event.sender == self.client.user:
return
Expand Down
43 changes: 42 additions & 1 deletion tests/test_bot.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest

from unittest.mock import AsyncMock, MagicMock, patch
from nio import MatrixRoom, RoomMessageText, LoginError
from nio import MatrixRoom, RoomMessageText, LoginError, SyncResponse

from matrix import Bot, Config, Context, Extension, Room, Space
from matrix.errors import (
Expand Down Expand Up @@ -720,6 +720,47 @@ def test_start_handles_keyboard_interrupt(caplog):
bot._client.close.assert_awaited_once()


def test_load_config_registers_sync_response_callback():
bot = Bot()
bot._load_config(Config(username="grace", password="grace1234"))

assert any(
cb.func == bot._on_sync and cb.filter is SyncResponse
for cb in bot.client.response_callbacks
)


@pytest.mark.asyncio
async def test_room_event_does_not_mark_synced(bot, room, event):
with patch.object(bot, "_dispatch_matrix_event", new_callable=AsyncMock):
await bot._on_matrix_event(room, event)

assert not bot._synced.is_set()


@pytest.mark.asyncio
async def test_run_becomes_ready_after_first_sync_without_room_events(bot_with_token):
async def sync_forever(**_kwargs):
# A completed sync with no room events, then the loop keeps going.
await bot_with_token._on_sync(MagicMock(spec=SyncResponse))
await asyncio.Event().wait()

bot_with_token._client.sync_forever = AsyncMock(side_effect=sync_forever)
bot_with_token._on_ready = AsyncMock()
bot_with_token.scheduler.start = MagicMock()

task = asyncio.create_task(bot_with_token.run())

await asyncio.wait_for(bot_with_token._synced.wait(), timeout=1.0)
await asyncio.sleep(0)

task.cancel()
await asyncio.gather(task, return_exceptions=True)

bot_with_token._on_ready.assert_awaited_once()
bot_with_token.scheduler.start.assert_called_once()


@pytest.mark.asyncio
async def test_on_ready_called_only_once(bot):
# Prepare
Expand Down
Loading