diff --git a/matrix_alertbot/callback.py b/matrix_alertbot/callback.py index b8998cf..6b42454 100644 --- a/matrix_alertbot/callback.py +++ b/matrix_alertbot/callback.py @@ -78,10 +78,24 @@ class Callbacks: ) return + source_content = event.source["content"] + try: + alert_event_id = source_content["m.relates_to"]["m.in_reply_to"]["event_id"] + except KeyError: + logger.debug("Unable to find the event ID of the alert") + return + # Remove the command prefix msg = msg[len(self.command_prefix) :] command = Command( - self.client, self.cache, self.alertmanager, self.config, msg, room, event + self.client, + self.cache, + self.alertmanager, + self.config, + msg, + room, + event.sender, + alert_event_id, ) await command.process() @@ -178,7 +192,8 @@ class Callbacks: self.config, f"ack {duration}", room, - event, + event.sender, + reacted_to_id, ) await command.process() @@ -222,9 +237,9 @@ class Callbacks: # Get the ID of the event this was a reaction to relation_dict = event.source.get("content", {}).get("m.relates_to", {}) - reacted_to = relation_dict.get("event_id") - if reacted_to and relation_dict.get("rel_type") == "m.annotation": - await self._reaction(room, event, reacted_to) + reacted_to_id = relation_dict.get("event_id") + if reacted_to_id and relation_dict.get("rel_type") == "m.annotation": + await self._reaction(room, event, reacted_to_id) return logger.debug( diff --git a/matrix_alertbot/command.py b/matrix_alertbot/command.py index 6a0d246..45991ea 100644 --- a/matrix_alertbot/command.py +++ b/matrix_alertbot/command.py @@ -1,11 +1,11 @@ import logging -from typing import List +from typing import List, Optional from diskcache import Cache -from nio import AsyncClient, MatrixRoom, RoomMessageText +from nio import AsyncClient, MatrixRoom from matrix_alertbot.alertmanager import AlertmanagerClient -from matrix_alertbot.chat_functions import react_to_event, send_text_to_room +from matrix_alertbot.chat_functions import send_text_to_room from matrix_alertbot.config import Config from matrix_alertbot.errors import AlertmanagerError from matrix_alertbot.matcher import AlertMatcher, AlertRegexMatcher @@ -22,7 +22,8 @@ class Command: config: Config, command: str, room: MatrixRoom, - event: RoomMessageText, + sender: str, + event_id: str, ): """A command made by a user. @@ -45,7 +46,8 @@ class Command: self.config = config self.command = command self.room = room - self.event = event + self.sender = sender + self.event_id = event_id self.args = self.command.split()[1:] async def process(self) -> None: @@ -54,8 +56,6 @@ class Command: await self._ack() elif self.command.startswith("unack") or self.command.startswith("nack"): await self._unack() - elif self.command.startswith("react"): - await self._react() elif self.command.startswith("help"): await self._show_help() else: @@ -83,20 +83,15 @@ class Command: duration = "1d" logger.debug( - f"Receiving a command to create a silence for a duration of {duration} | " - f"{self.room.user_name(self.event.sender)}: {self.event.body}" + f"Receiving a command to create a silence for a duration of {duration}" ) - source_content = self.event.source["content"] - try: - alert_event_id = source_content["m.relates_to"]["m.in_reply_to"]["event_id"] - except KeyError: - logger.debug("Unable to find the event ID of the alert") - return - logger.debug(f"Read alert fingerprints for event {alert_event_id} from cache") + logger.debug( + f"Read alert fingerprints for event {self.event_id} from cache" + ) count_created_silences = 0 - alert_fingerprints = self.cache[alert_event_id] + alert_fingerprints = self.cache[self.event_id] for alert_fingerprint in alert_fingerprints: logger.debug( f"Create silence for alert with fingerprint {alert_fingerprint} for a duration of {duration}" @@ -105,7 +100,7 @@ class Command: await self.alertmanager.create_silence( alert_fingerprint, duration, - self.room.user_name(self.event.sender), + self.room.user_name(self.sender), matchers, ) count_created_silences += 1 @@ -131,21 +126,13 @@ class Command: matcher = AlertMatcher(label, value) matchers.append(matcher) + logger.debug("Receiving a command to delete a silence") logger.debug( - f"Receiving a command to delete a silence | " - f"{self.room.user_name(self.event.sender)}: {self.event.body}" + f"Read alert fingerprints for event {self.event_id} from cache" ) - source_content = self.event.source["content"] - try: - alert_event_id = source_content["m.relates_to"]["m.in_reply_to"]["event_id"] - except KeyError: - logger.debug("Unable to find the event ID of the alert") - return - logger.debug(f"Read alert fingerprints for event {alert_event_id} from cache") - count_removed_silences = 0 - alert_fingerprints = self.cache[alert_event_id] + alert_fingerprints = self.cache[self.event_id] for alert_fingerprint in alert_fingerprints: logger.debug( f"Delete silence for alert with fingerprint {alert_fingerprint}" @@ -164,30 +151,9 @@ class Command: f"Removed {count_removed_silences} silences.", ) - async def _react(self) -> None: - """Make the bot react to the command message""" - # React with a start emoji - reaction = "⭐" - logger.debug( - f"Reacting with {reaction} to room {self.room.display_name} | " - f"{self.room.user_name(self.event.sender)}: {self.event.body}" - ) - await react_to_event( - self.client, self.room.room_id, self.event.event_id, reaction - ) - - # React with some generic text - reaction = "Some text" - await react_to_event( - self.client, self.room.room_id, self.event.event_id, reaction - ) - async def _show_help(self) -> None: """Show the help text""" - logger.debug( - f"Displaying help to room {self.room.display_name} | " - f"{self.room.user_name(self.event.sender)}: {self.event.body}" - ) + logger.debug(f"Displaying help to room {self.room.display_name}") if not self.args: text = ( "Hello, I am a bot made with matrix-nio! Use `help commands` to view " @@ -207,8 +173,7 @@ class Command: async def _unknown_command(self) -> None: logger.debug( - f"Sending unknown command response to room {self.room.display_name} | " - f"{self.room.user_name(self.event.sender)}: {self.event.body}" + f"Sending unknown command response to room {self.room.display_name}" ) await send_text_to_room( self.client, diff --git a/tests/test_callback.py b/tests/test_callback.py index e57ae35..526e444 100644 --- a/tests/test_callback.py +++ b/tests/test_callback.py @@ -64,7 +64,23 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase): fake_command.assert_not_called() @patch.object(matrix_alertbot.callback, "Command", autospec=True) - async def test_message_with_prefix(self, fake_command: Mock) -> None: + async def test_message_not_in_reply_with_prefix(self, fake_command: Mock) -> None: + """Tests the callback for RoomMessageText with the command prefix""" + # Tests that the bot process messages in the room that contain a command + + fake_message_event = Mock(spec=nio.RoomMessageText) + fake_message_event.sender = "@some_other_fake_user:example.com" + fake_message_event.body = "!alert help" + fake_message_event.source = {"content": {}} + + # Pretend that we received a text message event + await self.callbacks.message(self.fake_room, fake_message_event) + + # Check that the command was not executed + fake_command.assert_not_called() + + @patch.object(matrix_alertbot.callback, "Command", autospec=True) + async def test_message_in_reply_with_prefix(self, fake_command: Mock) -> None: """Tests the callback for RoomMessageText with the command prefix""" # Tests that the bot process messages in the room that contain a command fake_command_instance = fake_command.return_value @@ -72,6 +88,11 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase): fake_message_event = Mock(spec=nio.RoomMessageText) fake_message_event.sender = "@some_other_fake_user:example.com" fake_message_event.body = "!alert help" + fake_message_event.source = { + "content": { + "m.relates_to": {"m.in_reply_to": {"event_id": "some event id"}} + } + } # Pretend that we received a text message event await self.callbacks.message(self.fake_room, fake_message_event) @@ -84,7 +105,8 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase): self.fake_config, "help", self.fake_room, - fake_message_event, + fake_message_event.sender, + "some event id", ) fake_command_instance.process.assert_called_once() diff --git a/tests/test_command.py b/tests/test_command.py index 3f3b8bf..837f9f3 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -1,5 +1,5 @@ import unittest -from typing import Dict, List +from typing import List from unittest.mock import MagicMock, Mock, call, patch import nio @@ -53,16 +53,8 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): self.fake_room.display_name = "Fake Room" self.fake_room.user_name.side_effect = lambda x: x - self.fake_source_not_in_reply: Dict = {"content": {}} - self.fake_source_in_reply: Dict = { - "content": { - "m.relates_to": {"m.in_reply_to": {"event_id": "some event id"}} - } - } - - self.fake_message_event = Mock(spec=nio.RoomMessageText) - self.fake_message_event.sender = "@some_other_fake_user:example.com" - self.fake_message_event.body = "" + self.fake_event_id = "some event id" + self.fake_sender = "@some_other_fake_user:example.com" # We don't spec config, as it doesn't currently have well defined attributes self.fake_config = Mock() @@ -81,7 +73,8 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): self.fake_config, "ack", self.fake_room, - self.fake_message_event, + self.fake_sender, + self.fake_event_id ) await command.process() @@ -101,7 +94,8 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): self.fake_config, command_word, self.fake_room, - self.fake_message_event, + self.fake_sender, + self.fake_event_id ) await command.process() @@ -120,7 +114,8 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): self.fake_config, "help", self.fake_room, - self.fake_message_event, + self.fake_sender, + self.fake_event_id ) await command.process() @@ -139,55 +134,14 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): self.fake_config, "", self.fake_room, - self.fake_message_event, + self.fake_sender, + self.fake_event_id ) await command.process() # Check that we attempted to process the command fake_unknown.assert_called_once() - async def test_ack_not_in_reply_without_duration(self) -> None: - """Tests the callback for InviteMemberEvents""" - # Tests that the bot attempts to join a room after being invited to it - - self.fake_message_event.source = self.fake_source_not_in_reply - - command = Command( - self.fake_client, - self.fake_cache, - self.fake_alertmanager, - self.fake_config, - "ack", - self.fake_room, - self.fake_message_event, - ) - await command._ack() - - # Check that we didn't attempt to create silences - self.fake_alertmanager.create_silence.assert_not_called() - self.fake_client.room_send.assert_not_called() - - async def test_ack_not_in_reply_with_duration(self) -> None: - """Tests the callback for InviteMemberEvents""" - # Tests that the bot attempts to join a room after being invited to it - - self.fake_message_event.source = self.fake_source_not_in_reply - - command = Command( - self.fake_client, - self.fake_cache, - self.fake_alertmanager, - self.fake_config, - "ack 2d", - self.fake_room, - self.fake_message_event, - ) - await command._ack() - - # Check that we didn't attempt to create silences - self.fake_alertmanager.create_silence.assert_not_called() - self.fake_client.room_send.assert_not_called() - @patch.object(matrix_alertbot.command, "send_text_to_room") async def test_ack_in_reply_without_duration_nor_matchers( self, fake_send_text_to_room: Mock @@ -195,8 +149,6 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): """Tests the callback for InviteMemberEvents""" # Tests that the bot attempts to join a room after being invited to it - self.fake_message_event.source = self.fake_source_in_reply - command = Command( self.fake_client, self.fake_cache, @@ -204,14 +156,15 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): self.fake_config, "ack", self.fake_room, - self.fake_message_event, + self.fake_sender, + self.fake_event_id, ) await command._ack() # Check that we attempted to create silences self.fake_alertmanager.create_silence.assert_has_calls( [ - call(fingerprint, "1d", self.fake_message_event.sender, []) + call(fingerprint, "1d", self.fake_sender, []) for fingerprint in self.fake_fingerprints ] ) @@ -232,8 +185,6 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): AlertRegexMatcher(label="severity", regex="critical"), ] - self.fake_message_event.source = self.fake_source_in_reply - command = Command( self.fake_client, self.fake_cache, @@ -241,7 +192,8 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): self.fake_config, "ack alertname=alert1 severity=~critical", self.fake_room, - self.fake_message_event, + self.fake_sender, + self.fake_event_id, ) await command._ack() @@ -251,7 +203,7 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): call( fingerprint, "1d", - self.fake_message_event.sender, + self.fake_sender, matchers, ) for fingerprint in self.fake_fingerprints @@ -270,8 +222,6 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): """Tests the callback for InviteMemberEvents""" # Tests that the bot attempts to join a room after being invited to it - self.fake_message_event.source = self.fake_source_in_reply - command = Command( self.fake_client, self.fake_cache, @@ -279,14 +229,15 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): self.fake_config, "ack 1w 2d", self.fake_room, - self.fake_message_event, + self.fake_sender, + self.fake_event_id, ) await command._ack() # Check that we attempted to create silences self.fake_alertmanager.create_silence.assert_has_calls( [ - call(fingerprint, "1w 2d", self.fake_message_event.sender, []) + call(fingerprint, "1w 2d", self.fake_sender, []) for fingerprint in self.fake_fingerprints ] ) @@ -307,8 +258,6 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): AlertMatcher(label="severity", value="critical"), ] - self.fake_message_event.source = self.fake_source_in_reply - command = Command( self.fake_client, self.fake_cache, @@ -316,7 +265,8 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): self.fake_config, "ack 1w 2d alertname=alert1 severity=critical", self.fake_room, - self.fake_message_event, + self.fake_sender, + self.fake_event_id, ) await command._ack() @@ -326,7 +276,7 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): call( fingerprint, "1w 2d", - self.fake_message_event.sender, + self.fake_sender, matchers, ) for fingerprint in self.fake_fingerprints @@ -345,8 +295,6 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): """Tests the callback for InviteMemberEvents""" # Tests that the bot attempts to join a room after being invited to it - self.fake_message_event.source = self.fake_source_in_reply - command = Command( self.fake_client, self.fake_cache, @@ -354,7 +302,8 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): self.fake_config, "ack", self.fake_room, - self.fake_message_event, + self.fake_sender, + self.fake_event_id, ) self.fake_alertmanager.create_silence.side_effect = ( @@ -365,7 +314,7 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): # Check that we attempted to create silences self.fake_alertmanager.create_silence.assert_has_calls( [ - call(fingerprint, "1d", self.fake_message_event.sender, []) + call(fingerprint, "1d", self.fake_sender, []) for fingerprint in self.fake_fingerprints ] ) @@ -382,8 +331,6 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): """Tests the callback for InviteMemberEvents""" # Tests that the bot attempts to join a room after being invited to it - self.fake_message_event.source = self.fake_source_in_reply - command = Command( self.fake_client, self.fake_cache, @@ -391,7 +338,8 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): self.fake_config, "unack", self.fake_room, - self.fake_message_event, + self.fake_sender, + self.fake_event_id, ) await command._unack() @@ -415,8 +363,6 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): AlertRegexMatcher(label="severity", regex="critical"), ] - self.fake_message_event.source = self.fake_source_in_reply - command = Command( self.fake_client, self.fake_cache, @@ -424,7 +370,8 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): self.fake_config, "unack alertname=alert1 severity=~critical", self.fake_room, - self.fake_message_event, + self.fake_sender, + self.fake_event_id, ) await command._unack() @@ -443,8 +390,6 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): """Tests the callback for InviteMemberEvents""" # Tests that the bot attempts to join a room after being invited to it - self.fake_message_event.source = self.fake_source_in_reply - command = Command( self.fake_client, self.fake_cache, @@ -452,7 +397,8 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): self.fake_config, "unack", self.fake_room, - self.fake_message_event, + self.fake_sender, + self.fake_event_id, ) self.fake_alertmanager.delete_silences.side_effect = (