blob: 07925a73f82deff2a237ae3f5eba477e2c4ad582 [file] [log] [blame]
R David Murrayc27e5222012-05-25 15:01:48 -04001import io
R David Murray3edd22a2011-04-18 13:59:37 -04002import types
R David Murrayc27e5222012-05-25 15:01:48 -04003import textwrap
R David Murray3edd22a2011-04-18 13:59:37 -04004import unittest
5import email.policy
R David Murrayc27e5222012-05-25 15:01:48 -04006import email.parser
7import email.generator
R David Murray3edd22a2011-04-18 13:59:37 -04008
9class PolicyAPITests(unittest.TestCase):
10
11 longMessage = True
12
13 # These default values are the ones set on email.policy.default.
14 # If any of these defaults change, the docs must be updated.
15 policy_defaults = {
16 'max_line_length': 78,
17 'linesep': '\n',
R David Murrayc27e5222012-05-25 15:01:48 -040018 'cte_type': '8bit',
R David Murray3edd22a2011-04-18 13:59:37 -040019 'raise_on_defect': False,
20 }
21
22 # For each policy under test, we give here the values of the attributes
23 # that are different from the defaults for that policy.
24 policies = {
R David Murrayc27e5222012-05-25 15:01:48 -040025 email.policy.Compat32(): {},
26 email.policy.compat32: {},
R David Murray3edd22a2011-04-18 13:59:37 -040027 email.policy.default: {},
28 email.policy.SMTP: {'linesep': '\r\n'},
29 email.policy.HTTP: {'linesep': '\r\n', 'max_line_length': None},
30 email.policy.strict: {'raise_on_defect': True},
31 }
32
33 def test_defaults(self):
34 for policy, changed_defaults in self.policies.items():
35 expected = self.policy_defaults.copy()
36 expected.update(changed_defaults)
37 for attr, value in expected.items():
38 self.assertEqual(getattr(policy, attr), value,
39 ("change {} docs/docstrings if defaults have "
40 "changed").format(policy))
41
42 def test_all_attributes_covered(self):
43 for attr in dir(email.policy.default):
44 if (attr.startswith('_') or
45 isinstance(getattr(email.policy.Policy, attr),
46 types.FunctionType)):
47 continue
48 else:
49 self.assertIn(attr, self.policy_defaults,
50 "{} is not fully tested".format(attr))
51
R David Murrayc27e5222012-05-25 15:01:48 -040052 def test_abc(self):
53 with self.assertRaises(TypeError) as cm:
54 email.policy.Policy()
55 msg = str(cm.exception)
56 abstract_methods = ('fold',
57 'fold_binary',
58 'header_fetch_parse',
59 'header_source_parse',
60 'header_store_parse')
61 for method in abstract_methods:
62 self.assertIn(method, msg)
63
R David Murray3edd22a2011-04-18 13:59:37 -040064 def test_policy_is_immutable(self):
65 for policy in self.policies:
66 for attr in self.policy_defaults:
67 with self.assertRaisesRegex(AttributeError, attr+".*read-only"):
68 setattr(policy, attr, None)
69 with self.assertRaisesRegex(AttributeError, 'no attribute.*foo'):
70 policy.foo = None
71
72 def test_set_policy_attrs_when_calledl(self):
73 testattrdict = { attr: None for attr in self.policy_defaults }
74 for policyclass in self.policies:
75 policy = policyclass.clone(**testattrdict)
76 for attr in self.policy_defaults:
77 self.assertIsNone(getattr(policy, attr))
78
79 def test_reject_non_policy_keyword_when_called(self):
80 for policyclass in self.policies:
81 with self.assertRaises(TypeError):
82 policyclass(this_keyword_should_not_be_valid=None)
83 with self.assertRaises(TypeError):
84 policyclass(newtline=None)
85
86 def test_policy_addition(self):
87 expected = self.policy_defaults.copy()
88 p1 = email.policy.default.clone(max_line_length=100)
89 p2 = email.policy.default.clone(max_line_length=50)
90 added = p1 + p2
91 expected.update(max_line_length=50)
92 for attr, value in expected.items():
93 self.assertEqual(getattr(added, attr), value)
94 added = p2 + p1
95 expected.update(max_line_length=100)
96 for attr, value in expected.items():
97 self.assertEqual(getattr(added, attr), value)
98 added = added + email.policy.default
99 for attr, value in expected.items():
100 self.assertEqual(getattr(added, attr), value)
101
102 def test_register_defect(self):
103 class Dummy:
104 def __init__(self):
105 self.defects = []
106 obj = Dummy()
107 defect = object()
R David Murrayc27e5222012-05-25 15:01:48 -0400108 policy = email.policy.Compat32()
R David Murray3edd22a2011-04-18 13:59:37 -0400109 policy.register_defect(obj, defect)
110 self.assertEqual(obj.defects, [defect])
111 defect2 = object()
112 policy.register_defect(obj, defect2)
113 self.assertEqual(obj.defects, [defect, defect2])
114
115 class MyObj:
116 def __init__(self):
117 self.defects = []
118
119 class MyDefect(Exception):
120 pass
121
122 def test_handle_defect_raises_on_strict(self):
123 foo = self.MyObj()
124 defect = self.MyDefect("the telly is broken")
125 with self.assertRaisesRegex(self.MyDefect, "the telly is broken"):
126 email.policy.strict.handle_defect(foo, defect)
127
128 def test_handle_defect_registers_defect(self):
129 foo = self.MyObj()
130 defect1 = self.MyDefect("one")
131 email.policy.default.handle_defect(foo, defect1)
132 self.assertEqual(foo.defects, [defect1])
133 defect2 = self.MyDefect("two")
134 email.policy.default.handle_defect(foo, defect2)
135 self.assertEqual(foo.defects, [defect1, defect2])
136
R David Murrayc27e5222012-05-25 15:01:48 -0400137 class MyPolicy(email.policy.Compat32):
R David Murray7104c722012-03-16 21:39:57 -0400138 defects = None
139 def __init__(self, *args, **kw):
140 super().__init__(*args, defects=[], **kw)
R David Murray3edd22a2011-04-18 13:59:37 -0400141 def register_defect(self, obj, defect):
142 self.defects.append(defect)
143
144 def test_overridden_register_defect_still_raises(self):
145 foo = self.MyObj()
146 defect = self.MyDefect("the telly is broken")
147 with self.assertRaisesRegex(self.MyDefect, "the telly is broken"):
148 self.MyPolicy(raise_on_defect=True).handle_defect(foo, defect)
149
150 def test_overriden_register_defect_works(self):
151 foo = self.MyObj()
152 defect1 = self.MyDefect("one")
153 my_policy = self.MyPolicy()
154 my_policy.handle_defect(foo, defect1)
155 self.assertEqual(my_policy.defects, [defect1])
156 self.assertEqual(foo.defects, [])
157 defect2 = self.MyDefect("two")
158 my_policy.handle_defect(foo, defect2)
159 self.assertEqual(my_policy.defects, [defect1, defect2])
160 self.assertEqual(foo.defects, [])
161
162 # XXX: Need subclassing tests.
163 # For adding subclassed objects, make sure the usual rules apply (subclass
164 # wins), but that the order still works (right overrides left).
165
R David Murrayc27e5222012-05-25 15:01:48 -0400166
167class TestPolicyPropagation(unittest.TestCase):
168
169 # The abstract methods are used by the parser but not by the wrapper
170 # functions that call it, so if the exception gets raised we know that the
171 # policy was actually propagated all the way to feedparser.
172 class MyPolicy(email.policy.Policy):
173 def badmethod(self, *args, **kw):
174 raise Exception("test")
175 fold = fold_binary = header_fetch_parser = badmethod
176 header_source_parse = header_store_parse = badmethod
177
178 def test_message_from_string(self):
179 with self.assertRaisesRegex(Exception, "^test$"):
180 email.message_from_string("Subject: test\n\n",
181 policy=self.MyPolicy)
182
183 def test_message_from_bytes(self):
184 with self.assertRaisesRegex(Exception, "^test$"):
185 email.message_from_bytes(b"Subject: test\n\n",
186 policy=self.MyPolicy)
187
188 def test_message_from_file(self):
189 f = io.StringIO('Subject: test\n\n')
190 with self.assertRaisesRegex(Exception, "^test$"):
191 email.message_from_file(f, policy=self.MyPolicy)
192
193 def test_message_from_binary_file(self):
194 f = io.BytesIO(b'Subject: test\n\n')
195 with self.assertRaisesRegex(Exception, "^test$"):
196 email.message_from_binary_file(f, policy=self.MyPolicy)
197
198 # These are redundant, but we need them for black-box completeness.
199
200 def test_parser(self):
201 p = email.parser.Parser(policy=self.MyPolicy)
202 with self.assertRaisesRegex(Exception, "^test$"):
203 p.parsestr('Subject: test\n\n')
204
205 def test_bytes_parser(self):
206 p = email.parser.BytesParser(policy=self.MyPolicy)
207 with self.assertRaisesRegex(Exception, "^test$"):
208 p.parsebytes(b'Subject: test\n\n')
209
210 # Now that we've established that all the parse methods get the
211 # policy in to feedparser, we can use message_from_string for
212 # the rest of the propagation tests.
213
214 def _make_msg(self, source='Subject: test\n\n', policy=None):
215 self.policy = email.policy.default.clone() if policy is None else policy
216 return email.message_from_string(source, policy=self.policy)
217
218 def test_parser_propagates_policy_to_message(self):
219 msg = self._make_msg()
220 self.assertIs(msg.policy, self.policy)
221
222 def test_parser_propagates_policy_to_sub_messages(self):
223 msg = self._make_msg(textwrap.dedent("""\
224 Subject: mime test
225 MIME-Version: 1.0
226 Content-Type: multipart/mixed, boundary="XXX"
227
228 --XXX
229 Content-Type: text/plain
230
231 test
232 --XXX
233 Content-Type: text/plain
234
235 test2
236 --XXX--
237 """))
238 for part in msg.walk():
239 self.assertIs(part.policy, self.policy)
240
241 def test_message_policy_propagates_to_generator(self):
242 msg = self._make_msg("Subject: test\nTo: foo\n\n",
243 policy=email.policy.default.clone(linesep='X'))
244 s = io.StringIO()
245 g = email.generator.Generator(s)
246 g.flatten(msg)
247 self.assertEqual(s.getvalue(), "Subject: testXTo: fooXX")
248
249 def test_message_policy_used_by_as_string(self):
250 msg = self._make_msg("Subject: test\nTo: foo\n\n",
251 policy=email.policy.default.clone(linesep='X'))
252 self.assertEqual(msg.as_string(), "Subject: testXTo: fooXX")
253
254
R David Murray3edd22a2011-04-18 13:59:37 -0400255if __name__ == '__main__':
256 unittest.main()