diff --git a/matrix_alertbot/chat_functions.py b/matrix_alertbot/chat_functions.py index be16d7f..ab0a5b4 100644 --- a/matrix_alertbot/chat_functions.py +++ b/matrix_alertbot/chat_functions.py @@ -1,7 +1,14 @@ import logging from typing import Dict, Optional, TypedDict, Union -from nio import AsyncClient, ErrorResponse, Response, RoomSendResponse +from nio import ( + AsyncClient, + ErrorResponse, + Response, + RoomSendError, + RoomSendResponse, + SendRetryError, +) from typing_extensions import NotRequired logger = logging.getLogger(__name__) @@ -62,32 +69,16 @@ async def send_text_to_room( if reply_to_event_id: content["m.relates_to"] = {"m.in_reply_to": {"event_id": reply_to_event_id}} - return await matrix_client.room_send( + response_event = await matrix_client.room_send( room_id, "m.room.message", content, ignore_unverified_devices=True, ) - -def make_pill(user_id: str, displayname: str = None) -> str: - """Convert a user ID (and optionally a display name) to a formatted user 'pill' - - Args: - user_id: The MXID of the user. - - displayname: An optional displayname. Clients like Element will figure out the - correct display name no matter what, but other clients may not. If not - provided, the MXID will be used instead. - - Returns: - The formatted user pill. - """ - if not displayname: - # Use the user ID as the displayname if not provided - displayname = user_id - - return f'{displayname}' + if isinstance(response_event, RoomSendError): + raise SendRetryError(response_event.error) + return response_event async def react_to_event( @@ -121,13 +112,17 @@ async def react_to_event( } } - return await client.room_send( + response_event = await client.room_send( room_id, "m.reaction", content, ignore_unverified_devices=True, ) + if isinstance(response_event, RoomSendError): + raise SendRetryError(response_event.error) + return response_event + def strip_fallback(content: str) -> str: index = 0 diff --git a/matrix_alertbot/errors.py b/matrix_alertbot/errors.py index 8e06487..9be7555 100644 --- a/matrix_alertbot/errors.py +++ b/matrix_alertbot/errors.py @@ -1,7 +1,11 @@ # This file holds custom error types that you can define for your application. -class ConfigError(Exception): +class MatrixAlertbotError(Exception): + pass + + +class ConfigError(MatrixAlertbotError): """An error encountered during reading the config file.""" pass @@ -25,7 +29,7 @@ class RequiredConfigKeyError(ConfigError): pass -class AlertmanagerError(Exception): +class AlertmanagerError(MatrixAlertbotError): """An error encountered with Alertmanager.""" pass diff --git a/tests/test_chat_functions.py b/tests/test_chat_functions.py index cdbde4e..769b41f 100644 --- a/tests/test_chat_functions.py +++ b/tests/test_chat_functions.py @@ -4,7 +4,11 @@ from unittest.mock import Mock import nio -from matrix_alertbot.chat_functions import send_text_to_room, strip_fallback +from matrix_alertbot.chat_functions import ( + react_to_event, + send_text_to_room, + strip_fallback, +) from tests.utils import make_awaitable @@ -32,6 +36,61 @@ class ChatFunctionsTestCase(unittest.IsolatedAsyncioTestCase): message = strip_fallback(fake_body) self.assertEqual(fake_body, message) + async def test_react_to_event(self) -> None: + fake_response = Mock(spec=nio.RoomSendResponse) + fake_matrix_client = Mock(spec=nio.AsyncClient) + fake_matrix_client.room_send = Mock(return_value=make_awaitable(fake_response)) + fake_room_id = "!abcdefgh:example.com" + fake_event_id = "some event id" + fake_reaction_text = "some reaction" + + response = await react_to_event( + fake_matrix_client, fake_room_id, fake_event_id, fake_reaction_text + ) + + fake_matrix_client.room_send.assert_called_once_with( + fake_room_id, + "m.reaction", + { + "m.relates_to": { + "rel_type": "m.annotation", + "event_id": fake_event_id, + "key": fake_reaction_text, + } + }, + ignore_unverified_devices=True, + ) + self.assertEqual(fake_response, response) + + async def test_react_to_event_return_room_send_error(self) -> None: + fake_response = Mock(spec=nio.RoomSendError) + fake_response.error = "some error" + fake_matrix_client = Mock(spec=nio.AsyncClient) + fake_matrix_client.room_send.return_value = make_awaitable( + fake_response + ) + fake_room_id = "!abcdefgh:example.com" + fake_event_id = "some event id" + fake_reaction_text = "some reaction" + + with self.assertRaises(nio.SendRetryError): + await react_to_event( + fake_matrix_client, fake_room_id, fake_event_id, fake_reaction_text + ) + + fake_matrix_client.room_send.assert_called_once_with( + fake_room_id, + "m.reaction", + { + "m.relates_to": { + "rel_type": "m.annotation", + "event_id": fake_event_id, + "key": fake_reaction_text, + } + }, + ignore_unverified_devices=True, + ) + async def test_send_text_to_room_as_notice(self) -> None: fake_response = Mock(spec=nio.RoomSendResponse) fake_matrix_client = Mock(spec=nio.AsyncClient) @@ -60,7 +119,7 @@ class ChatFunctionsTestCase(unittest.IsolatedAsyncioTestCase): async def test_send_text_to_room_as_message(self) -> None: fake_response = Mock(spec=nio.RoomSendResponse) fake_matrix_client = Mock(spec=nio.AsyncClient) - fake_matrix_client.room_send = Mock(return_value=make_awaitable(fake_response)) + fake_matrix_client.room_send.return_value = make_awaitable(fake_response) fake_room_id = "!abcdefgh:example.com" fake_plaintext_body = "some plaintext message" fake_html_body = "some html message" @@ -89,7 +148,7 @@ class ChatFunctionsTestCase(unittest.IsolatedAsyncioTestCase): async def test_send_text_to_room_in_reply_to_event(self) -> None: fake_response = Mock(spec=nio.RoomSendResponse) fake_matrix_client = Mock(spec=nio.AsyncClient) - fake_matrix_client.room_send = Mock(return_value=make_awaitable(fake_response)) + fake_matrix_client.room_send.return_value = make_awaitable(fake_response) fake_room_id = "!abcdefgh:example.com" fake_plaintext_body = "some plaintext message" fake_html_body = "some html message" @@ -119,9 +178,8 @@ class ChatFunctionsTestCase(unittest.IsolatedAsyncioTestCase): async def test_send_text_to_room_raise_send_retry_error(self) -> None: fake_matrix_client = Mock(spec=nio.AsyncClient) - fake_matrix_client.room_send = Mock( - side_effect=send_room_raise_send_retry_error - ) + fake_matrix_client.room_send.side_effect = send_room_raise_send_retry_error + fake_room_id = "!abcdefgh:example.com" fake_plaintext_body = "some plaintext message" fake_html_body = "some html message" @@ -133,6 +191,36 @@ class ChatFunctionsTestCase(unittest.IsolatedAsyncioTestCase): fake_plaintext_body, fake_html_body, ) + + fake_matrix_client.room_send.assert_called_once_with( + fake_room_id, + "m.room.message", + { + "msgtype": "m.notice", + "format": "org.matrix.custom.html", + "body": fake_plaintext_body, + "formatted_body": fake_html_body, + }, + ignore_unverified_devices=True, + ) + + async def test_send_text_to_room_return_room_send_error(self) -> None: + fake_response = Mock(spec=nio.RoomSendError) + fake_response.error = "some error" + fake_matrix_client = Mock(spec=nio.AsyncClient) + fake_matrix_client.room_send.return_value = make_awaitable(fake_response) + fake_room_id = "!abcdefgh:example.com" + fake_plaintext_body = "some plaintext message" + fake_html_body = "some html message" + + with self.assertRaises(nio.SendRetryError): + await send_text_to_room( + fake_matrix_client, + fake_room_id, + fake_plaintext_body, + fake_html_body, + ) + fake_matrix_client.room_send.assert_called_once_with( fake_room_id, "m.room.message",