Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions shondesh/channels/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ async def send(self, data: dict) -> bool:
async with aiohttp.ClientSession() as session:
for attempt in range(self.config.get("retry_count", 1)):
try:
async with session.request(
response = await session.request(
self.config.get("method", "POST"),
self.config["url"],
json=payload,
headers=headers,
) as response:
)
async with response:
if response.status < 400:
return True
except Exception as e:
Expand Down
2 changes: 2 additions & 0 deletions shondesh/formatters/slack_message_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ def format(self, message: dict) -> list:
value.replace("`", "\\`").replace("*", "\\*").replace("_", "\\_")
)
attachment_fields.append({"title": key, "value": str(value), "short": True})

return attachment_fields
64 changes: 64 additions & 0 deletions tests/shondesh/channels/test_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import pytest
from shondesh.channels.email import Email


@pytest.fixture
def email_config():
return {
"from_address": "[email protected]",
"recipients": ["[email protected]"],
"subject_template": "Test Subject",
"password": "testpassword",
"smtp_server": "smtp.example.com",
"smtp_port": 587,
"username": "[email protected]",
}


@pytest.fixture
def email_channel(email_config):
return Email(config=email_config)


def test_email_properties(email_channel, email_config):
assert email_channel.config == email_config


@pytest.mark.asyncio
async def test_send_success(mocker, email_channel):
mock_smtp = mocker.patch("smtplib.SMTP", autospec=True)
data = {"key": "value"}
result = await email_channel.send(data)
assert result
mock_smtp.assert_called_with(
email_channel.config["smtp_server"], email_channel.config["smtp_port"]
)
instance = mock_smtp.return_value
instance.starttls.assert_called_once()
instance.login.assert_called_once_with(
email_channel.config["username"], email_channel.config["password"]
)
instance.sendmail.assert_called_once()
instance.quit.assert_called_once()


@pytest.mark.asyncio
async def test_send_env_password(mocker, email_channel, email_config, monkeypatch):
email_channel.config["password"] = "${EMAIL_PASSWORD}"
monkeypatch.setenv("EMAIL_PASSWORD", "envpassword")
mock_smtp = mocker.patch("smtplib.SMTP", autospec=True)
data = {"key": "value"}
result = await email_channel.send(data)
assert result
instance = mock_smtp.return_value
instance.login.assert_called_once_with(
email_channel.config["username"], "envpassword"
)


@pytest.mark.asyncio
async def test_send_failure(mocker, email_channel):
mocker.patch("smtplib.SMTP", side_effect=Exception("SMTP error"))
data = {"key": "value"}
result = await email_channel.send(data)
assert result is False
53 changes: 53 additions & 0 deletions tests/shondesh/channels/test_slack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import pytest
from shondesh.channels.slack import Slack
from shondesh.utils.constants import Severity


@pytest.fixture
def slack_config():
return {
"channel": "#alerts",
"username": "AlertBot",
"icon_emoji": ":robot_face:",
"webhook_url": "https://hooks.slack.com/services/test",
"mention_users": ["@user1", "@user2"],
}


@pytest.fixture
def slack_channel(slack_config):
return Slack(config=slack_config)


@pytest.mark.asyncio
async def test_send_success(mocker, slack_channel):
mocker.patch.object(
slack_channel, "send", mocker.AsyncMock(return_value={"ok": True})
)

data = {"severity": Severity.INFO, "message": "Test"}
result = await slack_channel.send(data)
assert result.get("ok") is True
slack_channel.send.assert_awaited_once_with(data)


@pytest.mark.asyncio
async def test_send_failure_status(mocker, slack_channel):
mocker.patch.object(
slack_channel, "send", mocker.AsyncMock(return_value={"ok": False})
)

data = {"severity": Severity.INFO, "message": "Test"}
result = await slack_channel.send(data)
assert result.get("ok") is False


@pytest.mark.asyncio
async def test_send_exception(mocker, slack_channel):
mock_session = mocker.MagicMock()
mock_session.__aenter__.side_effect = Exception("Connection error")
mocker.patch("aiohttp.ClientSession", return_value=mock_session)

data = {"severity": Severity.INFO, "message": "Test"}
result = await slack_channel.send(data)
assert result is False
47 changes: 47 additions & 0 deletions tests/shondesh/channels/test_telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,34 @@ def telegram_channel():
return Telegram(config)


def test_telegram_initialization(telegram_channel):
assert telegram_channel is not None
assert isinstance(telegram_channel, Telegram)
assert telegram_channel.config is not None
assert "token" in telegram_channel.config
assert "chat_id" in telegram_channel.config
assert "webhook_url" in telegram_channel.config


def test_telegram_initialization_missing_config():
with pytest.raises(ValueError):
Telegram(config={}) # Should raise an error due to missing token and chat_id


def test_raises_value_error_when_chat_id_is_missing():
config = {"webhook_url": "https://example.com/telegram"}
with pytest.raises(
ValueError, match="Telegram chat ID is required in the configuration."
):
Telegram(config=config)


def test_does_not_raise_error_when_chat_id_is_present():
config = {"webhook_url": "https://example.com/telegram", "chat_id": "12345"}
channel = Telegram(config=config)
assert channel.config["chat_id"] == "12345"


def test_telegram_properties(telegram_channel):
assert telegram_channel is not None
assert telegram_channel.config is not None
Expand All @@ -27,3 +55,22 @@ async def test_telegram_send_message(mocker, telegram_channel):
response = await telegram_channel.send(message)
assert response is not None
assert response.get("ok") is True


