Merge branch '11-amelioration-de-l-experience-utilisateur' into 'master'

Resolve "Amélioration de l'expérience utilisateur"

Closes #11

See merge request Neutrinet/matrix-alertbot!10
This commit is contained in:
HgO 2024-04-18 10:09:07 +00:00
commit db69401061
10 changed files with 324 additions and 121 deletions

View file

@ -112,12 +112,12 @@ matrix-alertbot other-config.yaml
Invite the bot to a room and it should accept the invite and join.
Matrix AlertBot will process any message starting with the prefix defined in the config. By default, this prefix is `!alert` . Let's test this now.
Matrix AlertBot will process any message where its name is mentionned. Let's test this now.
After the bot has successfully joined the room, try sending the following
in a message:
```
!alert help
@bot_name help
```
The bot should reply with an help message, explaining how to handle alerts.
@ -129,7 +129,7 @@ or by reacting with certain emojis.
For instance, if you reply to the alert with:
```
!alert ack
@bot_name ack
```
This will create a silence for this alert until it is resolved.
@ -138,7 +138,7 @@ You can at any moment reply to the alert with the following to remove the
silence:
```
!alert unack
@bot_name unack
```
Removing a reaction to an alert will also remove the silence.

View file

@ -2,9 +2,6 @@
# Below you will find various config sections and options
# Default values are shown
# The string to prefix messages with to talk to the bot in group chats
command_prefix: "!alert"
# Options for connecting to the bot's Matrix account
matrix:
accounts:

View file

