diff --git a/README.md b/README.md
index 04a3e4d..82016d3 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ msg.recipients = ['you@example.com', 'me@example.com']
msg.add_recipient('otherperson@example.com')
```
-If you have set MAIL_DEFAULT_SENDER you don’t need to set the message sender explicitly, as it will use this configuration value by default:
+If you have set `MAIL_DEFAULT_SENDER` you don’t need to set the message sender explicitly, as it will use this configuration value by default:
```
msg = Message('Hello',
@@ -85,7 +85,7 @@ msg.body = 'message body'
msg.html = 'Hello apistar_mail!'
```
-## Testing
+### Testing
To run the test suite with coverage first install the package in editable mode with it's testing requirements:
diff --git a/tests/test_apistar_mail.py b/tests/test_apistar_mail.py
index f70ec3f..4142ea5 100644
--- a/tests/test_apistar_mail.py
+++ b/tests/test_apistar_mail.py
@@ -1,4 +1,8 @@
-from apistar_mail.mail import Message, Mail, force_text
+import base64
+import email
+import re
+import time
+from apistar_mail.mail import Message, Mail, force_text, sanitize_address
from apistar_mail.exc import MailUnicodeDecodeError
import pytest
@@ -15,6 +19,7 @@ settings = {
}
}
+# Message
def test_message_init():
msg = Message(subject="subject",
@@ -49,4 +54,333 @@ def test_raise_unicode_decode_error():
value = b'\xe5\x93\x88\xe5\x93\x88'
with pytest.raises(MailUnicodeDecodeError) as excinfo:
force_text(value, encoding='ascii')
- assert 'You passed in' in str(excinfo)
\ No newline at end of file
+ assert 'You passed in' in str(excinfo)
+
+
+def test_esmtp_options_properly_initialized():
+ msg = Message(subject='subject')
+ assert msg.mail_options == []
+ assert msg.rcpt_options == []
+
+
+def test_sender_as_tuple():
+ msg = Message(subject='test',
+ sender=('Me', 'me@example.com'))
+ assert msg.sender == 'Me '
+
+
+def test_emails_are_sanitized():
+ msg = Message(subject="testing",
+ sender="sender\r\n@example.com",
+ reply_to="reply_to\r\n@example.com",
+ recipients=["recipient\r\n@example.com"])
+ assert 'sender@example.com' in msg.as_string()
+ assert 'reply_to@example.com' in msg.as_string()
+ assert 'recipient@example.com' in msg.as_string()
+
+
+def test_plain_message():
+ plain_text = "Hello Joe,\nHow are you?"
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["to@example.com"],
+ body=plain_text)
+ assert plain_text == msg.body
+ assert 'Content-Type: text/plain' in msg.as_string()
+
+
+def test_message_str():
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["to@example.com"],
+ body="some plain text")
+ assert msg.as_string() == str(msg)
+
+
+def test_plain_message_with_attachments():
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["to@example.com"],
+ body="hello")
+
+ msg.attach(data=b"this is a test",
+ content_type="text/plain")
+
+ assert 'Content-Type: multipart/mixed' in msg.as_string()
+
+
+def test_plain_message_with_ascii_attachment():
+ msg = Message(subject="subject",
+ recipients=["to@example.com"],
+ body="hello",
+ sender="me@example.com", )
+
+ msg.attach(data=b"this is a test",
+ content_type="text/plain",
+ filename='test doc.txt')
+
+ assert 'Content-Disposition: attachment; filename="test doc.txt"' in msg.as_string()
+
+
+def test_plain_message_with_unicode_attachment():
+ msg = Message(subject="subject",
+ recipients=["to@example.com"],
+ body="hello",
+ sender="me@example.com", )
+
+ msg.attach(data=b"this is a test",
+ content_type="text/plain",
+ filename='ünicöde ←→ ✓.txt')
+
+ parsed = email.message_from_string(msg.as_string())
+
+ assert re.sub(r'\s+', ' ', parsed.get_payload()[1].get('Content-Disposition')) in [
+ 'attachment; filename*="UTF8\'\'%C3%BCnic%C3%B6de%20%E2%86%90%E2%86%92%20%E2%9C%93.txt"',
+ 'attachment; filename*=UTF8\'\'%C3%BCnic%C3%B6de%20%E2%86%90%E2%86%92%20%E2%9C%93.txt'
+ ]
+
+
+def test_plain_message_with_ascii_converted_attachment():
+ msg = Message(subject="subject",
+ recipients=["to@example.com"],
+ body="hello",
+ sender="me@example.com",
+ ascii_attachments=True)
+
+ msg.attach(data=b"this is a test",
+ content_type="text/plain",
+ filename='ünicödeß ←.→ ✓.txt')
+
+ assert 'Content-Disposition: attachment; filename="unicode . .txt"' in msg.as_string()
+
+
+def test_html_message():
+ html_text = "Hello World
"
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["to@example.com"],
+ html=html_text)
+
+ assert html_text == msg.html
+ assert 'Content-Type: multipart/alternative' in msg.as_string()
+
+
+def test_json_message():
+ json_text = '{"msg": "Hello World!}'
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["to@example.com"],
+ alts={'json': json_text})
+
+ assert json_text == msg.alts['json']
+ assert 'Content-Type: multipart/alternative' in msg.as_string()
+
+
+def test_html_message_with_attachments():
+ html_text = "Hello World
"
+ plain_text = 'Hello World'
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["to@example.com"],
+ body=plain_text,
+ html=html_text)
+ msg.attach(data=b"this is a test",
+ content_type="text/plain")
+
+ assert html_text == msg.html
+ assert 'Content-Type: multipart/alternative' in msg.as_string()
+
+ parsed = email.message_from_string(msg.as_string())
+ assert len(parsed.get_payload()) == 2
+
+ body, attachment = parsed.get_payload()
+ assert len(body.get_payload()) == 2
+
+ plain, html = body.get_payload()
+ assert plain.get_payload() == plain_text
+ assert html.get_payload() == html_text
+ assert base64.b64decode(attachment.get_payload()), b'this is a test'
+
+
+def test_date_header():
+ before = time.time()
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["to@example.com"],
+ body="hello",
+ date=time.time())
+ after = time.time()
+
+ assert before <= msg.date <= after
+ date_formatted = email.utils.formatdate(msg.date, localtime=True)
+ assert 'Date: ' + date_formatted in msg.as_string()
+
+
+def test_msgid_header():
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["to@example.com"],
+ body="hello")
+
+ # see RFC 5322 section 3.6.4. for the exact format specification
+ r = re.compile(r"<\S+@\S+>").match(msg.msgId)
+ assert r is not None
+ assert 'Message-ID: ' + msg.msgId in msg.as_string()
+
+
+def test_unicode_sender_tuple():
+ msg = Message(subject="subject",
+ sender=("ÄÜÖ → ✓", 'from@example.com>'),
+ recipients=["to@example.com"])
+
+ assert 'From: =?utf-8?b?w4TDnMOWIOKGkiDinJM=?= ' in msg.as_string()
+
+
+def test_unicode_sender():
+ msg = Message(subject="subject",
+ sender='ÄÜÖ → ✓ >',
+ recipients=["to@example.com"])
+
+ assert 'From: =?utf-8?b?w4TDnMOWIOKGkiDinJM=?= ' in msg.as_string()
+
+
+def test_unicode_headers():
+ msg = Message(subject="subject",
+ sender='ÄÜÖ → ✓ ',
+ recipients=["Ä ", "Ü "],
+ cc=["Ö "])
+
+ response = msg.as_string()
+ a1 = sanitize_address("Ä ")
+ a2 = sanitize_address("Ü ")
+ h1_a = email.header.Header("To: %s, %s" % (a1, a2))
+ h1_b = email.header.Header("To: %s, %s" % (a2, a1))
+ h2 = email.header.Header("From: %s" % sanitize_address("ÄÜÖ → ✓ "))
+ h3 = email.header.Header("Cc: %s" % sanitize_address("Ö "))
+
+ # Ugly, but there's no guaranteed order of the recipients in the header
+ try:
+ assert h1_a.encode() in response
+ except AssertionError:
+ assert h1_b.encode() in response
+
+ assert h2.encode() in response
+ assert h3.encode() in response
+
+
+def test_unicode_subject():
+ msg = Message(subject="sübject",
+ sender='from@example.com',
+ recipients=["to@example.com"])
+ assert '=?utf-8?q?s=C3=BCbject?=' in msg.as_string()
+
+
+def test_extra_headers():
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["to@example.com"],
+ body="hello",
+ extra_headers={'X-Extra-Header': 'Yes'})
+ assert 'X-Extra-Header: Yes' in msg.as_string()
+
+
+def test_message_charset():
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["foo@bar.com"],
+ charset='us-ascii')
+
+ # ascii body
+ msg.body = "normal ascii text"
+ assert 'Content-Type: text/plain; charset="us-ascii"' in msg.as_string()
+
+ # ascii html
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["foo@bar.com"],
+ charset='us-ascii')
+ msg.body = None
+ msg.html = "hello
"
+ assert 'Content-Type: text/html; charset="us-ascii"' in msg.as_string()
+
+ # unicode body
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["foo@bar.com"])
+ msg.body = "ünicöde ←→ ✓"
+ assert 'Content-Type: text/plain; charset="utf-8"' in msg.as_string()
+
+ # unicode body and unicode html
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["foo@bar.com"])
+ msg.html = "ünicöde ←→ ✓"
+ assert 'Content-Type: text/plain; charset="utf-8"' in msg.as_string()
+ assert 'Content-Type: text/html; charset="utf-8"' in msg.as_string()
+
+ # unicode body and attachments
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["foo@bar.com"])
+ msg.html = None
+ msg.attach(data=b"foobar", content_type='text/csv')
+ assert 'Content-Type: text/plain; charset="utf-8"' in msg.as_string()
+
+ # unicode sender as tuple
+ msg = Message(sender=("送信者", "from@example.com"),
+ subject="表題",
+ recipients=["foo@bar.com"],
+ reply_to="返信先 ",
+ charset='shift_jis') # japanese
+ msg.body = '内容'
+ assert 'From: =?iso-2022-jp?' in msg.as_string()
+ assert 'From: =?utf-8?' not in msg.as_string()
+ assert 'Subject: =?iso-2022-jp?' in msg.as_string()
+ assert 'Subject: =?utf-8?' not in msg.as_string()
+ assert 'Reply-To: =?iso-2022-jp?' in msg.as_string()
+ assert 'Reply-To: =?utf-8?' not in msg.as_string()
+ assert 'Content-Type: text/plain; charset="iso-2022-jp"' in msg.as_string()
+
+ # unicode subject sjis
+ msg = Message(sender="from@example.com",
+ subject="表題",
+ recipients=["foo@bar.com"],
+ charset='shift_jis') # japanese
+ msg.body = '内容'
+ assert 'Subject: =?iso-2022-jp?' in msg.as_string()
+ assert 'Content-Type: text/plain; charset="iso-2022-jp"', msg.as_string()
+
+ # unicode subject utf-8
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["foo@bar.com"],
+ charset='utf-8')
+ msg.body = '内容'
+ assert 'Subject: subject' in msg.as_string()
+ assert 'Content-Type: text/plain; charset="utf-8"' in msg.as_string()
+
+ # ascii subject
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["foo@bar.com"],
+ charset='us-ascii')
+ msg.body = "normal ascii text"
+ assert 'Subject: =?us-ascii?' not in msg.as_string()
+ assert 'Content-Type: text/plain; charset="us-ascii"' in msg.as_string()
+
+ # default charset
+ msg = Message(sender="from@example.com",
+ subject="subject",
+ recipients=["foo@bar.com"])
+ msg.body = "normal ascii text"
+ assert 'Subject: =?' not in msg.as_string()
+ assert 'Content-Type: text/plain; charset="utf-8"' in msg.as_string()
+
+
+def test_empty_subject_header():
+ mail = Mail(settings)
+ msg = Message(sender="from@example.com",
+ recipients=["foo@bar.com"])
+ msg.body = "normal ascii text"
+ mail.send(msg)
+ assert 'Subject:' not in msg.as_string()