Files
Lamont/tests/unit/test_notifications.py
Chelsea Lee fbdf33e894
Some checks failed
CI / test (push) Has been cancelled
CI / compose-smoke (push) Has been cancelled
Build reusable bot framework
2026-07-19 21:53:24 -05:00

224 lines
7.2 KiB
Python

from unittest.mock import MagicMock, call
import pytest
from core import notifications
def test_get_notification_settings_returns_record_or_false(monkeypatch):
selectOne = MagicMock(
side_effect=[{"user_uuid": "user-1", "ntfy_enabled": True}, None]
)
monkeypatch.setattr(notifications.postgres, "select_one", selectOne)
assert notifications.getNotificationSettings("user-1") == {
"user_uuid": "user-1",
"ntfy_enabled": True,
}
assert notifications.getNotificationSettings("user-2") is False
assert selectOne.call_args_list == [
call("notifications", {"user_uuid": "user-1"}),
call("notifications", {"user_uuid": "user-2"}),
]
def test_notification_settings_filter_fields_and_update_existing(monkeypatch):
update = MagicMock()
insert = MagicMock()
monkeypatch.setattr(
notifications.postgres,
"select_one",
MagicMock(return_value={"id": "notification-1"}),
)
monkeypatch.setattr(notifications.postgres, "update", update)
monkeypatch.setattr(notifications.postgres, "insert", insert)
result = notifications.setNotificationSettings(
"user-1",
{
"ntfy_topic": "team-alerts",
"ntfy_enabled": True,
"user_uuid": "another-user",
"created_at": "not-allowed",
},
)
assert result is True
update.assert_called_once_with(
"notifications",
{"ntfy_topic": "team-alerts", "ntfy_enabled": True},
{"user_uuid": "user-1"},
)
insert.assert_not_called()
def test_notification_settings_insert_new_record(monkeypatch):
insert = MagicMock()
monkeypatch.setattr(
notifications.postgres,
"select_one",
MagicMock(return_value=None),
)
monkeypatch.setattr(notifications.postgres, "insert", insert)
monkeypatch.setattr(notifications.uuid, "uuid4", lambda: "notification-1")
result = notifications.setNotificationSettings(
"user-1",
{"discord_enabled": False, "ntfy_topic": "personal"},
)
assert result is True
insert.assert_called_once_with(
"notifications",
{
"discord_enabled": False,
"ntfy_topic": "personal",
"id": "notification-1",
"user_uuid": "user-1",
},
)
@pytest.mark.parametrize(
"settings",
[None, [], "invalid", {}, {"created_at": "not-allowed"}],
)
def test_notification_settings_reject_invalid_or_empty_updates(monkeypatch, settings):
selectOne = MagicMock()
monkeypatch.setattr(notifications.postgres, "select_one", selectOne)
assert notifications.setNotificationSettings("user-1", settings) is False
selectOne.assert_not_called()
@pytest.mark.parametrize(
"webhook",
[
"https://discord.com/api/webhooks/123/token",
"https://canary.discord.com/api/webhooks/123/token",
"https://ptb.discord.com/api/webhooks/123/token",
],
)
def test_discord_webhook_validation_accepts_official_https_urls(webhook):
assert notifications._validateDiscordWebhook(webhook) == webhook
@pytest.mark.parametrize(
("webhook", "error"),
[
(
"http://discord.com/api/webhooks/123/token",
"official HTTPS Discord host",
),
(
"https://discord.com.evil.example/api/webhooks/123/token",
"official HTTPS Discord host",
),
("https://discord.com/channels/123", "Invalid Discord webhook path"),
],
)
def test_discord_webhook_validation_rejects_unsafe_urls(webhook, error):
with pytest.raises(ValueError, match=error):
notifications._validateDiscordWebhook(webhook)
def test_discord_webhook_delivery_posts_content(monkeypatch):
post = MagicMock(return_value=MagicMock(status_code=204))
monkeypatch.setattr(notifications.requests, "post", post)
webhook = "https://discord.com/api/webhooks/123/token"
assert notifications.discord.send(webhook, 42) is True
post.assert_called_once_with(
webhook,
json={"content": "42"},
timeout=notifications.REQUEST_TIMEOUT,
)
@pytest.mark.parametrize("failure", [429, notifications.requests.ConnectionError("offline")])
def test_discord_webhook_delivery_reports_failures(monkeypatch, failure):
post = MagicMock()
if isinstance(failure, int):
post.return_value = MagicMock(status_code=failure)
else:
post.side_effect = failure
monkeypatch.setattr(notifications.requests, "post", post)
assert notifications.discord.send(
"https://discord.com/api/webhooks/123/token",
"hello",
) is False
def test_ntfy_encodes_topic_and_sends_bearer_token(monkeypatch):
post = MagicMock(return_value=MagicMock(status_code=201))
monkeypatch.setattr(notifications.requests, "post", post)
monkeypatch.setenv("NTFY_BASE_URL", "https://notify.example/base/")
monkeypatch.setenv("NTFY_TOKEN", "ntfy-secret")
assert notifications.ntfy.send(" alerts/team #1 ", 42) is True
post.assert_called_once_with(
"https://notify.example/base/alerts%2Fteam%20%231",
data=b"42",
headers={"Authorization": "Bearer ntfy-secret"},
timeout=notifications.REQUEST_TIMEOUT,
)
@pytest.mark.parametrize("failure", [500, notifications.requests.Timeout("slow")])
def test_ntfy_reports_http_and_transport_failures(monkeypatch, failure):
post = MagicMock()
if isinstance(failure, int):
post.return_value = MagicMock(status_code=failure)
else:
post.side_effect = failure
monkeypatch.setattr(notifications.requests, "post", post)
monkeypatch.delenv("NTFY_TOKEN", raising=False)
assert notifications.ntfy.send("alerts", "hello") is False
def test_ntfy_rejects_empty_topic_without_request(monkeypatch):
post = MagicMock()
monkeypatch.setattr(notifications.requests, "post", post)
assert notifications.ntfy.send(" ", "hello") is False
assert notifications.ntfy.send(None, "hello") is False
post.assert_not_called()
def test_channel_aggregation_tries_each_enabled_channel(monkeypatch):
discordSend = MagicMock(return_value=False)
ntfySend = MagicMock(return_value=True)
monkeypatch.setattr(notifications.discord, "send", discordSend)
monkeypatch.setattr(notifications.ntfy, "send", ntfySend)
result = notifications._sendToEnabledChannels(
{
"discord_enabled": True,
"discord_webhook": "https://discord.com/api/webhooks/123/token",
"ntfy_enabled": True,
"ntfy_topic": "alerts",
},
"hello",
)
assert result is True
discordSend.assert_called_once_with(
"https://discord.com/api/webhooks/123/token",
"hello",
)
ntfySend.assert_called_once_with("alerts", "hello")
@pytest.mark.parametrize("settings", [None, {}, {"discord_enabled": False}])
def test_channel_aggregation_skips_unconfigured_channels(monkeypatch, settings):
discordSend = MagicMock()
ntfySend = MagicMock()
monkeypatch.setattr(notifications.discord, "send", discordSend)
monkeypatch.setattr(notifications.ntfy, "send", ntfySend)
assert notifications._sendToEnabledChannels(settings, "hello") is False
discordSend.assert_not_called()
ntfySend.assert_not_called()