refactor command
This commit is contained in:
parent
529fb0886a
commit
1560093127
4 changed files with 94 additions and 146 deletions
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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 = (
|
||||
|
|
Loading…
Reference in a new issue