import importlib from unittest import mock import requests from django.core.mail import EmailMessage, EmailMultiAlternatives from django.db.utils import OperationalError from django.test import SimpleTestCase, override_settings from django.urls import Resolver404, clear_url_caches, resolve, reverse from . import urls from .mail import ResendEmailBackend class BrowserReloadUrlTests(SimpleTestCase): """django-browser-reload serves an open event stream and injects a script into every HTML response, so it must never be mounted outside DEBUG.""" def reload_urlconf(self): importlib.reload(urls) clear_url_caches() def tearDown(self): self.reload_urlconf() # restore the real (DEBUG=False) urlconf def test_the_reload_endpoint_is_mounted_in_debug(self): with override_settings(DEBUG=True): self.reload_urlconf() self.assertEqual(resolve("/__reload__/events/").view_name, "django_browser_reload:events") def test_the_reload_endpoint_is_absent_without_debug(self): self.reload_urlconf() with self.assertRaises(Resolver404): resolve("/__reload__/events/") class HealthCheckTests(SimpleTestCase): databases = {"default"} def test_it_reports_ok_when_the_database_and_cache_answer(self): response = self.client.get(reverse("healthz")) self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), {"status": "ok", "checks": {"database": True, "cache": True}}) def test_a_dead_database_is_a_503(self): # 200 while the database is unreachable is worse than no health check at all: the load # balancer would keep sending traffic to a node that cannot serve a single page. with mock.patch("rosterchief.health.connection") as db: db.cursor.side_effect = OperationalError("connection refused") response = self.client.get(reverse("healthz")) self.assertEqual(response.status_code, 503) self.assertFalse(response.json()["checks"]["database"]) def test_a_cache_that_swallows_writes_is_unhealthy(self): # Not a ping: a cache that accepts writes and returns nothing would have waffle read # every feature flag as unset. with mock.patch("rosterchief.health.cache") as broken: broken.get.return_value = None response = self.client.get(reverse("healthz")) self.assertEqual(response.status_code, 503) self.assertFalse(response.json()["checks"]["cache"]) def test_an_unreachable_cache_is_a_503(self): # Distinct from the cache that answers wrongly above: here Redis is simply down, and # the check must fail rather than raise its way to a 500. with mock.patch("rosterchief.health.cache") as down: down.set.side_effect = ConnectionError("redis is down") response = self.client.get(reverse("healthz")) self.assertEqual(response.status_code, 503) self.assertFalse(response.json()["checks"]["cache"]) def test_it_is_never_cached(self): response = self.client.get(reverse("healthz")) self.assertIn("no-cache", response["Cache-Control"]) @override_settings(ALLOWED_HOSTS=["example.com", "localhost", "127.0.0.1"]) def test_it_answers_over_the_loopback(self): # The container healthcheck and the deploy probe hit it as 127.0.0.1/localhost, before # a proxy supplies a real Host. If ALLOWED_HOSTS rejects those, /healthz 400s and the # container is unhealthy forever — which is exactly how the first deploy failed. for host in ("127.0.0.1", "localhost"): self.assertEqual(self.client.get(reverse("healthz"), HTTP_HOST=host).status_code, 200, host) @override_settings(RESEND_API_KEY="test-key") class ResendEmailBackendTests(SimpleTestCase): def ok_response(self): response = mock.Mock() response.raise_for_status.return_value = None return response def test_sends_a_plain_text_message(self): message = EmailMessage("Subject", "Body text.", "from@example.com", ["to@example.com"]) with mock.patch("rosterchief.mail.requests.Session.post", return_value=self.ok_response()) as post: sent = ResendEmailBackend().send_messages([message]) self.assertEqual(sent, 1) payload = post.call_args.kwargs["json"] self.assertEqual(payload["from"], "from@example.com") self.assertEqual(payload["to"], ["to@example.com"]) self.assertEqual(payload["subject"], "Subject") self.assertEqual(payload["text"], "Body text.") self.assertNotIn("html", payload) def test_uses_a_bearer_token_from_settings(self): message = EmailMessage("Subject", "Body.", "from@example.com", ["to@example.com"]) with mock.patch("rosterchief.mail.requests.Session.post", return_value=self.ok_response()) as post: ResendEmailBackend().send_messages([message]) self.assertEqual(post.call_args.kwargs["headers"]["Authorization"], "Bearer test-key") def test_includes_the_html_alternative(self): message = EmailMultiAlternatives("Subject", "Plain body.", "from@example.com", ["to@example.com"]) message.attach_alternative("

