2024-08-26 13:08:19 -05:00
|
|
|
# -*- coding: utf-8; -*-
|
|
|
|
|
|
|
|
from unittest import TestCase
|
|
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
|
2024-08-26 14:34:35 -05:00
|
|
|
import pytest
|
|
|
|
|
2024-08-26 13:08:19 -05:00
|
|
|
from wuttjamaican.email import handler as mod
|
|
|
|
from wuttjamaican.email import Message
|
|
|
|
from wuttjamaican.conf import WuttaConfig
|
|
|
|
from wuttjamaican.util import resource_path
|
|
|
|
from wuttjamaican.exc import ConfigurationError
|
|
|
|
|
|
|
|
|
|
|
|
class TestEmailHandler(TestCase):
|
|
|
|
|
|
|
|
def setUp(self):
|
2024-08-26 14:34:35 -05:00
|
|
|
try:
|
|
|
|
import mako
|
|
|
|
except ImportError:
|
|
|
|
pytest.skip("test not relevant without mako")
|
|
|
|
|
2024-08-26 13:08:19 -05:00
|
|
|
self.config = WuttaConfig()
|
|
|
|
self.app = self.config.get_app()
|
|
|
|
|
|
|
|
def make_handler(self, **kwargs):
|
|
|
|
return mod.EmailHandler(self.config, **kwargs)
|
|
|
|
|
|
|
|
def test_constructor_lookups(self):
|
|
|
|
|
|
|
|
# empty lookup paths by default, if no providers
|
|
|
|
with patch.object(self.app, 'providers', new={}):
|
|
|
|
handler = self.make_handler()
|
|
|
|
self.assertEqual(handler.txt_templates.directories, [])
|
|
|
|
self.assertEqual(handler.html_templates.directories, [])
|
|
|
|
|
|
|
|
# provider may specify paths as list
|
|
|
|
providers = {
|
|
|
|
'wuttatest': MagicMock(email_templates=['wuttjamaican.email:templates']),
|
|
|
|
}
|
|
|
|
with patch.object(self.app, 'providers', new=providers):
|
|
|
|
handler = self.make_handler()
|
|
|
|
path = resource_path('wuttjamaican.email:templates')
|
|
|
|
self.assertEqual(handler.txt_templates.directories, [path])
|
|
|
|
self.assertEqual(handler.html_templates.directories, [path])
|
|
|
|
|
|
|
|
# provider may specify paths as string
|
|
|
|
providers = {
|
|
|
|
'wuttatest': MagicMock(email_templates='wuttjamaican.email:templates'),
|
|
|
|
}
|
|
|
|
with patch.object(self.app, 'providers', new=providers):
|
|
|
|
handler = self.make_handler()
|
|
|
|
path = resource_path('wuttjamaican.email:templates')
|
|
|
|
self.assertEqual(handler.txt_templates.directories, [path])
|
|
|
|
self.assertEqual(handler.html_templates.directories, [path])
|
|
|
|
|
|
|
|
def test_make_message(self):
|
|
|
|
handler = self.make_handler()
|
|
|
|
msg = handler.make_message()
|
|
|
|
self.assertIsInstance(msg, Message)
|
|
|
|
|
|
|
|
def test_make_auto_message(self):
|
|
|
|
handler = self.make_handler()
|
|
|
|
|
|
|
|
# error if default sender not defined
|
|
|
|
self.assertRaises(ConfigurationError, handler.make_auto_message, 'foo')
|
|
|
|
|
|
|
|
# so let's define that
|
|
|
|
self.config.setdefault('wutta.email.default.sender', 'bob@example.com')
|
|
|
|
|
|
|
|
# message is empty by default
|
|
|
|
msg = handler.make_auto_message('foo')
|
|
|
|
self.assertIsInstance(msg, Message)
|
|
|
|
self.assertEqual(msg.key, 'foo')
|
|
|
|
self.assertEqual(msg.sender, 'bob@example.com')
|
|
|
|
self.assertEqual(msg.subject, "Automated message")
|
|
|
|
self.assertEqual(msg.to, [])
|
|
|
|
self.assertEqual(msg.cc, [])
|
|
|
|
self.assertEqual(msg.bcc, [])
|
|
|
|
self.assertIsNone(msg.replyto)
|
|
|
|
self.assertIsNone(msg.txt_body)
|
|
|
|
self.assertIsNone(msg.html_body)
|
|
|
|
|
|
|
|
# but if there is a proper email profile configured for key,
|
|
|
|
# then we should get back a more complete message
|
|
|
|
self.config.setdefault('wutta.email.test_foo.subject', "hello foo")
|
|
|
|
self.config.setdefault('wutta.email.test_foo.to', 'sally@example.com')
|
|
|
|
self.config.setdefault('wutta.email.templates', 'tests.email:templates')
|
|
|
|
handler = self.make_handler()
|
|
|
|
msg = handler.make_auto_message('test_foo')
|
|
|
|
self.assertEqual(msg.key, 'test_foo')
|
|
|
|
self.assertEqual(msg.sender, 'bob@example.com')
|
|
|
|
self.assertEqual(msg.subject, "hello foo")
|
|
|
|
self.assertEqual(msg.to, ['sally@example.com'])
|
|
|
|
self.assertEqual(msg.cc, [])
|
|
|
|
self.assertEqual(msg.bcc, [])
|
|
|
|
self.assertIsNone(msg.replyto)
|
|
|
|
self.assertEqual(msg.txt_body, "hello from foo txt template\n")
|
|
|
|
self.assertEqual(msg.html_body, "<p>hello from foo html template</p>\n")
|
|
|
|
|
|
|
|
# *some* auto methods get skipped if caller specifies the
|
|
|
|
# kwarg at all; others get skipped if kwarg is empty
|
|
|
|
|
|
|
|
# sender
|
|
|
|
with patch.object(handler, 'get_auto_sender') as get_auto_sender:
|
|
|
|
msg = handler.make_auto_message('foo', sender=None)
|
|
|
|
get_auto_sender.assert_not_called()
|
|
|
|
msg = handler.make_auto_message('foo')
|
|
|
|
get_auto_sender.assert_called_once_with('foo')
|
|
|
|
|
|
|
|
# subject
|
|
|
|
with patch.object(handler, 'get_auto_subject') as get_auto_subject:
|
|
|
|
msg = handler.make_auto_message('foo', subject=None)
|
|
|
|
get_auto_subject.assert_not_called()
|
|
|
|
msg = handler.make_auto_message('foo')
|
|
|
|
get_auto_subject.assert_called_once_with('foo', {})
|
|
|
|
|
|
|
|
# to
|
|
|
|
with patch.object(handler, 'get_auto_to') as get_auto_to:
|
|
|
|
msg = handler.make_auto_message('foo', to=None)
|
|
|
|
get_auto_to.assert_not_called()
|
|
|
|
get_auto_to.return_value = None
|
|
|
|
msg = handler.make_auto_message('foo')
|
|
|
|
get_auto_to.assert_called_once_with('foo')
|
|
|
|
|
|
|
|
# cc
|
|
|
|
with patch.object(handler, 'get_auto_cc') as get_auto_cc:
|
|
|
|
msg = handler.make_auto_message('foo', cc=None)
|
|
|
|
get_auto_cc.assert_not_called()
|
|
|
|
get_auto_cc.return_value = None
|
|
|
|
msg = handler.make_auto_message('foo')
|
|
|
|
get_auto_cc.assert_called_once_with('foo')
|
|
|
|
|
|
|
|
# bcc
|
|
|
|
with patch.object(handler, 'get_auto_bcc') as get_auto_bcc:
|
|
|
|
msg = handler.make_auto_message('foo', bcc=None)
|
|
|
|
get_auto_bcc.assert_not_called()
|
|
|
|
get_auto_bcc.return_value = None
|
|
|
|
msg = handler.make_auto_message('foo')
|
|
|
|
get_auto_bcc.assert_called_once_with('foo')
|
|
|
|
|
|
|
|
# txt_body
|
|
|
|
with patch.object(handler, 'get_auto_txt_body') as get_auto_txt_body:
|
|
|
|
msg = handler.make_auto_message('foo', txt_body=None)
|
|
|
|
get_auto_txt_body.assert_not_called()
|
|
|
|
msg = handler.make_auto_message('foo')
|
|
|
|
get_auto_txt_body.assert_called_once_with('foo', {})
|
|
|
|
|
|
|
|
# html_body
|
|
|
|
with patch.object(handler, 'get_auto_html_body') as get_auto_html_body:
|
|
|
|
msg = handler.make_auto_message('foo', html_body=None)
|
|
|
|
get_auto_html_body.assert_not_called()
|
|
|
|
msg = handler.make_auto_message('foo')
|
|
|
|
get_auto_html_body.assert_called_once_with('foo', {})
|
|
|
|
|
|
|
|
def test_get_auto_sender(self):
|
|
|
|
handler = self.make_handler()
|
|
|
|
|
|
|
|
# error if none configured
|
|
|
|
self.assertRaises(ConfigurationError, handler.get_auto_sender, 'foo')
|
|
|
|
|
|
|
|
# can set global default
|
|
|
|
self.config.setdefault('wutta.email.default.sender', 'bob@example.com')
|
|
|
|
self.assertEqual(handler.get_auto_sender('foo'), 'bob@example.com')
|
|
|
|
|
|
|
|
# can set for key
|
|
|
|
self.config.setdefault('wutta.email.foo.sender', 'sally@example.com')
|
|
|
|
self.assertEqual(handler.get_auto_sender('foo'), 'sally@example.com')
|
|
|
|
|
|
|
|
def test_get_auto_subject_template(self):
|
|
|
|
handler = self.make_handler()
|
|
|
|
|
|
|
|
# global default
|
|
|
|
template = handler.get_auto_subject_template('foo')
|
|
|
|
self.assertEqual(template, "Automated message")
|
|
|
|
|
|
|
|
# can configure alternate global default
|
|
|
|
self.config.setdefault('wutta.email.default.subject', "Wutta Message")
|
|
|
|
template = handler.get_auto_subject_template('foo')
|
|
|
|
self.assertEqual(template, "Wutta Message")
|
|
|
|
|
|
|
|
# can configure just for key
|
|
|
|
self.config.setdefault('wutta.email.foo.subject', "Foo Message")
|
|
|
|
template = handler.get_auto_subject_template('foo')
|
|
|
|
self.assertEqual(template, "Foo Message")
|
|
|
|
|
|
|
|
def test_get_auto_subject(self):
|
|
|
|
handler = self.make_handler()
|
|
|
|
|
|
|
|
# global default
|
|
|
|
subject = handler.get_auto_subject('foo')
|
|
|
|
self.assertEqual(subject, "Automated message")
|
|
|
|
|
|
|
|
# can configure alternate global default
|
|
|
|
self.config.setdefault('wutta.email.default.subject', "Wutta Message")
|
|
|
|
subject = handler.get_auto_subject('foo')
|
|
|
|
self.assertEqual(subject, "Wutta Message")
|
|
|
|
|
|
|
|
# can configure just for key
|
|
|
|
self.config.setdefault('wutta.email.foo.subject', "Foo Message")
|
|
|
|
subject = handler.get_auto_subject('foo')
|
|
|
|
self.assertEqual(subject, "Foo Message")
|
|
|
|
|
|
|
|
# proper template is rendered
|
|
|
|
self.config.setdefault('wutta.email.bar.subject', "${foo} Message")
|
|
|
|
subject = handler.get_auto_subject('bar', {'foo': "FOO"})
|
|
|
|
self.assertEqual(subject, "FOO Message")
|
|
|
|
|
|
|
|
# unless we ask it not to
|
|
|
|
subject = handler.get_auto_subject('bar', {'foo': "FOO"}, rendered=False)
|
|
|
|
self.assertEqual(subject, "${foo} Message")
|
|
|
|
|
|
|
|
def test_get_auto_recips(self):
|
|
|
|
handler = self.make_handler()
|
|
|
|
|
|
|
|
# error if bad type requested
|
|
|
|
self.assertRaises(ValueError, handler.get_auto_recips, 'foo', 'doesnotexist')
|
|
|
|
|
|
|
|
# can configure global default
|
|
|
|
self.config.setdefault('wutta.email.default.to', 'admin@example.com')
|
|
|
|
recips = handler.get_auto_recips('foo', 'to')
|
|
|
|
self.assertEqual(recips, ['admin@example.com'])
|
|
|
|
|
|
|
|
# can configure just for key
|
|
|
|
self.config.setdefault('wutta.email.foo.to', 'bob@example.com')
|
|
|
|
recips = handler.get_auto_recips('foo', 'to')
|
|
|
|
self.assertEqual(recips, ['bob@example.com'])
|
|
|
|
|
|
|
|
def test_get_auto_body_template(self):
|
|
|
|
from mako.template import Template
|
|
|
|
|
|
|
|
handler = self.make_handler()
|
|
|
|
|
|
|
|
# error if bad request
|
|
|
|
self.assertRaises(ValueError, handler.get_auto_body_template, 'foo', 'BADTYPE')
|
|
|
|
|
|
|
|
# empty by default
|
|
|
|
template = handler.get_auto_body_template('foo', 'txt')
|
|
|
|
self.assertIsNone(template)
|
|
|
|
|
|
|
|
# but returns a template if it exists
|
|
|
|
providers = {
|
|
|
|
'wuttatest': MagicMock(email_templates=['tests.email:templates']),
|
|
|
|
}
|
|
|
|
with patch.object(self.app, 'providers', new=providers):
|
|
|
|
handler = self.make_handler()
|
|
|
|
template = handler.get_auto_body_template('test_foo', 'txt')
|
|
|
|
self.assertIsInstance(template, Template)
|
|
|
|
self.assertEqual(template.uri, 'test_foo.txt.mako')
|
|
|
|
|
|
|
|
def test_get_auto_txt_body(self):
|
|
|
|
handler = self.make_handler()
|
|
|
|
|
|
|
|
# empty by default
|
|
|
|
body = handler.get_auto_txt_body('some-random-email')
|
|
|
|
self.assertIsNone(body)
|
|
|
|
|
|
|
|
# but returns body if template exists
|
|
|
|
providers = {
|
|
|
|
'wuttatest': MagicMock(email_templates=['tests.email:templates']),
|
|
|
|
}
|
|
|
|
with patch.object(self.app, 'providers', new=providers):
|
|
|
|
handler = self.make_handler()
|
|
|
|
body = handler.get_auto_txt_body('test_foo')
|
|
|
|
self.assertEqual(body, 'hello from foo txt template\n')
|
|
|
|
|
|
|
|
def test_get_auto_html_body(self):
|
|
|
|
handler = self.make_handler()
|
|
|
|
|
|
|
|
# empty by default
|
|
|
|
body = handler.get_auto_html_body('some-random-email')
|
|
|
|
self.assertIsNone(body)
|
|
|
|
|
|
|
|
# but returns body if template exists
|
|
|
|
providers = {
|
|
|
|
'wuttatest': MagicMock(email_templates=['tests.email:templates']),
|
|
|
|
}
|
|
|
|
with patch.object(self.app, 'providers', new=providers):
|
|
|
|
handler = self.make_handler()
|
|
|
|
body = handler.get_auto_html_body('test_foo')
|
|
|
|
self.assertEqual(body, '<p>hello from foo html template</p>\n')
|
|
|
|
|
|
|
|
def test_deliver_message(self):
|
|
|
|
handler = self.make_handler()
|
|
|
|
|
|
|
|
msg = handler.make_message(sender='bob@example.com', to='sally@example.com')
|
|
|
|
with patch.object(msg, 'as_string', return_value='msg-str'):
|
|
|
|
|
|
|
|
# no smtp session since sending email is disabled by default
|
|
|
|
with patch.object(mod, 'smtplib') as smtplib:
|
|
|
|
session = MagicMock()
|
|
|
|
smtplib.SMTP.return_value = session
|
|
|
|
handler.deliver_message(msg)
|
|
|
|
smtplib.SMTP.assert_not_called()
|
|
|
|
session.login.assert_not_called()
|
|
|
|
session.sendmail.assert_not_called()
|
|
|
|
|
|
|
|
# now let's enable sending
|
|
|
|
self.config.setdefault('wutta.mail.send_emails', 'true')
|
|
|
|
|
|
|
|
# smtp login not attempted by default
|
|
|
|
with patch.object(mod, 'smtplib') as smtplib:
|
|
|
|
session = MagicMock()
|
|
|
|
smtplib.SMTP.return_value = session
|
|
|
|
handler.deliver_message(msg)
|
|
|
|
smtplib.SMTP.assert_called_once_with('localhost')
|
|
|
|
session.login.assert_not_called()
|
|
|
|
session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str')
|
|
|
|
|
|
|
|
# but login attempted if config has credentials
|
|
|
|
self.config.setdefault('wutta.mail.smtp.username', 'bob')
|
|
|
|
self.config.setdefault('wutta.mail.smtp.password', 'seekrit')
|
|
|
|
with patch.object(mod, 'smtplib') as smtplib:
|
|
|
|
session = MagicMock()
|
|
|
|
smtplib.SMTP.return_value = session
|
|
|
|
handler.deliver_message(msg)
|
|
|
|
smtplib.SMTP.assert_called_once_with('localhost')
|
|
|
|
session.login.assert_called_once_with('bob', 'seekrit')
|
|
|
|
session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str')
|
|
|
|
|
|
|
|
# error if no sender
|
|
|
|
msg = handler.make_message(to='sally@example.com')
|
|
|
|
self.assertRaises(ValueError, handler.deliver_message, msg)
|
|
|
|
|
|
|
|
# error if no recips
|
|
|
|
msg = handler.make_message(sender='bob@example.com')
|
|
|
|
self.assertRaises(ValueError, handler.deliver_message, msg)
|
|
|
|
|
|
|
|
# can set recips as list
|
|
|
|
msg = handler.make_message(sender='bob@example.com')
|
|
|
|
with patch.object(msg, 'as_string', return_value='msg-str'):
|
|
|
|
with patch.object(mod, 'smtplib') as smtplib:
|
|
|
|
session = MagicMock()
|
|
|
|
smtplib.SMTP.return_value = session
|
|
|
|
handler.deliver_message(msg, recips=['sally@example.com'])
|
|
|
|
smtplib.SMTP.assert_called_once_with('localhost')
|
|
|
|
session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str')
|
|
|
|
|
|
|
|
# can set recips as string
|
|
|
|
msg = handler.make_message(sender='bob@example.com')
|
|
|
|
with patch.object(msg, 'as_string', return_value='msg-str'):
|
|
|
|
with patch.object(mod, 'smtplib') as smtplib:
|
|
|
|
session = MagicMock()
|
|
|
|
smtplib.SMTP.return_value = session
|
|
|
|
handler.deliver_message(msg, recips='sally@example.com')
|
|
|
|
smtplib.SMTP.assert_called_once_with('localhost')
|
|
|
|
session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str')
|
|
|
|
|
|
|
|
# can set recips via to
|
|
|
|
msg = handler.make_message(sender='bob@example.com', to='sally@example.com')
|
|
|
|
with patch.object(msg, 'as_string', return_value='msg-str'):
|
|
|
|
with patch.object(mod, 'smtplib') as smtplib:
|
|
|
|
session = MagicMock()
|
|
|
|
smtplib.SMTP.return_value = session
|
|
|
|
handler.deliver_message(msg)
|
|
|
|
smtplib.SMTP.assert_called_once_with('localhost')
|
|
|
|
session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str')
|
|
|
|
|
|
|
|
# can set recips via cc
|
|
|
|
msg = handler.make_message(sender='bob@example.com', cc='sally@example.com')
|
|
|
|
with patch.object(msg, 'as_string', return_value='msg-str'):
|
|
|
|
with patch.object(mod, 'smtplib') as smtplib:
|
|
|
|
session = MagicMock()
|
|
|
|
smtplib.SMTP.return_value = session
|
|
|
|
handler.deliver_message(msg)
|
|
|
|
smtplib.SMTP.assert_called_once_with('localhost')
|
|
|
|
session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str')
|
|
|
|
|
|
|
|
# can set recips via bcc
|
|
|
|
msg = handler.make_message(sender='bob@example.com', bcc='sally@example.com')
|
|
|
|
with patch.object(msg, 'as_string', return_value='msg-str'):
|
|
|
|
with patch.object(mod, 'smtplib') as smtplib:
|
|
|
|
session = MagicMock()
|
|
|
|
smtplib.SMTP.return_value = session
|
|
|
|
handler.deliver_message(msg)
|
|
|
|
smtplib.SMTP.assert_called_once_with('localhost')
|
|
|
|
session.sendmail.assert_called_once_with('bob@example.com', {'sally@example.com'}, 'msg-str')
|
|
|
|
|
|
|
|
def test_sending_is_enabled(self):
|
|
|
|
handler = self.make_handler()
|
|
|
|
|
|
|
|
# off by default
|
|
|
|
self.assertFalse(handler.sending_is_enabled())
|
|
|
|
|
|
|
|
# but can be turned on
|
|
|
|
self.config.setdefault('wutta.mail.send_emails', 'true')
|
|
|
|
self.assertTrue(handler.sending_is_enabled())
|
|
|
|
|
|
|
|
def test_send_email(self):
|
|
|
|
with patch.object(mod.EmailHandler, 'deliver_message') as deliver_message:
|
|
|
|
handler = self.make_handler()
|
|
|
|
|
|
|
|
# deliver_message() is called
|
|
|
|
handler.send_email('foo', sender='bob@example.com', to='sally@example.com',
|
|
|
|
txt_body='hello world')
|
|
|
|
deliver_message.assert_called_once()
|
|
|
|
|
|
|
|
# make_auto_message() called only if needed
|
|
|
|
with patch.object(handler, 'make_auto_message') as make_auto_message:
|
|
|
|
|
|
|
|
msg = handler.make_message()
|
|
|
|
handler.send_email(message=msg)
|
|
|
|
make_auto_message.assert_not_called()
|
|
|
|
|
|
|
|
handler.send_email('foo', sender='bob@example.com', to='sally@example.com',
|
|
|
|
txt_body='hello world')
|
|
|
|
make_auto_message.assert_called_once_with('foo', {},
|
|
|
|
sender='bob@example.com',
|
|
|
|
to='sally@example.com',
|
|
|
|
txt_body='hello world')
|