mirror of
https://github.com/python/cpython.git
synced 2025-08-03 00:23:06 +00:00
#12586: add provisional email policy with new header parsing and folding.
When the new policies are used (and only when the new policies are explicitly used) headers turn into objects that have attributes based on their parsed values, and can be set using objects that encapsulate the values, as well as set directly from unicode strings. The folding algorithm then takes care of encoding unicode where needed, and folding according to the highest level syntactic objects. With this patch only date and time headers are parsed as anything other than unstructured, but that is all the helper methods in the existing API handle. I do plan to add more parsers, and complete the set specified in the RFC before the package becomes stable.
This commit is contained in:
parent
0fa2edd08f
commit
0b6f6c82b5
16 changed files with 6992 additions and 114 deletions
|
@ -65,3 +65,9 @@ class TestEmailBase(unittest.TestCase):
|
|||
def assertBytesEqual(self, first, second, msg):
|
||||
"""Our byte strings are really encoded strings; improve diff output"""
|
||||
self.assertEqual(self._bytes_repr(first), self._bytes_repr(second))
|
||||
|
||||
def assertDefectsEqual(self, actual, expected):
|
||||
self.assertEqual(len(actual), len(expected), actual)
|
||||
for i in range(len(actual)):
|
||||
self.assertIsInstance(actual[i], expected[i],
|
||||
'item {}'.format(i))
|
||||
|
|
187
Lib/test/test_email/test__encoded_words.py
Normal file
187
Lib/test/test_email/test__encoded_words.py
Normal file
|
@ -0,0 +1,187 @@
|
|||
import unittest
|
||||
from email import _encoded_words as _ew
|
||||
from email import errors
|
||||
from test.test_email import TestEmailBase
|
||||
|
||||
|
||||
class TestDecodeQ(TestEmailBase):
|
||||
|
||||
def _test(self, source, ex_result, ex_defects=[]):
|
||||
result, defects = _ew.decode_q(source)
|
||||
self.assertEqual(result, ex_result)
|
||||
self.assertDefectsEqual(defects, ex_defects)
|
||||
|
||||
def test_no_encoded(self):
|
||||
self._test(b'foobar', b'foobar')
|
||||
|
||||
def test_spaces(self):
|
||||
self._test(b'foo=20bar=20', b'foo bar ')
|
||||
self._test(b'foo_bar_', b'foo bar ')
|
||||
|
||||
def test_run_of_encoded(self):
|
||||
self._test(b'foo=20=20=21=2Cbar', b'foo !,bar')
|
||||
|
||||
|
||||
class TestDecodeB(TestEmailBase):
|
||||
|
||||
def _test(self, source, ex_result, ex_defects=[]):
|
||||
result, defects = _ew.decode_b(source)
|
||||
self.assertEqual(result, ex_result)
|
||||
self.assertDefectsEqual(defects, ex_defects)
|
||||
|
||||
def test_simple(self):
|
||||
self._test(b'Zm9v', b'foo')
|
||||
|
||||
def test_missing_padding(self):
|
||||
self._test(b'dmk', b'vi', [errors.InvalidBase64PaddingDefect])
|
||||
|
||||
def test_invalid_character(self):
|
||||
self._test(b'dm\x01k===', b'vi', [errors.InvalidBase64CharactersDefect])
|
||||
|
||||
def test_invalid_character_and_bad_padding(self):
|
||||
self._test(b'dm\x01k', b'vi', [errors.InvalidBase64CharactersDefect,
|
||||
errors.InvalidBase64PaddingDefect])
|
||||
|
||||
|
||||
class TestDecode(TestEmailBase):
|
||||
|
||||
def test_wrong_format_input_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
_ew.decode('=?badone?=')
|
||||
with self.assertRaises(ValueError):
|
||||
_ew.decode('=?')
|
||||
with self.assertRaises(ValueError):
|
||||
_ew.decode('')
|
||||
|
||||
def _test(self, source, result, charset='us-ascii', lang='', defects=[]):
|
||||
res, char, l, d = _ew.decode(source)
|
||||
self.assertEqual(res, result)
|
||||
self.assertEqual(char, charset)
|
||||
self.assertEqual(l, lang)
|
||||
self.assertDefectsEqual(d, defects)
|
||||
|
||||
def test_simple_q(self):
|
||||
self._test('=?us-ascii?q?foo?=', 'foo')
|
||||
|
||||
def test_simple_b(self):
|
||||
self._test('=?us-ascii?b?dmk=?=', 'vi')
|
||||
|
||||
def test_q_case_ignored(self):
|
||||
self._test('=?us-ascii?Q?foo?=', 'foo')
|
||||
|
||||
def test_b_case_ignored(self):
|
||||
self._test('=?us-ascii?B?dmk=?=', 'vi')
|
||||
|
||||
def test_non_trivial_q(self):
|
||||
self._test('=?latin-1?q?=20F=fcr=20Elise=20?=', ' Für Elise ', 'latin-1')
|
||||
|
||||
def test_q_escpaed_bytes_preserved(self):
|
||||
self._test(b'=?us-ascii?q?=20\xACfoo?='.decode('us-ascii',
|
||||
'surrogateescape'),
|
||||
' \uDCACfoo',
|
||||
defects = [errors.UndecodableBytesDefect])
|
||||
|
||||
def test_b_undecodable_bytes_ignored_with_defect(self):
|
||||
self._test(b'=?us-ascii?b?dm\xACk?='.decode('us-ascii',
|
||||
'surrogateescape'),
|
||||
'vi',
|
||||
defects = [
|
||||
errors.InvalidBase64CharactersDefect,
|
||||
errors.InvalidBase64PaddingDefect])
|
||||
|
||||
def test_b_invalid_bytes_ignored_with_defect(self):
|
||||
self._test('=?us-ascii?b?dm\x01k===?=',
|
||||
'vi',
|
||||
defects = [errors.InvalidBase64CharactersDefect])
|
||||
|
||||
def test_b_invalid_bytes_incorrect_padding(self):
|
||||
self._test('=?us-ascii?b?dm\x01k?=',
|
||||
'vi',
|
||||
defects = [
|
||||
errors.InvalidBase64CharactersDefect,
|
||||
errors.InvalidBase64PaddingDefect])
|
||||
|
||||
def test_b_padding_defect(self):
|
||||
self._test('=?us-ascii?b?dmk?=',
|
||||
'vi',
|
||||
defects = [errors.InvalidBase64PaddingDefect])
|
||||
|
||||
def test_nonnull_lang(self):
|
||||
self._test('=?us-ascii*jive?q?test?=', 'test', lang='jive')
|
||||
|
||||
def test_unknown_8bit_charset(self):
|
||||
self._test('=?unknown-8bit?q?foo=ACbar?=',
|
||||
b'foo\xacbar'.decode('ascii', 'surrogateescape'),
|
||||
charset = 'unknown-8bit',
|
||||
defects = [])
|
||||
|
||||
def test_unknown_charset(self):
|
||||
self._test('=?foobar?q?foo=ACbar?=',
|
||||
b'foo\xacbar'.decode('ascii', 'surrogateescape'),
|
||||
charset = 'foobar',
|
||||
# XXX Should this be a new Defect instead?
|
||||
defects = [errors.CharsetError])
|
||||
|
||||
|
||||
class TestEncodeQ(TestEmailBase):
|
||||
|
||||
def _test(self, src, expected):
|
||||
self.assertEqual(_ew.encode_q(src), expected)
|
||||
|
||||
def test_all_safe(self):
|
||||
self._test(b'foobar', 'foobar')
|
||||
|
||||
def test_spaces(self):
|
||||
self._test(b'foo bar ', 'foo_bar_')
|
||||
|
||||
def test_run_of_encodables(self):
|
||||
self._test(b'foo ,,bar', 'foo__=2C=2Cbar')
|
||||
|
||||
|
||||
class TestEncodeB(TestEmailBase):
|
||||
|
||||
def test_simple(self):
|
||||
self.assertEqual(_ew.encode_b(b'foo'), 'Zm9v')
|
||||
|
||||
def test_padding(self):
|
||||
self.assertEqual(_ew.encode_b(b'vi'), 'dmk=')
|
||||
|
||||
|
||||
class TestEncode(TestEmailBase):
|
||||
|
||||
def test_q(self):
|
||||
self.assertEqual(_ew.encode('foo', 'utf-8', 'q'), '=?utf-8?q?foo?=')
|
||||
|
||||
def test_b(self):
|
||||
self.assertEqual(_ew.encode('foo', 'utf-8', 'b'), '=?utf-8?b?Zm9v?=')
|
||||
|
||||
def test_auto_q(self):
|
||||
self.assertEqual(_ew.encode('foo', 'utf-8'), '=?utf-8?q?foo?=')
|
||||
|
||||
def test_auto_q_if_short_mostly_safe(self):
|
||||
self.assertEqual(_ew.encode('vi.', 'utf-8'), '=?utf-8?q?vi=2E?=')
|
||||
|
||||
def test_auto_b_if_enough_unsafe(self):
|
||||
self.assertEqual(_ew.encode('.....', 'utf-8'), '=?utf-8?b?Li4uLi4=?=')
|
||||
|
||||
def test_auto_b_if_long_unsafe(self):
|
||||
self.assertEqual(_ew.encode('vi.vi.vi.vi.vi.', 'utf-8'),
|
||||
'=?utf-8?b?dmkudmkudmkudmkudmku?=')
|
||||
|
||||
def test_auto_q_if_long_mostly_safe(self):
|
||||
self.assertEqual(_ew.encode('vi vi vi.vi ', 'utf-8'),
|
||||
'=?utf-8?q?vi_vi_vi=2Evi_?=')
|
||||
|
||||
def test_utf8_default(self):
|
||||
self.assertEqual(_ew.encode('foo'), '=?utf-8?q?foo?=')
|
||||
|
||||
def test_lang(self):
|
||||
self.assertEqual(_ew.encode('foo', lang='jive'), '=?utf-8*jive?q?foo?=')
|
||||
|
||||
def test_unknown_8bit(self):
|
||||
self.assertEqual(_ew.encode('foo\uDCACbar', charset='unknown-8bit'),
|
||||
'=?unknown-8bit?q?foo=ACbar?=')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
2466
Lib/test/test_email/test__header_value_parser.py
Normal file
2466
Lib/test/test_email/test__header_value_parser.py
Normal file
File diff suppressed because it is too large
Load diff
717
Lib/test/test_email/test__headerregistry.py
Normal file
717
Lib/test/test_email/test__headerregistry.py
Normal file
|
@ -0,0 +1,717 @@
|
|||
import datetime
|
||||
import textwrap
|
||||
import unittest
|
||||
from email import errors
|
||||
from email import policy
|
||||
from test.test_email import TestEmailBase
|
||||
from email import _headerregistry
|
||||
# Address and Group are public but I'm not sure where to put them yet.
|
||||
from email._headerregistry import Address, Group
|
||||
|
||||
|
||||
class TestHeaderRegistry(TestEmailBase):
|
||||
|
||||
def test_arbitrary_name_unstructured(self):
|
||||
factory = _headerregistry.HeaderRegistry()
|
||||
h = factory('foobar', 'test')
|
||||
self.assertIsInstance(h, _headerregistry.BaseHeader)
|
||||
self.assertIsInstance(h, _headerregistry.UnstructuredHeader)
|
||||
|
||||
def test_name_case_ignored(self):
|
||||
factory = _headerregistry.HeaderRegistry()
|
||||
# Whitebox check that test is valid
|
||||
self.assertNotIn('Subject', factory.registry)
|
||||
h = factory('Subject', 'test')
|
||||
self.assertIsInstance(h, _headerregistry.BaseHeader)
|
||||
self.assertIsInstance(h, _headerregistry.UniqueUnstructuredHeader)
|
||||
|
||||
class FooBase:
|
||||
def __init__(self, *args, **kw):
|
||||
pass
|
||||
|
||||
def test_override_default_base_class(self):
|
||||
factory = _headerregistry.HeaderRegistry(base_class=self.FooBase)
|
||||
h = factory('foobar', 'test')
|
||||
self.assertIsInstance(h, self.FooBase)
|
||||
self.assertIsInstance(h, _headerregistry.UnstructuredHeader)
|
||||
|
||||
class FooDefault:
|
||||
parse = _headerregistry.UnstructuredHeader.parse
|
||||
|
||||
def test_override_default_class(self):
|
||||
factory = _headerregistry.HeaderRegistry(default_class=self.FooDefault)
|
||||
h = factory('foobar', 'test')
|
||||
self.assertIsInstance(h, _headerregistry.BaseHeader)
|
||||
self.assertIsInstance(h, self.FooDefault)
|
||||
|
||||
def test_override_default_class_only_overrides_default(self):
|
||||
factory = _headerregistry.HeaderRegistry(default_class=self.FooDefault)
|
||||
h = factory('subject', 'test')
|
||||
self.assertIsInstance(h, _headerregistry.BaseHeader)
|
||||
self.assertIsInstance(h, _headerregistry.UniqueUnstructuredHeader)
|
||||
|
||||
def test_dont_use_default_map(self):
|
||||
factory = _headerregistry.HeaderRegistry(use_default_map=False)
|
||||
h = factory('subject', 'test')
|
||||
self.assertIsInstance(h, _headerregistry.BaseHeader)
|
||||
self.assertIsInstance(h, _headerregistry.UnstructuredHeader)
|
||||
|
||||
def test_map_to_type(self):
|
||||
factory = _headerregistry.HeaderRegistry()
|
||||
h1 = factory('foobar', 'test')
|
||||
factory.map_to_type('foobar', _headerregistry.UniqueUnstructuredHeader)
|
||||
h2 = factory('foobar', 'test')
|
||||
self.assertIsInstance(h1, _headerregistry.BaseHeader)
|
||||
self.assertIsInstance(h1, _headerregistry.UnstructuredHeader)
|
||||
self.assertIsInstance(h2, _headerregistry.BaseHeader)
|
||||
self.assertIsInstance(h2, _headerregistry.UniqueUnstructuredHeader)
|
||||
|
||||
|
||||
class TestHeaderBase(TestEmailBase):
|
||||
|
||||
factory = _headerregistry.HeaderRegistry()
|
||||
|
||||
def make_header(self, name, value):
|
||||
return self.factory(name, value)
|
||||
|
||||
|
||||
class TestBaseHeaderFeatures(TestHeaderBase):
|
||||
|
||||
def test_str(self):
|
||||
h = self.make_header('subject', 'this is a test')
|
||||
self.assertIsInstance(h, str)
|
||||
self.assertEqual(h, 'this is a test')
|
||||
self.assertEqual(str(h), 'this is a test')
|
||||
|
||||
def test_substr(self):
|
||||
h = self.make_header('subject', 'this is a test')
|
||||
self.assertEqual(h[5:7], 'is')
|
||||
|
||||
def test_has_name(self):
|
||||
h = self.make_header('subject', 'this is a test')
|
||||
self.assertEqual(h.name, 'subject')
|
||||
|
||||
def _test_attr_ro(self, attr):
|
||||
h = self.make_header('subject', 'this is a test')
|
||||
with self.assertRaises(AttributeError):
|
||||
setattr(h, attr, 'foo')
|
||||
|
||||
def test_name_read_only(self):
|
||||
self._test_attr_ro('name')
|
||||
|
||||
def test_defects_read_only(self):
|
||||
self._test_attr_ro('defects')
|
||||
|
||||
def test_defects_is_tuple(self):
|
||||
h = self.make_header('subject', 'this is a test')
|
||||
self.assertEqual(len(h.defects), 0)
|
||||
self.assertIsInstance(h.defects, tuple)
|
||||
# Make sure it is still true when there are defects.
|
||||
h = self.make_header('date', '')
|
||||
self.assertEqual(len(h.defects), 1)
|
||||
self.assertIsInstance(h.defects, tuple)
|
||||
|
||||
# XXX: FIXME
|
||||
#def test_CR_in_value(self):
|
||||
# # XXX: this also re-raises the issue of embedded headers,
|
||||
# # need test and solution for that.
|
||||
# value = '\r'.join(['this is', ' a test'])
|
||||
# h = self.make_header('subject', value)
|
||||
# self.assertEqual(h, value)
|
||||
# self.assertDefectsEqual(h.defects, [errors.ObsoleteHeaderDefect])
|
||||
|
||||
def test_RFC2047_value_decoded(self):
|
||||
value = '=?utf-8?q?this_is_a_test?='
|
||||
h = self.make_header('subject', value)
|
||||
self.assertEqual(h, 'this is a test')
|
||||
|
||||
|
||||
class TestDateHeader(TestHeaderBase):
|
||||
|
||||
datestring = 'Sun, 23 Sep 2001 20:10:55 -0700'
|
||||
utcoffset = datetime.timedelta(hours=-7)
|
||||
tz = datetime.timezone(utcoffset)
|
||||
dt = datetime.datetime(2001, 9, 23, 20, 10, 55, tzinfo=tz)
|
||||
|
||||
def test_parse_date(self):
|
||||
h = self.make_header('date', self.datestring)
|
||||
self.assertEqual(h, self.datestring)
|
||||
self.assertEqual(h.datetime, self.dt)
|
||||
self.assertEqual(h.datetime.utcoffset(), self.utcoffset)
|
||||
self.assertEqual(h.defects, ())
|
||||
|
||||
def test_set_from_datetime(self):
|
||||
h = self.make_header('date', self.dt)
|
||||
self.assertEqual(h, self.datestring)
|
||||
self.assertEqual(h.datetime, self.dt)
|
||||
self.assertEqual(h.defects, ())
|
||||
|
||||
def test_date_header_properties(self):
|
||||
h = self.make_header('date', self.datestring)
|
||||
self.assertIsInstance(h, _headerregistry.UniqueDateHeader)
|
||||
self.assertEqual(h.max_count, 1)
|
||||
self.assertEqual(h.defects, ())
|
||||
|
||||
def test_resent_date_header_properties(self):
|
||||
h = self.make_header('resent-date', self.datestring)
|
||||
self.assertIsInstance(h, _headerregistry.DateHeader)
|
||||
self.assertEqual(h.max_count, None)
|
||||
self.assertEqual(h.defects, ())
|
||||
|
||||
def test_no_value_is_defect(self):
|
||||
h = self.make_header('date', '')
|
||||
self.assertEqual(len(h.defects), 1)
|
||||
self.assertIsInstance(h.defects[0], errors.HeaderMissingRequiredValue)
|
||||
|
||||
def test_datetime_read_only(self):
|
||||
h = self.make_header('date', self.datestring)
|
||||
with self.assertRaises(AttributeError):
|
||||
h.datetime = 'foo'
|
||||
|
||||
|
||||
class TestAddressHeader(TestHeaderBase):
|
||||
|
||||
examples = {
|
||||
|
||||
'empty':
|
||||
('<>',
|
||||
[errors.InvalidHeaderDefect],
|
||||
'<>',
|
||||
'',
|
||||
'<>',
|
||||
'',
|
||||
'',
|
||||
None),
|
||||
|
||||
'address_only':
|
||||
('zippy@pinhead.com',
|
||||
[],
|
||||
'zippy@pinhead.com',
|
||||
'',
|
||||
'zippy@pinhead.com',
|
||||
'zippy',
|
||||
'pinhead.com',
|
||||
None),
|
||||
|
||||
'name_and_address':
|
||||
('Zaphrod Beblebrux <zippy@pinhead.com>',
|
||||
[],
|
||||
'Zaphrod Beblebrux <zippy@pinhead.com>',
|
||||
'Zaphrod Beblebrux',
|
||||
'zippy@pinhead.com',
|
||||
'zippy',
|
||||
'pinhead.com',
|
||||
None),
|
||||
|
||||
'quoted_local_part':
|
||||
('Zaphrod Beblebrux <"foo bar"@pinhead.com>',
|
||||
[],
|
||||
'Zaphrod Beblebrux <"foo bar"@pinhead.com>',
|
||||
'Zaphrod Beblebrux',
|
||||
'"foo bar"@pinhead.com',
|
||||
'foo bar',
|
||||
'pinhead.com',
|
||||
None),
|
||||
|
||||
'quoted_parens_in_name':
|
||||
(r'"A \(Special\) Person" <person@dom.ain>',
|
||||
[],
|
||||
'"A (Special) Person" <person@dom.ain>',
|
||||
'A (Special) Person',
|
||||
'person@dom.ain',
|
||||
'person',
|
||||
'dom.ain',
|
||||
None),
|
||||
|
||||
'quoted_backslashes_in_name':
|
||||
(r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>',
|
||||
[],
|
||||
r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>',
|
||||
r'Arthur \Backslash\ Foobar',
|
||||
'person@dom.ain',
|
||||
'person',
|
||||
'dom.ain',
|
||||
None),
|
||||
|
||||
'name_with_dot':
|
||||
('John X. Doe <jxd@example.com>',
|
||||
[errors.ObsoleteHeaderDefect],
|
||||
'"John X. Doe" <jxd@example.com>',
|
||||
'John X. Doe',
|
||||
'jxd@example.com',
|
||||
'jxd',
|
||||
'example.com',
|
||||
None),
|
||||
|
||||
'quoted_strings_in_local_part':
|
||||
('""example" example"@example.com',
|
||||
[errors.InvalidHeaderDefect]*3,
|
||||
'"example example"@example.com',
|
||||
'',
|
||||
'"example example"@example.com',
|
||||
'example example',
|
||||
'example.com',
|
||||
None),
|
||||
|
||||
'escaped_quoted_strings_in_local_part':
|
||||
(r'"\"example\" example"@example.com',
|
||||
[],
|
||||
r'"\"example\" example"@example.com',
|
||||
'',
|
||||
r'"\"example\" example"@example.com',
|
||||
r'"example" example',
|
||||
'example.com',
|
||||
None),
|
||||
|
||||
'escaped_escapes_in_local_part':
|
||||
(r'"\\"example\\" example"@example.com',
|
||||
[errors.InvalidHeaderDefect]*5,
|
||||
r'"\\example\\\\ example"@example.com',
|
||||
'',
|
||||
r'"\\example\\\\ example"@example.com',
|
||||
r'\example\\ example',
|
||||
'example.com',
|
||||
None),
|
||||
|
||||
'spaces_in_unquoted_local_part_collapsed':
|
||||
('merwok wok @example.com',
|
||||
[errors.InvalidHeaderDefect]*2,
|
||||
'"merwok wok"@example.com',
|
||||
'',
|
||||
'"merwok wok"@example.com',
|
||||
'merwok wok',
|
||||
'example.com',
|
||||
None),
|
||||
|
||||
'spaces_around_dots_in_local_part_removed':
|
||||
('merwok. wok . wok@example.com',
|
||||
[errors.ObsoleteHeaderDefect],
|
||||
'merwok.wok.wok@example.com',
|
||||
'',
|
||||
'merwok.wok.wok@example.com',
|
||||
'merwok.wok.wok',
|
||||
'example.com',
|
||||
None),
|
||||
|
||||
}
|
||||
|
||||
# XXX: Need many more examples, and in particular some with names in
|
||||
# trailing comments, which aren't currently handled. comments in
|
||||
# general are not handled yet.
|
||||
|
||||
def _test_single_addr(self, source, defects, decoded, display_name,
|
||||
addr_spec, username, domain, comment):
|
||||
h = self.make_header('sender', source)
|
||||
self.assertEqual(h, decoded)
|
||||
self.assertDefectsEqual(h.defects, defects)
|
||||
a = h.address
|
||||
self.assertEqual(str(a), decoded)
|
||||
self.assertEqual(len(h.groups), 1)
|
||||
self.assertEqual([a], list(h.groups[0].addresses))
|
||||
self.assertEqual([a], list(h.addresses))
|
||||
self.assertEqual(a.display_name, display_name)
|
||||
self.assertEqual(a.addr_spec, addr_spec)
|
||||
self.assertEqual(a.username, username)
|
||||
self.assertEqual(a.domain, domain)
|
||||
# XXX: we have no comment support yet.
|
||||
#self.assertEqual(a.comment, comment)
|
||||
|
||||
for name in examples:
|
||||
locals()['test_'+name] = (
|
||||
lambda self, name=name:
|
||||
self._test_single_addr(*self.examples[name]))
|
||||
|
||||
def _test_group_single_addr(self, source, defects, decoded, display_name,
|
||||
addr_spec, username, domain, comment):
|
||||
source = 'foo: {};'.format(source)
|
||||
gdecoded = 'foo: {};'.format(decoded) if decoded else 'foo:;'
|
||||
h = self.make_header('to', source)
|
||||
self.assertEqual(h, gdecoded)
|
||||
self.assertDefectsEqual(h.defects, defects)
|
||||
self.assertEqual(h.groups[0].addresses, h.addresses)
|
||||
self.assertEqual(len(h.groups), 1)
|
||||
self.assertEqual(len(h.addresses), 1)
|
||||
a = h.addresses[0]
|
||||
self.assertEqual(str(a), decoded)
|
||||
self.assertEqual(a.display_name, display_name)
|
||||
self.assertEqual(a.addr_spec, addr_spec)
|
||||
self.assertEqual(a.username, username)
|
||||
self.assertEqual(a.domain, domain)
|
||||
|
||||
for name in examples:
|
||||
locals()['test_group_'+name] = (
|
||||
lambda self, name=name:
|
||||
self._test_group_single_addr(*self.examples[name]))
|
||||
|
||||
def test_simple_address_list(self):
|
||||
value = ('Fred <dinsdale@python.org>, foo@example.com, '
|
||||
'"Harry W. Hastings" <hasty@example.com>')
|
||||
h = self.make_header('to', value)
|
||||
self.assertEqual(h, value)
|
||||
self.assertEqual(len(h.groups), 3)
|
||||
self.assertEqual(len(h.addresses), 3)
|
||||
for i in range(3):
|
||||
self.assertEqual(h.groups[i].addresses[0], h.addresses[i])
|
||||
self.assertEqual(str(h.addresses[0]), 'Fred <dinsdale@python.org>')
|
||||
self.assertEqual(str(h.addresses[1]), 'foo@example.com')
|
||||
self.assertEqual(str(h.addresses[2]),
|
||||
'"Harry W. Hastings" <hasty@example.com>')
|
||||
self.assertEqual(h.addresses[2].display_name,
|
||||
'Harry W. Hastings')
|
||||
|
||||
def test_complex_address_list(self):
|
||||
examples = list(self.examples.values())
|
||||
source = ('dummy list:;, another: (empty);,' +
|
||||
', '.join([x[0] for x in examples[:4]]) + ', ' +
|
||||
r'"A \"list\"": ' +
|
||||
', '.join([x[0] for x in examples[4:6]]) + ';,' +
|
||||
', '.join([x[0] for x in examples[6:]])
|
||||
)
|
||||
# XXX: the fact that (empty) disappears here is a potential API design
|
||||
# bug. We don't currently have a way to preserve comments.
|
||||
expected = ('dummy list:;, another:;, ' +
|
||||
', '.join([x[2] for x in examples[:4]]) + ', ' +
|
||||
r'"A \"list\"": ' +
|
||||
', '.join([x[2] for x in examples[4:6]]) + ';, ' +
|
||||
', '.join([x[2] for x in examples[6:]])
|
||||
)
|
||||
|
||||
h = self.make_header('to', source)
|
||||
self.assertEqual(h.split(','), expected.split(','))
|
||||
self.assertEqual(h, expected)
|
||||
self.assertEqual(len(h.groups), 7 + len(examples) - 6)
|
||||
self.assertEqual(h.groups[0].display_name, 'dummy list')
|
||||
self.assertEqual(h.groups[1].display_name, 'another')
|
||||
self.assertEqual(h.groups[6].display_name, 'A "list"')
|
||||
self.assertEqual(len(h.addresses), len(examples))
|
||||
for i in range(4):
|
||||
self.assertIsNone(h.groups[i+2].display_name)
|
||||
self.assertEqual(str(h.groups[i+2].addresses[0]), examples[i][2])
|
||||
for i in range(7, 7 + len(examples) - 6):
|
||||
self.assertIsNone(h.groups[i].display_name)
|
||||
self.assertEqual(str(h.groups[i].addresses[0]), examples[i-1][2])
|
||||
for i in range(len(examples)):
|
||||
self.assertEqual(str(h.addresses[i]), examples[i][2])
|
||||
self.assertEqual(h.addresses[i].addr_spec, examples[i][4])
|
||||
|
||||
def test_address_read_only(self):
|
||||
h = self.make_header('sender', 'abc@xyz.com')
|
||||
with self.assertRaises(AttributeError):
|
||||
h.address = 'foo'
|
||||
|
||||
def test_addresses_read_only(self):
|
||||
h = self.make_header('sender', 'abc@xyz.com')
|
||||
with self.assertRaises(AttributeError):
|
||||
h.addresses = 'foo'
|
||||
|
||||
def test_groups_read_only(self):
|
||||
h = self.make_header('sender', 'abc@xyz.com')
|
||||
with self.assertRaises(AttributeError):
|
||||
h.groups = 'foo'
|
||||
|
||||
def test_addresses_types(self):
|
||||
source = 'me <who@example.com>'
|
||||
h = self.make_header('to', source)
|
||||
self.assertIsInstance(h.addresses, tuple)
|
||||
self.assertIsInstance(h.addresses[0], Address)
|
||||
|
||||
def test_groups_types(self):
|
||||
source = 'me <who@example.com>'
|
||||
h = self.make_header('to', source)
|
||||
self.assertIsInstance(h.groups, tuple)
|
||||
self.assertIsInstance(h.groups[0], Group)
|
||||
|
||||
def test_set_from_Address(self):
|
||||
h = self.make_header('to', Address('me', 'foo', 'example.com'))
|
||||
self.assertEqual(h, 'me <foo@example.com>')
|
||||
|
||||
def test_set_from_Address_list(self):
|
||||
h = self.make_header('to', [Address('me', 'foo', 'example.com'),
|
||||
Address('you', 'bar', 'example.com')])
|
||||
self.assertEqual(h, 'me <foo@example.com>, you <bar@example.com>')
|
||||
|
||||
def test_set_from_Address_and_Group_list(self):
|
||||
h = self.make_header('to', [Address('me', 'foo', 'example.com'),
|
||||
Group('bing', [Address('fiz', 'z', 'b.com'),
|
||||
Address('zif', 'f', 'c.com')]),
|
||||
Address('you', 'bar', 'example.com')])
|
||||
self.assertEqual(h, 'me <foo@example.com>, bing: fiz <z@b.com>, '
|
||||
'zif <f@c.com>;, you <bar@example.com>')
|
||||
self.assertEqual(h.fold(policy=policy.default.clone(max_line_length=40)),
|
||||
'to: me <foo@example.com>,\n'
|
||||
' bing: fiz <z@b.com>, zif <f@c.com>;,\n'
|
||||
' you <bar@example.com>\n')
|
||||
|
||||
def test_set_from_Group_list(self):
|
||||
h = self.make_header('to', [Group('bing', [Address('fiz', 'z', 'b.com'),
|
||||
Address('zif', 'f', 'c.com')])])
|
||||
self.assertEqual(h, 'bing: fiz <z@b.com>, zif <f@c.com>;')
|
||||
|
||||
|
||||
class TestAddressAndGroup(TestEmailBase):
|
||||
|
||||
def _test_attr_ro(self, obj, attr):
|
||||
with self.assertRaises(AttributeError):
|
||||
setattr(obj, attr, 'foo')
|
||||
|
||||
def test_address_display_name_ro(self):
|
||||
self._test_attr_ro(Address('foo', 'bar', 'baz'), 'display_name')
|
||||
|
||||
def test_address_username_ro(self):
|
||||
self._test_attr_ro(Address('foo', 'bar', 'baz'), 'username')
|
||||
|
||||
def test_address_domain_ro(self):
|
||||
self._test_attr_ro(Address('foo', 'bar', 'baz'), 'domain')
|
||||
|
||||
def test_group_display_name_ro(self):
|
||||
self._test_attr_ro(Group('foo'), 'display_name')
|
||||
|
||||
def test_group_addresses_ro(self):
|
||||
self._test_attr_ro(Group('foo'), 'addresses')
|
||||
|
||||
def test_address_from_username_domain(self):
|
||||
a = Address('foo', 'bar', 'baz')
|
||||
self.assertEqual(a.display_name, 'foo')
|
||||
self.assertEqual(a.username, 'bar')
|
||||
self.assertEqual(a.domain, 'baz')
|
||||
self.assertEqual(a.addr_spec, 'bar@baz')
|
||||
self.assertEqual(str(a), 'foo <bar@baz>')
|
||||
|
||||
def test_address_from_addr_spec(self):
|
||||
a = Address('foo', addr_spec='bar@baz')
|
||||
self.assertEqual(a.display_name, 'foo')
|
||||
self.assertEqual(a.username, 'bar')
|
||||
self.assertEqual(a.domain, 'baz')
|
||||
self.assertEqual(a.addr_spec, 'bar@baz')
|
||||
self.assertEqual(str(a), 'foo <bar@baz>')
|
||||
|
||||
def test_address_with_no_display_name(self):
|
||||
a = Address(addr_spec='bar@baz')
|
||||
self.assertEqual(a.display_name, '')
|
||||
self.assertEqual(a.username, 'bar')
|
||||
self.assertEqual(a.domain, 'baz')
|
||||
self.assertEqual(a.addr_spec, 'bar@baz')
|
||||
self.assertEqual(str(a), 'bar@baz')
|
||||
|
||||
def test_null_address(self):
|
||||
a = Address()
|
||||
self.assertEqual(a.display_name, '')
|
||||
self.assertEqual(a.username, '')
|
||||
self.assertEqual(a.domain, '')
|
||||
self.assertEqual(a.addr_spec, '<>')
|
||||
self.assertEqual(str(a), '<>')
|
||||
|
||||
def test_domain_only(self):
|
||||
# This isn't really a valid address.
|
||||
a = Address(domain='buzz')
|
||||
self.assertEqual(a.display_name, '')
|
||||
self.assertEqual(a.username, '')
|
||||
self.assertEqual(a.domain, 'buzz')
|
||||
self.assertEqual(a.addr_spec, '@buzz')
|
||||
self.assertEqual(str(a), '@buzz')
|
||||
|
||||
def test_username_only(self):
|
||||
# This isn't really a valid address.
|
||||
a = Address(username='buzz')
|
||||
self.assertEqual(a.display_name, '')
|
||||
self.assertEqual(a.username, 'buzz')
|
||||
self.assertEqual(a.domain, '')
|
||||
self.assertEqual(a.addr_spec, 'buzz')
|
||||
self.assertEqual(str(a), 'buzz')
|
||||
|
||||
def test_display_name_only(self):
|
||||
a = Address('buzz')
|
||||
self.assertEqual(a.display_name, 'buzz')
|
||||
self.assertEqual(a.username, '')
|
||||
self.assertEqual(a.domain, '')
|
||||
self.assertEqual(a.addr_spec, '<>')
|
||||
self.assertEqual(str(a), 'buzz <>')
|
||||
|
||||
def test_quoting(self):
|
||||
# Ideally we'd check every special individually, but I'm not up for
|
||||
# writing that many tests.
|
||||
a = Address('Sara J.', 'bad name', 'example.com')
|
||||
self.assertEqual(a.display_name, 'Sara J.')
|
||||
self.assertEqual(a.username, 'bad name')
|
||||
self.assertEqual(a.domain, 'example.com')
|
||||
self.assertEqual(a.addr_spec, '"bad name"@example.com')
|
||||
self.assertEqual(str(a), '"Sara J." <"bad name"@example.com>')
|
||||
|
||||
def test_il8n(self):
|
||||
a = Address('Éric', 'wok', 'exàmple.com')
|
||||
self.assertEqual(a.display_name, 'Éric')
|
||||
self.assertEqual(a.username, 'wok')
|
||||
self.assertEqual(a.domain, 'exàmple.com')
|
||||
self.assertEqual(a.addr_spec, 'wok@exàmple.com')
|
||||
self.assertEqual(str(a), 'Éric <wok@exàmple.com>')
|
||||
|
||||
# XXX: there is an API design issue that needs to be solved here.
|
||||
#def test_non_ascii_username_raises(self):
|
||||
# with self.assertRaises(ValueError):
|
||||
# Address('foo', 'wők', 'example.com')
|
||||
|
||||
def test_non_ascii_username_in_addr_spec_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
Address('foo', addr_spec='wők@example.com')
|
||||
|
||||
def test_address_addr_spec_and_username_raises(self):
|
||||
with self.assertRaises(TypeError):
|
||||
Address('foo', username='bing', addr_spec='bar@baz')
|
||||
|
||||
def test_address_addr_spec_and_domain_raises(self):
|
||||
with self.assertRaises(TypeError):
|
||||
Address('foo', domain='bing', addr_spec='bar@baz')
|
||||
|
||||
def test_address_addr_spec_and_username_and_domain_raises(self):
|
||||
with self.assertRaises(TypeError):
|
||||
Address('foo', username='bong', domain='bing', addr_spec='bar@baz')
|
||||
|
||||
def test_space_in_addr_spec_username_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
Address('foo', addr_spec="bad name@example.com")
|
||||
|
||||
def test_bad_addr_sepc_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
Address('foo', addr_spec="name@ex[]ample.com")
|
||||
|
||||
def test_empty_group(self):
|
||||
g = Group('foo')
|
||||
self.assertEqual(g.display_name, 'foo')
|
||||
self.assertEqual(g.addresses, tuple())
|
||||
self.assertEqual(str(g), 'foo:;')
|
||||
|
||||
def test_empty_group_list(self):
|
||||
g = Group('foo', addresses=[])
|
||||
self.assertEqual(g.display_name, 'foo')
|
||||
self.assertEqual(g.addresses, tuple())
|
||||
self.assertEqual(str(g), 'foo:;')
|
||||
|
||||
def test_null_group(self):
|
||||
g = Group()
|
||||
self.assertIsNone(g.display_name)
|
||||
self.assertEqual(g.addresses, tuple())
|
||||
self.assertEqual(str(g), 'None:;')
|
||||
|
||||
def test_group_with_addresses(self):
|
||||
addrs = [Address('b', 'b', 'c'), Address('a', 'b','c')]
|
||||
g = Group('foo', addrs)
|
||||
self.assertEqual(g.display_name, 'foo')
|
||||
self.assertEqual(g.addresses, tuple(addrs))
|
||||
self.assertEqual(str(g), 'foo: b <b@c>, a <b@c>;')
|
||||
|
||||
def test_group_with_addresses_no_display_name(self):
|
||||
addrs = [Address('b', 'b', 'c'), Address('a', 'b','c')]
|
||||
g = Group(addresses=addrs)
|
||||
self.assertIsNone(g.display_name)
|
||||
self.assertEqual(g.addresses, tuple(addrs))
|
||||
self.assertEqual(str(g), 'None: b <b@c>, a <b@c>;')
|
||||
|
||||
def test_group_with_one_address_no_display_name(self):
|
||||
addrs = [Address('b', 'b', 'c')]
|
||||
g = Group(addresses=addrs)
|
||||
self.assertIsNone(g.display_name)
|
||||
self.assertEqual(g.addresses, tuple(addrs))
|
||||
self.assertEqual(str(g), 'b <b@c>')
|
||||
|
||||
def test_display_name_quoting(self):
|
||||
g = Group('foo.bar')
|
||||
self.assertEqual(g.display_name, 'foo.bar')
|
||||
self.assertEqual(g.addresses, tuple())
|
||||
self.assertEqual(str(g), '"foo.bar":;')
|
||||
|
||||
def test_display_name_blanks_not_quoted(self):
|
||||
g = Group('foo bar')
|
||||
self.assertEqual(g.display_name, 'foo bar')
|
||||
self.assertEqual(g.addresses, tuple())
|
||||
self.assertEqual(str(g), 'foo bar:;')
|
||||
|
||||
|
||||
class TestFolding(TestHeaderBase):
|
||||
|
||||
def test_short_unstructured(self):
|
||||
h = self.make_header('subject', 'this is a test')
|
||||
self.assertEqual(h.fold(policy=self.policy),
|
||||
'subject: this is a test\n')
|
||||
|
||||
def test_long_unstructured(self):
|
||||
h = self.make_header('Subject', 'This is a long header '
|
||||
'line that will need to be folded into two lines '
|
||||
'and will demonstrate basic folding')
|
||||
self.assertEqual(h.fold(policy=self.policy),
|
||||
'Subject: This is a long header line that will '
|
||||
'need to be folded into two lines\n'
|
||||
' and will demonstrate basic folding\n')
|
||||
|
||||
def test_unstructured_short_max_line_length(self):
|
||||
h = self.make_header('Subject', 'this is a short header '
|
||||
'that will be folded anyway')
|
||||
self.assertEqual(
|
||||
h.fold(policy=policy.default.clone(max_line_length=20)),
|
||||
textwrap.dedent("""\
|
||||
Subject: this is a
|
||||
short header that
|
||||
will be folded
|
||||
anyway
|
||||
"""))
|
||||
|
||||
def test_fold_unstructured_single_word(self):
|
||||
h = self.make_header('Subject', 'test')
|
||||
self.assertEqual(h.fold(policy=self.policy), 'Subject: test\n')
|
||||
|
||||
def test_fold_unstructured_short(self):
|
||||
h = self.make_header('Subject', 'test test test')
|
||||
self.assertEqual(h.fold(policy=self.policy),
|
||||
'Subject: test test test\n')
|
||||
|
||||
def test_fold_unstructured_with_overlong_word(self):
|
||||
h = self.make_header('Subject', 'thisisaverylonglineconsistingofa'
|
||||
'singlewordthatwontfit')
|
||||
self.assertEqual(
|
||||
h.fold(policy=policy.default.clone(max_line_length=20)),
|
||||
'Subject: thisisaverylonglineconsistingofasinglewordthatwontfit\n')
|
||||
|
||||
def test_fold_unstructured_with_two_overlong_words(self):
|
||||
h = self.make_header('Subject', 'thisisaverylonglineconsistingofa'
|
||||
'singlewordthatwontfit plusanotherverylongwordthatwontfit')
|
||||
self.assertEqual(
|
||||
h.fold(policy=policy.default.clone(max_line_length=20)),
|
||||
'Subject: thisisaverylonglineconsistingofasinglewordthatwontfit\n'
|
||||
' plusanotherverylongwordthatwontfit\n')
|
||||
|
||||
def test_fold_unstructured_with_slightly_long_word(self):
|
||||
h = self.make_header('Subject', 'thislongwordislessthanmaxlinelen')
|
||||
self.assertEqual(
|
||||
h.fold(policy=policy.default.clone(max_line_length=35)),
|
||||
'Subject:\n thislongwordislessthanmaxlinelen\n')
|
||||
|
||||
def test_fold_unstructured_with_commas(self):
|
||||
# The old wrapper would fold this at the commas.
|
||||
h = self.make_header('Subject', "This header is intended to "
|
||||
"demonstrate, in a fairly susinct way, that we now do "
|
||||
"not give a , special treatment in unstructured headers.")
|
||||
self.assertEqual(
|
||||
h.fold(policy=policy.default.clone(max_line_length=60)),
|
||||
textwrap.dedent("""\
|
||||
Subject: This header is intended to demonstrate, in a fairly
|
||||
susinct way, that we now do not give a , special treatment
|
||||
in unstructured headers.
|
||||
"""))
|
||||
|
||||
def test_fold_address_list(self):
|
||||
h = self.make_header('To', '"Theodore H. Perfect" <yes@man.com>, '
|
||||
'"My address is very long because my name is long" <foo@bar.com>, '
|
||||
'"Only A. Friend" <no@yes.com>')
|
||||
self.assertEqual(h.fold(policy=self.policy), textwrap.dedent("""\
|
||||
To: "Theodore H. Perfect" <yes@man.com>,
|
||||
"My address is very long because my name is long" <foo@bar.com>,
|
||||
"Only A. Friend" <no@yes.com>
|
||||
"""))
|
||||
|
||||
def test_fold_date_header(self):
|
||||
h = self.make_header('Date', 'Sat, 2 Feb 2002 17:00:06 -0800')
|
||||
self.assertEqual(h.fold(policy=self.policy),
|
||||
'Date: Sat, 02 Feb 2002 17:00:06 -0800\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -6,14 +6,16 @@ from email.generator import Generator, BytesGenerator
|
|||
from email import policy
|
||||
from test.test_email import TestEmailBase
|
||||
|
||||
# XXX: move generator tests from test_email into here at some point.
|
||||
|
||||
class TestGeneratorBase:
|
||||
|
||||
class TestGeneratorBase():
|
||||
policy = policy.default
|
||||
|
||||
policy = policy.compat32
|
||||
def msgmaker(self, msg, policy=None):
|
||||
policy = self.policy if policy is None else policy
|
||||
return self.msgfunc(msg, policy=policy)
|
||||
|
||||
long_subject = {
|
||||
refold_long_expected = {
|
||||
0: textwrap.dedent("""\
|
||||
To: whom_it_may_concern@example.com
|
||||
From: nobody_you_want_to_know@example.com
|
||||
|
@ -23,33 +25,32 @@ class TestGeneratorBase():
|
|||
|
||||
None
|
||||
"""),
|
||||
# From is wrapped because wrapped it fits in 40.
|
||||
40: textwrap.dedent("""\
|
||||
To: whom_it_may_concern@example.com
|
||||
From:\x20
|
||||
From:
|
||||
nobody_you_want_to_know@example.com
|
||||
Subject: We the willing led by the
|
||||
unknowing are doing the
|
||||
impossible for the ungrateful. We have
|
||||
done so much for so long with so little
|
||||
we are now qualified to do anything
|
||||
with nothing.
|
||||
unknowing are doing the impossible for
|
||||
the ungrateful. We have done so much
|
||||
for so long with so little we are now
|
||||
qualified to do anything with nothing.
|
||||
|
||||
None
|
||||
"""),
|
||||
# Neither to nor from fit even if put on a new line,
|
||||
# so we leave them sticking out on the first line.
|
||||
20: textwrap.dedent("""\
|
||||
To:\x20
|
||||
whom_it_may_concern@example.com
|
||||
From:\x20
|
||||
nobody_you_want_to_know@example.com
|
||||
To: whom_it_may_concern@example.com
|
||||
From: nobody_you_want_to_know@example.com
|
||||
Subject: We the
|
||||
willing led by the
|
||||
unknowing are doing
|
||||
the
|
||||
impossible for the
|
||||
ungrateful. We have
|
||||
done so much for so
|
||||
long with so little
|
||||
we are now
|
||||
the impossible for
|
||||
the ungrateful. We
|
||||
have done so much
|
||||
for so long with so
|
||||
little we are now
|
||||
qualified to do
|
||||
anything with
|
||||
nothing.
|
||||
|
@ -57,65 +58,90 @@ class TestGeneratorBase():
|
|||
None
|
||||
"""),
|
||||
}
|
||||
long_subject[100] = long_subject[0]
|
||||
refold_long_expected[100] = refold_long_expected[0]
|
||||
|
||||
def maxheaderlen_parameter_test(self, n):
|
||||
msg = self.msgmaker(self.typ(self.long_subject[0]))
|
||||
refold_all_expected = refold_long_expected.copy()
|
||||
refold_all_expected[0] = (
|
||||
"To: whom_it_may_concern@example.com\n"
|
||||
"From: nobody_you_want_to_know@example.com\n"
|
||||
"Subject: We the willing led by the unknowing are doing the "
|
||||
"impossible for the ungrateful. We have done so much for "
|
||||
"so long with so little we are now qualified to do anything "
|
||||
"with nothing.\n"
|
||||
"\n"
|
||||
"None\n")
|
||||
refold_all_expected[100] = (
|
||||
"To: whom_it_may_concern@example.com\n"
|
||||
"From: nobody_you_want_to_know@example.com\n"
|
||||
"Subject: We the willing led by the unknowing are doing the "
|
||||
"impossible for the ungrateful. We have\n"
|
||||
" done so much for so long with so little we are now qualified "
|
||||
"to do anything with nothing.\n"
|
||||
"\n"
|
||||
"None\n")
|
||||
|
||||
def _test_maxheaderlen_parameter(self, n):
|
||||
msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
|
||||
s = self.ioclass()
|
||||
g = self.genclass(s, maxheaderlen=n)
|
||||
g = self.genclass(s, maxheaderlen=n, policy=self.policy)
|
||||
g.flatten(msg)
|
||||
self.assertEqual(s.getvalue(), self.typ(self.long_subject[n]))
|
||||
self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[n]))
|
||||
|
||||
def test_maxheaderlen_parameter_0(self):
|
||||
self.maxheaderlen_parameter_test(0)
|
||||
for n in refold_long_expected:
|
||||
locals()['test_maxheaderlen_parameter_' + str(n)] = (
|
||||
lambda self, n=n:
|
||||
self._test_maxheaderlen_parameter(n))
|
||||
|
||||
def test_maxheaderlen_parameter_100(self):
|
||||
self.maxheaderlen_parameter_test(100)
|
||||
|
||||
def test_maxheaderlen_parameter_40(self):
|
||||
self.maxheaderlen_parameter_test(40)
|
||||
|
||||
def test_maxheaderlen_parameter_20(self):
|
||||
self.maxheaderlen_parameter_test(20)
|
||||
|
||||
def maxheaderlen_policy_test(self, n):
|
||||
msg = self.msgmaker(self.typ(self.long_subject[0]))
|
||||
def _test_max_line_length_policy(self, n):
|
||||
msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
|
||||
s = self.ioclass()
|
||||
g = self.genclass(s, policy=policy.default.clone(max_line_length=n))
|
||||
g = self.genclass(s, policy=self.policy.clone(max_line_length=n))
|
||||
g.flatten(msg)
|
||||
self.assertEqual(s.getvalue(), self.typ(self.long_subject[n]))
|
||||
self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[n]))
|
||||
|
||||
def test_maxheaderlen_policy_0(self):
|
||||
self.maxheaderlen_policy_test(0)
|
||||
for n in refold_long_expected:
|
||||
locals()['test_max_line_length_policy' + str(n)] = (
|
||||
lambda self, n=n:
|
||||
self._test_max_line_length_policy(n))
|
||||
|
||||
def test_maxheaderlen_policy_100(self):
|
||||
self.maxheaderlen_policy_test(100)
|
||||
|
||||
def test_maxheaderlen_policy_40(self):
|
||||
self.maxheaderlen_policy_test(40)
|
||||
|
||||
def test_maxheaderlen_policy_20(self):
|
||||
self.maxheaderlen_policy_test(20)
|
||||
|
||||
def maxheaderlen_parm_overrides_policy_test(self, n):
|
||||
msg = self.msgmaker(self.typ(self.long_subject[0]))
|
||||
def _test_maxheaderlen_parm_overrides_policy(self, n):
|
||||
msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
|
||||
s = self.ioclass()
|
||||
g = self.genclass(s, maxheaderlen=n,
|
||||
policy=policy.default.clone(max_line_length=10))
|
||||
policy=self.policy.clone(max_line_length=10))
|
||||
g.flatten(msg)
|
||||
self.assertEqual(s.getvalue(), self.typ(self.long_subject[n]))
|
||||
self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[n]))
|
||||
|
||||
def test_maxheaderlen_parm_overrides_policy_0(self):
|
||||
self.maxheaderlen_parm_overrides_policy_test(0)
|
||||
for n in refold_long_expected:
|
||||
locals()['test_maxheaderlen_parm_overrides_policy' + str(n)] = (
|
||||
lambda self, n=n:
|
||||
self._test_maxheaderlen_parm_overrides_policy(n))
|
||||
|
||||
def test_maxheaderlen_parm_overrides_policy_100(self):
|
||||
self.maxheaderlen_parm_overrides_policy_test(100)
|
||||
def _test_refold_none_does_not_fold(self, n):
|
||||
msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
|
||||
s = self.ioclass()
|
||||
g = self.genclass(s, policy=self.policy.clone(refold_source='none',
|
||||
max_line_length=n))
|
||||
g.flatten(msg)
|
||||
self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[0]))
|
||||
|
||||
def test_maxheaderlen_parm_overrides_policy_40(self):
|
||||
self.maxheaderlen_parm_overrides_policy_test(40)
|
||||
for n in refold_long_expected:
|
||||
locals()['test_refold_none_does_not_fold' + str(n)] = (
|
||||
lambda self, n=n:
|
||||
self._test_refold_none_does_not_fold(n))
|
||||
|
||||
def test_maxheaderlen_parm_overrides_policy_20(self):
|
||||
self.maxheaderlen_parm_overrides_policy_test(20)
|
||||
def _test_refold_all(self, n):
|
||||
msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
|
||||
s = self.ioclass()
|
||||
g = self.genclass(s, policy=self.policy.clone(refold_source='all',
|
||||
max_line_length=n))
|
||||
g.flatten(msg)
|
||||
self.assertEqual(s.getvalue(), self.typ(self.refold_all_expected[n]))
|
||||
|
||||
for n in refold_long_expected:
|
||||
locals()['test_refold_all' + str(n)] = (
|
||||
lambda self, n=n:
|
||||
self._test_refold_all(n))
|
||||
|
||||
def test_crlf_control_via_policy(self):
|
||||
source = "Subject: test\r\n\r\ntest body\r\n"
|
||||
|
@ -138,30 +164,24 @@ class TestGeneratorBase():
|
|||
|
||||
class TestGenerator(TestGeneratorBase, TestEmailBase):
|
||||
|
||||
msgfunc = staticmethod(message_from_string)
|
||||
genclass = Generator
|
||||
ioclass = io.StringIO
|
||||
typ = str
|
||||
|
||||
def msgmaker(self, msg, policy=None):
|
||||
policy = self.policy if policy is None else policy
|
||||
return message_from_string(msg, policy=policy)
|
||||
|
||||
|
||||
class TestBytesGenerator(TestGeneratorBase, TestEmailBase):
|
||||
|
||||
msgfunc = staticmethod(message_from_bytes)
|
||||
genclass = BytesGenerator
|
||||
ioclass = io.BytesIO
|
||||
typ = lambda self, x: x.encode('ascii')
|
||||
|
||||
def msgmaker(self, msg, policy=None):
|
||||
policy = self.policy if policy is None else policy
|
||||
return message_from_bytes(msg, policy=policy)
|
||||
|
||||
def test_cte_type_7bit_handles_unknown_8bit(self):
|
||||
source = ("Subject: Maintenant je vous présente mon "
|
||||
"collègue\n\n").encode('utf-8')
|
||||
expected = ('Subject: =?unknown-8bit?q?Maintenant_je_vous_pr=C3=A9sente_mon_'
|
||||
'coll=C3=A8gue?=\n\n').encode('ascii')
|
||||
expected = ('Subject: Maintenant je vous =?unknown-8bit?q?'
|
||||
'pr=C3=A9sente_mon_coll=C3=A8gue?=\n\n').encode('ascii')
|
||||
msg = message_from_bytes(source)
|
||||
s = io.BytesIO()
|
||||
g = BytesGenerator(s, policy=self.policy.clone(cte_type='7bit'))
|
||||
|
|
57
Lib/test/test_email/test_pickleable.py
Normal file
57
Lib/test/test_email/test_pickleable.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
import unittest
|
||||
import textwrap
|
||||
import copy
|
||||
import pickle
|
||||
from email import policy
|
||||
from email import message_from_string
|
||||
from email._headerregistry import HeaderRegistry
|
||||
from test.test_email import TestEmailBase
|
||||
|
||||
class TestPickleCopyHeader(TestEmailBase):
|
||||
|
||||
unstructured = HeaderRegistry()('subject', 'this is a test')
|
||||
|
||||
def test_deepcopy_unstructured(self):
|
||||
h = copy.deepcopy(self.unstructured)
|
||||
self.assertEqual(str(h), str(self.unstructured))
|
||||
|
||||
def test_pickle_unstructured(self):
|
||||
p = pickle.dumps(self.unstructured)
|
||||
h = pickle.loads(p)
|
||||
self.assertEqual(str(h), str(self.unstructured))
|
||||
|
||||
address = HeaderRegistry()('from', 'frodo@mordor.net')
|
||||
|
||||
def test_deepcopy_address(self):
|
||||
h = copy.deepcopy(self.address)
|
||||
self.assertEqual(str(h), str(self.address))
|
||||
|
||||
def test_pickle_address(self):
|
||||
p = pickle.dumps(self.address)
|
||||
h = pickle.loads(p)
|
||||
self.assertEqual(str(h), str(self.address))
|
||||
|
||||
|
||||
class TestPickleCopyMessage(TestEmailBase):
|
||||
|
||||
testmsg = message_from_string(textwrap.dedent("""\
|
||||
From: frodo@mordor.net
|
||||
To: bilbo@underhill.org
|
||||
Subject: help
|
||||
|
||||
I think I forgot the ring.
|
||||
"""), policy=policy.default)
|
||||
|
||||
def test_deepcopy(self):
|
||||
msg2 = copy.deepcopy(self.testmsg)
|
||||
self.assertEqual(msg2.as_string(), self.testmsg.as_string())
|
||||
|
||||
def test_pickle(self):
|
||||
p = pickle.dumps(self.testmsg)
|
||||
msg2 = pickle.loads(p)
|
||||
self.assertEqual(msg2.as_string(), self.testmsg.as_string())
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -5,49 +5,70 @@ import unittest
|
|||
import email.policy
|
||||
import email.parser
|
||||
import email.generator
|
||||
from email import _headerregistry
|
||||
|
||||
def make_defaults(base_defaults, differences):
|
||||
defaults = base_defaults.copy()
|
||||
defaults.update(differences)
|
||||
return defaults
|
||||
|
||||
class PolicyAPITests(unittest.TestCase):
|
||||
|
||||
longMessage = True
|
||||
|
||||
# These default values are the ones set on email.policy.default.
|
||||
# If any of these defaults change, the docs must be updated.
|
||||
policy_defaults = {
|
||||
# Base default values.
|
||||
compat32_defaults = {
|
||||
'max_line_length': 78,
|
||||
'linesep': '\n',
|
||||
'cte_type': '8bit',
|
||||
'raise_on_defect': False,
|
||||
}
|
||||
# These default values are the ones set on email.policy.default.
|
||||
# If any of these defaults change, the docs must be updated.
|
||||
policy_defaults = compat32_defaults.copy()
|
||||
policy_defaults.update({
|
||||
'raise_on_defect': False,
|
||||
'header_factory': email.policy.EmailPolicy.header_factory,
|
||||
'refold_source': 'long',
|
||||
})
|
||||
|
||||
# For each policy under test, we give here the values of the attributes
|
||||
# that are different from the defaults for that policy.
|
||||
# For each policy under test, we give here what we expect the defaults to
|
||||
# be for that policy. The second argument to make defaults is the
|
||||
# difference between the base defaults and that for the particular policy.
|
||||
new_policy = email.policy.EmailPolicy()
|
||||
policies = {
|
||||
email.policy.Compat32(): {},
|
||||
email.policy.compat32: {},
|
||||
email.policy.default: {},
|
||||
email.policy.SMTP: {'linesep': '\r\n'},
|
||||
email.policy.HTTP: {'linesep': '\r\n', 'max_line_length': None},
|
||||
email.policy.strict: {'raise_on_defect': True},
|
||||
email.policy.compat32: make_defaults(compat32_defaults, {}),
|
||||
email.policy.default: make_defaults(policy_defaults, {}),
|
||||
email.policy.SMTP: make_defaults(policy_defaults,
|
||||
{'linesep': '\r\n'}),
|
||||
email.policy.HTTP: make_defaults(policy_defaults,
|
||||
{'linesep': '\r\n',
|
||||
'max_line_length': None}),
|
||||
email.policy.strict: make_defaults(policy_defaults,
|
||||
{'raise_on_defect': True}),
|
||||
new_policy: make_defaults(policy_defaults, {}),
|
||||
}
|
||||
# Creating a new policy creates a new header factory. There is a test
|
||||
# later that proves this.
|
||||
policies[new_policy]['header_factory'] = new_policy.header_factory
|
||||
|
||||
def test_defaults(self):
|
||||
for policy, changed_defaults in self.policies.items():
|
||||
expected = self.policy_defaults.copy()
|
||||
expected.update(changed_defaults)
|
||||
for policy, expected in self.policies.items():
|
||||
for attr, value in expected.items():
|
||||
self.assertEqual(getattr(policy, attr), value,
|
||||
("change {} docs/docstrings if defaults have "
|
||||
"changed").format(policy))
|
||||
|
||||
def test_all_attributes_covered(self):
|
||||
for attr in dir(email.policy.default):
|
||||
if (attr.startswith('_') or
|
||||
isinstance(getattr(email.policy.Policy, attr),
|
||||
types.FunctionType)):
|
||||
continue
|
||||
else:
|
||||
self.assertIn(attr, self.policy_defaults,
|
||||
"{} is not fully tested".format(attr))
|
||||
for policy, expected in self.policies.items():
|
||||
for attr in dir(policy):
|
||||
if (attr.startswith('_') or
|
||||
isinstance(getattr(email.policy.EmailPolicy, attr),
|
||||
types.FunctionType)):
|
||||
continue
|
||||
else:
|
||||
self.assertIn(attr, expected,
|
||||
"{} is not fully tested".format(attr))
|
||||
|
||||
def test_abc(self):
|
||||
with self.assertRaises(TypeError) as cm:
|
||||
|
@ -62,18 +83,20 @@ class PolicyAPITests(unittest.TestCase):
|
|||
self.assertIn(method, msg)
|
||||
|
||||
def test_policy_is_immutable(self):
|
||||
for policy in self.policies:
|
||||
for attr in self.policy_defaults:
|
||||
for policy, defaults in self.policies.items():
|
||||
for attr in defaults:
|
||||
with self.assertRaisesRegex(AttributeError, attr+".*read-only"):
|
||||
setattr(policy, attr, None)
|
||||
with self.assertRaisesRegex(AttributeError, 'no attribute.*foo'):
|
||||
policy.foo = None
|
||||
|
||||
def test_set_policy_attrs_when_calledl(self):
|
||||
testattrdict = { attr: None for attr in self.policy_defaults }
|
||||
for policyclass in self.policies:
|
||||
def test_set_policy_attrs_when_cloned(self):
|
||||
# None of the attributes has a default value of None, so we set them
|
||||
# all to None in the clone call and check that it worked.
|
||||
for policyclass, defaults in self.policies.items():
|
||||
testattrdict = {attr: None for attr in defaults}
|
||||
policy = policyclass.clone(**testattrdict)
|
||||
for attr in self.policy_defaults:
|
||||
for attr in defaults:
|
||||
self.assertIsNone(getattr(policy, attr))
|
||||
|
||||
def test_reject_non_policy_keyword_when_called(self):
|
||||
|
@ -105,7 +128,7 @@ class PolicyAPITests(unittest.TestCase):
|
|||
self.defects = []
|
||||
obj = Dummy()
|
||||
defect = object()
|
||||
policy = email.policy.Compat32()
|
||||
policy = email.policy.EmailPolicy()
|
||||
policy.register_defect(obj, defect)
|
||||
self.assertEqual(obj.defects, [defect])
|
||||
defect2 = object()
|
||||
|
@ -134,7 +157,7 @@ class PolicyAPITests(unittest.TestCase):
|
|||
email.policy.default.handle_defect(foo, defect2)
|
||||
self.assertEqual(foo.defects, [defect1, defect2])
|
||||
|
||||
class MyPolicy(email.policy.Compat32):
|
||||
class MyPolicy(email.policy.EmailPolicy):
|
||||
defects = None
|
||||
def __init__(self, *args, **kw):
|
||||
super().__init__(*args, defects=[], **kw)
|
||||
|
@ -159,6 +182,49 @@ class PolicyAPITests(unittest.TestCase):
|
|||
self.assertEqual(my_policy.defects, [defect1, defect2])
|
||||
self.assertEqual(foo.defects, [])
|
||||
|
||||
def test_default_header_factory(self):
|
||||
h = email.policy.default.header_factory('Test', 'test')
|
||||
self.assertEqual(h.name, 'Test')
|
||||
self.assertIsInstance(h, _headerregistry.UnstructuredHeader)
|
||||
self.assertIsInstance(h, _headerregistry.BaseHeader)
|
||||
|
||||
class Foo:
|
||||
parse = _headerregistry.UnstructuredHeader.parse
|
||||
|
||||
def test_each_Policy_gets_unique_factory(self):
|
||||
policy1 = email.policy.EmailPolicy()
|
||||
policy2 = email.policy.EmailPolicy()
|
||||
policy1.header_factory.map_to_type('foo', self.Foo)
|
||||
h = policy1.header_factory('foo', 'test')
|
||||
self.assertIsInstance(h, self.Foo)
|
||||
self.assertNotIsInstance(h, _headerregistry.UnstructuredHeader)
|
||||
h = policy2.header_factory('foo', 'test')
|
||||
self.assertNotIsInstance(h, self.Foo)
|
||||
self.assertIsInstance(h, _headerregistry.UnstructuredHeader)
|
||||
|
||||
def test_clone_copies_factory(self):
|
||||
policy1 = email.policy.EmailPolicy()
|
||||
policy2 = policy1.clone()
|
||||
policy1.header_factory.map_to_type('foo', self.Foo)
|
||||
h = policy1.header_factory('foo', 'test')
|
||||
self.assertIsInstance(h, self.Foo)
|
||||
h = policy2.header_factory('foo', 'test')
|
||||
self.assertIsInstance(h, self.Foo)
|
||||
|
||||
def test_new_factory_overrides_default(self):
|
||||
mypolicy = email.policy.EmailPolicy()
|
||||
myfactory = mypolicy.header_factory
|
||||
newpolicy = mypolicy + email.policy.strict
|
||||
self.assertEqual(newpolicy.header_factory, myfactory)
|
||||
newpolicy = email.policy.strict + mypolicy
|
||||
self.assertEqual(newpolicy.header_factory, myfactory)
|
||||
|
||||
def test_adding_default_policies_preserves_default_factory(self):
|
||||
newpolicy = email.policy.default + email.policy.strict
|
||||
self.assertEqual(newpolicy.header_factory,
|
||||
email.policy.EmailPolicy.header_factory)
|
||||
self.assertEqual(newpolicy.__dict__, {'raise_on_defect': True})
|
||||
|
||||
# XXX: Need subclassing tests.
|
||||
# For adding subclassed objects, make sure the usual rules apply (subclass
|
||||
# wins), but that the order still works (right overrides left).
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue