»Core Development>Code coverage>Lib/test/test_email/test_message.py

Python code coverage for Lib/test/test_email/test_message.py

#countcontent
1n/aimport unittest
2n/aimport textwrap
3n/afrom email import policy, message_from_string
4n/afrom email.message import EmailMessage, MIMEPart
5n/afrom test.test_email import TestEmailBase, parameterize
6n/a
7n/a
8n/a# Helper.
9n/adef first(iterable):
10n/a return next(filter(lambda x: x is not None, iterable), None)
11n/a
12n/a
13n/aclass Test(TestEmailBase):
14n/a
15n/a policy = policy.default
16n/a
17n/a def test_error_on_setitem_if_max_count_exceeded(self):
18n/a m = self._str_msg("")
19n/a m['To'] = 'abc@xyz'
20n/a with self.assertRaises(ValueError):
21n/a m['To'] = 'xyz@abc'
22n/a
23n/a def test_rfc2043_auto_decoded_and_emailmessage_used(self):
24n/a m = message_from_string(textwrap.dedent("""\
25n/a Subject: Ayons asperges pour le =?utf-8?q?d=C3=A9jeuner?=
26n/a From: =?utf-8?q?Pep=C3=A9?= Le Pew <pepe@example.com>
27n/a To: "Penelope Pussycat" <"penelope@example.com">
28n/a MIME-Version: 1.0
29n/a Content-Type: text/plain; charset="utf-8"
30n/a
31n/a sample text
32n/a """), policy=policy.default)
33n/a self.assertEqual(m['subject'], "Ayons asperges pour le déjeuner")
34n/a self.assertEqual(m['from'], "Pepé Le Pew <pepe@example.com>")
35n/a self.assertIsInstance(m, EmailMessage)
36n/a
37n/a
38n/a@parameterize
39n/aclass TestEmailMessageBase:
40n/a
41n/a policy = policy.default
42n/a
43n/a # The first argument is a triple (related, html, plain) of indices into the
44n/a # list returned by 'walk' called on a Message constructed from the third.
45n/a # The indices indicate which part should match the corresponding part-type
46n/a # when passed to get_body (ie: the "first" part of that type in the
47n/a # message). The second argument is a list of indices into the 'walk' list
48n/a # of the attachments that should be returned by a call to
49n/a # 'iter_attachments'. The third argument is a list of indices into 'walk'
50n/a # that should be returned by a call to 'iter_parts'. Note that the first
51n/a # item returned by 'walk' is the Message itself.
52n/a
53n/a message_params = {
54n/a
55n/a 'empty_message': (
56n/a (None, None, 0),
57n/a (),
58n/a (),
59n/a ""),
60n/a
61n/a 'non_mime_plain': (
62n/a (None, None, 0),
63n/a (),
64n/a (),
65n/a textwrap.dedent("""\
66n/a To: foo@example.com
67n/a
68n/a simple text body
69n/a """)),
70n/a
71n/a 'mime_non_text': (
72n/a (None, None, None),
73n/a (),
74n/a (),
75n/a textwrap.dedent("""\
76n/a To: foo@example.com
77n/a MIME-Version: 1.0
78n/a Content-Type: image/jpg
79n/a
80n/a bogus body.
81n/a """)),
82n/a
83n/a 'plain_html_alternative': (
84n/a (None, 2, 1),
85n/a (),
86n/a (1, 2),
87n/a textwrap.dedent("""\
88n/a To: foo@example.com
89n/a MIME-Version: 1.0
90n/a Content-Type: multipart/alternative; boundary="==="
91n/a
92n/a preamble
93n/a
94n/a --===
95n/a Content-Type: text/plain
96n/a
97n/a simple body
98n/a
99n/a --===
100n/a Content-Type: text/html
101n/a
102n/a <p>simple body</p>
103n/a --===--
104n/a """)),
105n/a
106n/a 'plain_html_mixed': (
107n/a (None, 2, 1),
108n/a (),
109n/a (1, 2),
110n/a textwrap.dedent("""\
111n/a To: foo@example.com
112n/a MIME-Version: 1.0
113n/a Content-Type: multipart/mixed; boundary="==="
114n/a
115n/a preamble
116n/a
117n/a --===
118n/a Content-Type: text/plain
119n/a
120n/a simple body
121n/a
122n/a --===
123n/a Content-Type: text/html
124n/a
125n/a <p>simple body</p>
126n/a
127n/a --===--
128n/a """)),
129n/a
130n/a 'plain_html_attachment_mixed': (
131n/a (None, None, 1),
132n/a (2,),
133n/a (1, 2),
134n/a textwrap.dedent("""\
135n/a To: foo@example.com
136n/a MIME-Version: 1.0
137n/a Content-Type: multipart/mixed; boundary="==="
138n/a
139n/a --===
140n/a Content-Type: text/plain
141n/a
142n/a simple body
143n/a
144n/a --===
145n/a Content-Type: text/html
146n/a Content-Disposition: attachment
147n/a
148n/a <p>simple body</p>
149n/a
150n/a --===--
151n/a """)),
152n/a
153n/a 'html_text_attachment_mixed': (
154n/a (None, 2, None),
155n/a (1,),
156n/a (1, 2),
157n/a textwrap.dedent("""\
158n/a To: foo@example.com
159n/a MIME-Version: 1.0
160n/a Content-Type: multipart/mixed; boundary="==="
161n/a
162n/a --===
163n/a Content-Type: text/plain
164n/a Content-Disposition: AtTaChment
165n/a
166n/a simple body
167n/a
168n/a --===
169n/a Content-Type: text/html
170n/a
171n/a <p>simple body</p>
172n/a
173n/a --===--
174n/a """)),
175n/a
176n/a 'html_text_attachment_inline_mixed': (
177n/a (None, 2, 1),
178n/a (),
179n/a (1, 2),
180n/a textwrap.dedent("""\
181n/a To: foo@example.com
182n/a MIME-Version: 1.0
183n/a Content-Type: multipart/mixed; boundary="==="
184n/a
185n/a --===
186n/a Content-Type: text/plain
187n/a Content-Disposition: InLine
188n/a
189n/a simple body
190n/a
191n/a --===
192n/a Content-Type: text/html
193n/a Content-Disposition: inline
194n/a
195n/a <p>simple body</p>
196n/a
197n/a --===--
198n/a """)),
199n/a
200n/a # RFC 2387
201n/a 'related': (
202n/a (0, 1, None),
203n/a (2,),
204n/a (1, 2),
205n/a textwrap.dedent("""\
206n/a To: foo@example.com
207n/a MIME-Version: 1.0
208n/a Content-Type: multipart/related; boundary="==="; type=text/html
209n/a
210n/a --===
211n/a Content-Type: text/html
212n/a
213n/a <p>simple body</p>
214n/a
215n/a --===
216n/a Content-Type: image/jpg
217n/a Content-ID: <image1>
218n/a
219n/a bogus data
220n/a
221n/a --===--
222n/a """)),
223n/a
224n/a # This message structure will probably never be seen in the wild, but
225n/a # it proves we distinguish between text parts based on 'start'. The
226n/a # content would not, of course, actually work :)
227n/a 'related_with_start': (
228n/a (0, 2, None),
229n/a (1,),
230n/a (1, 2),
231n/a textwrap.dedent("""\
232n/a To: foo@example.com
233n/a MIME-Version: 1.0
234n/a Content-Type: multipart/related; boundary="==="; type=text/html;
235n/a start="<body>"
236n/a
237n/a --===
238n/a Content-Type: text/html
239n/a Content-ID: <include>
240n/a
241n/a useless text
242n/a
243n/a --===
244n/a Content-Type: text/html
245n/a Content-ID: <body>
246n/a
247n/a <p>simple body</p>
248n/a <!--#include file="<include>"-->
249n/a
250n/a --===--
251n/a """)),
252n/a
253n/a
254n/a 'mixed_alternative_plain_related': (
255n/a (3, 4, 2),
256n/a (6, 7),
257n/a (1, 6, 7),
258n/a textwrap.dedent("""\
259n/a To: foo@example.com
260n/a MIME-Version: 1.0
261n/a Content-Type: multipart/mixed; boundary="==="
262n/a
263n/a --===
264n/a Content-Type: multipart/alternative; boundary="+++"
265n/a
266n/a --+++
267n/a Content-Type: text/plain
268n/a
269n/a simple body
270n/a
271n/a --+++
272n/a Content-Type: multipart/related; boundary="___"
273n/a
274n/a --___
275n/a Content-Type: text/html
276n/a
277n/a <p>simple body</p>
278n/a
279n/a --___
280n/a Content-Type: image/jpg
281n/a Content-ID: <image1@cid>
282n/a
283n/a bogus jpg body
284n/a
285n/a --___--
286n/a
287n/a --+++--
288n/a
289n/a --===
290n/a Content-Type: image/jpg
291n/a Content-Disposition: attachment
292n/a
293n/a bogus jpg body
294n/a
295n/a --===
296n/a Content-Type: image/jpg
297n/a Content-Disposition: AttacHmenT
298n/a
299n/a another bogus jpg body
300n/a
301n/a --===--
302n/a """)),
303n/a
304n/a # This structure suggested by Stephen J. Turnbull...may not exist/be
305n/a # supported in the wild, but we want to support it.
306n/a 'mixed_related_alternative_plain_html': (
307n/a (1, 4, 3),
308n/a (6, 7),
309n/a (1, 6, 7),
310n/a textwrap.dedent("""\
311n/a To: foo@example.com
312n/a MIME-Version: 1.0
313n/a Content-Type: multipart/mixed; boundary="==="
314n/a
315n/a --===
316n/a Content-Type: multipart/related; boundary="+++"
317n/a
318n/a --+++
319n/a Content-Type: multipart/alternative; boundary="___"
320n/a
321n/a --___
322n/a Content-Type: text/plain
323n/a
324n/a simple body
325n/a
326n/a --___
327n/a Content-Type: text/html
328n/a
329n/a <p>simple body</p>
330n/a
331n/a --___--
332n/a
333n/a --+++
334n/a Content-Type: image/jpg
335n/a Content-ID: <image1@cid>
336n/a
337n/a bogus jpg body
338n/a
339n/a --+++--
340n/a
341n/a --===
342n/a Content-Type: image/jpg
343n/a Content-Disposition: attachment
344n/a
345n/a bogus jpg body
346n/a
347n/a --===
348n/a Content-Type: image/jpg
349n/a Content-Disposition: attachment
350n/a
351n/a another bogus jpg body
352n/a
353n/a --===--
354n/a """)),
355n/a
356n/a # Same thing, but proving we only look at the root part, which is the
357n/a # first one if there isn't any start parameter. That is, this is a
358n/a # broken related.
359n/a 'mixed_related_alternative_plain_html_wrong_order': (
360n/a (1, None, None),
361n/a (6, 7),
362n/a (1, 6, 7),
363n/a textwrap.dedent("""\
364n/a To: foo@example.com
365n/a MIME-Version: 1.0
366n/a Content-Type: multipart/mixed; boundary="==="
367n/a
368n/a --===
369n/a Content-Type: multipart/related; boundary="+++"
370n/a
371n/a --+++
372n/a Content-Type: image/jpg
373n/a Content-ID: <image1@cid>
374n/a
375n/a bogus jpg body
376n/a
377n/a --+++
378n/a Content-Type: multipart/alternative; boundary="___"
379n/a
380n/a --___
381n/a Content-Type: text/plain
382n/a
383n/a simple body
384n/a
385n/a --___
386n/a Content-Type: text/html
387n/a
388n/a <p>simple body</p>
389n/a
390n/a --___--
391n/a
392n/a --+++--
393n/a
394n/a --===
395n/a Content-Type: image/jpg
396n/a Content-Disposition: attachment
397n/a
398n/a bogus jpg body
399n/a
400n/a --===
401n/a Content-Type: image/jpg
402n/a Content-Disposition: attachment
403n/a
404n/a another bogus jpg body
405n/a
406n/a --===--
407n/a """)),
408n/a
409n/a 'message_rfc822': (
410n/a (None, None, None),
411n/a (),
412n/a (),
413n/a textwrap.dedent("""\
414n/a To: foo@example.com
415n/a MIME-Version: 1.0
416n/a Content-Type: message/rfc822
417n/a
418n/a To: bar@example.com
419n/a From: robot@examp.com
420n/a
421n/a this is a message body.
422n/a """)),
423n/a
424n/a 'mixed_text_message_rfc822': (
425n/a (None, None, 1),
426n/a (2,),
427n/a (1, 2),
428n/a textwrap.dedent("""\
429n/a To: foo@example.com
430n/a MIME-Version: 1.0
431n/a Content-Type: multipart/mixed; boundary="==="
432n/a
433n/a --===
434n/a Content-Type: text/plain
435n/a
436n/a Your message has bounced, ser.
437n/a
438n/a --===
439n/a Content-Type: message/rfc822
440n/a
441n/a To: bar@example.com
442n/a From: robot@examp.com
443n/a
444n/a this is a message body.
445n/a
446n/a --===--
447n/a """)),
448n/a
449n/a }
450n/a
451n/a def message_as_get_body(self, body_parts, attachments, parts, msg):
452n/a m = self._str_msg(msg)
453n/a allparts = list(m.walk())
454n/a expected = [None if n is None else allparts[n] for n in body_parts]
455n/a related = 0; html = 1; plain = 2
456n/a self.assertEqual(m.get_body(), first(expected))
457n/a self.assertEqual(m.get_body(preferencelist=(
458n/a 'related', 'html', 'plain')),
459n/a first(expected))
460n/a self.assertEqual(m.get_body(preferencelist=('related', 'html')),
461n/a first(expected[related:html+1]))
462n/a self.assertEqual(m.get_body(preferencelist=('related', 'plain')),
463n/a first([expected[related], expected[plain]]))
464n/a self.assertEqual(m.get_body(preferencelist=('html', 'plain')),
465n/a first(expected[html:plain+1]))
466n/a self.assertEqual(m.get_body(preferencelist=['related']),
467n/a expected[related])
468n/a self.assertEqual(m.get_body(preferencelist=['html']), expected[html])
469n/a self.assertEqual(m.get_body(preferencelist=['plain']), expected[plain])
470n/a self.assertEqual(m.get_body(preferencelist=('plain', 'html')),
471n/a first(expected[plain:html-1:-1]))
472n/a self.assertEqual(m.get_body(preferencelist=('plain', 'related')),
473n/a first([expected[plain], expected[related]]))
474n/a self.assertEqual(m.get_body(preferencelist=('html', 'related')),
475n/a first(expected[html::-1]))
476n/a self.assertEqual(m.get_body(preferencelist=('plain', 'html', 'related')),
477n/a first(expected[::-1]))
478n/a self.assertEqual(m.get_body(preferencelist=('html', 'plain', 'related')),
479n/a first([expected[html],
480n/a expected[plain],
481n/a expected[related]]))
482n/a
483n/a def message_as_iter_attachment(self, body_parts, attachments, parts, msg):
484n/a m = self._str_msg(msg)
485n/a allparts = list(m.walk())
486n/a attachments = [allparts[n] for n in attachments]
487n/a self.assertEqual(list(m.iter_attachments()), attachments)
488n/a
489n/a def message_as_iter_parts(self, body_parts, attachments, parts, msg):
490n/a m = self._str_msg(msg)
491n/a allparts = list(m.walk())
492n/a parts = [allparts[n] for n in parts]
493n/a self.assertEqual(list(m.iter_parts()), parts)
494n/a
495n/a class _TestContentManager:
496n/a def get_content(self, msg, *args, **kw):
497n/a return msg, args, kw
498n/a def set_content(self, msg, *args, **kw):
499n/a self.msg = msg
500n/a self.args = args
501n/a self.kw = kw
502n/a
503n/a def test_get_content_with_cm(self):
504n/a m = self._str_msg('')
505n/a cm = self._TestContentManager()
506n/a self.assertEqual(m.get_content(content_manager=cm), (m, (), {}))
507n/a msg, args, kw = m.get_content('foo', content_manager=cm, bar=1, k=2)
508n/a self.assertEqual(msg, m)
509n/a self.assertEqual(args, ('foo',))
510n/a self.assertEqual(kw, dict(bar=1, k=2))
511n/a
512n/a def test_get_content_default_cm_comes_from_policy(self):
513n/a p = policy.default.clone(content_manager=self._TestContentManager())
514n/a m = self._str_msg('', policy=p)
515n/a self.assertEqual(m.get_content(), (m, (), {}))
516n/a msg, args, kw = m.get_content('foo', bar=1, k=2)
517n/a self.assertEqual(msg, m)
518n/a self.assertEqual(args, ('foo',))
519n/a self.assertEqual(kw, dict(bar=1, k=2))
520n/a
521n/a def test_set_content_with_cm(self):
522n/a m = self._str_msg('')
523n/a cm = self._TestContentManager()
524n/a m.set_content(content_manager=cm)
525n/a self.assertEqual(cm.msg, m)
526n/a self.assertEqual(cm.args, ())
527n/a self.assertEqual(cm.kw, {})
528n/a m.set_content('foo', content_manager=cm, bar=1, k=2)
529n/a self.assertEqual(cm.msg, m)
530n/a self.assertEqual(cm.args, ('foo',))
531n/a self.assertEqual(cm.kw, dict(bar=1, k=2))
532n/a
533n/a def test_set_content_default_cm_comes_from_policy(self):
534n/a cm = self._TestContentManager()
535n/a p = policy.default.clone(content_manager=cm)
536n/a m = self._str_msg('', policy=p)
537n/a m.set_content()
538n/a self.assertEqual(cm.msg, m)
539n/a self.assertEqual(cm.args, ())
540n/a self.assertEqual(cm.kw, {})
541n/a m.set_content('foo', bar=1, k=2)
542n/a self.assertEqual(cm.msg, m)
543n/a self.assertEqual(cm.args, ('foo',))
544n/a self.assertEqual(cm.kw, dict(bar=1, k=2))
545n/a
546n/a # outcome is whether xxx_method should raise ValueError error when called
547n/a # on multipart/subtype. Blank outcome means it depends on xxx (add
548n/a # succeeds, make raises). Note: 'none' means there are content-type
549n/a # headers but payload is None...this happening in practice would be very
550n/a # unusual, so treating it as if there were content seems reasonable.
551n/a # method subtype outcome
552n/a subtype_params = (
553n/a ('related', 'no_content', 'succeeds'),
554n/a ('related', 'none', 'succeeds'),
555n/a ('related', 'plain', 'succeeds'),
556n/a ('related', 'related', ''),
557n/a ('related', 'alternative', 'raises'),
558n/a ('related', 'mixed', 'raises'),
559n/a ('alternative', 'no_content', 'succeeds'),
560n/a ('alternative', 'none', 'succeeds'),
561n/a ('alternative', 'plain', 'succeeds'),
562n/a ('alternative', 'related', 'succeeds'),
563n/a ('alternative', 'alternative', ''),
564n/a ('alternative', 'mixed', 'raises'),
565n/a ('mixed', 'no_content', 'succeeds'),
566n/a ('mixed', 'none', 'succeeds'),
567n/a ('mixed', 'plain', 'succeeds'),
568n/a ('mixed', 'related', 'succeeds'),
569n/a ('mixed', 'alternative', 'succeeds'),
570n/a ('mixed', 'mixed', ''),
571n/a )
572n/a
573n/a def _make_subtype_test_message(self, subtype):
574n/a m = self.message()
575n/a payload = None
576n/a msg_headers = [
577n/a ('To', 'foo@bar.com'),
578n/a ('From', 'bar@foo.com'),
579n/a ]
580n/a if subtype != 'no_content':
581n/a ('content-shadow', 'Logrus'),
582n/a msg_headers.append(('X-Random-Header', 'Corwin'))
583n/a if subtype == 'text':
584n/a payload = ''
585n/a msg_headers.append(('Content-Type', 'text/plain'))
586n/a m.set_payload('')
587n/a elif subtype != 'no_content':
588n/a payload = []
589n/a msg_headers.append(('Content-Type', 'multipart/' + subtype))
590n/a msg_headers.append(('X-Trump', 'Random'))
591n/a m.set_payload(payload)
592n/a for name, value in msg_headers:
593n/a m[name] = value
594n/a return m, msg_headers, payload
595n/a
596n/a def _check_disallowed_subtype_raises(self, m, method_name, subtype, method):
597n/a with self.assertRaises(ValueError) as ar:
598n/a getattr(m, method)()
599n/a exc_text = str(ar.exception)
600n/a self.assertIn(subtype, exc_text)
601n/a self.assertIn(method_name, exc_text)
602n/a
603n/a def _check_make_multipart(self, m, msg_headers, payload):
604n/a count = 0
605n/a for name, value in msg_headers:
606n/a if not name.lower().startswith('content-'):
607n/a self.assertEqual(m[name], value)
608n/a count += 1
609n/a self.assertEqual(len(m), count+1) # +1 for new Content-Type
610n/a part = next(m.iter_parts())
611n/a count = 0
612n/a for name, value in msg_headers:
613n/a if name.lower().startswith('content-'):
614n/a self.assertEqual(part[name], value)
615n/a count += 1
616n/a self.assertEqual(len(part), count)
617n/a self.assertEqual(part.get_payload(), payload)
618n/a
619n/a def subtype_as_make(self, method, subtype, outcome):
620n/a m, msg_headers, payload = self._make_subtype_test_message(subtype)
621n/a make_method = 'make_' + method
622n/a if outcome in ('', 'raises'):
623n/a self._check_disallowed_subtype_raises(m, method, subtype, make_method)
624n/a return
625n/a getattr(m, make_method)()
626n/a self.assertEqual(m.get_content_maintype(), 'multipart')
627n/a self.assertEqual(m.get_content_subtype(), method)
628n/a if subtype == 'no_content':
629n/a self.assertEqual(len(m.get_payload()), 0)
630n/a self.assertEqual(m.items(),
631n/a msg_headers + [('Content-Type',
632n/a 'multipart/'+method)])
633n/a else:
634n/a self.assertEqual(len(m.get_payload()), 1)
635n/a self._check_make_multipart(m, msg_headers, payload)
636n/a
637n/a def subtype_as_make_with_boundary(self, method, subtype, outcome):
638n/a # Doing all variation is a bit of overkill...
639n/a m = self.message()
640n/a if outcome in ('', 'raises'):
641n/a m['Content-Type'] = 'multipart/' + subtype
642n/a with self.assertRaises(ValueError) as cm:
643n/a getattr(m, 'make_' + method)()
644n/a return
645n/a if subtype == 'plain':
646n/a m['Content-Type'] = 'text/plain'
647n/a elif subtype != 'no_content':
648n/a m['Content-Type'] = 'multipart/' + subtype
649n/a getattr(m, 'make_' + method)(boundary="abc")
650n/a self.assertTrue(m.is_multipart())
651n/a self.assertEqual(m.get_boundary(), 'abc')
652n/a
653n/a def test_policy_on_part_made_by_make_comes_from_message(self):
654n/a for method in ('make_related', 'make_alternative', 'make_mixed'):
655n/a m = self.message(policy=self.policy.clone(content_manager='foo'))
656n/a m['Content-Type'] = 'text/plain'
657n/a getattr(m, method)()
658n/a self.assertEqual(m.get_payload(0).policy.content_manager, 'foo')
659n/a
660n/a class _TestSetContentManager:
661n/a def set_content(self, msg, content, *args, **kw):
662n/a msg['Content-Type'] = 'text/plain'
663n/a msg.set_payload(content)
664n/a
665n/a def subtype_as_add(self, method, subtype, outcome):
666n/a m, msg_headers, payload = self._make_subtype_test_message(subtype)
667n/a cm = self._TestSetContentManager()
668n/a add_method = 'add_attachment' if method=='mixed' else 'add_' + method
669n/a if outcome == 'raises':
670n/a self._check_disallowed_subtype_raises(m, method, subtype, add_method)
671n/a return
672n/a getattr(m, add_method)('test', content_manager=cm)
673n/a self.assertEqual(m.get_content_maintype(), 'multipart')
674n/a self.assertEqual(m.get_content_subtype(), method)
675n/a if method == subtype or subtype == 'no_content':
676n/a self.assertEqual(len(m.get_payload()), 1)
677n/a for name, value in msg_headers:
678n/a self.assertEqual(m[name], value)
679n/a part = m.get_payload()[0]
680n/a else:
681n/a self.assertEqual(len(m.get_payload()), 2)
682n/a self._check_make_multipart(m, msg_headers, payload)
683n/a part = m.get_payload()[1]
684n/a self.assertEqual(part.get_content_type(), 'text/plain')
685n/a self.assertEqual(part.get_payload(), 'test')
686n/a if method=='mixed':
687n/a self.assertEqual(part['Content-Disposition'], 'attachment')
688n/a elif method=='related':
689n/a self.assertEqual(part['Content-Disposition'], 'inline')
690n/a else:
691n/a # Otherwise we don't guess.
692n/a self.assertIsNone(part['Content-Disposition'])
693n/a
694n/a class _TestSetRaisingContentManager:
695n/a def set_content(self, msg, content, *args, **kw):
696n/a raise Exception('test')
697n/a
698n/a def test_default_content_manager_for_add_comes_from_policy(self):
699n/a cm = self._TestSetRaisingContentManager()
700n/a m = self.message(policy=self.policy.clone(content_manager=cm))
701n/a for method in ('add_related', 'add_alternative', 'add_attachment'):
702n/a with self.assertRaises(Exception) as ar:
703n/a getattr(m, method)('')
704n/a self.assertEqual(str(ar.exception), 'test')
705n/a
706n/a def message_as_clear(self, body_parts, attachments, parts, msg):
707n/a m = self._str_msg(msg)
708n/a m.clear()
709n/a self.assertEqual(len(m), 0)
710n/a self.assertEqual(list(m.items()), [])
711n/a self.assertIsNone(m.get_payload())
712n/a self.assertEqual(list(m.iter_parts()), [])
713n/a
714n/a def message_as_clear_content(self, body_parts, attachments, parts, msg):
715n/a m = self._str_msg(msg)
716n/a expected_headers = [h for h in m.keys()
717n/a if not h.lower().startswith('content-')]
718n/a m.clear_content()
719n/a self.assertEqual(list(m.keys()), expected_headers)
720n/a self.assertIsNone(m.get_payload())
721n/a self.assertEqual(list(m.iter_parts()), [])
722n/a
723n/a def test_is_attachment(self):
724n/a m = self._make_message()
725n/a self.assertFalse(m.is_attachment())
726n/a m['Content-Disposition'] = 'inline'
727n/a self.assertFalse(m.is_attachment())
728n/a m.replace_header('Content-Disposition', 'attachment')
729n/a self.assertTrue(m.is_attachment())
730n/a m.replace_header('Content-Disposition', 'AtTachMent')
731n/a self.assertTrue(m.is_attachment())
732n/a m.set_param('filename', 'abc.png', 'Content-Disposition')
733n/a self.assertTrue(m.is_attachment())
734n/a
735n/a def test_iter_attachments_mutation(self):
736n/a # We had a bug where iter_attachments was mutating the list.
737n/a m = self._make_message()
738n/a m.set_content('arbitrary text as main part')
739n/a m.add_related('more text as a related part')
740n/a m.add_related('yet more text as a second "attachment"')
741n/a orig = m.get_payload().copy()
742n/a self.assertEqual(len(list(m.iter_attachments())), 2)
743n/a self.assertEqual(m.get_payload(), orig)
744n/a
745n/a
746n/aclass TestEmailMessage(TestEmailMessageBase, TestEmailBase):
747n/a message = EmailMessage
748n/a
749n/a def test_set_content_adds_MIME_Version(self):
750n/a m = self._str_msg('')
751n/a cm = self._TestContentManager()
752n/a self.assertNotIn('MIME-Version', m)
753n/a m.set_content(content_manager=cm)
754n/a self.assertEqual(m['MIME-Version'], '1.0')
755n/a
756n/a class _MIME_Version_adding_CM:
757n/a def set_content(self, msg, *args, **kw):
758n/a msg['MIME-Version'] = '1.0'
759n/a
760n/a def test_set_content_does_not_duplicate_MIME_Version(self):
761n/a m = self._str_msg('')
762n/a cm = self._MIME_Version_adding_CM()
763n/a self.assertNotIn('MIME-Version', m)
764n/a m.set_content(content_manager=cm)
765n/a self.assertEqual(m['MIME-Version'], '1.0')
766n/a
767n/a def test_as_string_uses_max_header_length_by_default(self):
768n/a m = self._str_msg('Subject: long line' + ' ab'*50 + '\n\n')
769n/a self.assertEqual(len(m.as_string().strip().splitlines()), 3)
770n/a
771n/a def test_as_string_allows_maxheaderlen(self):
772n/a m = self._str_msg('Subject: long line' + ' ab'*50 + '\n\n')
773n/a self.assertEqual(len(m.as_string(maxheaderlen=0).strip().splitlines()),
774n/a 1)
775n/a self.assertEqual(len(m.as_string(maxheaderlen=34).strip().splitlines()),
776n/a 6)
777n/a
778n/a def test_str_defaults_to_policy_max_line_length(self):
779n/a m = self._str_msg('Subject: long line' + ' ab'*50 + '\n\n')
780n/a self.assertEqual(len(str(m).strip().splitlines()), 3)
781n/a
782n/a def test_str_defaults_to_utf8(self):
783n/a m = EmailMessage()
784n/a m['Subject'] = 'unicöde'
785n/a self.assertEqual(str(m), 'Subject: unicöde\n\n')
786n/a
787n/a
788n/aclass TestMIMEPart(TestEmailMessageBase, TestEmailBase):
789n/a # Doing the full test run here may seem a bit redundant, since the two
790n/a # classes are almost identical. But what if they drift apart? So we do
791n/a # the full tests so that any future drift doesn't introduce bugs.
792n/a message = MIMEPart
793n/a
794n/a def test_set_content_does_not_add_MIME_Version(self):
795n/a m = self._str_msg('')
796n/a cm = self._TestContentManager()
797n/a self.assertNotIn('MIME-Version', m)
798n/a m.set_content(content_manager=cm)
799n/a self.assertNotIn('MIME-Version', m)
800n/a
801n/a
802n/aif __name__ == '__main__':
803n/a unittest.main()