matrix-alertbot/tests/test_command.py

744 lines
25 KiB
Python
Raw Normal View History

import unittest
2022-07-10 14:06:36 +02:00
from typing import List
from unittest.mock import MagicMock, Mock, call, patch
import nio
from diskcache import Cache
import matrix_alertbot.callback
import matrix_alertbot.command
from matrix_alertbot.alertmanager import AlertmanagerClient
from matrix_alertbot.command import (
AckAlertCommand,
CommandFactory,
HelpCommand,
UnackAlertCommand,
UnknownCommand,
)
from matrix_alertbot.errors import (
AlertmanagerError,
AlertNotFoundError,
SilenceNotFoundError,
)
2022-07-10 12:51:49 +02:00
from matrix_alertbot.matcher import AlertMatcher, AlertRegexMatcher
from tests.utils import make_awaitable
2022-07-09 09:56:28 +02:00
async def create_silence_raise_alertmanager_error(
2022-07-10 12:51:49 +02:00
fingerprint: str, duration: str, user: str, matchers: List[AlertMatcher]
2022-07-09 09:56:28 +02:00
) -> str:
if fingerprint == "fingerprint1":
raise AlertmanagerError
2022-07-10 02:40:04 +02:00
return "silence1"
2022-07-09 09:56:28 +02:00
async def create_silence_raise_alert_not_found_error(
fingerprint: str, duration: str, user: str, matchers: List[AlertMatcher]
) -> str:
if fingerprint == "fingerprint1":
raise AlertNotFoundError
return "silence1"
2022-07-10 12:51:49 +02:00
async def delete_silence_raise_alertmanager_error(
fingerprint: str, matchers: List[AlertMatcher]
) -> List[str]:
2022-07-09 09:56:28 +02:00
if fingerprint == "fingerprint1":
raise AlertmanagerError
2022-07-09 10:38:40 +02:00
return ["silence1"]
2022-07-09 09:56:28 +02:00
async def delete_silence_raise_silence_not_found_error(
fingerprint: str, matchers: List[AlertMatcher]
) -> List[str]:
if fingerprint == "fingerprint1":
raise SilenceNotFoundError
return ["silence1"]
class CommandTestCase(unittest.IsolatedAsyncioTestCase):
def setUp(self) -> None:
# Create a Command object and give it some Mock'd objects to use
self.fake_client = Mock(spec=nio.AsyncClient)
self.fake_client.user = "@fake_user:example.com"
# Pretend that attempting to send a message is always successful
self.fake_client.room_send.return_value = make_awaitable(None)
2022-07-09 09:56:28 +02:00
self.fake_fingerprints = ["fingerprint1", "fingerprint2"]
self.fake_silences = ["silence1", "silence2"]
self.fake_cache = MagicMock(spec=Cache)
2022-07-10 15:11:25 +02:00
self.fake_cache.__getitem__.return_value = self.fake_fingerprints
self.fake_cache.__contains__.return_value = True
self.fake_alertmanager = Mock(spec=AlertmanagerClient)
2022-07-09 09:56:28 +02:00
self.fake_alertmanager.delete_silences.return_value = self.fake_silences
# Create a fake room to play with
self.fake_room = Mock(spec=nio.MatrixRoom)
self.fake_room.room_id = "!abcdefg:example.com"
self.fake_room.display_name = "Fake Room"
self.fake_room.user_name.side_effect = lambda x: x
2022-07-10 14:06:36 +02:00
self.fake_event_id = "some event id"
self.fake_sender = "@some_other_fake_user:example.com"
self.fake_alert_event_id = "some alert event id"
2022-07-09 09:56:28 +02:00
# We don't spec config, as it doesn't currently have well defined attributes
self.fake_config = Mock()
self.fake_config.room_id = self.fake_room.room_id
self.fake_config.command_prefix = "!alert "
@patch.object(matrix_alertbot.command.AckAlertCommand, "process")
async def test_process_ack_command(self, fake_ack: Mock) -> None:
"""Tests the callback for InviteMemberEvents"""
# Tests that the bot attempts to join a room after being invited to it
command = CommandFactory.create(
"ack",
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
self.fake_room,
2022-07-10 14:06:36 +02:00
self.fake_sender,
2022-07-10 14:09:00 +02:00
self.fake_event_id,
self.fake_alert_event_id,
)
await command.process()
2022-07-09 09:56:28 +02:00
# Check that we attempted to process the command
fake_ack.assert_called_once()
@patch.object(matrix_alertbot.command.UnackAlertCommand, "process")
async def test_process_unack_command(self, fake_unack: Mock) -> None:
"""Tests the callback for InviteMemberEvents"""
# Tests that the bot attempts to join a room after being invited to it
for unack_cmd in ("unack", "nack"):
command = CommandFactory.create(
unack_cmd,
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
self.fake_room,
2022-07-10 14:06:36 +02:00
self.fake_sender,
2022-07-10 14:09:00 +02:00
self.fake_event_id,
self.fake_alert_event_id,
)
await command.process()
# Check that we attempted to process the command
fake_unack.assert_has_calls([call(), call()])
@patch.object(matrix_alertbot.command.HelpCommand, "process")
async def test_process_help_command(self, fake_help: Mock) -> None:
"""Tests the callback for InviteMemberEvents"""
# Tests that the bot attempts to join a room after being invited to it
command = CommandFactory.create(
"help",
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
self.fake_room,
2022-07-10 14:06:36 +02:00
self.fake_sender,
2022-07-10 14:09:00 +02:00
self.fake_event_id,
)
await command.process()
# Check that we attempted to process the command
fake_help.assert_called_once()
@patch.object(matrix_alertbot.command.UnknownCommand, "process")
async def test_process_unknown_command(self, fake_unknown: Mock) -> None:
"""Tests the callback for InviteMemberEvents"""
# Tests that the bot attempts to join a room after being invited to it
command = CommandFactory.create(
"",
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
self.fake_room,
2022-07-10 14:06:36 +02:00
self.fake_sender,
2022-07-10 14:09:00 +02:00
self.fake_event_id,
)
await command.process()
# Check that we attempted to process the command
fake_unknown.assert_called_once()
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_ack_without_duration_nor_matchers(
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 = AckAlertCommand(
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"ack",
self.fake_room,
2022-07-10 14:06:36 +02:00
self.fake_sender,
self.fake_event_id,
self.fake_alert_event_id,
)
await command.process()
2022-07-10 02:40:04 +02:00
# Check that we attempted to create silences
self.fake_alertmanager.create_silence.assert_has_calls(
[
call(fingerprint, 86400, self.fake_sender, [])
2022-07-10 02:40:04 +02:00
for fingerprint in self.fake_fingerprints
]
)
fake_send_text_to_room.assert_called_once_with(
self.fake_client,
self.fake_room.room_id,
"Created 2 silences with a duration of 1d.",
)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_ack_without_duration_and_with_matchers(
2022-07-10 02:40:04 +02:00
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
2022-07-10 12:51:49 +02:00
matchers = [
2022-07-10 02:40:04 +02:00
AlertMatcher(label="alertname", value="alert1"),
2022-07-10 12:51:49 +02:00
AlertRegexMatcher(label="severity", regex="critical"),
2022-07-10 02:40:04 +02:00
]
command = AckAlertCommand(
2022-07-10 02:40:04 +02:00
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
2022-07-10 12:51:49 +02:00
"ack alertname=alert1 severity=~critical",
2022-07-10 02:40:04 +02:00
self.fake_room,
2022-07-10 14:06:36 +02:00
self.fake_sender,
self.fake_event_id,
self.fake_alert_event_id,
2022-07-10 02:40:04 +02:00
)
await command.process()
2022-07-10 02:40:04 +02:00
# Check that we attempted to create silences
self.fake_alertmanager.create_silence.assert_has_calls(
2022-07-09 09:56:28 +02:00
[
call(
fingerprint,
86400,
2022-07-10 14:06:36 +02:00
self.fake_sender,
2022-07-10 02:40:04 +02:00
matchers,
)
2022-07-09 09:56:28 +02:00
for fingerprint in self.fake_fingerprints
]
)
fake_send_text_to_room.assert_called_once_with(
self.fake_client,
self.fake_room.room_id,
"Created 2 silences with a duration of 1d.",
)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_ack_with_duration_and_without_matchers(
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 = AckAlertCommand(
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"ack 1w 3d",
2022-07-10 02:40:04 +02:00
self.fake_room,
2022-07-10 14:06:36 +02:00
self.fake_sender,
self.fake_event_id,
self.fake_alert_event_id,
2022-07-10 02:40:04 +02:00
)
await command.process()
2022-07-10 02:40:04 +02:00
# Check that we attempted to create silences
self.fake_alertmanager.create_silence.assert_has_calls(
[
call(fingerprint, 864000, self.fake_sender, [])
2022-07-10 02:40:04 +02:00
for fingerprint in self.fake_fingerprints
]
)
fake_send_text_to_room.assert_called_once_with(
self.fake_client,
self.fake_room.room_id,
"Created 2 silences with a duration of 1w 3d.",
2022-07-10 02:40:04 +02:00
)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_ack_with_duration_and_matchers(
2022-07-10 02:40:04 +02:00
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
2022-07-10 12:51:49 +02:00
matchers = [
2022-07-10 02:40:04 +02:00
AlertMatcher(label="alertname", value="alert1"),
AlertMatcher(label="severity", value="critical"),
]
command = AckAlertCommand(
2022-07-10 02:40:04 +02:00
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"ack 1w 3d alertname=alert1 severity=critical",
self.fake_room,
2022-07-10 14:06:36 +02:00
self.fake_sender,
self.fake_event_id,
self.fake_alert_event_id,
)
await command.process()
# Check that we attempted to create silences
self.fake_alertmanager.create_silence.assert_has_calls(
2022-07-09 09:56:28 +02:00
[
call(
fingerprint,
864000,
2022-07-10 14:06:36 +02:00
self.fake_sender,
2022-07-10 02:40:04 +02:00
matchers,
)
2022-07-09 09:56:28 +02:00
for fingerprint in self.fake_fingerprints
]
)
fake_send_text_to_room.assert_called_once_with(
self.fake_client,
self.fake_room.room_id,
"Created 2 silences with a duration of 1w 3d.",
)
2022-07-09 09:56:28 +02:00
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_ack_raise_alertmanager_error(
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 = AckAlertCommand(
2022-07-09 09:56:28 +02:00
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"ack",
self.fake_room,
2022-07-10 14:06:36 +02:00
self.fake_sender,
self.fake_event_id,
self.fake_alert_event_id,
2022-07-09 09:56:28 +02:00
)
self.fake_alertmanager.create_silence.side_effect = (
create_silence_raise_alertmanager_error
)
await command.process()
2022-07-09 09:56:28 +02:00
# Check that we attempted to create silences
self.fake_alertmanager.create_silence.assert_has_calls(
[
call(fingerprint, 86400, self.fake_sender, [])
2022-07-09 09:56:28 +02:00
for fingerprint in self.fake_fingerprints
]
)
fake_send_text_to_room.assert_called_once_with(
self.fake_client,
self.fake_room.room_id,
"Created 1 silences with a duration of 1d.",
)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_ack_raise_alert_not_found_error(
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 = AckAlertCommand(
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"ack",
self.fake_room,
self.fake_sender,
self.fake_event_id,
self.fake_alert_event_id,
)
self.fake_alertmanager.create_silence.side_effect = (
create_silence_raise_alert_not_found_error
)
await command.process()
# Check that we attempted to create silences
self.fake_alertmanager.create_silence.assert_has_calls(
[
call(fingerprint, 86400, self.fake_sender, [])
for fingerprint in self.fake_fingerprints
]
)
fake_send_text_to_room.assert_has_calls(
[
call(
self.fake_client,
self.fake_room.room_id,
"Sorry, I couldn't find 1 alerts, therefore I couldn't create their silence.",
),
call(
self.fake_client,
self.fake_room.room_id,
"Created 1 silences with a duration of 1d.",
),
]
)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_ack_with_invalid_duration(
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 = AckAlertCommand(
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
2022-07-12 00:29:46 +02:00
"ack invalid duration",
self.fake_room,
self.fake_sender,
self.fake_event_id,
self.fake_alert_event_id,
)
await command.process()
# Check that we attempted to create silences
self.fake_alertmanager.create_silence.assert_not_called()
fake_send_text_to_room.assert_called_once_with(
self.fake_client,
self.fake_room.room_id,
2022-07-12 00:29:46 +02:00
"I tried really hard, but I can't convert the duration 'invalid duration' to a number of seconds.",
)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_ack_with_event_not_found_in_cache(
2022-07-10 12:51:49 +02:00
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
self.fake_cache.__contains__.return_value = False
command = AckAlertCommand(
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"ack",
self.fake_room,
self.fake_sender,
self.fake_event_id,
self.fake_alert_event_id,
)
await command.process()
# Check that we attempted to create silences
self.fake_alertmanager.create_silence.assert_not_called()
fake_send_text_to_room.assert_not_called()
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_unack_without_matchers(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 = UnackAlertCommand(
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"unack",
self.fake_room,
2022-07-10 14:06:36 +02:00
self.fake_sender,
self.fake_event_id,
self.fake_alert_event_id,
)
await command.process()
# Check that we attempted to create silences
2022-07-09 09:56:28 +02:00
self.fake_alertmanager.delete_silences.assert_has_calls(
2022-07-10 12:51:49 +02:00
[call(fingerprint, []) for fingerprint in self.fake_fingerprints]
)
fake_send_text_to_room.assert_called_with(
self.fake_client, self.fake_room.room_id, "Removed 4 silences."
)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_unack_with_matchers(self, fake_send_text_to_room: Mock) -> None:
2022-07-10 12:51:49 +02:00
"""Tests the callback for InviteMemberEvents"""
# Tests that the bot attempts to join a room after being invited to it
matchers = [
AlertMatcher(label="alertname", value="alert1"),
AlertRegexMatcher(label="severity", regex="critical"),
]
command = UnackAlertCommand(
2022-07-10 12:51:49 +02:00
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"unack alertname=alert1 severity=~critical",
self.fake_room,
2022-07-10 14:06:36 +02:00
self.fake_sender,
self.fake_event_id,
self.fake_alert_event_id,
2022-07-10 12:51:49 +02:00
)
await command.process()
2022-07-10 12:51:49 +02:00
# Check that we attempted to create silences
self.fake_alertmanager.delete_silences.assert_has_calls(
[call(fingerprint, matchers) for fingerprint in self.fake_fingerprints]
2022-07-09 09:56:28 +02:00
)
fake_send_text_to_room.assert_called_with(
self.fake_client, self.fake_room.room_id, "Removed 4 silences."
)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_unack_silence_raise_alertmanager_error(
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 = UnackAlertCommand(
2022-07-09 09:56:28 +02:00
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"unack",
self.fake_room,
2022-07-10 14:06:36 +02:00
self.fake_sender,
self.fake_event_id,
self.fake_alert_event_id,
2022-07-09 09:56:28 +02:00
)
self.fake_alertmanager.delete_silences.side_effect = (
delete_silence_raise_alertmanager_error
)
await command.process()
2022-07-09 09:56:28 +02:00
# Check that we attempted to create silences
self.fake_alertmanager.delete_silences.assert_has_calls(
2022-07-10 12:51:49 +02:00
[call(fingerprint, []) for fingerprint in self.fake_fingerprints]
2022-07-09 09:56:28 +02:00
)
fake_send_text_to_room.assert_called_with(
2022-07-09 10:38:40 +02:00
self.fake_client, self.fake_room.room_id, "Removed 1 silences."
2022-07-09 09:56:28 +02:00
)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_unack_raise_silence_not_found_error(
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 = UnackAlertCommand(
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"unack",
self.fake_room,
self.fake_sender,
self.fake_event_id,
self.fake_alert_event_id,
)
self.fake_alertmanager.delete_silences.side_effect = (
delete_silence_raise_silence_not_found_error
)
await command.process()
# Check that we attempted to create silences
self.fake_alertmanager.delete_silences.assert_has_calls(
[call(fingerprint, []) for fingerprint in self.fake_fingerprints]
)
fake_send_text_to_room.assert_has_calls(
[
call(
self.fake_client,
self.fake_room.room_id,
"Sorry, I couldn't find 1 alerts, therefore I couldn't remove their silences.",
),
call(
self.fake_client,
self.fake_room.room_id,
"Removed 1 silences.",
),
]
)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_unack_with_event_not_found_in_cache(
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
self.fake_cache.__contains__.return_value = False
command = UnackAlertCommand(
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"unack",
self.fake_room,
self.fake_sender,
self.fake_event_id,
self.fake_alert_event_id,
)
await command.process()
# Check that we attempted to create silences
self.fake_alertmanager.create_silence.assert_not_called()
fake_send_text_to_room.assert_not_called()
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_help_without_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_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"help",
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 = 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_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"help rules",
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 = 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"""
# Tests that the bot attempts to join a room after being invited to it
command = HelpCommand(
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"help commands",
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 = fake_send_text_to_room.call_args.args
self.assertIn("Available commands", text)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_help_with_unknown_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_client,
self.fake_cache,
self.fake_alertmanager,
self.fake_config,
"help unknown",
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 = fake_send_text_to_room.call_args.args
self.assertEqual("Unknown help topic!", text)
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_unknown_command(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 = UnknownCommand(
self.fake_client,
self.fake_cache,
self.fake_alertmanager,
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_with(
self.fake_client,
self.fake_room.room_id,
"Unknown command ''. Try the 'help' command for more information.",
)
if __name__ == "__main__":
unittest.main()