diff --git a/matrix_alertbot/alertmanager.py b/matrix_alertbot/alertmanager.py
index 304861a..9cab57a 100644
--- a/matrix_alertbot/alertmanager.py
+++ b/matrix_alertbot/alertmanager.py
@@ -12,10 +12,12 @@ from matrix_alertbot.errors import (
AlertmanagerServerError,
AlertNotFoundError,
SilenceExpiredError,
+ SilenceExtendError,
SilenceNotFoundError,
)
-MAX_DURATION_DAYS = 3652
+DEFAULT_DURATION = timedelta(hours=3)
+MAX_DURATION = timedelta(days=3652)
class AlertmanagerClient:
@@ -60,7 +62,6 @@ class AlertmanagerClient:
fingerprint: str,
user: str,
duration_seconds: Optional[int] = None,
- silence_id: Optional[str] = None,
) -> str:
alert = await self.get_alert(fingerprint)
@@ -69,13 +70,50 @@ class AlertmanagerClient:
for label, value in alert["labels"].items()
]
- start_time = datetime.now()
- max_duration = timedelta(days=MAX_DURATION_DAYS)
- if duration_seconds is None or duration_seconds > max_duration.total_seconds():
- end_time = start_time + max_duration
+ return await self._create_or_update_silence(
+ fingerprint, silence_matchers, user, duration_seconds
+ )
+
+ async def update_silence(self, fingerprint: str) -> str:
+ try:
+ silence_id: Optional[str]
+ expire_time: Optional[int]
+ silence_id, expire_time = self.cache.get(fingerprint, expire_time=True)
+ except TypeError:
+ silence_id = None
+
+ if silence_id is None:
+ raise SilenceNotFoundError(
+ f"Cannot find silence for alert with fingerprint {fingerprint} in cache."
+ )
+
+ if expire_time is not None:
+ raise SilenceExtendError(
+ f"Cannot extend silence ID {silence_id} with static duration."
+ )
+
+ silence = await self.get_silence(silence_id)
+ user = silence["createdBy"]
+ silence_matchers = silence["matchers"]
+
+ return await self._create_or_update_silence(fingerprint, silence_matchers, user)
+
+ async def _create_or_update_silence(
+ self,
+ fingerprint: str,
+ silence_matchers: List,
+ user: str,
+ duration_seconds: Optional[int] = None,
+ silence_id: Optional[str] = None,
+ ) -> str:
+ if duration_seconds is None:
+ duration_delta = DEFAULT_DURATION
+ elif duration_seconds > MAX_DURATION.total_seconds():
+ duration_delta = MAX_DURATION
else:
duration_delta = timedelta(seconds=duration_seconds)
- end_time = start_time + duration_delta
+ start_time = datetime.now()
+ end_time = start_time + duration_delta
silence = {
"id": silence_id,
@@ -97,6 +135,8 @@ class AlertmanagerClient:
f"Cannot create silence for alert fingerprint {fingerprint}"
) from e
+ self.cache.set(fingerprint, data["silenceID"], expire=duration_seconds)
+
return data["silenceID"]
async def delete_silence(self, silence_id: str) -> None:
diff --git a/matrix_alertbot/callback.py b/matrix_alertbot/callback.py
index f984e35..25d8d37 100644
--- a/matrix_alertbot/callback.py
+++ b/matrix_alertbot/callback.py
@@ -24,8 +24,8 @@ logger = logging.getLogger(__name__)
class Callbacks:
def __init__(
self,
- client: AsyncClient,
- alertmanager: AlertmanagerClient,
+ matrix_client: AsyncClient,
+ alertmanager_client: AlertmanagerClient,
cache: Cache,
config: Config,
):
@@ -39,9 +39,9 @@ class Callbacks:
config: Bot configuration parameters.
"""
- self.client = client
+ self.matrix_client = matrix_client
self.cache = cache
- self.alertmanager = alertmanager
+ self.alertmanager_client = alertmanager_client
self.config = config
self.command_prefix = config.command_prefix
@@ -54,7 +54,7 @@ class Callbacks:
event: The event defining the message.
"""
# Ignore messages from ourselves
- if event.sender == self.client.user:
+ if event.sender == self.matrix_client.user:
return
# Ignore messages from unauthorized room
@@ -91,9 +91,9 @@ class Callbacks:
try:
command = CommandFactory.create(
cmd,
- self.client,
+ self.matrix_client,
self.cache,
- self.alertmanager,
+ self.alertmanager_client,
self.config,
room,
event.sender,
@@ -122,7 +122,7 @@ class Callbacks:
# Attempt to join 3 times before giving up
for attempt in range(3):
- result = await self.client.join(room.room_id)
+ result = await self.matrix_client.join(room.room_id)
if type(result) == JoinError:
logger.error(
f"Error joining room {room.room_id} (attempt %d): %s",
@@ -146,7 +146,7 @@ class Callbacks:
not actually our own invite event (such as the inviter's membership).
This makes sure we only call `callbacks.invite` with our own invite events.
"""
- if event.state_key == self.client.user_id:
+ if event.state_key == self.matrix_client.user_id:
# This is our own membership (invite) event
await self.invite(room, event)
@@ -167,7 +167,7 @@ class Callbacks:
return
# Ignore reactions from ourselves
- if event.sender == self.client.user:
+ if event.sender == self.matrix_client.user:
return
reaction = event.source.get("content", {}).get("m.relates_to", {}).get("key")
@@ -178,7 +178,9 @@ class Callbacks:
return
# Get the original event that was reacted to
- event_response = await self.client.room_get_event(room.room_id, alert_event_id)
+ event_response = await self.matrix_client.room_get_event(
+ room.room_id, alert_event_id
+ )
if isinstance(event_response, RoomGetEventError):
logger.warning(
f"Error getting event that was reacted to ({alert_event_id})"
@@ -192,9 +194,9 @@ class Callbacks:
# Send a message acknowledging the reaction
command = AckAlertCommand(
- self.client,
+ self.matrix_client,
self.cache,
- self.alertmanager,
+ self.alertmanager_client,
self.config,
room,
event.sender,
@@ -210,15 +212,15 @@ class Callbacks:
return
# Ignore redactions from ourselves
- if event.sender == self.client.user:
+ if event.sender == self.matrix_client.user:
return
logger.debug(f"Received event to remove event ID {event.redacts}")
command = UnackAlertCommand(
- self.client,
+ self.matrix_client,
self.cache,
- self.alertmanager,
+ self.alertmanager_client,
self.config,
room,
event.sender,
diff --git a/matrix_alertbot/command.py b/matrix_alertbot/command.py
index cc42784..a4c23d7 100644
--- a/matrix_alertbot/command.py
+++ b/matrix_alertbot/command.py
@@ -21,9 +21,9 @@ logger = logging.getLogger(__name__)
class BaseCommand:
def __init__(
self,
- client: AsyncClient,
+ matrix_client: AsyncClient,
cache: Cache,
- alertmanager: AlertmanagerClient,
+ alertmanager_client: AlertmanagerClient,
config: Config,
room: MatrixRoom,
sender: str,
@@ -49,9 +49,9 @@ class BaseCommand:
event_id: The ID of the event describing the command.
"""
- self.client = client
+ self.matrix_client = matrix_client
self.cache = cache
- self.alertmanager = alertmanager
+ self.alertmanager_client = alertmanager_client
self.config = config
self.room = room
self.sender = sender
@@ -98,7 +98,7 @@ class AckAlertCommand(BaseAlertCommand):
if duration_seconds is None:
logger.error(f"Unable to create silence: Invalid duration '{duration}'")
await send_text_to_room(
- self.client,
+ self.matrix_client,
self.room.room_id,
f"I tried really hard, but I can't convert the duration '{duration}' to a number of seconds.",
)
@@ -108,16 +108,13 @@ class AckAlertCommand(BaseAlertCommand):
f"Unable to create silence: Duration must be positive, got '{duration}'"
)
await send_text_to_room(
- self.client,
+ self.matrix_client,
self.room.room_id,
"I can't create a silence with a negative duration!",
)
return
-
- cache_expire_time = duration_seconds
else:
duration_seconds = None
- cache_expire_time = self.config.cache_expire_time
logger.debug(
"Receiving a command to create a silence for an indefinite period"
)
@@ -133,27 +130,14 @@ class AckAlertCommand(BaseAlertCommand):
)
return
- cached_silence_id = self.cache.get(alert_fingerprint)
- if cached_silence_id is None:
- logger.debug(
- f"Creating silence for alert with fingerprint {alert_fingerprint}."
- )
- else:
- logger.debug(
- f"Updating silence with ID {cached_silence_id} for alert with fingerprint {alert_fingerprint}."
- )
-
try:
- silence_id = await self.alertmanager.create_silence(
- alert_fingerprint,
- self.room.user_name(self.sender),
- duration_seconds,
- cached_silence_id,
+ silence_id = await self.alertmanager_client.create_silence(
+ alert_fingerprint, self.room.user_name(self.sender), duration_seconds
)
except AlertNotFoundError as e:
logger.warning(f"Unable to create silence: {e}")
await send_text_to_room(
- self.client,
+ self.matrix_client,
self.room.room_id,
f"Sorry, I couldn't create silence for alert with fingerprint {alert_fingerprint}: {e}",
)
@@ -161,18 +145,17 @@ class AckAlertCommand(BaseAlertCommand):
except AlertmanagerError as e:
logger.exception(f"Unable to create silence: {e}", exc_info=e)
await send_text_to_room(
- self.client,
+ self.matrix_client,
self.room.room_id,
f"Sorry, I couldn't create silence for alert with fingerprint {alert_fingerprint} "
f"because something went wrong with Alertmanager: {e}",
)
return
- self.cache.set(self.event_id, alert_fingerprint, expire=cache_expire_time)
- self.cache.set(alert_fingerprint, silence_id, expire=duration_seconds)
+ self.cache.set(self.event_id, alert_fingerprint, expire=duration_seconds)
await send_text_to_room(
- self.client,
+ self.matrix_client,
self.room.room_id,
f"Created silence with ID {silence_id}.",
)
@@ -212,11 +195,11 @@ class UnackAlertCommand(BaseAlertCommand):
)
try:
- await self.alertmanager.delete_silence(silence_id)
+ await self.alertmanager_client.delete_silence(silence_id)
except (SilenceNotFoundError, SilenceExpiredError) as e:
logger.error(f"Unable to delete silence: {e}")
await send_text_to_room(
- self.client,
+ self.matrix_client,
self.room.room_id,
f"Sorry, I couldn't remove silence for alert with fingerprint {alert_fingerprint}: {e}",
)
@@ -224,7 +207,7 @@ class UnackAlertCommand(BaseAlertCommand):
except AlertmanagerError as e:
logger.exception(f"Unable to delete silence: {e}", exc_info=e)
await send_text_to_room(
- self.client,
+ self.matrix_client,
self.room.room_id,
f"Sorry, I couldn't remove silence for alert with fingerprint {alert_fingerprint} "
f"because something went wrong with Alertmanager: {e}",
@@ -234,7 +217,7 @@ class UnackAlertCommand(BaseAlertCommand):
self.cache.delete(alert_fingerprint)
await send_text_to_room(
- self.client,
+ self.matrix_client,
self.room.room_id,
f"Removed silence with ID {silence_id}.",
)
@@ -249,7 +232,7 @@ class HelpCommand(BaseCommand):
"Hello, I am a bot made with matrix-nio! Use `help commands` to view "
"available commands."
)
- await send_text_to_room(self.client, self.room.room_id, text)
+ await send_text_to_room(self.matrix_client, self.room.room_id, text)
return
topic = self.args[0]
@@ -259,7 +242,7 @@ class HelpCommand(BaseCommand):
text = "Available commands: ..."
else:
text = "Unknown help topic!"
- await send_text_to_room(self.client, self.room.room_id, text)
+ await send_text_to_room(self.matrix_client, self.room.room_id, text)
class UnknownCommand(BaseCommand):
@@ -268,7 +251,7 @@ class UnknownCommand(BaseCommand):
f"Sending unknown command response to room {self.room.display_name}"
)
await send_text_to_room(
- self.client,
+ self.matrix_client,
self.room.room_id,
"Unknown command. Try the 'help' command for more information.",
)
diff --git a/matrix_alertbot/errors.py b/matrix_alertbot/errors.py
index 2715b18..8e06487 100644
--- a/matrix_alertbot/errors.py
+++ b/matrix_alertbot/errors.py
@@ -49,6 +49,12 @@ class SilenceExpiredError(AlertmanagerError):
pass
+class SilenceExtendError(AlertmanagerError):
+ """An error encountered when a silence cannot be extended."""
+
+ pass
+
+
class AlertmanagerServerError(AlertmanagerError):
"""An error encountered with Alertmanager server."""
diff --git a/matrix_alertbot/main.py b/matrix_alertbot/main.py
index 87d0b4c..1b246df 100644
--- a/matrix_alertbot/main.py
+++ b/matrix_alertbot/main.py
@@ -28,7 +28,7 @@ logger = logging.getLogger(__name__)
def create_matrix_client(config: Config) -> AsyncClient:
# Configuration options for the AsyncClient
- client_config = AsyncClientConfig(
+ matrix_client_config = AsyncClientConfig(
max_limit_exceeded=0,
max_timeouts=0,
store_sync_tokens=True,
@@ -36,38 +36,38 @@ def create_matrix_client(config: Config) -> AsyncClient:
)
# Initialize the matrix client
- client = AsyncClient(
+ matrix_client = AsyncClient(
config.homeserver_url,
config.user_id,
device_id=config.device_id,
store_path=config.store_dir,
- config=client_config,
+ config=matrix_client_config,
)
if config.user_token:
- client.access_token = config.user_token
- client.user_id = config.user_id
+ matrix_client.access_token = config.user_token
+ matrix_client.user_id = config.user_id
- return client
+ return matrix_client
async def start_matrix_client(
- client: AsyncClient, cache: Cache, config: Config
+ matrix_client: AsyncClient, cache: Cache, config: Config
) -> bool:
# Keep trying to reconnect on failure (with some time in-between)
while True:
try:
if config.user_token:
# Use token to log in
- client.load_store()
+ matrix_client.load_store()
# Sync encryption keys with the server
- if client.should_upload_keys:
- await client.keys_upload()
+ if matrix_client.should_upload_keys:
+ await matrix_client.keys_upload()
else:
# Try to login with the configured username/password
try:
- login_response = await client.login(
+ login_response = await matrix_client.login(
password=config.user_password,
device_name=config.device_name,
)
@@ -90,14 +90,14 @@ async def start_matrix_client(
# Login succeeded!
logger.info(f"Logged in as {config.user_id}")
- await client.sync_forever(timeout=30000, full_state=True)
+ await matrix_client.sync_forever(timeout=30000, full_state=True)
except (ClientConnectionError, ServerDisconnectedError, TimeoutError):
logger.warning("Unable to connect to homeserver, retrying in 15s...")
# Sleep so we don't bombard the server with login requests
await asyncio.sleep(15)
finally:
- await client.close()
+ await matrix_client.close()
def main() -> None:
@@ -113,30 +113,30 @@ def main() -> None:
# Read the parsed config file and create a Config object
config = Config(config_path)
- client = create_matrix_client(config)
+ matrix_client = create_matrix_client(config)
# Configure the cache
cache = Cache(config.cache_dir)
# Configure Alertmanager client
- alertmanager = AlertmanagerClient(config.alertmanager_url, cache)
+ alertmanager_client = AlertmanagerClient(config.alertmanager_url, cache)
# Set up event callbacks
- callbacks = Callbacks(client, alertmanager, cache, config)
- client.add_event_callback(callbacks.message, (RoomMessageText,))
- client.add_event_callback(
+ callbacks = Callbacks(matrix_client, alertmanager_client, cache, config)
+ matrix_client.add_event_callback(callbacks.message, (RoomMessageText,))
+ matrix_client.add_event_callback(
callbacks.invite_event_filtered_callback, (InviteMemberEvent,)
)
- client.add_event_callback(callbacks.decryption_failure, (MegolmEvent,))
- client.add_event_callback(callbacks.unknown, (UnknownEvent,))
- client.add_event_callback(callbacks.redaction, (RedactionEvent,))
+ matrix_client.add_event_callback(callbacks.decryption_failure, (MegolmEvent,))
+ matrix_client.add_event_callback(callbacks.unknown, (UnknownEvent,))
+ matrix_client.add_event_callback(callbacks.redaction, (RedactionEvent,))
# Configure webhook server
- webhook_server = Webhook(client, cache, config)
+ webhook_server = Webhook(matrix_client, alertmanager_client, cache, config)
loop = asyncio.get_event_loop()
loop.create_task(webhook_server.start())
- loop.create_task(start_matrix_client(client, cache, config))
+ loop.create_task(start_matrix_client(matrix_client, cache, config))
try:
loop.run_forever()
@@ -144,6 +144,6 @@ def main() -> None:
logger.error(e)
finally:
loop.run_until_complete(webhook_server.close())
- loop.run_until_complete(alertmanager.close())
- loop.run_until_complete(client.close())
+ loop.run_until_complete(alertmanager_client.close())
+ loop.run_until_complete(matrix_client.close())
cache.close()
diff --git a/matrix_alertbot/webhook.py b/matrix_alertbot/webhook.py
index d1541ed..c43035e 100644
--- a/matrix_alertbot/webhook.py
+++ b/matrix_alertbot/webhook.py
@@ -10,8 +10,14 @@ from diskcache import Cache
from nio import AsyncClient, LocalProtocolError
from matrix_alertbot.alert import Alert, AlertRenderer
+from matrix_alertbot.alertmanager import AlertmanagerClient
from matrix_alertbot.chat_functions import send_text_to_room
from matrix_alertbot.config import Config
+from matrix_alertbot.errors import (
+ AlertmanagerError,
+ SilenceExtendError,
+ SilenceNotFoundError,
+)
logger = logging.getLogger(__name__)
@@ -28,10 +34,7 @@ async def create_alerts(request: web_request.Request) -> web.Response:
data = await request.json()
room_id = request.match_info["room_id"]
- client: AsyncClient = request.app["client"]
config: Config = request.app["config"]
- cache: Cache = request.app["cache"]
- alert_renderer: AlertRenderer = request.app["alert_renderer"]
if room_id not in config.allowed_rooms:
logger.error("Cannot send alerts to room ID {room_id}.")
@@ -43,33 +46,39 @@ async def create_alerts(request: web_request.Request) -> web.Response:
logger.error("Received data without 'alerts' key")
return web.Response(status=400, body="Data must contain 'alerts' key.")
- alerts = data["alerts"]
+ alert_dicts = data["alerts"]
if not isinstance(data["alerts"], list):
- alerts_type = type(alerts).__name__
+ alerts_type = type(alert_dicts).__name__
logger.error(f"Received data with invalid alerts type '{alerts_type}'.")
return web.Response(
status=400, body=f"Alerts must be a list, got '{alerts_type}'."
)
- logger.info(f"Received {len(alerts)} alerts for room ID {room_id}: {data}")
+ logger.info(f"Received {len(alert_dicts)} alerts for room ID {room_id}: {data}")
if len(data["alerts"]) == 0:
return web.Response(status=400, body="Alerts cannot be empty.")
- for alert in alerts:
+ alerts = []
+ for alert in alert_dicts:
try:
alert = Alert.from_dict(alert)
except KeyError as e:
logger.error(f"Cannot parse alert dict: {e}")
return web.Response(status=400, body=f"Invalid alert: {alert}.")
+ alerts.append(alert)
- plaintext = alert_renderer.render(alert, html=False)
- html = alert_renderer.render(alert, html=True)
-
+ for alert in alerts:
try:
- event = await send_text_to_room(
- client, room_id, plaintext, html, notice=False
+ await create_alert(alert, room_id, request)
+ except AlertmanagerError as e:
+ logger.error(
+ f"An error occured with Alertmanager when handling alert with fingerprint {alert.fingerprint}: {e}"
+ )
+ return web.Response(
+ status=500,
+ body=f"An error occured with Alertmanager when handling alert with fingerprint {alert.fingerprint}.",
)
except (LocalProtocolError, ClientError) as e:
logger.error(
@@ -80,18 +89,60 @@ async def create_alerts(request: web_request.Request) -> web.Response:
body=f"An error occured when sending alert with fingerprint '{alert.fingerprint}' to Matrix room.",
)
- if alert.firing:
- cache.set(
- event.event_id, alert.fingerprint, expire=config.cache_expire_time
- )
-
return web.Response(status=200)
+async def create_alert(
+ alert: Alert, room_id: str, request: web_request.Request
+) -> None:
+ alertmanager_client: AlertmanagerClient = request.app["alertmanager_client"]
+ alert_renderer: AlertRenderer = request.app["alert_renderer"]
+ matrix_client: AsyncClient = request.app["matrix_client"]
+ cache: Cache = request.app["cache"]
+ config: Config = request.app["config"]
+
+ if alert.firing:
+ try:
+ silence_id = await alertmanager_client.update_silence(alert.fingerprint)
+ logger.debug(
+ f"Extended silence ID {silence_id} for alert with fingerprint {alert.fingerprint}"
+ )
+ return
+ except SilenceNotFoundError as e:
+ logger.debug(
+ f"Unable to extend silence for alert with fingerprint {alert.fingerprint}: {e}"
+ )
+ cache.delete(alert.fingerprint)
+ except SilenceExtendError as e:
+ logger.debug(
+ f"Unable to extend silence for alert with fingerprint {alert.fingerprint}: {e}"
+ )
+
+ plaintext = alert_renderer.render(alert, html=False)
+ html = alert_renderer.render(alert, html=True)
+
+ event = await send_text_to_room(
+ matrix_client, room_id, plaintext, html, notice=False
+ )
+
+ if alert.firing:
+ cache.set(event.event_id, alert.fingerprint, expire=config.cache_expire_time)
+ else:
+ cache.delete(event.event_id)
+ cache.delete(alert.fingerprint)
+
+
class Webhook:
- def __init__(self, client: AsyncClient, cache: Cache, config: Config) -> None:
+ def __init__(
+ self,
+ matrix_client: AsyncClient,
+ alertmanager_client: AlertmanagerClient,
+ cache: Cache,
+ config: Config,
+ ) -> None:
self.app = web.Application(logger=logger)
- self.app["client"] = client
+ self.app["matrix_client"] = matrix_client
+ self.app["alertmanager_client"] = alertmanager_client
self.app["config"] = config
self.app["cache"] = cache
self.app["alert_renderer"] = AlertRenderer(config.template_dir)
diff --git a/tests/test_alertmanager.py b/tests/test_alertmanager.py
index 97427b0..d125266 100644
--- a/tests/test_alertmanager.py
+++ b/tests/test_alertmanager.py
@@ -3,8 +3,8 @@ from __future__ import annotations
import json
import unittest
from datetime import datetime, timedelta
-from typing import Any
-from unittest.mock import MagicMock, Mock
+from typing import Any, Dict, Optional, Tuple
+from unittest.mock import Mock
import aiohttp
import aiohttp.test_utils
@@ -18,16 +18,25 @@ from matrix_alertbot.errors import (
AlertmanagerServerError,
AlertNotFoundError,
SilenceExpiredError,
+ SilenceExtendError,
SilenceNotFoundError,
)
-class FakeTimeDelta:
- def __init__(self, seconds: int) -> None:
- self.seconds = seconds
+class FakeCache:
+ def __init__(self, cache_dict: Optional[Dict] = None) -> None:
+ if cache_dict is None:
+ cache_dict = {}
+ self.cache = cache_dict
- def __radd__(self, other: Any) -> datetime:
- return datetime.utcfromtimestamp(self.seconds)
+ def get(
+ self, key: str, expire_time: bool = False
+ ) -> Optional[Tuple[str, Optional[int]] | str]:
+ return self.cache.get(key)
+
+ def set(self, key: str, value: str, expire: int) -> None:
+ self.cache[key] = value, expire
+ print(self.cache)
class AbstractFakeAlertmanagerServer:
@@ -42,8 +51,18 @@ class AbstractFakeAlertmanagerServer:
]
)
self.app["silences"] = [
- {"id": "silence1", "status": {"state": "active"}},
- {"id": "silence2", "status": {"state": "expired"}},
+ {
+ "id": "silence1",
+ "createdBy": "user1",
+ "status": {"state": "active"},
+ "matchers": [],
+ },
+ {
+ "id": "silence2",
+ "createdBy": "user2",
+ "status": {"state": "expired"},
+ "matchers": [],
+ },
]
self.runner = web.AppRunner(self.app)
@@ -111,8 +130,9 @@ class FakeAlertmanagerServer(AbstractFakeAlertmanagerServer):
silences = self.app["silences"]
silence = await request.json()
- if silence["id"] is None:
- silence["id"] = "silence1"
+
+ silence_count = len(silences) + 1
+ silence["id"] = f"silence{silence_count}"
silence["status"] = {"state": "active"}
silences.append(silence)
@@ -165,17 +185,17 @@ class FakeAlertmanagerServerWithErrorDeleteSilence(FakeAlertmanagerServer):
class AlertmanagerClientTestCase(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self) -> None:
self.fake_fingerprints = Mock(return_value=["fingerprint1", "fingerprint2"])
- self.fake_cache = MagicMock(spec=Cache)
- self.fake_cache.__getitem__ = self.fake_fingerprints
async def test_get_alerts_happy(self) -> None:
+ fake_cache = FakeCache()
+
async with FakeAlertmanagerServer() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
- alerts = await alertmanager.get_alerts()
+ async with aiotools.closing_async(alertmanager_client):
+ alerts = await alertmanager_client.get_alerts()
self.assertEqual(
[
@@ -197,72 +217,94 @@ class AlertmanagerClientTestCase(unittest.IsolatedAsyncioTestCase):
)
async def test_get_alerts_empty(self) -> None:
+ fake_cache = FakeCache()
+
async with FakeAlertmanagerServerWithoutAlert() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
- alerts = await alertmanager.get_alerts()
+ async with aiotools.closing_async(alertmanager_client):
+ alerts = await alertmanager_client.get_alerts()
self.assertEqual([], alerts)
async def test_get_alerts_raise_alertmanager_error(self) -> None:
+ fake_cache = FakeCache()
+
async with FakeAlertmanagerServerWithErrorAlerts() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
+ async with aiotools.closing_async(alertmanager_client):
with self.assertRaises(AlertmanagerServerError):
- await alertmanager.get_alerts()
+ await alertmanager_client.get_alerts()
async def test_get_silences_happy(self) -> None:
+ fake_cache = FakeCache()
+
async with FakeAlertmanagerServer() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
- silences = await alertmanager.get_silences()
+ async with aiotools.closing_async(alertmanager_client):
+ silences = await alertmanager_client.get_silences()
self.assertEqual(
[
- {"id": "silence1", "status": {"state": "active"}},
- {"id": "silence2", "status": {"state": "expired"}},
+ {
+ "id": "silence1",
+ "createdBy": "user1",
+ "status": {"state": "active"},
+ "matchers": [],
+ },
+ {
+ "id": "silence2",
+ "createdBy": "user2",
+ "status": {"state": "expired"},
+ "matchers": [],
+ },
],
silences,
)
async def test_get_silences_empty(self) -> None:
+ fake_cache = FakeCache()
+
async with FakeAlertmanagerServerWithoutSilence() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
- silences = await alertmanager.get_silences()
+ async with aiotools.closing_async(alertmanager_client):
+ silences = await alertmanager_client.get_silences()
self.assertEqual([], silences)
async def test_get_silences_raise_alertmanager_error(self) -> None:
+ fake_cache = FakeCache()
+
async with FakeAlertmanagerServerWithErrorSilences() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
+ async with aiotools.closing_async(alertmanager_client):
with self.assertRaises(AlertmanagerServerError):
- await alertmanager.get_silences()
+ await alertmanager_client.get_silences()
async def test_get_alert_happy(self) -> None:
+ fake_cache = FakeCache()
+
async with FakeAlertmanagerServer() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
- alert = await alertmanager.get_alert("fingerprint1")
+ async with aiotools.closing_async(alertmanager_client):
+ alert = await alertmanager_client.get_alert("fingerprint1")
self.assertEqual(
{
@@ -274,76 +316,98 @@ class AlertmanagerClientTestCase(unittest.IsolatedAsyncioTestCase):
)
async def test_get_alert_raise_alert_not_found(self) -> None:
+ fake_cache = FakeCache()
+
async with FakeAlertmanagerServerWithoutAlert() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
+ async with aiotools.closing_async(alertmanager_client):
with self.assertRaises(AlertNotFoundError):
- await alertmanager.get_alert("fingerprint1")
+ await alertmanager_client.get_alert("fingerprint1")
async def test_get_alert_raise_alertmanager_error(self) -> None:
+ fake_cache = FakeCache()
+
async with FakeAlertmanagerServerWithErrorAlerts() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
+ async with aiotools.closing_async(alertmanager_client):
with self.assertRaises(AlertmanagerServerError):
- await alertmanager.get_alert("fingerprint1")
+ await alertmanager_client.get_alert("fingerprint1")
async def test_get_silence_happy(self) -> None:
+ fake_cache = FakeCache()
+
async with FakeAlertmanagerServer() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
- silence1 = await alertmanager.get_silence("silence1")
- silence2 = await alertmanager.get_silence("silence2")
+ async with aiotools.closing_async(alertmanager_client):
+ silence1 = await alertmanager_client.get_silence("silence1")
+ silence2 = await alertmanager_client.get_silence("silence2")
self.assertEqual(
- {"id": "silence1", "status": {"state": "active"}},
+ {
+ "id": "silence1",
+ "createdBy": "user1",
+ "status": {"state": "active"},
+ "matchers": [],
+ },
silence1,
)
self.assertEqual(
- {"id": "silence2", "status": {"state": "expired"}},
+ {
+ "id": "silence2",
+ "createdBy": "user2",
+ "status": {"state": "expired"},
+ "matchers": [],
+ },
silence2,
)
async def test_get_silence_raise_silence_not_found(self) -> None:
+ fake_cache = FakeCache()
+
async with FakeAlertmanagerServerWithoutSilence() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
+ async with aiotools.closing_async(alertmanager_client):
with self.assertRaises(SilenceNotFoundError):
- await alertmanager.get_silence("silence1")
+ await alertmanager_client.get_silence("silence1")
async def test_get_silence_raise_alertmanager_error(self) -> None:
+ fake_cache = FakeCache()
+
async with FakeAlertmanagerServerWithErrorSilences() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
+ async with aiotools.closing_async(alertmanager_client):
with self.assertRaises(AlertmanagerServerError):
- await alertmanager.get_silence("silence1")
+ await alertmanager_client.get_silence("silence1")
@freeze_time(datetime.utcfromtimestamp(0))
- async def test_create_silence(self) -> None:
+ async def test_create_silence_with_duration(self) -> None:
+ fake_cache = Mock(return_value=FakeCache())
+
async with FakeAlertmanagerServerWithoutSilence() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
- silence_id = await alertmanager.create_silence(
+ async with aiotools.closing_async(alertmanager_client):
+ silence_id = await alertmanager_client.create_silence(
"fingerprint1", "user", 86400
)
- silence = await alertmanager.get_silence("silence1")
+ silence = await alertmanager_client.get_silence("silence1")
self.assertEqual("silence1", silence_id)
self.assertEqual(
@@ -365,21 +429,81 @@ class AlertmanagerClientTestCase(unittest.IsolatedAsyncioTestCase):
},
silence,
)
+ fake_cache.set.assert_called_once_with("fingerprint1", "silence1", expire=86400)
@freeze_time(datetime.utcfromtimestamp(0))
- async def test_create_silence_with_id(self) -> None:
+ async def test_update_silence_with_duration(self) -> None:
+ fake_cache = FakeCache()
+
async with FakeAlertmanagerServerWithoutSilence() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
- silence_id = await alertmanager.create_silence(
- "fingerprint1", "user", 86400, "silence2"
- )
- silence = await alertmanager.get_silence("silence2")
+ async with aiotools.closing_async(alertmanager_client):
+ await alertmanager_client.create_silence("fingerprint1", "user", 86400)
+ with self.assertRaises(SilenceExtendError):
+ await alertmanager_client.update_silence("fingerprint1")
+ with self.assertRaises(SilenceNotFoundError):
+ await alertmanager_client.get_silence("silence2")
+ self.assertEqual({"fingerprint1": ("silence1", 86400)}, fake_cache.cache)
- self.assertEqual("silence2", silence_id)
+ @freeze_time(datetime.utcfromtimestamp(0))
+ async def test_create_silence_without_duration(self) -> None:
+ fake_cache = Mock(spec=Cache)
+ fake_cache.get.return_value = None
+
+ async with FakeAlertmanagerServerWithoutSilence() as fake_alertmanager_server:
+ port = fake_alertmanager_server.port
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
+ )
+ async with aiotools.closing_async(alertmanager_client):
+ silence_id = await alertmanager_client.create_silence(
+ "fingerprint1", "user"
+ )
+ silence = await alertmanager_client.get_silence("silence1")
+
+ self.assertEqual("silence1", silence_id)
+ self.assertEqual(
+ {
+ "id": "silence1",
+ "status": {"state": "active"},
+ "matchers": [
+ {
+ "name": "alertname",
+ "value": "alert1",
+ "isRegex": False,
+ "isEqual": True,
+ }
+ ],
+ "createdBy": "user",
+ "startsAt": "1970-01-01T00:00:00",
+ "endsAt": "1970-01-01T03:00:00",
+ "comment": "Acknowledge alert from Matrix",
+ },
+ silence,
+ )
+ fake_cache.set.assert_called_once_with("fingerprint1", "silence1", expire=None)
+
+ @freeze_time(datetime.utcfromtimestamp(0))
+ async def test_update_silence_without_duration(self) -> None:
+ fake_cache = FakeCache()
+
+ async with FakeAlertmanagerServerWithoutSilence() as fake_alertmanager_server:
+ port = fake_alertmanager_server.port
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
+ )
+ async with aiotools.closing_async(alertmanager_client):
+ silence1_id = await alertmanager_client.create_silence(
+ "fingerprint1", "user"
+ )
+ silence2_id = await alertmanager_client.update_silence("fingerprint1")
+ silence = await alertmanager_client.get_silence("silence2")
+
+ self.assertEqual("silence1", silence1_id)
+ self.assertEqual("silence2", silence2_id)
self.assertEqual(
{
"id": "silence2",
@@ -394,56 +518,27 @@ class AlertmanagerClientTestCase(unittest.IsolatedAsyncioTestCase):
],
"createdBy": "user",
"startsAt": "1970-01-01T00:00:00",
- "endsAt": "1970-01-02T00:00:00",
- "comment": "Acknowledge alert from Matrix",
- },
- silence,
- )
-
- @freeze_time(datetime.utcfromtimestamp(0))
- async def test_create_silence_with_indefinite_duration(self) -> None:
- async with FakeAlertmanagerServerWithoutSilence() as fake_alertmanager_server:
- port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
- )
- async with aiotools.closing_async(alertmanager):
- silence_id = await alertmanager.create_silence("fingerprint1", "user")
- silence = await alertmanager.get_silence("silence1")
-
- self.assertEqual("silence1", silence_id)
- self.assertEqual(
- {
- "id": "silence1",
- "status": {"state": "active"},
- "matchers": [
- {
- "name": "alertname",
- "value": "alert1",
- "isRegex": False,
- "isEqual": True,
- }
- ],
- "createdBy": "user",
- "startsAt": "1970-01-01T00:00:00",
- "endsAt": "1980-01-01T00:00:00",
+ "endsAt": "1970-01-01T03:00:00",
"comment": "Acknowledge alert from Matrix",
},
silence,
)
+ self.assertEqual({"fingerprint1": ("silence2", None)}, fake_cache.cache)
@freeze_time(datetime.utcfromtimestamp(0))
async def test_create_silence_with_max_duration(self) -> None:
+ fake_cache = Mock(spec=Cache)
+ fake_cache.get.return_value = None
async with FakeAlertmanagerServerWithoutSilence() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
- silence_id = await alertmanager.create_silence(
- "fingerprint1", "user", int(timedelta.max.total_seconds())
+ async with aiotools.closing_async(alertmanager_client):
+ silence_id = await alertmanager_client.create_silence(
+ "fingerprint1", "user", int(timedelta.max.total_seconds()) + 1
)
- silence = await alertmanager.get_silence("silence1")
+ silence = await alertmanager_client.get_silence("silence1")
self.assertEqual("silence1", silence_id)
self.assertEqual(
@@ -467,99 +562,165 @@ class AlertmanagerClientTestCase(unittest.IsolatedAsyncioTestCase):
)
async def test_create_silence_raise_alert_not_found(self) -> None:
+ fake_cache = Mock(spec=Cache)
+ fake_cache.get.return_value = None
+
async with FakeAlertmanagerServerWithoutAlert() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
+ async with aiotools.closing_async(alertmanager_client):
with self.assertRaises(AlertNotFoundError):
- await alertmanager.create_silence("fingerprint1", "user")
+ await alertmanager_client.create_silence("fingerprint1", "user")
async def test_create_silence_raise_alertmanager_error(self) -> None:
+ fake_cache = Mock(spec=Cache)
+ fake_cache.get.return_value = None
+
async with FakeAlertmanagerServerWithErrorCreateSilence() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
- await alertmanager.get_alert("fingerprint1")
+ async with aiotools.closing_async(alertmanager_client):
+ await alertmanager_client.get_alert("fingerprint1")
with self.assertRaises(AlertmanagerServerError):
- await alertmanager.create_silence("fingerprint1", "user")
+ await alertmanager_client.create_silence("fingerprint1", "user")
+
+ async def test_update_silence_raise_silence_not_found(self) -> None:
+ fake_cache = FakeCache({"fingerprint1": ("silence1", None)})
+
+ async with FakeAlertmanagerServerWithoutSilence() as fake_alertmanager_server:
+ port = fake_alertmanager_server.port
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
+ )
+ async with aiotools.closing_async(alertmanager_client):
+ with self.assertRaises(SilenceNotFoundError):
+ await alertmanager_client.update_silence("fingerprint1")
+ with self.assertRaises(SilenceNotFoundError):
+ await alertmanager_client.update_silence("fingerprint2")
+
+ async def test_update_silence_raise_silence_extend_error(self) -> None:
+ fake_cache = FakeCache({"fingerprint1": ("silence1", 86400)})
+
+ async with FakeAlertmanagerServerWithoutAlert() as fake_alertmanager_server:
+ port = fake_alertmanager_server.port
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
+ )
+ async with aiotools.closing_async(alertmanager_client):
+ with self.assertRaises(SilenceExtendError):
+ await alertmanager_client.update_silence("fingerprint1")
+
+ async def test_update_silence_raise_alertmanager_error(self) -> None:
+ fake_cache = FakeCache({"fingerprint1": ("silence1", None)})
+
+ async with FakeAlertmanagerServerWithErrorCreateSilence() as fake_alertmanager_server:
+ port = fake_alertmanager_server.port
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
+ )
+ async with aiotools.closing_async(alertmanager_client):
+ await alertmanager_client.get_alert("fingerprint1")
+
+ with self.assertRaises(AlertmanagerServerError):
+ await alertmanager_client.update_silence("fingerprint1")
async def test_delete_silence(self) -> None:
+ fake_cache = Mock(spec=Cache)
+
async with FakeAlertmanagerServer() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
- await alertmanager.delete_silence("silence1")
- silences = await alertmanager.get_silences()
-
- self.assertEqual([{"id": "silence2", "status": {"state": "expired"}}], silences)
-
- async def test_delete_silence_raise_silence_expired(self) -> None:
- async with FakeAlertmanagerServer() as fake_alertmanager_server:
- port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
- )
- async with aiotools.closing_async(alertmanager):
- with self.assertRaises(SilenceExpiredError):
- await alertmanager.delete_silence("silence2")
- silences = await alertmanager.get_silences()
+ async with aiotools.closing_async(alertmanager_client):
+ await alertmanager_client.delete_silence("silence1")
+ silences = await alertmanager_client.get_silences()
self.assertEqual(
[
- {"id": "silence1", "status": {"state": "active"}},
- {"id": "silence2", "status": {"state": "expired"}},
+ {
+ "id": "silence2",
+ "createdBy": "user2",
+ "status": {"state": "expired"},
+ "matchers": [],
+ }
],
silences,
)
+ async def test_delete_silence_raise_silence_expired(self) -> None:
+ fake_cache = Mock(spec=Cache)
+
+ async with FakeAlertmanagerServer() as fake_alertmanager_server:
+ port = fake_alertmanager_server.port
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
+ )
+ async with aiotools.closing_async(alertmanager_client):
+ with self.assertRaises(SilenceExpiredError):
+ await alertmanager_client.delete_silence("silence2")
+ silences = await alertmanager_client.get_silences()
+
+ self.assertEqual(2, len(silences))
+
async def test_delete_silence_raise_alertmanager_error(self) -> None:
+ fake_cache = Mock(spec=Cache)
+
async with FakeAlertmanagerServerWithErrorDeleteSilence() as fake_alertmanager_server:
port = fake_alertmanager_server.port
- alertmanager = AlertmanagerClient(
- f"http://localhost:{port}", self.fake_cache
+ alertmanager_client = AlertmanagerClient(
+ f"http://localhost:{port}", fake_cache
)
- async with aiotools.closing_async(alertmanager):
- await alertmanager.get_alert("fingerprint1")
+ async with aiotools.closing_async(alertmanager_client):
+ await alertmanager_client.get_alert("fingerprint1")
with self.assertRaises(AlertmanagerServerError):
- await alertmanager.delete_silence("silence1")
+ await alertmanager_client.delete_silence("silence1")
async def test_find_alert_happy(self) -> None:
- alertmanager = AlertmanagerClient("http://localhost", self.fake_cache)
- alert = alertmanager._find_alert(
+ fake_cache = Mock(spec=Cache)
+
+ alertmanager_client = AlertmanagerClient("http://localhost", fake_cache)
+ alert = alertmanager_client._find_alert(
"fingerprint1", [{"fingerprint": "fingerprint1"}]
)
self.assertEqual({"fingerprint": "fingerprint1"}, alert)
async def test_find_alert_raise_alert_not_found(self) -> None:
- alertmanager = AlertmanagerClient("http://localhost", self.fake_cache)
+ fake_cache = Mock(spec=Cache)
+
+ alertmanager_client = AlertmanagerClient("http://localhost", fake_cache)
with self.assertRaises(AlertNotFoundError):
- alertmanager._find_alert("fingerprint1", [])
+ alertmanager_client._find_alert("fingerprint1", [])
with self.assertRaises(AlertNotFoundError):
- alertmanager._find_alert("fingerprint2", [{"fingerprint": "fingerprint1"}])
+ alertmanager_client._find_alert(
+ "fingerprint2", [{"fingerprint": "fingerprint1"}]
+ )
async def test_find_silence_happy(self) -> None:
- alertmanager = AlertmanagerClient("http://localhost", self.fake_cache)
- silence = alertmanager._find_silence("silence1", [{"id": "silence1"}])
+ fake_cache = Mock(spec=Cache)
+
+ alertmanager_client = AlertmanagerClient("http://localhost", fake_cache)
+ silence = alertmanager_client._find_silence("silence1", [{"id": "silence1"}])
self.assertEqual({"id": "silence1"}, silence)
async def test_find_silence_raise_silence_not_found(self) -> None:
- alertmanager = AlertmanagerClient("http://localhost", self.fake_cache)
+ fake_cache = Mock(spec=Cache)
+
+ alertmanager_client = AlertmanagerClient("http://localhost", fake_cache)
with self.assertRaises(SilenceNotFoundError):
- alertmanager._find_silence("silence1", [])
+ alertmanager_client._find_silence("silence1", [])
with self.assertRaises(SilenceNotFoundError):
- alertmanager._find_silence("silence2", [{"id": "silence1"}])
+ alertmanager_client._find_silence("silence2", [{"id": "silence1"}])
if __name__ == "__main__":
diff --git a/tests/test_callback.py b/tests/test_callback.py
index 31d8273..3f67fe2 100644
--- a/tests/test_callback.py
+++ b/tests/test_callback.py
@@ -17,11 +17,11 @@ from tests.utils import make_awaitable
class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
def setUp(self) -> None:
# Create a Callbacks 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"
+ self.fake_matrix_client = Mock(spec=nio.AsyncClient)
+ self.fake_matrix_client.user = "@fake_user:example.com"
self.fake_cache = MagicMock(spec=Cache)
- self.fake_alertmanager = Mock(spec=AlertmanagerClient)
+ self.fake_alertmanager_client = Mock(spec=AlertmanagerClient)
# Create a fake room to play with
self.fake_room = Mock(spec=nio.MatrixRoom)
@@ -35,7 +35,10 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_config.command_prefix = "!alert "
self.callbacks = Callbacks(
- self.fake_client, self.fake_alertmanager, self.fake_cache, self.fake_config
+ self.fake_matrix_client,
+ self.fake_alertmanager_client,
+ self.fake_cache,
+ self.fake_config,
)
async def test_invite(self) -> None:
@@ -45,13 +48,13 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
fake_invite_event.sender = "@some_other_fake_user:example.com"
# Pretend that attempting to join a room is always successful
- self.fake_client.join.return_value = make_awaitable(None)
+ self.fake_matrix_client.join.return_value = make_awaitable(None)
# Pretend that we received an invite event
await self.callbacks.invite(self.fake_room, fake_invite_event)
# Check that we attempted to join the room
- self.fake_client.join.assert_called_once_with(self.fake_room.room_id)
+ self.fake_matrix_client.join.assert_called_once_with(self.fake_room.room_id)
@patch.object(matrix_alertbot.callback.CommandFactory, "create", autospec=True)
async def test_message_without_prefix(self, fake_command_create: Mock) -> None:
@@ -84,9 +87,9 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# Check that the command was not executed
fake_command.assert_called_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
fake_message_event.sender,
@@ -115,9 +118,9 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# Check that we attempted to execute the command
fake_command.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
fake_message_event.sender,
@@ -132,7 +135,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# Tests that the bot process messages in the room that contain a command
fake_message_event = Mock(spec=nio.RoomMessageText)
- fake_message_event.sender = self.fake_client.user
+ fake_message_event.sender = self.fake_matrix_client.user
# Pretend that we received a text message event
await self.callbacks.message(self.fake_room, fake_message_event)
@@ -195,9 +198,9 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# Check that the command was not executed
fake_command.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
fake_message_event.sender,
@@ -244,9 +247,9 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# Check that the command was not executed
fake_command.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
fake_message_event.sender,
@@ -280,7 +283,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
fake_event_response = Mock(spec=nio.RoomGetEventResponse)
fake_event_response.event = fake_alert_event
- self.fake_client.room_get_event.return_value = make_awaitable(
+ self.fake_matrix_client.room_get_event.return_value = make_awaitable(
fake_event_response
)
@@ -289,9 +292,9 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# Check that we attempted to execute the command
fake_command.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
fake_reaction_event.sender,
@@ -299,7 +302,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
"some alert event id",
)
fake_command.return_value.process.assert_called_once()
- self.fake_client.room_get_event.assert_called_once_with(
+ self.fake_matrix_client.room_get_event.assert_called_once_with(
self.fake_room.room_id, fake_alert_event.event_id
)
@@ -324,7 +327,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
}
fake_event_response = Mock(spec=nio.RoomGetEventError)
- self.fake_client.room_get_event.return_value = make_awaitable(
+ self.fake_matrix_client.room_get_event.return_value = make_awaitable(
fake_event_response
)
@@ -334,7 +337,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# Check that we attempted to execute the command
fake_command.assert_not_called()
self.fake_cache.set.assert_not_called()
- self.fake_client.room_get_event.assert_called_once_with(
+ self.fake_matrix_client.room_get_event.assert_called_once_with(
self.fake_room.room_id, fake_alert_event_id
)
@@ -364,7 +367,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
fake_event_response = Mock(spec=nio.RoomGetEventResponse)
fake_event_response.event = fake_alert_event
- self.fake_client.room_get_event.return_value = make_awaitable(
+ self.fake_matrix_client.room_get_event.return_value = make_awaitable(
fake_event_response
)
@@ -374,7 +377,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# Check that we attempted to execute the command
fake_command.assert_not_called()
self.fake_cache.set.assert_not_called()
- self.fake_client.room_get_event.assert_called_once_with(
+ self.fake_matrix_client.room_get_event.assert_called_once_with(
self.fake_room.room_id, fake_alert_event.event_id
)
@@ -403,7 +406,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# Check that we attempted to execute the command
fake_command.assert_not_called()
- self.fake_client.room_get_event.assert_not_called()
+ self.fake_matrix_client.room_get_event.assert_not_called()
@patch.object(matrix_alertbot.callback, "AckAlertCommand", autospec=True)
async def test_ignore_reaction_sent_by_bot_user(self, fake_command: Mock) -> None:
@@ -414,7 +417,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
fake_reaction_event = Mock(spec=nio.UnknownEvent)
fake_reaction_event.type = "m.reaction"
fake_reaction_event.event_id = "some event id"
- fake_reaction_event.sender = self.fake_client.user
+ fake_reaction_event.sender = self.fake_matrix_client.user
fake_reaction_event.source = {
"content": {
"m.relates_to": {
@@ -433,7 +436,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# Check that we attempted to execute the command
fake_command.assert_not_called()
- self.fake_client.room_get_event.assert_not_called()
+ self.fake_matrix_client.room_get_event.assert_not_called()
@patch.object(matrix_alertbot.callback, "AckAlertCommand", autospec=True)
async def test_ignore_reaction_in_unauthorized_room(
@@ -467,7 +470,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# Check that we attempted to execute the command
fake_command.assert_not_called()
- self.fake_client.room_get_event.assert_not_called()
+ self.fake_matrix_client.room_get_event.assert_not_called()
@patch.object(matrix_alertbot.callback, "UnackAlertCommand", autospec=True)
async def test_redaction(self, fake_command: Mock) -> None:
@@ -488,9 +491,9 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
# Check that we attempted to execute the command
fake_command.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
fake_redaction_event.sender,
@@ -504,7 +507,7 @@ class CallbacksTestCase(unittest.IsolatedAsyncioTestCase):
"""Tests the callback for RoomMessageText with the command prefix"""
# 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_client.user
+ fake_redaction_event.sender = self.fake_matrix_client.user
fake_cache_dict: Dict = {}
self.fake_cache.__getitem__.side_effect = fake_cache_dict.__getitem__
diff --git a/tests/test_command.py b/tests/test_command.py
index daad2f6..d578933 100644
--- a/tests/test_command.py
+++ b/tests/test_command.py
@@ -32,10 +32,7 @@ def cache_get_item(key: str) -> str:
async def create_silence(
- fingerprint: str,
- user: str,
- seconds: Optional[int] = None,
- silence_id: Optional[str] = None,
+ fingerprint: str, user: str, seconds: Optional[int] = None
) -> str:
if fingerprint == "fingerprint1":
return "silence1"
@@ -45,10 +42,7 @@ async def create_silence(
async def create_silence_raise_alertmanager_error(
- fingerprint: str,
- user: str,
- seconds: Optional[int] = None,
- silence_id: Optional[str] = None,
+ fingerprint: str, user: str, seconds: Optional[int] = None
) -> str:
if fingerprint == "fingerprint1":
raise AlertmanagerError
@@ -56,10 +50,7 @@ async def create_silence_raise_alertmanager_error(
async def create_silence_raise_alert_not_found_error(
- fingerprint: str,
- user: str,
- seconds: Optional[int] = None,
- silence_id: Optional[str] = None,
+ fingerprint: str, user: str, seconds: Optional[int] = None
) -> str:
if fingerprint == "fingerprint1":
raise AlertNotFoundError
@@ -79,17 +70,17 @@ async def delete_silence_raise_silence_not_found_error(silence_id: str) -> None:
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"
+ self.fake_matrix_client = Mock(spec=nio.AsyncClient)
+ self.fake_matrix_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)
+ self.fake_matrix_client.room_send.return_value = make_awaitable(None)
self.fake_cache = MagicMock(spec=Cache)
self.fake_cache.__getitem__.side_effect = cache_get_item
self.fake_cache.__contains__.return_value = True
- self.fake_alertmanager = Mock(spec=AlertmanagerClient)
- self.fake_alertmanager.create_silence.side_effect = create_silence
+ self.fake_alertmanager_client = Mock(spec=AlertmanagerClient)
+ self.fake_alertmanager_client.create_silence.side_effect = create_silence
# Create a fake room to play with
self.fake_room = Mock(spec=nio.MatrixRoom)
@@ -113,9 +104,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
command = CommandFactory.create(
"ack",
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -134,9 +125,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
command = CommandFactory.create(
"ack 1w 3d",
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -156,9 +147,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
for unack_cmd in ("unack", "nack"):
command = CommandFactory.create(
unack_cmd,
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -177,9 +168,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
command = CommandFactory.create(
"help",
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -197,9 +188,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
command = CommandFactory.create(
"",
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -219,12 +210,11 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
}
self.fake_cache.__getitem__.side_effect = fake_cache_dict.__getitem__
- self.fake_cache.get.side_effect = fake_cache_dict.get
command = AckAlertCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -234,25 +224,17 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
await command.process()
# Check that we attempted to create silences
- self.fake_alertmanager.create_silence.assert_called_once_with(
- "fingerprint1", self.fake_sender, None, None
+ self.fake_alertmanager_client.create_silence.assert_called_once_with(
+ "fingerprint1", self.fake_sender, None
)
fake_send_text_to_room.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_room.room_id,
"Created silence with ID silence1.",
)
self.fake_cache.__getitem__.assert_called_once_with(self.fake_alert_event_id)
- self.fake_cache.get.assert_called_once_with("fingerprint1")
- self.fake_cache.set.assert_has_calls(
- [
- call(
- "some event id",
- "fingerprint1",
- expire=self.fake_config.cache_expire_time,
- ),
- call("fingerprint1", "silence1", expire=None),
- ]
+ self.fake_cache.set.assert_called_once_with(
+ "some event id", "fingerprint1", expire=None
)
@patch.object(matrix_alertbot.command, "send_text_to_room")
@@ -264,12 +246,11 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
}
self.fake_cache.__getitem__.side_effect = fake_cache_dict.__getitem__
- self.fake_cache.get.side_effect = fake_cache_dict.get
command = AckAlertCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -280,21 +261,17 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
await command.process()
# Check that we attempted to create silences
- self.fake_alertmanager.create_silence.assert_called_once_with(
- "fingerprint1", self.fake_sender, 864000, None
+ self.fake_alertmanager_client.create_silence.assert_called_once_with(
+ "fingerprint1", self.fake_sender, 864000
)
fake_send_text_to_room.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_room.room_id,
"Created silence with ID silence1.",
)
self.fake_cache.__getitem__.assert_called_once_with(self.fake_alert_event_id)
- self.fake_cache.get.assert_called_once_with("fingerprint1")
- self.fake_cache.set.assert_has_calls(
- [
- call("some event id", "fingerprint1", expire=864000),
- call("fingerprint1", "silence1", expire=864000),
- ]
+ self.fake_cache.set.assert_called_once_with(
+ "some event id", "fingerprint1", expire=864000
)
@patch.object(matrix_alertbot.command, "send_text_to_room")
@@ -308,12 +285,11 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
}
self.fake_cache.__getitem__.side_effect = fake_cache_dict.__getitem__
- self.fake_cache.get.side_effect = fake_cache_dict.get
command = AckAlertCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -321,22 +297,21 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_alert_event_id,
)
- self.fake_alertmanager.create_silence.side_effect = (
+ self.fake_alertmanager_client.create_silence.side_effect = (
create_silence_raise_alertmanager_error
)
await command.process()
# Check that we attempted to create silences
- self.fake_alertmanager.create_silence.assert_called_once_with(
- "fingerprint1", self.fake_sender, None, None
+ self.fake_alertmanager_client.create_silence.assert_called_once_with(
+ "fingerprint1", self.fake_sender, None
)
fake_send_text_to_room.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_room.room_id,
"Sorry, I couldn't create silence for alert with fingerprint fingerprint1 because something went wrong with Alertmanager: ",
)
self.fake_cache.__getitem__.assert_called_once_with(self.fake_alert_event_id)
- self.fake_cache.get.assert_called_once_with("fingerprint1")
self.fake_cache.set.assert_not_called()
@patch.object(matrix_alertbot.command, "send_text_to_room")
@@ -350,12 +325,11 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
}
self.fake_cache.__getitem__.side_effect = fake_cache_dict.__getitem__
- self.fake_cache.get.side_effect = fake_cache_dict.get
command = AckAlertCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -363,22 +337,21 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_alert_event_id,
)
- self.fake_alertmanager.create_silence.side_effect = (
+ self.fake_alertmanager_client.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_called_once_with(
- "fingerprint1", self.fake_sender, None, None
+ self.fake_alertmanager_client.create_silence.assert_called_once_with(
+ "fingerprint1", self.fake_sender, None
)
fake_send_text_to_room.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_room.room_id,
"Sorry, I couldn't create silence for alert with fingerprint fingerprint1: ",
)
self.fake_cache.__getitem__.assert_called_once_with(self.fake_alert_event_id)
- self.fake_cache.get.assert_called_once_with("fingerprint1")
self.fake_cache.set.assert_not_called()
@patch.object(matrix_alertbot.command, "send_text_to_room")
@@ -388,9 +361,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
"""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_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -402,9 +375,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
await command.process()
# Check that we attempted to create silences
- self.fake_alertmanager.create_silence.assert_not_called()
+ self.fake_alertmanager_client.create_silence.assert_not_called()
fake_send_text_to_room.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_room.room_id,
"I tried really hard, but I can't convert the duration 'invalid duration' to a number of seconds.",
)
@@ -419,9 +392,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
"""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_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -433,9 +406,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
await command.process()
# Check that we attempted to create silences
- self.fake_alertmanager.create_silence.assert_not_called()
+ self.fake_alertmanager_client.create_silence.assert_not_called()
fake_send_text_to_room.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_room.room_id,
"I can't create a silence with a negative duration!",
)
@@ -455,9 +428,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_cache.get.side_effect = fake_cache_dict.get
command = AckAlertCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -468,61 +441,12 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
await command.process()
# Check that we attempted to create silences
- self.fake_alertmanager.create_silence.assert_not_called()
+ self.fake_alertmanager_client.create_silence.assert_not_called()
fake_send_text_to_room.assert_not_called()
self.fake_cache.__getitem__.assert_called_once_with("some alert event id")
self.fake_cache.get.assert_not_called()
self.fake_cache.set.assert_not_called()
- @patch.object(matrix_alertbot.command, "send_text_to_room")
- async def test_ack_with_silence_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
- fake_cache_dict = {
- self.fake_alert_event_id: "fingerprint1",
- "fingerprint1": "silence2",
- }
-
- self.fake_cache.__getitem__.side_effect = fake_cache_dict.__getitem__
- self.fake_cache.get.side_effect = fake_cache_dict.get
-
- command = AckAlertCommand(
- self.fake_client,
- self.fake_cache,
- self.fake_alertmanager,
- self.fake_config,
- 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_called_once_with(
- "fingerprint1", self.fake_sender, None, "silence2"
- )
- fake_send_text_to_room.assert_called_once_with(
- self.fake_client,
- self.fake_room.room_id,
- "Created silence with ID silence1.",
- )
- self.fake_cache.__getitem__.assert_called_once_with("some alert event id")
- self.fake_cache.get.assert_called_once_with("fingerprint1")
- self.fake_cache.set.assert_has_calls(
- [
- call(
- self.fake_event_id,
- "fingerprint1",
- expire=self.fake_config.cache_expire_time,
- ),
- call("fingerprint1", "silence1", expire=None),
- ]
- )
-
@patch.object(matrix_alertbot.command, "send_text_to_room")
async def test_unack(self, fake_send_text_to_room: Mock) -> None:
"""Tests the callback for InviteMemberEvents"""
@@ -535,9 +459,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_cache.__getitem__.side_effect = fake_cache_dict.__getitem__
command = UnackAlertCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -547,9 +471,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
await command.process()
# Check that we attempted to create silences
- self.fake_alertmanager.delete_silence.assert_called_once_with("silence1")
+ self.fake_alertmanager_client.delete_silence.assert_called_once_with("silence1")
fake_send_text_to_room.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_room.room_id,
"Removed silence with ID silence1.",
)
@@ -571,9 +495,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_cache.__getitem__.side_effect = fake_cache_dict.__getitem__
command = UnackAlertCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -581,15 +505,15 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_alert_event_id,
)
- self.fake_alertmanager.delete_silence.side_effect = (
+ self.fake_alertmanager_client.delete_silence.side_effect = (
delete_silence_raise_alertmanager_error
)
await command.process()
# Check that we attempted to create silences
- self.fake_alertmanager.delete_silence.assert_called_once_with("silence1")
+ self.fake_alertmanager_client.delete_silence.assert_called_once_with("silence1")
fake_send_text_to_room.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_room.room_id,
"Sorry, I couldn't remove silence for alert with fingerprint fingerprint1 because something went wrong with Alertmanager: ",
)
@@ -611,9 +535,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_cache.__getitem__.side_effect = fake_cache_dict.__getitem__
command = UnackAlertCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -621,15 +545,15 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_alert_event_id,
)
- self.fake_alertmanager.delete_silence.side_effect = (
+ self.fake_alertmanager_client.delete_silence.side_effect = (
delete_silence_raise_silence_not_found_error
)
await command.process()
# Check that we attempted to create silences
- self.fake_alertmanager.delete_silence.assert_called_once_with("silence1")
+ self.fake_alertmanager_client.delete_silence.assert_called_once_with("silence1")
fake_send_text_to_room.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_room.room_id,
"Sorry, I couldn't remove silence for alert with fingerprint fingerprint1: ",
)
@@ -648,9 +572,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_cache.__getitem__.side_effect = fake_cache_dict.__getitem__
command = UnackAlertCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -661,7 +585,7 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
await command.process()
# Check that we attempted to create silences
- self.fake_alertmanager.delete_silence.assert_not_called()
+ self.fake_alertmanager_client.delete_silence.assert_not_called()
fake_send_text_to_room.assert_not_called()
self.fake_cache.__getitem__.assert_called_once_with(self.fake_alert_event_id)
@@ -676,9 +600,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_cache.__getitem__.side_effect = fake_cache_dict.__getitem__
command = UnackAlertCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -689,7 +613,7 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
await command.process()
# Check that we attempted to create silences
- self.fake_alertmanager.delete_silence.assert_not_called()
+ self.fake_alertmanager_client.delete_silence.assert_not_called()
fake_send_text_to_room.assert_not_called()
self.fake_cache.__getitem__.assert_has_calls(
[call(self.fake_alert_event_id), call("fingerprint1")]
@@ -701,9 +625,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
# Tests that the bot attempts to join a room after being invited to it
command = HelpCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -723,9 +647,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
# Tests that the bot attempts to join a room after being invited to it
command = HelpCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -746,9 +670,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
# Tests that the bot attempts to join a room after being invited to it
command = HelpCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -769,9 +693,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
# Tests that the bot attempts to join a room after being invited to it
command = HelpCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -792,9 +716,9 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
# Tests that the bot attempts to join a room after being invited to it
command = UnknownCommand(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_cache,
- self.fake_alertmanager,
+ self.fake_alertmanager_client,
self.fake_config,
self.fake_room,
self.fake_sender,
@@ -805,7 +729,7 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
# Check that we attempted to create silences
fake_send_text_to_room.assert_called_once_with(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_room.room_id,
"Unknown command. Try the 'help' command for more information.",
)
diff --git a/tests/test_webhook.py b/tests/test_webhook.py
index fbb4368..bee3d4b 100644
--- a/tests/test_webhook.py
+++ b/tests/test_webhook.py
@@ -9,7 +9,13 @@ from diskcache import Cache
from nio import LocalProtocolError, RoomSendResponse
import matrix_alertbot.webhook
+from matrix_alertbot.alertmanager import AlertmanagerClient
from matrix_alertbot.config import Config
+from matrix_alertbot.errors import (
+ AlertmanagerError,
+ SilenceExtendError,
+ SilenceNotFoundError,
+)
from matrix_alertbot.webhook import Webhook
@@ -19,9 +25,22 @@ def send_text_to_room_raise_error(
raise LocalProtocolError()
+def update_silence_raise_silence_not_found(fingerprint: str) -> str:
+ raise SilenceNotFoundError
+
+
+def update_silence_raise_silence_extend_error(fingerprint: str) -> str:
+ raise SilenceExtendError
+
+
+def update_silence_raise_alertmanager_error(fingerprint: str) -> str:
+ raise AlertmanagerError
+
+
class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
async def get_application(self) -> web.Application:
- self.fake_client = Mock(spec=nio.AsyncClient)
+ self.fake_matrix_client = Mock(spec=nio.AsyncClient)
+ self.fake_alertmanager_client = Mock(spec=AlertmanagerClient)
self.fake_cache = Mock(spec=Cache)
self.fake_room_id = "!abcdefg:example.com"
@@ -61,21 +80,36 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
]
}
- webhook = Webhook(self.fake_client, self.fake_cache, self.fake_config)
+ webhook = Webhook(
+ self.fake_matrix_client,
+ self.fake_alertmanager_client,
+ self.fake_cache,
+ self.fake_config,
+ )
return webhook.app
@patch.object(matrix_alertbot.webhook, "send_text_to_room")
- async def test_post_alerts(self, fake_send_text_to_room: Mock) -> None:
+ async def test_post_alerts_with_silence_not_found(
+ self, fake_send_text_to_room: Mock
+ ) -> None:
+ self.fake_alertmanager_client.update_silence.side_effect = (
+ update_silence_raise_silence_not_found
+ )
+
data = self.fake_alerts
async with self.client.request(
"POST", f"/alerts/{self.fake_room_id}", json=data
) as response:
self.assertEqual(200, response.status)
+ self.fake_alertmanager_client.update_silence.assert_called_once_with(
+ "fingerprint1"
+ )
+ self.assertEqual(2, fake_send_text_to_room.call_count)
fake_send_text_to_room.assert_has_calls(
[
call(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_room_id,
"[🔥 CRITICAL] alert1: some description1",
'\n [🔥 CRITICAL]\n '
@@ -84,7 +118,7 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
notice=False,
),
call(
- self.fake_client,
+ self.fake_matrix_client,
self.fake_room_id,
"[🥦 RESOLVED] alert2: some description2",
'\n [🥦 RESOLVED]\n '
@@ -99,6 +133,118 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
"fingerprint1",
expire=self.fake_config.cache_expire_time,
)
+ self.assertEqual(3, self.fake_cache.delete.call_count)
+ self.fake_cache.delete.assert_has_calls(
+ [
+ call("fingerprint1"),
+ call(fake_send_text_to_room.return_value.event_id),
+ call("fingerprint2"),
+ ]
+ )
+
+ @patch.object(matrix_alertbot.webhook, "send_text_to_room")
+ async def test_post_alerts_with_silence_extend_error(
+ self, fake_send_text_to_room: Mock
+ ) -> None:
+ self.fake_alertmanager_client.update_silence.side_effect = (
+ update_silence_raise_silence_extend_error
+ )
+
+ data = self.fake_alerts
+ async with self.client.request(
+ "POST", f"/alerts/{self.fake_room_id}", json=data
+ ) as response:
+ self.assertEqual(200, response.status)
+
+ self.fake_alertmanager_client.update_silence.assert_called_once_with(
+ "fingerprint1"
+ )
+ self.assertEqual(2, fake_send_text_to_room.call_count)
+ fake_send_text_to_room.assert_has_calls(
+ [
+ call(
+ self.fake_matrix_client,
+ self.fake_room_id,
+ "[🔥 CRITICAL] alert1: some description1",
+ '\n [🔥 CRITICAL]\n '
+ 'alert1\n (job1)
\n'
+ "some description1",
+ notice=False,
+ ),
+ call(
+ self.fake_matrix_client,
+ self.fake_room_id,
+ "[🥦 RESOLVED] alert2: some description2",
+ '\n [🥦 RESOLVED]\n '
+ 'alert2\n (job2)
\n'
+ "some description2",
+ notice=False,
+ ),
+ ]
+ )
+ self.fake_cache.set.assert_called_once_with(
+ fake_send_text_to_room.return_value.event_id,
+ "fingerprint1",
+ expire=self.fake_config.cache_expire_time,
+ )
+ self.assertEqual(2, self.fake_cache.delete.call_count)
+ self.fake_cache.delete.assert_has_calls(
+ [call(fake_send_text_to_room.return_value.event_id), call("fingerprint2")]
+ )
+
+ @patch.object(matrix_alertbot.webhook, "send_text_to_room")
+ async def test_post_alerts_with_alertmanager_error(
+ self, fake_send_text_to_room: Mock
+ ) -> None:
+ self.fake_alertmanager_client.update_silence.side_effect = (
+ update_silence_raise_alertmanager_error
+ )
+
+ data = self.fake_alerts
+ async with self.client.request(
+ "POST", f"/alerts/{self.fake_room_id}", json=data
+ ) as response:
+ self.assertEqual(500, response.status)
+
+ self.fake_alertmanager_client.update_silence.assert_called_once_with(
+ "fingerprint1"
+ )
+ fake_send_text_to_room.assert_not_called()
+ self.fake_cache.set.assert_not_called()
+ self.fake_cache.delete.assert_not_called()
+
+ @patch.object(matrix_alertbot.webhook, "send_text_to_room")
+ async def test_post_alerts_with_existing_silence(
+ self, fake_send_text_to_room: Mock
+ ) -> None:
+ self.fake_alertmanager_client.update_silence.return_value = "silence1"
+
+ data = self.fake_alerts
+ async with self.client.request(
+ "POST", f"/alerts/{self.fake_room_id}", json=data
+ ) as response:
+ self.assertEqual(200, response.status)
+
+ self.fake_alertmanager_client.update_silence.assert_called_once_with(
+ "fingerprint1"
+ )
+ fake_send_text_to_room.assert_called_once_with(
+ self.fake_matrix_client,
+ self.fake_room_id,
+ "[🥦 RESOLVED] alert2: some description2",
+ '\n [🥦 RESOLVED]\n '
+ 'alert2\n (job2)
\n'
+ "some description2",
+ notice=False,
+ )
+ self.fake_cache.set.assert_not_called()
+ self.assertEqual(2, self.fake_cache.delete.call_count)
+ self.fake_cache.delete.assert_has_calls(
+ [
+ call(fake_send_text_to_room.return_value.event_id),
+ call("fingerprint2"),
+ ]
+ )
@patch.object(matrix_alertbot.webhook, "send_text_to_room")
async def test_post_alerts_in_unauthorized_room(
@@ -116,6 +262,7 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
)
fake_send_text_to_room.assert_not_called()
self.fake_cache.set.assert_not_called()
+ self.fake_cache.delete.assert_not_called()
@patch.object(matrix_alertbot.webhook, "send_text_to_room")
async def test_post_alerts_with_empty_data(
@@ -130,6 +277,7 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
self.assertEqual("Data must contain 'alerts' key.", error_msg)
fake_send_text_to_room.assert_not_called()
self.fake_cache.set.assert_not_called()
+ self.fake_cache.delete.assert_not_called()
@patch.object(matrix_alertbot.webhook, "send_text_to_room")
async def test_post_empty_alerts(self, fake_send_text_to_room: Mock) -> None:
@@ -143,6 +291,7 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
self.assertEqual("Alerts cannot be empty.", error_msg)
fake_send_text_to_room.assert_not_called()
self.fake_cache.set.assert_not_called()
+ self.fake_cache.delete.assert_not_called()
@patch.object(matrix_alertbot.webhook, "send_text_to_room")
async def test_post_invalid_alerts(self, fake_send_text_to_room: Mock) -> None:
@@ -156,6 +305,7 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
self.assertEqual("Alerts must be a list, got 'str'.", error_msg)
fake_send_text_to_room.assert_not_called()
self.fake_cache.set.assert_not_called()
+ self.fake_cache.delete.assert_not_called()
@patch.object(matrix_alertbot.webhook, "send_text_to_room")
async def test_post_alerts_with_empty_items(
@@ -171,6 +321,7 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
self.assertEqual("Invalid alert: {}.", error_msg)
fake_send_text_to_room.assert_not_called()
self.fake_cache.set.assert_not_called()
+ self.fake_cache.delete.assert_not_called()
@patch.object(
matrix_alertbot.webhook,
@@ -180,6 +331,10 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
async def test_post_alerts_raise_send_error(
self, fake_send_text_to_room: Mock
) -> None:
+ self.fake_alertmanager_client.update_silence.side_effect = (
+ update_silence_raise_silence_not_found
+ )
+
data = self.fake_alerts
async with self.client.request(
"POST", f"/alerts/{self.fake_room_id}", json=data
@@ -193,6 +348,7 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
)
fake_send_text_to_room.assert_called_once()
self.fake_cache.set.assert_not_called()
+ self.fake_cache.delete.assert_called_once_with("fingerprint1")
async def test_health(self) -> None:
async with self.client.request("GET", "/health") as response:
@@ -205,7 +361,8 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
class WebhookServerTestCase(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self) -> None:
- self.fake_client = Mock(spec=nio.AsyncClient)
+ self.fake_matrix_client = Mock(spec=nio.AsyncClient)
+ self.fake_alertmanager_client = Mock(spec=AlertmanagerClient)
self.fake_cache = Mock(spec=Cache)
self.fake_config = Mock(spec=Config)
@@ -217,7 +374,12 @@ class WebhookServerTestCase(unittest.IsolatedAsyncioTestCase):
@patch.object(matrix_alertbot.webhook.web, "TCPSite", autospec=True)
async def test_webhook_start_address_port(self, fake_tcp_site: Mock) -> None:
- webhook = Webhook(self.fake_client, self.fake_cache, self.fake_config)
+ webhook = Webhook(
+ self.fake_matrix_client,
+ self.fake_alertmanager_client,
+ self.fake_cache,
+ self.fake_config,
+ )
await webhook.start()
fake_tcp_site.assert_called_once_with(
@@ -231,7 +393,12 @@ class WebhookServerTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_config.address = None
self.fake_config.port = None
- webhook = Webhook(self.fake_client, self.fake_cache, self.fake_config)
+ webhook = Webhook(
+ self.fake_matrix_client,
+ self.fake_alertmanager_client,
+ self.fake_cache,
+ self.fake_config,
+ )
await webhook.start()
fake_unix_site.assert_called_once_with(webhook.runner, self.fake_config.socket)