refactor command

This commit is contained in:
HgO 2022-07-10 14:06:36 +02:00
parent 529fb0886a
commit 1560093127
4 changed files with 94 additions and 146 deletions

View file

@ -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(

View file

@ -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,

View file

@ -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()

View file

@ -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 = (