@ -1,6 +1,7 @@
from __future__ import annotations
import logging
import re
from diskcache import Cache
from nio.client import AsyncClient
@ -23,7 +24,12 @@ from nio.rooms import MatrixRoom
import matrix_alertbot.matrix
from matrix_alertbot.alertmanager import AlertmanagerClient
from matrix_alertbot.chat_functions import strip_fallback
from matrix_alertbot.command import AckAlertCommand, CommandFactory, UnackAlertCommand
from matrix_alertbot.command import (
AckAlertCommand,
AngryUserCommand,
CommandFactory,
UnackAlertCommand,
)
from matrix_alertbot.config import Config
logger = logging.getLogger(__name__)
@ -53,7 +59,6 @@ class Callbacks:
self.cache = cache
self.alertmanager_client = alertmanager_client
self.config = config
self.command_prefix = config.command_prefix
async def message(self, room: MatrixRoom, event: RoomMessageText) -> None:
"""Callback for when a message event is received
@ -83,13 +88,22 @@ class Callbacks:
f"Event ID {event.event_id} | Sender {event.sender} | "
f"Message received: {msg}"
)
# Process as message if in a public room without command prefix
has_command_prefix = msg.startswith(self.command_prefix)
if not has_command_prefix:
user_id_patterns = []
for user_id in self.config.user_ids:
user, homeserver = user_id.split(":")
username = user[1:]
user_id_patterns.append(rf"@?{username}(:{homeserver})?")
pattern = re.compile(
rf"(^|\s+)({'|'.join(user_id_patterns)})(\s+|$)",
re.IGNORECASE | re.MULTILINE,
)
if pattern.search(msg) is None:
logger.debug(
f"Bot {self.matrix_client.user_id} | Room ID {room.room_id} | "
f"Event ID {event.event_id} | Sender {event.sender} | "
f"Cannot process message: Command prefix {self.command_prefix} not provided."
f"Cannot process message: Bot was not mentionned."
)
return
@ -107,8 +121,8 @@ class Callbacks:
f"Command received is in reply to event ID {reacted_to_event_id}"
)
# Remove the command prefix
cmd = msg[len(self.command_prefix) :]
# Remove the mention of the bot
cmd = pattern.sub(" ", msg).strip()
try:
command = CommandFactory.create(
cmd,
@ -271,6 +285,27 @@ class Callbacks:
exc_info=e,
)
if event.key in self.config.insult_reactions:
command = AngryUserCommand(
self.matrix_client,
self.cache,
self.alertmanager_client,
self.config,
room,
event.sender,
event.event_id,
)
try:
await command.process()
except (SendRetryError, LocalProtocolError) as e:
logger.exception(
f"Bot {self.matrix_client.user_id} | Room ID {room.room_id} | "
f"Event ID {event.event_id} | Sender {event.sender} | "
f"Cannot send message to room.",
exc_info=e,
)
async def redaction(self, room: MatrixRoom, event: RedactionEvent) -> None:
# Ignore message when we aren't the leader in the client pool
if self.matrix_client is not self.matrix_client_pool.matrix_client:

View file

@ -1,6 +1,7 @@
from __future__ import annotations
import logging
import random
from typing import Optional, Tuple, cast
import pytimeparse2
@ -237,22 +238,58 @@ class HelpCommand(BaseCommand):
async def process(self) -> None:
"""Show the help text"""
logger.debug(f"Displaying help to room {self.room.display_name}")
if not self.args:
if len(self.args) == 0:
text = (
"Hello, I am a bot made with matrix-nio! Use `help commands` to view "
"Hello, I am a bot made with matrix-nio! Use 'help commands' to view "
"available commands."
)
await send_text_to_room(self.matrix_client, self.room.room_id, text)
return
topic = self.args[0]
if topic == "rules":
text = "These are the rules!"
elif topic == "commands":
text = "Available commands: ..."
else:
text = "Unknown help topic!"
await send_text_to_room(self.matrix_client, self.room.room_id, text)
topic = self.args[0]
if topic == "commands":
reactions = " ".join(
sorted(self.config.allowed_reactions - self.config.insult_reactions)
)
text = (
"Here is the list of available commands:\n"
"- help: Display this help message.\n"
"- ack: Create a silence for the alert that is replied to.\n"
"- unack: Remove a silence for the alert that is replied to.\n\n"
"You can also react with an emoji to an alert to create a silence. "
"Removing a reaction will remove the silence.\n"
f"Here is the list of allowed emoji to trigger a silence: {reactions}\n"
)
else:
text = (
"I'm sorry, I don't know much about this topic. "
"You can type 'help commands' to view a list of available commands."
)
await send_text_to_room(
self.matrix_client, self.room.room_id, text, notice=False
)
class AngryUserCommand(BaseCommand):
async def process(self) -> None:
"""React to an insult from the user"""
sender_user_name = self.room.user_name(self.sender)
if sender_user_name is None:
sender_user_name = self.sender
replies = [
"You seem upset 😕 Take a deep breath 😌 and a cup of coffee ☕",
"Don't shoot the messenger! 😰",
"You're doing just fine, you're trying your best. If no one ever told you, it's all gonna be okay! 🎶",
]
random.shuffle(replies)
reply = replies.pop()
await send_text_to_room(
self.matrix_client,
self.room.room_id,
plaintext=f"{sender_user_name} {reply}",
html=f'<a href="https://matrix.to/#/{self.sender}">{sender_user_name}</a> {reply}',
notice=False,
)
class UnknownCommand(BaseCommand):

View file

@ -21,7 +21,37 @@ logging.getLogger("peewee").setLevel(
) # Prevent debug messages from peewee lib
DEFAULT_REACTIONS = {"🤫", "😶", "🤐", "🙊", "🔇", "🔕"}
DEFAULT_REACTIONS = {
"🤫",
"😶",
"🤐",
"🙊",
"🔇",
"🔕",
"🚮",
"",
"🚫",
"🤬",
"🫥",
"😶‍🌫️",
"🫣",
"🫢",
"😪",
"😴",
"💤",
"🥱",
"🤌",
"🤏",
"🤚",
"👎",
"🖕",
}
INSULT_REACTIONS = {
"🤬",
"🤌",
"🖕",
}
class AccountConfig:
@ -147,6 +177,9 @@ class Config:
self.allowed_reactions = set(
self._get_cfg(["matrix", "allowed_reactions"], default=DEFAULT_REACTIONS)
)
self.insult_reactions = set(
self._get_cfg(["matrix", "insult_reactions"], default=INSULT_REACTIONS)
)
self.address: str = self._get_cfg(["webhook", "address"], required=False)
self.port: int = self._get_cfg(["webhook", "port"], required=False)
@ -164,10 +197,6 @@ class Config:
"Supplied both webhook.socket and both webhook.address"
)
self.command_prefix: str = (
self._get_cfg(["command_prefix"], default="!alert") + " "
)
def _get_cfg(
self,
path: List[str],

View file

@ -2,9 +2,6 @@
# Below you will find various config sections and options
# Default values are shown
# The string to prefix messages with to talk to the bot in group chats
command_prefix: "!alert"
# Options for connecting to the bot's Matrix account
matrix:
accounts:
@ -62,7 +59,8 @@ matrix:
- "!abcdefgh:matrix.example.com"
# List of allowed reactions to create silences.
allowed_reactions: [🤫, 😶, 🤐]
allowed_reactions: [🤫, 😶, 🤐, 🤗]
insult_reactions: [🤗]
webhook:
# Path to the socket for which the bot should listen to.

View file

@ -34,8 +34,8 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# We don't spec config, as it doesn't currently have well defined attributes
self.fake_config = Mock()
self.fake_config.allowed_rooms = [self.fake_room.room_id]
self.fake_config.allowed_reactions = ["🤫"]
self.fake_config.command_prefix = "!alert "
self.fake_config.allowed_reactions = ["🤫", "🤗"]
self.fake_config.insult_reactions = ["🤗"]
self.fake_config.user_ids = [self.fake_matrix_client.user_id]
self.fake_matrix_client_pool = Mock(
@ -100,8 +100,8 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
)
@patch.object(matrix_alertbot.callback.CommandFactory, "create", autospec=True)
async def test_message_without_prefix(self, fake_command_create: Mock) -> None:
"""Tests the callback for RoomMessageText without any command prefix"""
async def test_message_without_mention(self, fake_command_create: Mock) -> None:
"""Tests the callback for RoomMessageText without any mention of the bot"""
# Tests that the bot process messages in the room
fake_message_event = Mock(spec=nio.RoomMessageText)
fake_message_event.sender = "@some_other_fake_user:example.com"
@ -116,12 +116,12 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
@patch.object(matrix_alertbot.command, "HelpCommand", autospec=True)
async def test_message_help_client_not_in_pool(self, fake_command: Mock) -> None:
"""Tests the callback for RoomMessageText without any command prefix"""
"""Tests the callback for RoomMessageText without any mention of the bot"""
# Tests that the bot process messages in the room
fake_message_event = Mock(spec=nio.RoomMessageText)
fake_message_event.event_id = "some event id"
fake_message_event.sender = "@some_other_fake_user:example.com"
fake_message_event.body = "!alert help"
fake_message_event.body = "@fake_user help"
fake_message_event.source = {"content": {}}
self.fake_matrix_client_pool.matrix_client = None
@ -133,15 +133,15 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
fake_command.assert_not_called()
@patch.object(matrix_alertbot.command, "HelpCommand", autospec=True)
async def test_message_help_not_in_reply_with_prefix(
async def test_message_help_not_in_reply_with_mention(
self, fake_command: Mock
) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_message_event = Mock(spec=nio.RoomMessageText)
fake_message_event.event_id = "some event id"
fake_message_event.sender = "@some_other_fake_user:example.com"
fake_message_event.body = "!alert help"
fake_message_event.body = "@fake_user help"
fake_message_event.source = {"content": {}}
# Pretend that we received a text message event
@ -161,14 +161,14 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
fake_command.return_value.process.assert_called_once()
@patch.object(matrix_alertbot.command, "HelpCommand", autospec=True)
async def test_message_help_in_reply_with_prefix(self, fake_command: Mock) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
async def test_message_help_in_reply_with_mention(self, fake_command: Mock) -> None:
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_message_event = Mock(spec=nio.RoomMessageText)
fake_message_event.event_id = "some event id"
fake_message_event.sender = "@some_other_fake_user:example.com"
fake_message_event.body = "!alert help"
fake_message_event.body = "@fake_user help"
fake_message_event.source = {
"content": {
"m.relates_to": {"m.in_reply_to": {"event_id": "some alert event id"}}
@ -193,7 +193,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
@patch.object(matrix_alertbot.command.CommandFactory, "create", autospec=True)
async def test_ignore_message_sent_by_bot(self, fake_create_command: Mock) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_message_event = Mock(spec=nio.RoomMessageText)
@ -209,7 +209,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
async def test_ignore_message_sent_on_unauthorized_room(
self, fake_create_command: Mock
) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
self.fake_room.room_id = "!unauthorizedroom@example.com"
@ -224,15 +224,15 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
fake_create_command.assert_not_called()
@patch.object(matrix_alertbot.command, "AckAlertCommand", autospec=True)
async def test_message_ack_not_in_reply_with_prefix(
async def test_message_ack_not_in_reply_with_mention(
self, fake_command: Mock
) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_message_event = Mock(spec=nio.RoomMessageText)
fake_message_event.event_id = "some event id"
fake_message_event.sender = "@some_other_fake_user:example.com"
fake_message_event.body = "!alert ack"
fake_message_event.body = "@fake_user ack"
fake_message_event.source = {"content": {}}
# Pretend that we received a text message event
@ -242,13 +242,48 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
fake_command.assert_not_called()
@patch.object(matrix_alertbot.command, "AckAlertCommand", autospec=True)
async def test_message_ack_in_reply_with_prefix(self, fake_command: Mock) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
async def test_message_ack_in_reply_with_full_mention(
self, fake_command: Mock
) -> None:
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_message_event = Mock(spec=nio.RoomMessageText)
fake_message_event.event_id = "some event id"
fake_message_event.sender = "@some_other_fake_user:example.com"
fake_message_event.body = "!alert ack"
fake_message_event.body = "@fake_user:example.com ack"
fake_message_event.source = {
"content": {
"m.relates_to": {"m.in_reply_to": {"event_id": "some alert event id"}}
}
}
# 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_called_once_with(
self.fake_matrix_client,
self.fake_cache,
self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
fake_message_event.sender,
fake_message_event.event_id,
"some alert event id",
(),
)
fake_command.return_value.process.assert_called_once()
@patch.object(matrix_alertbot.command, "AckAlertCommand", autospec=True)
async def test_message_ack_in_reply_with_short_mention(
self, fake_command: Mock
) -> None:
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_message_event = Mock(spec=nio.RoomMessageText)
fake_message_event.event_id = "some event id"
fake_message_event.sender = "@some_other_fake_user:example.com"
fake_message_event.body = "fake_user ack"
fake_message_event.source = {
"content": {
"m.relates_to": {"m.in_reply_to": {"event_id": "some alert event id"}}
@ -273,15 +308,15 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
fake_command.return_value.process.assert_called_once()
@patch.object(matrix_alertbot.callback, "UnackAlertCommand", autospec=True)
async def test_message_unack_not_in_reply_with_prefix(
async def test_message_unack_not_in_reply_with_mention(
self, fake_command: Mock
) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_message_event = Mock(spec=nio.RoomMessageText)
fake_message_event.event_id = "some event id"
fake_message_event.sender = "@some_other_fake_user:example.com"
fake_message_event.body = "!alert unack"
fake_message_event.body = "@fake_user unack"
fake_message_event.source = {"content": {}}
# Pretend that we received a text message event
@ -291,13 +326,15 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
fake_command.assert_not_called()
@patch.object(matrix_alertbot.command, "UnackAlertCommand", autospec=True)
async def test_message_unack_in_reply_with_prefix(self, fake_command: Mock) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
async def test_message_unack_in_reply_with_mention(
self, fake_command: Mock
) -> None:
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_message_event = Mock(spec=nio.RoomMessageText)
fake_message_event.event_id = "some event id"
fake_message_event.sender = "@some_other_fake_user:example.com"
fake_message_event.body = "!alert unack"
fake_message_event.body = "@fake_user unack"
fake_message_event.source = {
"content": {
"m.relates_to": {"m.in_reply_to": {"event_id": "some alert event id"}}
@ -326,12 +363,12 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
async def test_message_raise_exception(
self, fake_command: Mock, fake_logger
) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_message_event = Mock(spec=nio.RoomMessageText)
fake_message_event.event_id = "some event id"
fake_message_event.sender = "@some_other_fake_user:example.com"
fake_message_event.body = "!alert ack"
fake_message_event.body = "@fake_user ack"
fake_message_event.source = {
"content": {
"m.relates_to": {"m.in_reply_to": {"event_id": "some alert event id"}}
@ -363,7 +400,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
@patch.object(matrix_alertbot.callback, "AckAlertCommand", autospec=True)
async def test_reaction_client_not_in_pool(self, fake_command: Mock) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_alert_event = Mock(spec=nio.RoomMessageText)
fake_alert_event.event_id = "some alert event id"
@ -387,9 +424,12 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# Check that we attempted to execute the command
fake_command.assert_not_called()
@patch.object(matrix_alertbot.callback, "AngryUserCommand", autospec=True)
@patch.object(matrix_alertbot.callback, "AckAlertCommand", autospec=True)
async def test_reaction_to_existing_alert(self, fake_command: Mock) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
async def test_reaction_to_existing_alert(
self, fake_command: Mock, fake_angry_user_command
) -> None:
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_alert_event = Mock(spec=nio.RoomMessageText)
fake_alert_event.event_id = "some alert event id"
@ -424,9 +464,62 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_room.room_id, fake_alert_event.event_id
)
fake_angry_user_command.assert_not_called()
@patch.object(matrix_alertbot.callback, "AngryUserCommand", autospec=True)
@patch.object(matrix_alertbot.callback, "AckAlertCommand", autospec=True)
async def test_insult_reaction(
self, fake_command: Mock, fake_angry_user_command: Mock
) -> None:
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_alert_event = Mock(spec=nio.RoomMessageText)
fake_alert_event.event_id = "some alert event id"
fake_alert_event.sender = self.fake_matrix_client.user_id
fake_reaction_event = Mock(spec=nio.ReactionEvent)
fake_reaction_event.event_id = "some event id"
fake_reaction_event.sender = "@some_other_fake_user:example.com"
fake_reaction_event.reacts_to = fake_alert_event.event_id
fake_reaction_event.key = "🤗"
fake_event_response = Mock(spec=nio.RoomGetEventResponse)
fake_event_response.event = fake_alert_event
self.fake_matrix_client.room_get_event.return_value = fake_event_response
# Pretend that we received a text message event
await self.callbacks.reaction(self.fake_room, fake_reaction_event)
# Check that we attempted to execute the command
fake_command.assert_called_once_with(
self.fake_matrix_client,
self.fake_cache,
self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
fake_reaction_event.sender,
fake_reaction_event.event_id,
"some alert event id",
)
fake_command.return_value.process.assert_called_once()
self.fake_matrix_client.room_get_event.assert_called_once_with(
self.fake_room.room_id, fake_alert_event.event_id
)
fake_angry_user_command.assert_called_once_with(
self.fake_matrix_client,
self.fake_cache,
self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
fake_reaction_event.sender,
fake_reaction_event.event_id,
)
fake_command.return_value.process.assert_called_once()
@patch.object(matrix_alertbot.callback, "AckAlertCommand", autospec=True)
async def test_reaction_to_inexistent_event(self, fake_command: Mock) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_alert_event_id = "some alert event id"
@ -454,7 +547,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
async def test_reaction_to_event_not_from_bot_user(
self, fake_command: Mock
) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_alert_event = Mock(spec=nio.RoomMessageText)
fake_alert_event.event_id = "some alert event id"
@ -486,7 +579,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
async def test_reaction_raise_exception(
self, fake_command: Mock, fake_logger: Mock
) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_alert_event = Mock(spec=nio.RoomMessageText)
fake_alert_event.event_id = "some alert event id"
@ -529,7 +622,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
@patch.object(matrix_alertbot.callback, "AckAlertCommand", autospec=True)
async def test_reaction_unknown(self, fake_command: Mock) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_alert_event_id = "some alert event id"
@ -549,7 +642,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
@patch.object(matrix_alertbot.callback, "AckAlertCommand", autospec=True)
async def test_ignore_reaction_sent_by_bot_user(self, fake_command: Mock) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_alert_event_id = "some alert event id"
@ -571,7 +664,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
async def test_ignore_reaction_in_unauthorized_room(
self, fake_command: Mock
) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
self.fake_room.room_id = "!unauthorizedroom@example.com"
@ -593,7 +686,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
@patch.object(matrix_alertbot.callback, "UnackAlertCommand", autospec=True)
async def test_redaction_client_not_in_pool(self, fake_command: Mock) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_alert_event_id = "some alert event id"
@ -615,7 +708,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
@patch.object(matrix_alertbot.callback, "UnackAlertCommand", autospec=True)
async def test_redaction(self, fake_command: Mock) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_alert_event_id = "some alert event id"
@ -648,7 +741,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
async def test_redaction_raise_exception(
self, fake_command: Mock, fake_logger
) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_alert_event_id = "some alert event id"
@ -684,7 +777,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
@patch.object(matrix_alertbot.callback, "UnackAlertCommand", autospec=True)
async def test_ignore_redaction_sent_by_bot_user(self, fake_command: Mock) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_redaction_event = Mock(spec=nio.RedactionEvent)
fake_redaction_event.sender = self.fake_matrix_client.user_id
@ -703,7 +796,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
async def test_ignore_redaction_in_unauthorized_room(
self, fake_command: Mock
) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
self.fake_room.room_id = "!unauthorizedroom@example.com"
@ -721,7 +814,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_cache.__getitem__.assert_not_called()
async def test_key_verification_start(self) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_transaction_id = "fake transaction id"
@ -745,7 +838,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_matrix_client.to_device.assert_called_once_with(fake_sas.share_key())
async def test_key_verification_start_with_emoji_not_supported(self) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_transaction_id = "fake transaction id"
@ -769,7 +862,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
async def test_key_verification_start_with_accept_key_verification_error(
self,
) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_transaction_id = "fake transaction id"
@ -799,7 +892,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
async def test_key_verification_start_with_to_device_error(
self,
) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_transaction_id = "fake transaction id"
@ -825,7 +918,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_matrix_client.to_device.assert_called_once_with(fake_sas.share_key())
async def test_key_verification_cancel(self) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_key_verification_event = Mock(spec=nio.KeyVerificationCancel)
fake_key_verification_event.sender = "@some_other_fake_user:example.com"
@ -837,7 +930,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# Check that we attempted to execute the command
async def test_key_verification_confirm(self) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_transaction_id = "fake transaction id"
@ -862,7 +955,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
)
async def test_key_verification_confirm_with_error(self) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_transaction_id = "fake transaction id"
@ -891,7 +984,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
)
async def test_key_verification_end(self) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_transaction_id = "fake transaction id"
@ -912,7 +1005,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_matrix_client.to_device.assert_called_once_with(fake_sas.get_mac())
async def test_key_verification_end_with_missing_transaction_id(self) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_transaction_id = "fake transaction id"
@ -932,7 +1025,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_matrix_client.to_device.assert_not_called()
async def test_key_verification_end_with_mac_error(self) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_transaction_id = "fake transaction id"
@ -953,7 +1046,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_matrix_client.to_device.assert_not_called()
async def test_key_verification_end_with_to_device_error(self) -> None:
"""Tests the callback for RoomMessageText with the command prefix"""
"""Tests the callback for RoomMessageText with a mention of the bot"""
# Tests that the bot process messages in the room that contain a command
fake_transaction_id = "fake transaction id"

View file

@ -1,3 +1,4 @@
import random
import unittest
from typing import Dict, Optional
from unittest.mock import MagicMock, Mock, call, patch
@ -10,6 +11,7 @@ import matrix_alertbot.command
from matrix_alertbot.alertmanager import AlertmanagerClient
from matrix_alertbot.command import (
AckAlertCommand,
AngryUserCommand,
CommandFactory,
HelpCommand,
UnackAlertCommand,
@ -79,6 +81,8 @@ async def delete_silence_raise_silence_not_found_error(silence_id: str) -> None:
class CommandTestCase(unittest.IsolatedAsyncioTestCase):
def setUp(self) -> None:
random.seed(42)
# Create a Command object and give it some Mock'd objects to use
self.fake_matrix_client = Mock(spec=nio.AsyncClient)
self.fake_matrix_client.user = "@fake_user:example.com"
@ -105,7 +109,8 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
# We don't spec config, as it doesn't currently have well defined attributes
self.fake_config = Mock()
self.fake_config.allowed_rooms = [self.fake_room.room_id]
self.fake_config.command_prefix = "!alert "
self.fake_config.allowed_reactions = {"🤫", "😶", "🤐", "🤗"}
self.fake_config.insult_reactions = {"🤗"}
@patch.object(matrix_alertbot.command.AckAlertCommand, "process")
async def test_process_ack_command(self, fake_ack: Mock) -> None:
@ -645,29 +650,6 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
_, _, text = fake_send_text_to_room.call_args.args
self.assertIn("help commands", text)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_help_with_rules_topic(self, fake_send_text_to_room: Mock) -> None:
"""Tests the callback for InviteMemberEvents"""
# Tests that the bot attempts to join a room after being invited to it
command = HelpCommand(
self.fake_matrix_client,
self.fake_cache,
self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
self.fake_event_id,
("rules",),
)
await command.process()
# Check that we attempted to create silences
fake_send_text_to_room.assert_called_once()
_, _, text = fake_send_text_to_room.call_args.args
self.assertIn("rules!", text)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_help_with_commands_topic(self, fake_send_text_to_room: Mock) -> None:
"""Tests the callback for InviteMemberEvents"""
@ -689,7 +671,43 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
# Check that we attempted to create silences
fake_send_text_to_room.assert_called_once()
_, _, text = fake_send_text_to_room.call_args.args
self.assertIn("Available commands", text)
self.assertIn("Here is the list of available commands", text)
reactions = (
self.fake_config.allowed_reactions - self.fake_config.insult_reactions
)
for reaction in reactions:
self.assertIn(reaction, text)
for reaction in self.fake_config.insult_reactions:
self.assertNotIn(reaction, text)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_angry_user(self, fake_send_text_to_room: Mock) -> None:
"""Tests the callback for InviteMemberEvents"""
# Tests that the bot attempts to join a room after being invited to it
command = AngryUserCommand(
self.fake_matrix_client,
self.fake_cache,
self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
self.fake_event_id,
)
await command.process()
# Check that we attempted to create silences
fake_send_text_to_room.assert_called_once()
text, html, _ = fake_send_text_to_room.call_args.kwargs.values()
self.assertRegex(
text,
"^@some_other_fake_user:example.com ",
)
self.assertRegex(
html,
'^<a href="https://matrix.to/#/@some_other_fake_user:example.com">@some_other_fake_user:example.com</a> ',
)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_help_with_unknown_topic(self, fake_send_text_to_room: Mock) -> None:
@ -712,7 +730,7 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
# Check that we attempted to create silences
fake_send_text_to_room.assert_called_once()
_, _, text = fake_send_text_to_room.call_args.args
self.assertEqual("Unknown help topic!", text)
self.assertIn("I'm sorry, I don't know much about this topic.", text)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_unknown_command(self, fake_send_text_to_room: Mock) -> None:

View file

@ -91,8 +91,6 @@ class ConfigTestCase(unittest.TestCase):
self.assertIsNone(config.template_dir)
self.assertEqual("!alert ", config.command_prefix)
@patch("os.path.isdir")
@patch("os.path.exists")
@patch("os.mkdir")
@ -140,7 +138,8 @@ class ConfigTestCase(unittest.TestCase):
self.assertEqual("https://matrix.domain.tld", config.accounts[1].homeserver_url)
self.assertEqual("fake_device_name", config.device_name)
self.assertEqual(["!abcdefgh:matrix.example.com"], config.allowed_rooms)
self.assertEqual({"🤫", "😶", "🤐"}, config.allowed_reactions)
self.assertEqual({"🤫", "😶", "🤐", "🤗"}, config.allowed_reactions)
self.assertEqual({"🤗"}, config.insult_reactions)
self.assertIsNone(config.address)
self.assertIsNone(config.port)
@ -156,8 +155,6 @@ class ConfigTestCase(unittest.TestCase):
self.assertEqual("data/templates", config.template_dir)
self.assertEqual("!alert ", config.command_prefix)
def test_read_config_raise_config_error(self) -> None:
with self.assertRaises(ParseConfigError):
Config("")

View file

@ -62,7 +62,6 @@ class MatrixClientPoolTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_account_config_2.token_file = "account2.token.secret"
self.fake_config = Mock(spec=Config)
self.fake_config.store_dir = "/dev/null"
self.fake_config.command_prefix = "!alert"
self.fake_config.accounts = [
self.fake_account_config_1,
self.fake_account_config_2,