@pytest.mark.asyncio
async def test_telegram_send_message_failure(mocker, telegram_channel):
message = {"msg": "Test message", "severity": "info"}
mocker.patch.object(telegram_channel, "send", return_value={"ok": False})
response = await telegram_channel.send(message)
assert response is not None
assert response.get("ok") is False


@pytest.mark.asyncio
async def test_telegram_send_message_exception(mocker, telegram_channel):
message = {"msg": "Test message", "severity": "info"}
mocker.patch.object(
telegram_channel, "send", side_effect=Exception("Network error")
)
with pytest.raises(Exception):
await telegram_channel.send(message)
80 changes: 80 additions & 0 deletions tests/shondesh/channels/test_webhook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import pytest
from shondesh.channels.webhook import Webhook


@pytest.fixture
def webhook_config():
return {
"url": "https://example.com/webhook",
"method": "POST",
"headers": {"Authorization": "Bearer testtoken"},
"retry_count": 1,
}


@pytest.fixture
def webhook_channel(webhook_config):
return Webhook(config=webhook_config)


@pytest.mark.asyncio
async def test_send_success(mocker, webhook_channel):
mock_response = mocker.MagicMock()
mock_response.status = 200
mock_request = mocker.AsyncMock(return_value=mock_response)
mock_session = mocker.MagicMock()
mock_session.__aenter__.return_value = mock_session
mock_session.request = mock_request
mocker.patch("aiohttp.ClientSession", return_value=mock_session)

data = {"message": "Test"}
result = await webhook_channel.send(data)
assert result is True
mock_request.assert_awaited_once()


@pytest.mark.asyncio
async def test_send_failure_status(mocker, webhook_channel):
mock_response = mocker.MagicMock()
mock_response.status = 500
mock_request = mocker.AsyncMock(return_value=mock_response)
mock_session = mocker.MagicMock()
mock_session.__aenter__.return_value = mock_session
mock_session.request = mock_request
mocker.patch("aiohttp.ClientSession", return_value=mock_session)

data = {"message": "Test"}
result = await webhook_channel.send(data)
assert result is False


@pytest.mark.asyncio
async def test_send_exception(mocker, webhook_channel):
mock_session = mocker.MagicMock()
mock_session.__aenter__.side_effect = Exception("Connection error")
mocker.patch("aiohttp.ClientSession", return_value=mock_session)

data = {"message": "Test"}
result = await webhook_channel.send(data)
assert result is False


@pytest.mark.asyncio
async def test_send_env_header(monkeypatch, webhook_config, mocker):
webhook_config["headers"] = {"Authorization": "${WEBHOOK_TOKEN}"}
monkeypatch.setenv("WEBHOOK_TOKEN", "envtoken")
channel = Webhook(config=webhook_config)

mock_response = mocker.MagicMock()
mock_response.status = 200
mock_request = mocker.AsyncMock(return_value=mock_response)
mock_session = mocker.MagicMock()
mock_session.__aenter__.return_value = mock_session
mock_session.request = mock_request
mocker.patch("aiohttp.ClientSession", return_value=mock_session)

data = {"message": "Test"}
result = await channel.send(data)
assert result is True
_, kwargs = mock_request.call_args
assert kwargs["headers"]["Authorization"] == "envtoken"
Empty file.
17 changes: 17 additions & 0 deletions tests/shondesh/formatters/test_dict_table_formatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from shondesh.formatters.dict_table_formatter import DictTableFormatter


def test_dict_table_formatter():
formatter = DictTableFormatter()
data = {"Name": "Alice", "Age": 30, "Country": "Wonderland"}
expected = (
"Key | Value \n"
"--------+-----------\n"
"Name | Alice \n"
"Age | 30 \n"
"Country | Wonderland"
)
result = formatter.format(data)
assert [line.rstrip() for line in result.splitlines()] == [
line.rstrip() for line in expected.splitlines()
]
37 changes: 37 additions & 0 deletions tests/shondesh/formatters/test_slack_message_formatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import pytest
from shondesh.formatters.slack_message_formatter import (
SlackMessageFormatter as SlackFormatter,
)


@pytest.fixture
def slack_data():
return {
"message": "Test message",
"severity": "info",
"extra": {"foo": "bar"},
"timestamp": "2023-10-01T12:00:00Z",
}


def test_slack_formatter_basic(slack_data):
formatter = SlackFormatter()
formatted = formatter.format(slack_data)
assert isinstance(formatted, list)
assert formatted
assert any(field["title"] == "message" for key, field in enumerate(formatted))


def test_slack_formatter_severity(slack_data):
formatter = SlackFormatter()
slack_data["severity"] = "critical"
formatted = formatter.format(slack_data)
assert "danger" in str(formatted).lower() or "critical" in str(formatted).lower()


def test_slack_formatter_extra_fields(slack_data):
formatter = SlackFormatter()
formatted = formatter.format(slack_data)
assert formatted
assert any(field["title"] == "extra" for key, field in enumerate(formatted))
assert any(field["value"] == "{'foo': 'bar'}" for field in formatted)
Empty file.
25 changes: 25 additions & 0 deletions tests/shondesh/utils/test_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import pytest
from shondesh.utils.constants import Severity


def returns_correct_value_for_info():
assert Severity.INFO.value == "info"


def returns_correct_value_for_warning():
assert Severity.WARNING.value == "warning"


def returns_correct_value_for_critical():
assert Severity.CRITICAL.value == "critical"


def has_all_expected_severity_levels():
expected_levels = {"info", "warning", "critical"}
actual_levels = {severity.value for severity in Severity}
assert actual_levels == expected_levels


def raises_attribute_error_for_invalid_severity():
with pytest.raises(AttributeError):
_ = Severity.INVALID