HTML body.

", "text/html") with mock.patch("rosterchief.mail.requests.Session.post", return_value=self.ok_response()) as post: ResendEmailBackend().send_messages([message]) self.assertEqual(post.call_args.kwargs["json"]["html"], "

HTML body.

") def test_cc_bcc_and_reply_to_are_included(self): message = EmailMessage("Subject", "Body.", "from@example.com", ["to@example.com"], cc=["cc@example.com"], bcc=["bcc@example.com"], reply_to=["reply@example.com"]) with mock.patch("rosterchief.mail.requests.Session.post", return_value=self.ok_response()) as post: ResendEmailBackend().send_messages([message]) payload = post.call_args.kwargs["json"] self.assertEqual(payload["cc"], ["cc@example.com"]) self.assertEqual(payload["bcc"], ["bcc@example.com"]) self.assertEqual(payload["reply_to"], ["reply@example.com"]) def test_attachments_are_base64_encoded(self): message = EmailMessage("Subject", "Body.", "from@example.com", ["to@example.com"]) message.attach("notes.txt", "hello world", "text/plain") with mock.patch("rosterchief.mail.requests.Session.post", return_value=self.ok_response()) as post: ResendEmailBackend().send_messages([message]) [attachment] = post.call_args.kwargs["json"]["attachments"] self.assertEqual(attachment["filename"], "notes.txt") self.assertEqual(attachment["content"], "aGVsbG8gd29ybGQ=") def test_sends_each_message_in_a_batch(self): messages = [EmailMessage("A", "Body.", "from@example.com", ["a@example.com"]), EmailMessage("B", "Body.", "from@example.com", ["b@example.com"])] with mock.patch("rosterchief.mail.requests.Session.post", return_value=self.ok_response()) as post: sent = ResendEmailBackend().send_messages(messages) self.assertEqual(sent, 2) self.assertEqual(post.call_count, 2) @override_settings(RESEND_API_KEY="") def test_a_missing_api_key_raises_by_default(self): with self.assertRaises(ValueError): ResendEmailBackend().send_messages([EmailMessage("Subject", "Body.", "from@example.com", ["to@example.com"])]) @override_settings(RESEND_API_KEY="") def test_a_missing_api_key_is_silent_when_fail_silently(self): sent = ResendEmailBackend(fail_silently=True).send_messages([EmailMessage("Subject", "Body.", "from@example.com", ["to@example.com"])]) self.assertEqual(sent, 0) def test_a_failed_request_raises_by_default(self): message = EmailMessage("Subject", "Body.", "from@example.com", ["to@example.com"]) with mock.patch("rosterchief.mail.requests.Session.post", side_effect=requests.ConnectionError("down")): with self.assertRaises(requests.ConnectionError): ResendEmailBackend().send_messages([message]) def test_a_failed_request_is_silent_when_fail_silently(self): message = EmailMessage("Subject", "Body.", "from@example.com", ["to@example.com"]) with mock.patch("rosterchief.mail.requests.Session.post", side_effect=requests.ConnectionError("down")): sent = ResendEmailBackend(fail_silently=True).send_messages([message]) self.assertEqual(sent, 0) def test_no_messages_is_a_no_op(self): with mock.patch("rosterchief.mail.requests.Session.post") as post: sent = ResendEmailBackend().send_messages([]) self.assertEqual(sent, 0) post.assert_not_called()