from datetime import datetime
from io import StringIO
from unittest.mock import patch
from django.conf import settings
from django.core import mail
from django.core.management import call_command
from django.test import TestCase
from django.utils import timezone
from main import models
from main.management.commands import mailexports, processnotifications
class ProcessNotificationsTest(TestCase):
"""
Test processnotifications sends emails to the blog's subscibers.
"""
def setUp(self):
self.user = models.User.objects.create(
username="alice", email="alice@mataroa.blog", notifications_on=True
)
post_data = {
"title": "Yesterday post",
"slug": "yesterday-post",
"body": "Content sentence.",
"published_at": timezone.make_aware(datetime(2020, 1, 1)),
}
self.post_yesterday = models.Post.objects.create(owner=self.user, **post_data)
post_data = {
"title": "Today post",
"slug": "today-post",
"body": "Content sentence.",
"published_at": timezone.make_aware(datetime(2020, 1, 2)),
}
self.post_today = models.Post.objects.create(owner=self.user, **post_data)
self.notification = models.Notification.objects.create(
blog_user=self.user, email="subscriber@example.com"
)
def test_mail_backend(self):
connection = processnotifications.get_mail_connection()
self.assertEqual(connection.host, settings.EMAIL_HOST_BROADCASTS)
def test_command(self):
output = StringIO()
with (
patch.object(timezone, "now", return_value=datetime(2020, 1, 2, 13, 00)),
patch.object(
# Django default test runner overrides SMTP EmailBackend with locmem,
# but because we re-import the SMTP backend in
# processnotifications.get_mail_connection, we need to mock it here too.
processnotifications,
"get_mail_connection",
return_value=mail.get_connection(
"django.core.mail.backends.locmem.EmailBackend"
),
),
):
call_command("processnotifications", "--no-dryrun", stdout=output)
# notification records
records = models.NotificationRecord.objects.all()
self.assertEqual(len(records), 1)
record = records[0]
# notification record for yesterday's post
self.assertEqual(record.notification.email, self.notification.email)
self.assertEqual(record.post.title, "Yesterday post")
# logging
self.assertIn("Processing notifications.", output.getvalue())
self.assertIn(
"Email sent for 'Yesterday post' to 'subscriber@example.com'",
output.getvalue(),
)
# email
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "Yesterday post")
self.assertIn("Unsubscribe", mail.outbox[0].body)
# email headers
self.assertEqual(mail.outbox[0].to, [self.notification.email])
self.assertEqual(mail.outbox[0].reply_to, [])
self.assertEqual(
mail.outbox[0].from_email,
f"{self.user.username} <{self.user.username}@{settings.EMAIL_FROM_HOST}>",
)
self.assertEqual(
mail.outbox[0].extra_headers["X-PM-Message-Stream"], "newsletters"
)
self.assertIn(
"/newsletter/unsubscribe/",
mail.outbox[0].extra_headers["List-Unsubscribe"],
)
self.assertEqual(
mail.outbox[0].extra_headers["List-Unsubscribe-Post"],
"List-Unsubscribe=One-Click",
)
def tearDown(self):
models.User.objects.all().delete()
models.Post.objects.all().delete()
class MailExportsTest(TestCase):
"""
Test mail_export sends emails to users with `mail_export_on` enabled.
"""
def setUp(self):
self.user = models.User.objects.create(
username="alice", email="alice@mataroa.blog", mail_export_on=True
)
post_data = {
"title": "A post",
"slug": "a-post",
"body": "Content sentence.",
"published_at": timezone.make_aware(datetime(2020, 1, 1)),
}
self.post_a = models.Post.objects.create(owner=self.user, **post_data)
post_data = {
"title": "Second post",
"slug": "second-post",
"body": "Content sentence two.",
"published_at": timezone.make_aware(datetime(2020, 1, 2)),
}
self.post_b = models.Post.objects.create(owner=self.user, **post_data)
def test_mail_backend(self):
connection = mailexports.get_mail_connection()
self.assertEqual(connection.host, settings.EMAIL_HOST_BROADCASTS)
def test_command(self):
output = StringIO()
with (
patch.object(timezone, "now", return_value=datetime(2020, 1, 1, 00, 00)),
patch.object(
# Django default test runner overrides SMTP EmailBackend with locmem,
# but because we re-import the SMTP backend in
# processnotifications.get_mail_connection, we need to mock it here too.
mailexports,
"get_mail_connection",
return_value=mail.get_connection(
"django.core.mail.backends.locmem.EmailBackend"
),
),
):
call_command("mailexports", stdout=output)
# export records
records = models.ExportRecord.objects.all()
self.assertEqual(len(records), 1)
self.assertEqual(records[0].user, self.user)
self.assertIn("export-markdown-", records[0].name)
# logging
self.assertIn("Processing email exports.", output.getvalue())
self.assertIn(f"Processing user {self.user.username}.", output.getvalue())
self.assertIn(f"Export sent to {self.user.username}.", output.getvalue())
self.assertIn(
f"Logging export record for '{records[0].name}'.", output.getvalue()
)
self.assertIn("Emailing all exports complete.", output.getvalue())
# email
self.assertEqual(len(mail.outbox), 1)
self.assertIn("Mataroa export", mail.outbox[0].subject)
self.assertIn("Stop receiving exports", mail.outbox[0].body)
# email headers
self.assertEqual(mail.outbox[0].to, [self.user.email])
self.assertEqual(
mail.outbox[0].from_email,
settings.DEFAULT_FROM_EMAIL,
)
self.assertEqual(mail.outbox[0].extra_headers["X-PM-Message-Stream"], "exports")
self.assertIn(
"/export/unsubscribe/",
mail.outbox[0].extra_headers["List-Unsubscribe"],
)
self.assertEqual(
mail.outbox[0].extra_headers["List-Unsubscribe-Post"],
"List-Unsubscribe=One-Click",
)
def tearDown(self):
models.User.objects.all().delete()
models.Post.objects.all().delete()