import pytest from infrasynth.notifications.channels.base import Attachment, BaseChannel from infrasynth.notifications.channels.email_sendgrid import SendGridChannel from infrasynth.notifications.channels.email_smtp import SMTPChannel from infrasynth.notifications.channels.sms_twilio import TwilioSMSChannel from infrasynth.notifications.channels.telegram import TelegramChannel class TestBaseChannel: def test_is_abstract(self): with pytest.raises(TypeError): BaseChannel() def test_attachment_fields(self): attachment = Attachment("f.txt", b"data", "text/plain") assert attachment.filename == "f.txt" assert attachment.content == b"data" assert attachment.mime_type == "text/plain" assert attachment.cid is None assert attachment.inline is False class TestSMTPChannel: def test_from_config_lowercases_keys(self): channel = SMTPChannel.from_config({"HOST": "smtp.example.com", "PORT": 2525, "USERNAME": "u"}) assert channel.host == "smtp.example.com" assert channel.port == 2525 assert channel.username == "u" def test_send_without_host_returns_err(self): channel = SMTPChannel() result = channel.send("a@b.com", "s", "b") assert result.is_err assert "SMTP host" in result.error def test_send_success_with_locmem_backend(self, settings): settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" channel = SMTPChannel.from_config( {"host": "smtp.example.com", "backend": "django.core.mail.backends.locmem.EmailBackend"} ) result = channel.send("a@b.com", "Hello", "Hi", is_html=True) assert result.is_ok def test_send_success_with_attachment(self, settings): settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" channel = SMTPChannel.from_config( {"host": "smtp.example.com", "backend": "django.core.mail.backends.locmem.EmailBackend"} ) attachment = Attachment("doc.pdf", b"%PDF", "application/pdf") result = channel.send("a@b.com", "Hello", "Hi", attachments=[attachment]) assert result.is_ok def test_health_check(self): assert SMTPChannel.from_config({"host": "x"}).health_check() is True assert SMTPChannel().health_check() is False class TestSendGridChannel: def test_send_without_key_returns_err(self): channel = SendGridChannel() result = channel.send("a@b.com", "s", "b") assert result.is_err assert "API key" in result.error def test_send_success(self): channel = SendGridChannel(api_key="sg-key") with __import__("unittest").mock.patch( "infrasynth.notifications.channels.email_sendgrid.requests.post" ) as post: post.return_value.status_code = 202 post.return_value.text = "accepted" result = channel.send("a@b.com", "Hello", "World") assert result.is_ok payload = post.call_args.kwargs["json"] assert payload["personalizations"][0]["to"][0]["email"] == "a@b.com" assert payload["content"][0]["type"] == "text/html" assert payload["from"]["email"] == "noreply@example.com" def test_send_http_error_returns_err(self): channel = SendGridChannel(api_key="sg-key") with __import__("unittest").mock.patch( "infrasynth.notifications.channels.email_sendgrid.requests.post" ) as post: post.return_value.status_code = 401 post.return_value.text = "unauthorized" result = channel.send("a@b.com", "Hello", "World") assert result.is_err assert "401" in result.error def test_send_request_exception_returns_err(self): import requests channel = SendGridChannel(api_key="sg-key") with __import__("unittest").mock.patch( "infrasynth.notifications.channels.email_sendgrid.requests.post", side_effect=requests.RequestException("timeout"), ): result = channel.send("a@b.com", "Hello", "World") assert result.is_err assert "timeout" in result.error def test_health_check(self): assert SendGridChannel(api_key="k").health_check() is True assert SendGridChannel().health_check() is False class TestTwilioSMSChannel: def test_send_without_credentials_returns_err(self): channel = TwilioSMSChannel() result = channel.send("+573001234567", "s", "b") assert result.is_err assert "credentials" in result.error def test_send_success(self): channel = TwilioSMSChannel(account_sid="sid", auth_token="token", from_number="+15005550006") with __import__("unittest").mock.patch("infrasynth.notifications.channels.sms_twilio.Client") as client_cls: client = client_cls.return_value result = channel.send("+573001234567", "s", "Hello SMS") assert result.is_ok client.messages.create.assert_called_once_with(to="+573001234567", from_="+15005550006", body="Hello SMS") def test_send_twilio_error_returns_err(self): from twilio.base.exceptions import TwilioRestException channel = TwilioSMSChannel(account_sid="sid", auth_token="token", from_number="+15005550006") with __import__("unittest").mock.patch("infrasynth.notifications.channels.sms_twilio.Client") as client_cls: client_cls.return_value.messages.create.side_effect = TwilioRestException( 400, "http://twilio", msg="Invalid number" ) result = channel.send("+573001234567", "s", "Hello") assert result.is_err assert "400" in result.error def test_health_check(self): assert TwilioSMSChannel(account_sid="a", auth_token="b", from_number="c").health_check() is True assert TwilioSMSChannel().health_check() is False class TestTelegramChannel: def test_send_without_token_returns_err(self): channel = TelegramChannel() result = channel.send("12345", "s", "b") assert result.is_err assert "bot token" in result.error def test_send_without_chat_id_returns_err(self): channel = TelegramChannel(bot_token="tok") result = channel.send("", "s", "b") assert result.is_err assert "chat_id" in result.error def test_send_success(self): from unittest import mock channel = TelegramChannel(bot_token="tok", chat_id="12345") with mock.patch("infrasynth.notifications.channels.telegram.requests.post") as post: post.return_value.json.return_value = {"ok": True} result = channel.send("", "Subject", "Hello") assert result.is_ok call_kwargs = post.call_args.kwargs assert "bot" not in str(call_kwargs["json"]) def test_send_api_error_returns_err(self): from unittest import mock channel = TelegramChannel(bot_token="tok", chat_id="12345") with mock.patch("infrasynth.notifications.channels.telegram.requests.post") as post: post.return_value.json.return_value = { "ok": False, "description": "chat not found", } result = channel.send("", "Subject", "Hello") assert result.is_err assert "chat not found" in result.error def test_send_request_exception_returns_err(self): from unittest import mock import requests channel = TelegramChannel(bot_token="tok", chat_id="12345") with mock.patch( "infrasynth.notifications.channels.telegram.requests.post", side_effect=requests.RequestException("network"), ): result = channel.send("", "Subject", "Hello") assert result.is_err assert "network" in result.error def test_health_check(self): assert TelegramChannel(bot_token="k").health_check() is True assert TelegramChannel().health_check() is False