|
11 | 11 | import shutil |
12 | 12 | import types |
13 | 13 |
|
14 | | -from mock import patch |
| 14 | +from mock import call, patch |
15 | 15 | from pyquery import PyQuery |
16 | 16 | from typing import Dict, List # pyflakes:ignore |
17 | 17 |
|
| 18 | +from email.message import Message |
18 | 19 | from email.mime.image import MIMEImage |
19 | 20 | from email.mime.multipart import MIMEMultipart |
20 | 21 | from email.mime.text import MIMEText |
|
32 | 33 | from django.template.defaulttags import URLNode |
33 | 34 | from django.template.loader import get_template, render_to_string |
34 | 35 | from django.templatetags.static import StaticNode |
| 36 | +from django.test import RequestFactory |
35 | 37 | from django.urls import reverse as urlreverse |
36 | 38 |
|
37 | 39 | import debug # pyflakes:ignore |
|
42 | 44 | from ietf.utils.draft import PlaintextDraft, getmeta |
43 | 45 | from ietf.utils.fields import SearchableField |
44 | 46 | from ietf.utils.log import unreachable, assertion |
45 | | -from ietf.utils.mail import send_mail_preformatted, send_mail_text, send_mail_mime, outbox, get_payload_text |
| 47 | +from ietf.utils.mail import ( |
| 48 | + send_mail_preformatted, |
| 49 | + send_mail_text, |
| 50 | + send_mail_mime, |
| 51 | + outbox, |
| 52 | + get_payload_text, |
| 53 | + decode_header_value, |
| 54 | + show_that_mail_was_sent, |
| 55 | +) |
46 | 56 | from ietf.utils.test_runner import get_template_paths, set_coverage_checking |
47 | 57 | from ietf.utils.test_utils import TestCase, unicontent |
48 | 58 | from ietf.utils.text import parse_unicode |
@@ -109,6 +119,135 @@ def test_send_mail_preformatted(self): |
109 | 119 | recv = outbox[-1] |
110 | 120 | self.assertEqual(recv['Fuzz'], 'bucket, monger') |
111 | 121 |
|
| 122 | + |
| 123 | +class MailUtilsTests(TestCase): |
| 124 | + def test_decode_header_value(self): |
| 125 | + self.assertEqual( |
| 126 | + decode_header_value("cake"), |
| 127 | + "cake", |
| 128 | + "decodes simple string value", |
| 129 | + ) |
| 130 | + self.assertEqual( |
| 131 | + decode_header_value("=?utf-8?b?8J+Ogg==?="), |
| 132 | + "\U0001f382", |
| 133 | + "decodes single utf-8-encoded part", |
| 134 | + ) |
| 135 | + self.assertEqual( |
| 136 | + decode_header_value("=?utf-8?b?8J+Ogg==?= = =?macintosh?b?jYxrjg==?="), |
| 137 | + "\U0001f382 = çåké", |
| 138 | + "decodes a value with non-utf-8 encodings", |
| 139 | + ) |
| 140 | + |
| 141 | + # Patch in a side_effect so we can distinguish values that came from decode_header_value. |
| 142 | + @patch("ietf.utils.mail.decode_header_value", side_effect=lambda s: f"decoded-{s}") |
| 143 | + @patch("ietf.utils.mail.messages") |
| 144 | + def test_show_that_mail_was_sent(self, mock_messages, mock_decode_header_value): |
| 145 | + request = RequestFactory().get("/some/path") |
| 146 | + request.user = object() # just needs to exist |
| 147 | + msg = Message() |
| 148 | + msg["To"] = "to-value" |
| 149 | + msg["Subject"] = "subject-value" |
| 150 | + msg["Cc"] = "cc-value" |
| 151 | + with patch("ietf.ietfauth.utils.has_role", return_value=True): |
| 152 | + show_that_mail_was_sent(request, "mail was sent", msg, "bcc-value") |
| 153 | + self.assertCountEqual( |
| 154 | + mock_decode_header_value.call_args_list, |
| 155 | + [call("to-value"), call("subject-value"), call("cc-value"), call("bcc-value")], |
| 156 | + ) |
| 157 | + self.assertEqual(mock_messages.info.call_args[0][0], request) |
| 158 | + self.assertIn("mail was sent", mock_messages.info.call_args[0][1]) |
| 159 | + self.assertIn("decoded-subject-value", mock_messages.info.call_args[0][1]) |
| 160 | + self.assertIn("decoded-to-value", mock_messages.info.call_args[0][1]) |
| 161 | + self.assertIn("decoded-cc-value", mock_messages.info.call_args[0][1]) |
| 162 | + self.assertIn("decoded-bcc-value", mock_messages.info.call_args[0][1]) |
| 163 | + mock_messages.reset_mock() |
| 164 | + mock_decode_header_value.reset_mock() |
| 165 | + |
| 166 | + # no bcc |
| 167 | + with patch("ietf.ietfauth.utils.has_role", return_value=True): |
| 168 | + show_that_mail_was_sent(request, "mail was sent", msg, None) |
| 169 | + self.assertCountEqual( |
| 170 | + mock_decode_header_value.call_args_list, |
| 171 | + [call("to-value"), call("subject-value"), call("cc-value")], |
| 172 | + ) |
| 173 | + self.assertEqual(mock_messages.info.call_args[0][0], request) |
| 174 | + self.assertIn("mail was sent", mock_messages.info.call_args[0][1]) |
| 175 | + self.assertIn("decoded-subject-value", mock_messages.info.call_args[0][1]) |
| 176 | + self.assertIn("decoded-to-value", mock_messages.info.call_args[0][1]) |
| 177 | + self.assertIn("decoded-cc-value", mock_messages.info.call_args[0][1]) |
| 178 | + # Note: here and below - when using assertNotIn(), leaving off the "decoded-" prefix |
| 179 | + # proves that neither the original value nor the decoded value appear. |
| 180 | + self.assertNotIn("bcc-value", mock_messages.info.call_args[0][1]) |
| 181 | + mock_messages.reset_mock() |
| 182 | + mock_decode_header_value.reset_mock() |
| 183 | + |
| 184 | + # no cc |
| 185 | + del msg["Cc"] |
| 186 | + with patch("ietf.ietfauth.utils.has_role", return_value=True): |
| 187 | + show_that_mail_was_sent(request, "mail was sent", msg, None) |
| 188 | + self.assertCountEqual( |
| 189 | + mock_decode_header_value.call_args_list, |
| 190 | + [call("to-value"), call("subject-value")], |
| 191 | + ) |
| 192 | + self.assertEqual(mock_messages.info.call_args[0][0], request) |
| 193 | + self.assertIn("mail was sent", mock_messages.info.call_args[0][1]) |
| 194 | + self.assertIn("decoded-subject-value", mock_messages.info.call_args[0][1]) |
| 195 | + self.assertIn("decoded-to-value", mock_messages.info.call_args[0][1]) |
| 196 | + self.assertNotIn("cc-value", mock_messages.info.call_args[0][1]) |
| 197 | + self.assertNotIn("bcc-value", mock_messages.info.call_args[0][1]) |
| 198 | + mock_messages.reset_mock() |
| 199 | + mock_decode_header_value.reset_mock() |
| 200 | + |
| 201 | + # no to |
| 202 | + del msg["To"] |
| 203 | + with patch("ietf.ietfauth.utils.has_role", return_value=True): |
| 204 | + show_that_mail_was_sent(request, "mail was sent", msg, None) |
| 205 | + self.assertCountEqual( |
| 206 | + mock_decode_header_value.call_args_list, |
| 207 | + [call("[no to]"), call("subject-value")], |
| 208 | + ) |
| 209 | + self.assertEqual(mock_messages.info.call_args[0][0], request) |
| 210 | + self.assertIn("mail was sent", mock_messages.info.call_args[0][1]) |
| 211 | + self.assertIn("decoded-subject-value", mock_messages.info.call_args[0][1]) |
| 212 | + self.assertIn("decoded-[no to]", mock_messages.info.call_args[0][1]) |
| 213 | + self.assertNotIn("to-value", mock_messages.info.call_args[0][1]) |
| 214 | + self.assertNotIn("cc-value", mock_messages.info.call_args[0][1]) |
| 215 | + self.assertNotIn("bcc-value", mock_messages.info.call_args[0][1]) |
| 216 | + mock_messages.reset_mock() |
| 217 | + mock_decode_header_value.reset_mock() |
| 218 | + |
| 219 | + # no subject |
| 220 | + del msg["Subject"] |
| 221 | + with patch("ietf.ietfauth.utils.has_role", return_value=True): |
| 222 | + show_that_mail_was_sent(request, "mail was sent", msg, None) |
| 223 | + self.assertCountEqual( |
| 224 | + mock_decode_header_value.call_args_list, |
| 225 | + [call("[no to]"), call("[no subject]")], |
| 226 | + ) |
| 227 | + self.assertEqual(mock_messages.info.call_args[0][0], request) |
| 228 | + self.assertIn("mail was sent", mock_messages.info.call_args[0][1]) |
| 229 | + self.assertIn("decoded-[no subject]", mock_messages.info.call_args[0][1]) |
| 230 | + self.assertNotIn("subject-value", mock_messages.info.call_args[0][1]) |
| 231 | + self.assertIn("decoded-[no to]", mock_messages.info.call_args[0][1]) |
| 232 | + self.assertNotIn("to-value", mock_messages.info.call_args[0][1]) |
| 233 | + self.assertNotIn("cc-value", mock_messages.info.call_args[0][1]) |
| 234 | + self.assertNotIn("bcc-value", mock_messages.info.call_args[0][1]) |
| 235 | + mock_messages.reset_mock() |
| 236 | + mock_decode_header_value.reset_mock() |
| 237 | + |
| 238 | + # user does not have role |
| 239 | + with patch("ietf.ietfauth.utils.has_role", return_value=False): |
| 240 | + show_that_mail_was_sent(request, "mail was sent", msg, None) |
| 241 | + self.assertFalse(mock_messages.called) |
| 242 | + |
| 243 | + # no user |
| 244 | + request.user = None |
| 245 | + with patch("ietf.ietfauth.utils.has_role", return_value=True) as mock_has_role: |
| 246 | + show_that_mail_was_sent(request, "mail was sent", msg, None) |
| 247 | + self.assertFalse(mock_messages.called) |
| 248 | + self.assertFalse(mock_has_role.called) |
| 249 | + |
| 250 | + |
112 | 251 | class TestSMTPServer(TestCase): |
113 | 252 |
|
114 | 253 | def test_address_rejected(self): |
|
0 commit comments