diff --git a/elementary/messages/messaging_integrations/__init__.py b/elementary/messages/messaging_integrations/__init__.py index e69de29bb..64556ee55 100644 --- a/elementary/messages/messaging_integrations/__init__.py +++ b/elementary/messages/messaging_integrations/__init__.py @@ -0,0 +1,9 @@ +from .file_system import FileSystemMessagingIntegration +from .slack_webhook import SlackWebhookMessagingIntegration +from .teams_webhook import TeamsWebhookMessagingIntegration + +__all__ = [ + "FileSystemMessagingIntegration", + "TeamsWebhookMessagingIntegration", + "SlackWebhookMessagingIntegration", +] diff --git a/elementary/messages/messaging_integrations/file_system.py b/elementary/messages/messaging_integrations/file_system.py new file mode 100644 index 000000000..173f3e9b5 --- /dev/null +++ b/elementary/messages/messaging_integrations/file_system.py @@ -0,0 +1,73 @@ +import json +from datetime import datetime +from pathlib import Path + +from elementary.messages.message_body import MessageBody +from elementary.messages.messaging_integrations.base_messaging_integration import ( + BaseMessagingIntegration, + MessageSendResult, +) +from elementary.messages.messaging_integrations.empty_message_context import ( + EmptyMessageContext, +) +from elementary.messages.messaging_integrations.exceptions import ( + MessagingIntegrationError, +) +from elementary.utils.log import get_logger + +logger = get_logger(__name__) + + +class FileSystemMessagingIntegration( + BaseMessagingIntegration[str, EmptyMessageContext] +): + def __init__(self, directory: str, create_if_missing: bool = True) -> None: + self.directory = Path(directory).expanduser().resolve() + self._create_if_missing = create_if_missing + + if not self.directory.exists(): + if self._create_if_missing: + logger.info( + f"Creating directory for FileSystemMessagingIntegration: {self.directory}" + ) + self.directory.mkdir(parents=True, exist_ok=True) + else: + raise MessagingIntegrationError( + f"Directory {self.directory} does not exist and create_if_missing is False" + ) + + def supports_reply(self) -> bool: + return False + + def send_message( + self, destination: str, body: MessageBody + ) -> MessageSendResult[EmptyMessageContext]: + channel_dir = self.directory / destination + if not channel_dir.exists(): + if self._create_if_missing: + channel_dir.mkdir(parents=True, exist_ok=True) + else: + raise MessagingIntegrationError( + f"Channel directory {channel_dir} does not exist and create_if_missing is False" + ) + + filename = datetime.utcnow().strftime("%Y%m%dT%H%M%S_%fZ.json") + file_path = channel_dir / filename + + try: + json_str = json.dumps(body.dict(), indent=2) + file_path.write_text(json_str, encoding="utf-8") + except Exception as exc: + logger.error( + f"Failed to write alert message to file {file_path}: {exc}", + exc_info=True, + ) + raise MessagingIntegrationError( + f"Failed writing alert message to file {file_path}" + ) from exc + + return MessageSendResult( + timestamp=datetime.utcnow(), + message_format="json", + message_context=EmptyMessageContext(), + ) diff --git a/tests/unit/messages/messaging_integrations/test_file_system.py b/tests/unit/messages/messaging_integrations/test_file_system.py new file mode 100644 index 000000000..185a763a3 --- /dev/null +++ b/tests/unit/messages/messaging_integrations/test_file_system.py @@ -0,0 +1,59 @@ +import json +from pathlib import Path + +import pytest + +from elementary.messages.blocks import LineBlock, LinesBlock, TextBlock +from elementary.messages.message_body import MessageBody +from elementary.messages.messaging_integrations.exceptions import ( + MessagingIntegrationError, +) +from elementary.messages.messaging_integrations.file_system import ( + FileSystemMessagingIntegration, +) + + +def _build_body() -> MessageBody: + return MessageBody( + blocks=[LinesBlock(lines=[LineBlock(inlines=[TextBlock(text="hello")])])] + ) + + +def test_send_message_creates_file_in_channel_dir(tmp_path: Path) -> None: + root_dir = tmp_path / "alerts" + integ = FileSystemMessagingIntegration(directory=str(root_dir)) + body = _build_body() + + integ.send_message("channel", body) + + channel_dir = root_dir / "channel" + files = list(channel_dir.glob("*.json")) + assert len(files) == 1, "Expected exactly one file in the channel directory" + + message_json = files[0].read_text() + assert json.loads(message_json) == json.loads(body.json()) + + +def test_send_multiple_messages_creates_multiple_files(tmp_path: Path) -> None: + root_dir = tmp_path / "alerts" + integ = FileSystemMessagingIntegration(directory=str(root_dir)) + body1 = _build_body() + body2 = _build_body() + + integ.send_message("channel", body1) + integ.send_message("channel", body2) + + channel_dir = root_dir / "channel" + files = sorted(channel_dir.glob("*.json")) + + assert len(files) == 2 + assert json.loads(files[0].read_text()) == json.loads(body1.json()) + assert json.loads(files[1].read_text()) == json.loads(body2.json()) + + +def test_send_message_no_create_flag(tmp_path: Path) -> None: + directory = tmp_path / "alerts-no-create" + with pytest.raises(MessagingIntegrationError): + FileSystemMessagingIntegration( + directory=str(directory), create_if_missing=False + )