How to use content method in Playwright Python

Best Python code snippet using playwright-python

Run Playwright Python automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

test_contentmanager.py

Source: test_contentmanager.py Github

copy
1import unittest
2from test.test_email import TestEmailBase, parameterize
3import textwrap
4from email import policy
5from email.message import EmailMessage
6from email.contentmanager import ContentManager, raw_data_manager
7
8
9@parameterize
10class TestContentManager(TestEmailBase):
11
12    policy = policy.default
13    message = EmailMessage
14
15    get_key_params = {
16        'full_type':        (1, 'text/plain',),
17        'maintype_only':    (2, 'text',),
18        'null_key':         (3, '',),
19        }
20
21    def get_key_as_get_content_key(self, order, key):
22        def foo_getter(msg, foo=None):
23            bar = msg['X-Bar-Header']
24            return foo, bar
25        cm = ContentManager()
26        cm.add_get_handler(key, foo_getter)
27        m = self._make_message()
28        m['Content-Type'] = 'text/plain'
29        m['X-Bar-Header'] = 'foo'
30        self.assertEqual(cm.get_content(m, foo='bar'), ('bar', 'foo'))
31
32    def get_key_as_get_content_key_order(self, order, key):
33        def bar_getter(msg):
34            return msg['X-Bar-Header']
35        def foo_getter(msg):
36            return msg['X-Foo-Header']
37        cm = ContentManager()
38        cm.add_get_handler(key, foo_getter)
39        for precedence, key in self.get_key_params.values():
40            if precedence > order:
41                cm.add_get_handler(key, bar_getter)
42        m = self._make_message()
43        m['Content-Type'] = 'text/plain'
44        m['X-Bar-Header'] = 'bar'
45        m['X-Foo-Header'] = 'foo'
46        self.assertEqual(cm.get_content(m), ('foo'))
47
48    def test_get_content_raises_if_unknown_mimetype_and_no_default(self):
49        cm = ContentManager()
50        m = self._make_message()
51        m['Content-Type'] = 'text/plain'
52        with self.assertRaisesRegex(KeyError, 'text/plain'):
53            cm.get_content(m)
54
55    class BaseThing(str):
56        pass
57    baseobject_full_path = __name__ + '.' + 'TestContentManager.BaseThing'
58    class Thing(BaseThing):
59        pass
60    testobject_full_path = __name__ + '.' + 'TestContentManager.Thing'
61
62    set_key_params = {
63        'type':             (0,  Thing,),
64        'full_path':        (1,  testobject_full_path,),
65        'qualname':         (2,  'TestContentManager.Thing',),
66        'name':             (3,  'Thing',),
67        'base_type':        (4,  BaseThing,),
68        'base_full_path':   (5,  baseobject_full_path,),
69        'base_qualname':    (6,  'TestContentManager.BaseThing',),
70        'base_name':        (7,  'BaseThing',),
71        'str_type':         (8,  str,),
72        'str_full_path':    (9,  'builtins.str',),
73        'str_name':         (10, 'str',),   # str name and qualname are the same
74        'null_key':         (11, None,),
75        }
76
77    def set_key_as_set_content_key(self, order, key):
78        def foo_setter(msg, obj, foo=None):
79            msg['X-Foo-Header'] = foo
80            msg.set_payload(obj)
81        cm = ContentManager()
82        cm.add_set_handler(key, foo_setter)
83        m = self._make_message()
84        msg_obj = self.Thing()
85        cm.set_content(m, msg_obj, foo='bar')
86        self.assertEqual(m['X-Foo-Header'], 'bar')
87        self.assertEqual(m.get_payload(), msg_obj)
88
89    def set_key_as_set_content_key_order(self, order, key):
90        def foo_setter(msg, obj):
91            msg['X-FooBar-Header'] = 'foo'
92            msg.set_payload(obj)
93        def bar_setter(msg, obj):
94            msg['X-FooBar-Header'] = 'bar'
95        cm = ContentManager()
96        cm.add_set_handler(key, foo_setter)
97        for precedence, key in self.get_key_params.values():
98            if precedence > order:
99                cm.add_set_handler(key, bar_setter)
100        m = self._make_message()
101        msg_obj = self.Thing()
102        cm.set_content(m, msg_obj)
103        self.assertEqual(m['X-FooBar-Header'], 'foo')
104        self.assertEqual(m.get_payload(), msg_obj)
105
106    def test_set_content_raises_if_unknown_type_and_no_default(self):
107        cm = ContentManager()
108        m = self._make_message()
109        msg_obj = self.Thing()
110        with self.assertRaisesRegex(KeyError, self.testobject_full_path):
111            cm.set_content(m, msg_obj)
112
113    def test_set_content_raises_if_called_on_multipart(self):
114        cm = ContentManager()
115        m = self._make_message()
116        m['Content-Type'] = 'multipart/foo'
117        with self.assertRaises(TypeError):
118            cm.set_content(m, 'test')
119
120    def test_set_content_calls_clear_content(self):
121        m = self._make_message()
122        m['Content-Foo'] = 'bar'
123        m['Content-Type'] = 'text/html'
124        m['To'] = 'test'
125        m.set_payload('abc')
126        cm = ContentManager()
127        cm.add_set_handler(str, lambda *args, **kw: None)
128        m.set_content('xyz', content_manager=cm)
129        self.assertIsNone(m['Content-Foo'])
130        self.assertIsNone(m['Content-Type'])
131        self.assertEqual(m['To'], 'test')
132        self.assertIsNone(m.get_payload())
133
134
135@parameterize
136class TestRawDataManager(TestEmailBase):
137    # Note: these tests are dependent on the order in which headers are added
138    # to the message objects by the code.  There's no defined ordering in
139    # RFC5322/MIME, so this makes the tests more fragile than the standards
140    # require.  However, if the header order changes it is best to understand
141    # *why*, and make sure it isn't a subtle bug in whatever change was
142    # applied.
143
144    policy = policy.default.clone(max_line_length=60,
145                                  content_manager=raw_data_manager)
146    message = EmailMessage
147
148    def test_get_text_plain(self):
149        m = self._str_msg(textwrap.dedent("""\
150            Content-Type: text/plain
151
152            Basic text.
153            """))
154        self.assertEqual(raw_data_manager.get_content(m), "Basic text.\n")
155
156    def test_get_text_html(self):
157        m = self._str_msg(textwrap.dedent("""\
158            Content-Type: text/html
159
160            <p>Basic text.</p>
161            """))
162        self.assertEqual(raw_data_manager.get_content(m),
163                         "<p>Basic text.</p>\n")
164
165    def test_get_text_plain_latin1(self):
166        m = self._bytes_msg(textwrap.dedent("""\
167            Content-Type: text/plain; charset=latin1
168
169            Basìc tëxt.
170            """).encode('latin1'))
171        self.assertEqual(raw_data_manager.get_content(m), "Basìc tëxt.\n")
172
173    def test_get_text_plain_latin1_quoted_printable(self):
174        m = self._str_msg(textwrap.dedent("""\
175            Content-Type: text/plain; charset="latin-1"
176            Content-Transfer-Encoding: quoted-printable
177
178            Bas=ECc t=EBxt.
179            """))
180        self.assertEqual(raw_data_manager.get_content(m), "Basìc tëxt.\n")
181
182    def test_get_text_plain_utf8_base64(self):
183        m = self._str_msg(textwrap.dedent("""\
184            Content-Type: text/plain; charset="utf8"
185            Content-Transfer-Encoding: base64
186
187            QmFzw6xjIHTDq3h0Lgo=
188            """))
189        self.assertEqual(raw_data_manager.get_content(m), "Basìc tëxt.\n")
190
191    def test_get_text_plain_bad_utf8_quoted_printable(self):
192        m = self._str_msg(textwrap.dedent("""\
193            Content-Type: text/plain; charset="utf8"
194            Content-Transfer-Encoding: quoted-printable
195
196            Bas=c3=acc t=c3=abxt=fd.
197            """))
198        self.assertEqual(raw_data_manager.get_content(m), "Basìc tëxt�.\n")
199
200    def test_get_text_plain_bad_utf8_quoted_printable_ignore_errors(self):
201        m = self._str_msg(textwrap.dedent("""\
202            Content-Type: text/plain; charset="utf8"
203            Content-Transfer-Encoding: quoted-printable
204
205            Bas=c3=acc t=c3=abxt=fd.
206            """))
207        self.assertEqual(raw_data_manager.get_content(m, errors='ignore'),
208                         "Basìc tëxt.\n")
209
210    def test_get_text_plain_utf8_base64_recoverable_bad_CTE_data(self):
211        m = self._str_msg(textwrap.dedent("""\
212            Content-Type: text/plain; charset="utf8"
213            Content-Transfer-Encoding: base64
214
215            QmFzw6xjIHTDq3h0Lgo\xFF=
216            """))
217        self.assertEqual(raw_data_manager.get_content(m, errors='ignore'),
218                         "Basìc tëxt.\n")
219
220    def test_get_text_invalid_keyword(self):
221        m = self._str_msg(textwrap.dedent("""\
222            Content-Type: text/plain
223
224            Basic text.
225            """))
226        with self.assertRaises(TypeError):
227            raw_data_manager.get_content(m, foo='ignore')
228
229    def test_get_non_text(self):
230        template = textwrap.dedent("""\
231            Content-Type: {}
232            Content-Transfer-Encoding: base64
233
234            Ym9ndXMgZGF0YQ==
235            """)
236        for maintype in 'audio image video application'.split():
237            with self.subTest(maintype=maintype):
238                m = self._str_msg(template.format(maintype+'/foo'))
239                self.assertEqual(raw_data_manager.get_content(m), b"bogus data")
240
241    def test_get_non_text_invalid_keyword(self):
242        m = self._str_msg(textwrap.dedent("""\
243            Content-Type: image/jpg
244            Content-Transfer-Encoding: base64
245
246            Ym9ndXMgZGF0YQ==
247            """))
248        with self.assertRaises(TypeError):
249            raw_data_manager.get_content(m, errors='ignore')
250
251    def test_get_raises_on_multipart(self):
252        m = self._str_msg(textwrap.dedent("""\
253            Content-Type: multipart/mixed; boundary="==="
254
255            --===
256            --===--
257            """))
258        with self.assertRaises(KeyError):
259            raw_data_manager.get_content(m)
260
261    def test_get_message_rfc822_and_external_body(self):
262        template = textwrap.dedent("""\
263            Content-Type: message/{}
264
265            To: [email protected]
266            From: [email protected]
267            Subject: example
268
269            an example message
270            """)
271        for subtype in 'rfc822 external-body'.split():
272            with self.subTest(subtype=subtype):
273                m = self._str_msg(template.format(subtype))
274                sub_msg = raw_data_manager.get_content(m)
275                self.assertIsInstance(sub_msg, self.message)
276                self.assertEqual(raw_data_manager.get_content(sub_msg),
277                                 "an example message\n")
278                self.assertEqual(sub_msg['to'], '[email protected]')
279                self.assertEqual(sub_msg['from'].addresses[0].username, 'bar')
280
281    def test_get_message_non_rfc822_or_external_body_yields_bytes(self):
282        m = self._str_msg(textwrap.dedent("""\
283            Content-Type: message/partial
284
285            To: [email protected]
286            From: [email protected]
287            Subject: example
288
289            The real body is in another message.
290            """))
291        self.assertEqual(raw_data_manager.get_content(m)[:10], b'To: [email protected]')
292
293    def test_set_text_plain(self):
294        m = self._make_message()
295        content = "Simple message.\n"
296        raw_data_manager.set_content(m, content)
297        self.assertEqual(str(m), textwrap.dedent("""\
298            Content-Type: text/plain; charset="utf-8"
299            Content-Transfer-Encoding: 7bit
300
301            Simple message.
302            """))
303        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
304        self.assertEqual(m.get_content(), content)
305
306    def test_set_text_html(self):
307        m = self._make_message()
308        content = "<p>Simple message.</p>\n"
309        raw_data_manager.set_content(m, content, subtype='html')
310        self.assertEqual(str(m), textwrap.dedent("""\
311            Content-Type: text/html; charset="utf-8"
312            Content-Transfer-Encoding: 7bit
313
314            <p>Simple message.</p>
315            """))
316        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
317        self.assertEqual(m.get_content(), content)
318
319    def test_set_text_charset_latin_1(self):
320        m = self._make_message()
321        content = "Simple message.\n"
322        raw_data_manager.set_content(m, content, charset='latin-1')
323        self.assertEqual(str(m), textwrap.dedent("""\
324            Content-Type: text/plain; charset="iso-8859-1"
325            Content-Transfer-Encoding: 7bit
326
327            Simple message.
328            """))
329        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
330        self.assertEqual(m.get_content(), content)
331
332    def test_set_text_short_line_minimal_non_ascii_heuristics(self):
333        m = self._make_message()
334        content = "et là il est monté sur moi et il commence à m'éto.\n"
335        raw_data_manager.set_content(m, content)
336        self.assertEqual(bytes(m), textwrap.dedent("""\
337            Content-Type: text/plain; charset="utf-8"
338            Content-Transfer-Encoding: 8bit
339
340            et là il est monté sur moi et il commence à m'éto.
341            """).encode('utf-8'))
342        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
343        self.assertEqual(m.get_content(), content)
344
345    def test_set_text_long_line_minimal_non_ascii_heuristics(self):
346        m = self._make_message()
347        content = ("j'ai un problème de python. il est sorti de son"
348                   " vivarium.  et là il est monté sur moi et il commence"
349                   " à m'éto.\n")
350        raw_data_manager.set_content(m, content)
351        self.assertEqual(bytes(m), textwrap.dedent("""\
352            Content-Type: text/plain; charset="utf-8"
353            Content-Transfer-Encoding: quoted-printable
354
355            j'ai un probl=C3=A8me de python. il est sorti de son vivari=
356            um.  et l=C3=A0 il est mont=C3=A9 sur moi et il commence =
357            =C3=A0 m'=C3=A9to.
358            """).encode('utf-8'))
359        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
360        self.assertEqual(m.get_content(), content)
361
362    def test_set_text_11_lines_long_line_minimal_non_ascii_heuristics(self):
363        m = self._make_message()
364        content = '\n'*10 + (
365                  "j'ai un problème de python. il est sorti de son"
366                  " vivarium.  et là il est monté sur moi et il commence"
367                  " à m'éto.\n")
368        raw_data_manager.set_content(m, content)
369        self.assertEqual(bytes(m), textwrap.dedent("""\
370            Content-Type: text/plain; charset="utf-8"
371            Content-Transfer-Encoding: quoted-printable
372            """ + '\n'*10 + """
373            j'ai un probl=C3=A8me de python. il est sorti de son vivari=
374            um.  et l=C3=A0 il est mont=C3=A9 sur moi et il commence =
375            =C3=A0 m'=C3=A9to.
376            """).encode('utf-8'))
377        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
378        self.assertEqual(m.get_content(), content)
379
380    def test_set_text_maximal_non_ascii_heuristics(self):
381        m = self._make_message()
382        content = "áàäéèęöő.\n"
383        raw_data_manager.set_content(m, content)
384        self.assertEqual(bytes(m), textwrap.dedent("""\
385            Content-Type: text/plain; charset="utf-8"
386            Content-Transfer-Encoding: 8bit
387
388            áàäéèęöő.
389            """).encode('utf-8'))
390        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
391        self.assertEqual(m.get_content(), content)
392
393    def test_set_text_11_lines_maximal_non_ascii_heuristics(self):
394        m = self._make_message()
395        content = '\n'*10 + "áàäéèęöő.\n"
396        raw_data_manager.set_content(m, content)
397        self.assertEqual(bytes(m), textwrap.dedent("""\
398            Content-Type: text/plain; charset="utf-8"
399            Content-Transfer-Encoding: 8bit
400            """ + '\n'*10 + """
401            áàäéèęöő.
402            """).encode('utf-8'))
403        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
404        self.assertEqual(m.get_content(), content)
405
406    def test_set_text_long_line_maximal_non_ascii_heuristics(self):
407        m = self._make_message()
408        content = ("áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő"
409                   "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő"
410                   "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő.\n")
411        raw_data_manager.set_content(m, content)
412        self.assertEqual(bytes(m), textwrap.dedent("""\
413            Content-Type: text/plain; charset="utf-8"
414            Content-Transfer-Encoding: base64
415
416            w6HDoMOkw6nDqMSZw7bFkcOhw6DDpMOpw6jEmcO2xZHDocOgw6TDqcOoxJnD
417            tsWRw6HDoMOkw6nDqMSZw7bFkcOhw6DDpMOpw6jEmcO2xZHDocOgw6TDqcOo
418            xJnDtsWRw6HDoMOkw6nDqMSZw7bFkcOhw6DDpMOpw6jEmcO2xZHDocOgw6TD
419            qcOoxJnDtsWRw6HDoMOkw6nDqMSZw7bFkcOhw6DDpMOpw6jEmcO2xZHDocOg
420            w6TDqcOoxJnDtsWRLgo=
421            """).encode('utf-8'))
422        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
423        self.assertEqual(m.get_content(), content)
424
425    def test_set_text_11_lines_long_line_maximal_non_ascii_heuristics(self):
426        # Yes, it chooses "wrong" here.  It's a heuristic.  So this result
427        # could change if we come up with a better heuristic.
428        m = self._make_message()
429        content = ('\n'*10 +
430                   "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő"
431                   "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő"
432                   "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő.\n")
433        raw_data_manager.set_content(m, "\n"*10 +
434                                        "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő"
435                                        "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő"
436                                        "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő.\n")
437        self.assertEqual(bytes(m), textwrap.dedent("""\
438            Content-Type: text/plain; charset="utf-8"
439            Content-Transfer-Encoding: quoted-printable
440            """ + '\n'*10 + """
441            =C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=
442            =A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=
443            =C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=
444            =A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=
445            =C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=
446            =91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=
447            =C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=
448            =A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=
449            =C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=
450            =99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=
451            =C5=91.
452            """).encode('utf-8'))
453        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
454        self.assertEqual(m.get_content(), content)
455
456    def test_set_text_non_ascii_with_cte_7bit_raises(self):
457        m = self._make_message()
458        with self.assertRaises(UnicodeError):
459            raw_data_manager.set_content(m,"áàäéèęöő.\n", cte='7bit')
460
461    def test_set_text_non_ascii_with_charset_ascii_raises(self):
462        m = self._make_message()
463        with self.assertRaises(UnicodeError):
464            raw_data_manager.set_content(m,"áàäéèęöő.\n", charset='ascii')
465
466    def test_set_text_non_ascii_with_cte_7bit_and_charset_ascii_raises(self):
467        m = self._make_message()
468        with self.assertRaises(UnicodeError):
469            raw_data_manager.set_content(m,"áàäéèęöő.\n", cte='7bit', charset='ascii')
470
471    def test_set_message(self):
472        m = self._make_message()
473        m['Subject'] = "Forwarded message"
474        content = self._make_message()
475        content['To'] = '[email protected]'
476        content['From'] = '[email protected]'
477        content['Subject'] = "get back in your box"
478        content.set_content("Or face the comfy chair.")
479        raw_data_manager.set_content(m, content)
480        self.assertEqual(str(m), textwrap.dedent("""\
481            Subject: Forwarded message
482            Content-Type: message/rfc822
483            Content-Transfer-Encoding: 8bit
484
485            To: [email protected]
486            From: [email protected]
487            Subject: get back in your box
488            Content-Type: text/plain; charset="utf-8"
489            Content-Transfer-Encoding: 7bit
490            MIME-Version: 1.0
491
492            Or face the comfy chair.
493            """))
494        payload = m.get_payload(0)
495        self.assertIsInstance(payload, self.message)
496        self.assertEqual(str(payload), str(content))
497        self.assertIsInstance(m.get_content(), self.message)
498        self.assertEqual(str(m.get_content()), str(content))
499
500    def test_set_message_with_non_ascii_and_coercion_to_7bit(self):
501        m = self._make_message()
502        m['Subject'] = "Escape report"
503        content = self._make_message()
504        content['To'] = '[email protected]'
505        content['From'] = '[email protected]'
506        content['Subject'] = "Help"
507        content.set_content("j'ai un problème de python. il est sorti de son"
508                            " vivarium.")
509        raw_data_manager.set_content(m, content)
510        self.assertEqual(bytes(m), textwrap.dedent("""\
511            Subject: Escape report
512            Content-Type: message/rfc822
513            Content-Transfer-Encoding: 8bit
514
515            To: [email protected]
516            From: [email protected]
517            Subject: Help
518            Content-Type: text/plain; charset="utf-8"
519            Content-Transfer-Encoding: 8bit
520            MIME-Version: 1.0
521
522            j'ai un problème de python. il est sorti de son vivarium.
523            """).encode('utf-8'))
524        # The choice of base64 for the body encoding is because generator
525        # doesn't bother with heuristics and uses it unconditionally for utf-8
526        # text.
527        # XXX: the first cte should be 7bit, too...that's a generator bug.
528        # XXX: the line length in the body also looks like a generator bug.
529        self.assertEqual(m.as_string(maxheaderlen=self.policy.max_line_length),
530                         textwrap.dedent("""\
531            Subject: Escape report
532            Content-Type: message/rfc822
533            Content-Transfer-Encoding: 8bit
534
535            To: [email protected]
536            From: [email protected]
537            Subject: Help
538            Content-Type: text/plain; charset="utf-8"
539            Content-Transfer-Encoding: base64
540            MIME-Version: 1.0
541
542            aidhaSB1biBwcm9ibMOobWUgZGUgcHl0aG9uLiBpbCBlc3Qgc29ydGkgZGUgc29uIHZpdmFyaXVt
543            Lgo=
544            """))
545        self.assertIsInstance(m.get_content(), self.message)
546        self.assertEqual(str(m.get_content()), str(content))
547
548    def test_set_message_invalid_cte_raises(self):
549        m = self._make_message()
550        content = self._make_message()
551        for cte in 'quoted-printable base64'.split():
552            for subtype in 'rfc822 external-body'.split():
553                with self.subTest(cte=cte, subtype=subtype):
554                    with self.assertRaises(ValueError) as ar:
555                        m.set_content(content, subtype, cte=cte)
556                    exc = str(ar.exception)
557                    self.assertIn(cte, exc)
558                    self.assertIn(subtype, exc)
559        subtype = 'external-body'
560        for cte in '8bit binary'.split():
561            with self.subTest(cte=cte, subtype=subtype):
562                with self.assertRaises(ValueError) as ar:
563                    m.set_content(content, subtype, cte=cte)
564                exc = str(ar.exception)
565                self.assertIn(cte, exc)
566                self.assertIn(subtype, exc)
567
568    def test_set_image_jpg(self):
569        for content in (b"bogus content",
570                        bytearray(b"bogus content"),
571                        memoryview(b"bogus content")):
572            with self.subTest(content=content):
573                m = self._make_message()
574                raw_data_manager.set_content(m, content, 'image', 'jpeg')
575                self.assertEqual(str(m), textwrap.dedent("""\
576                    Content-Type: image/jpeg
577                    Content-Transfer-Encoding: base64
578
579                    Ym9ndXMgY29udGVudA==
580                    """))
581                self.assertEqual(m.get_payload(decode=True), content)
582                self.assertEqual(m.get_content(), content)
583
584    def test_set_audio_aif_with_quoted_printable_cte(self):
585        # Why you would use qp, I don't know, but it is technically supported.
586        # XXX: the incorrect line length is because binascii.b2a_qp doesn't
587        # support a line length parameter, but we must use it to get newline
588        # encoding.
589        # XXX: what about that lack of tailing newline?  Do we actually handle
590        # that correctly in all cases?  That is, if the *source* has an
591        # unencoded newline, do we add an extra newline to the returned payload
592        # or not?  And can that actually be disambiguated based on the RFC?
593        m = self._make_message()
594        content = b'b\xFFgus\tcon\nt\rent ' + b'z'*100
595        m.set_content(content, 'audio', 'aif', cte='quoted-printable')
596        self.assertEqual(bytes(m), textwrap.dedent("""\
597            Content-Type: audio/aif
598            Content-Transfer-Encoding: quoted-printable
599            MIME-Version: 1.0
600
601            b=FFgus=09con=0At=0Dent=20zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz=
602            zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz""").encode('latin-1'))
603        self.assertEqual(m.get_payload(decode=True), content)
604        self.assertEqual(m.get_content(), content)
605
606    def test_set_video_mpeg_with_binary_cte(self):
607        m = self._make_message()
608        content = b'b\xFFgus\tcon\nt\rent ' + b'z'*100
609        m.set_content(content, 'video', 'mpeg', cte='binary')
610        self.assertEqual(bytes(m), textwrap.dedent("""\
611            Content-Type: video/mpeg
612            Content-Transfer-Encoding: binary
613            MIME-Version: 1.0
614
615            """).encode('ascii') +
616            # XXX: the second \n ought to be a \r, but generator gets it wrong.
617            # THIS MEANS WE DON'T ACTUALLY SUPPORT THE 'binary' CTE.
618            b'b\xFFgus\tcon\nt\nent zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' +
619            b'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz')
620        self.assertEqual(m.get_payload(decode=True), content)
621        self.assertEqual(m.get_content(), content)
622
623    def test_set_application_octet_stream_with_8bit_cte(self):
624        # In 8bit mode, univeral line end logic applies.  It is up to the
625        # application to make sure the lines are short enough; we don't check.
626        m = self._make_message()
627        content = b'b\xFFgus\tcon\nt\rent\n' + b'z'*60 + b'\n'
628        m.set_content(content, 'application', 'octet-stream', cte='8bit')
629        self.assertEqual(bytes(m), textwrap.dedent("""\
630            Content-Type: application/octet-stream
631            Content-Transfer-Encoding: 8bit
632            MIME-Version: 1.0
633
634            """).encode('ascii') +
635            b'b\xFFgus\tcon\nt\nent\n' +
636            b'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\n')
637        self.assertEqual(m.get_payload(decode=True), content)
638        self.assertEqual(m.get_content(), content)
639
640    def test_set_headers_from_header_objects(self):
641        m = self._make_message()
642        content = "Simple message.\n"
643        header_factory = self.policy.header_factory
644        raw_data_manager.set_content(m, content, headers=(
645            header_factory("To", "[email protected]"),
646            header_factory("From", "[email protected]"),
647            header_factory("Subject", "I'm talking to myself.")))
648        self.assertEqual(str(m), textwrap.dedent("""\
649            Content-Type: text/plain; charset="utf-8"
650            To: [email protected]
651            From: [email protected]
652            Subject: I'm talking to myself.
653            Content-Transfer-Encoding: 7bit
654
655            Simple message.
656            """))
657
658    def test_set_headers_from_strings(self):
659        m = self._make_message()
660        content = "Simple message.\n"
661        raw_data_manager.set_content(m, content, headers=(
662            "X-Foo-Header: foo",
663            "X-Bar-Header: bar",))
664        self.assertEqual(str(m), textwrap.dedent("""\
665            Content-Type: text/plain; charset="utf-8"
666            X-Foo-Header: foo
667            X-Bar-Header: bar
668            Content-Transfer-Encoding: 7bit
669
670            Simple message.
671            """))
672
673    def test_set_headers_with_invalid_duplicate_string_header_raises(self):
674        m = self._make_message()
675        content = "Simple message.\n"
676        with self.assertRaisesRegex(ValueError, 'Content-Type'):
677            raw_data_manager.set_content(m, content, headers=(
678                "Content-Type: foo/bar",)
679                )
680
681    def test_set_headers_with_invalid_duplicate_header_header_raises(self):
682        m = self._make_message()
683        content = "Simple message.\n"
684        header_factory = self.policy.header_factory
685        with self.assertRaisesRegex(ValueError, 'Content-Type'):
686            raw_data_manager.set_content(m, content, headers=(
687                header_factory("Content-Type", " foo/bar"),)
688                )
689
690    def test_set_headers_with_defective_string_header_raises(self):
691        m = self._make_message()
692        content = "Simple message.\n"
693        with self.assertRaisesRegex(ValueError, '[email protected]@@[email protected]'):
694            raw_data_manager.set_content(m, content, headers=(
695                'To: [email protected]@@[email protected]',)
696                )
697            print(m['To'].defects)
698
699    def test_set_headers_with_defective_header_header_raises(self):
700        m = self._make_message()
701        content = "Simple message.\n"
702        header_factory = self.policy.header_factory
703        with self.assertRaisesRegex(ValueError, '[email protected]@@[email protected]'):
704            raw_data_manager.set_content(m, content, headers=(
705                header_factory('To', '[email protected]@@[email protected]'),)
706                )
707            print(m['To'].defects)
708
709    def test_set_disposition_inline(self):
710        m = self._make_message()
711        m.set_content('foo', disposition='inline')
712        self.assertEqual(m['Content-Disposition'], 'inline')
713
714    def test_set_disposition_attachment(self):
715        m = self._make_message()
716        m.set_content('foo', disposition='attachment')
717        self.assertEqual(m['Content-Disposition'], 'attachment')
718
719    def test_set_disposition_foo(self):
720        m = self._make_message()
721        m.set_content('foo', disposition='foo')
722        self.assertEqual(m['Content-Disposition'], 'foo')
723
724    # XXX: we should have a 'strict' policy mode (beyond raise_on_defect) that
725    # would cause 'foo' above to raise.
726
727    def test_set_filename(self):
728        m = self._make_message()
729        m.set_content('foo', filename='bar.txt')
730        self.assertEqual(m['Content-Disposition'],
731                         'attachment; filename="bar.txt"')
732
733    def test_set_filename_and_disposition_inline(self):
734        m = self._make_message()
735        m.set_content('foo', disposition='inline', filename='bar.txt')
736        self.assertEqual(m['Content-Disposition'], 'inline; filename="bar.txt"')
737
738    def test_set_non_ascii_filename(self):
739        m = self._make_message()
740        m.set_content('foo', filename='ábárî.txt')
741        self.assertEqual(bytes(m), textwrap.dedent("""\
742            Content-Type: text/plain; charset="utf-8"
743            Content-Transfer-Encoding: 7bit
744            Content-Disposition: attachment;
745             filename*=utf-8''%C3%A1b%C3%A1r%C3%AE.txt
746            MIME-Version: 1.0
747
748            foo
749            """).encode('ascii'))
750
751    content_object_params = {
752        'text_plain': ('content', ()),
753        'text_html': ('content', ('html',)),
754        'application_octet_stream': (b'content',
755                                     ('application', 'octet_stream')),
756        'image_jpeg': (b'content', ('image', 'jpeg')),
757        'message_rfc822': (message(), ()),
758        'message_external_body': (message(), ('external-body',)),
759        }
760
761    def content_object_as_header_receiver(self, obj, mimetype):
762        m = self._make_message()
763        m.set_content(obj, *mimetype, headers=(
764            'To: [email protected]',
765            'From: [email protected]'))
766        self.assertEqual(m['to'], '[email protected]')
767        self.assertEqual(m['from'], '[email protected]')
768
769    def content_object_as_disposition_inline_receiver(self, obj, mimetype):
770        m = self._make_message()
771        m.set_content(obj, *mimetype, disposition='inline')
772        self.assertEqual(m['Content-Disposition'], 'inline')
773
774    def content_object_as_non_ascii_filename_receiver(self, obj, mimetype):
775        m = self._make_message()
776        m.set_content(obj, *mimetype, disposition='inline', filename='bár.txt')
777        self.assertEqual(m['Content-Disposition'], 'inline; filename="bár.txt"')
778        self.assertEqual(m.get_filename(), "bár.txt")
779        self.assertEqual(m['Content-Disposition'].params['filename'], "bár.txt")
780
781    def content_object_as_cid_receiver(self, obj, mimetype):
782        m = self._make_message()
783        m.set_content(obj, *mimetype, cid='some_random_stuff')
784        self.assertEqual(m['Content-ID'], 'some_random_stuff')
785
786    def content_object_as_params_receiver(self, obj, mimetype):
787        m = self._make_message()
788        params = {'foo': 'bár', 'abc': 'xyz'}
789        m.set_content(obj, *mimetype, params=params)
790        if isinstance(obj, str):
791            params['charset'] = 'utf-8'
792        self.assertEqual(m['Content-Type'].params, params)
793
794
795if __name__ == '__main__':
796    unittest.main()
797
Full Screen

test_message.py

Source: test_message.py Github

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

connresp.py

Source: connresp.py Github

copy
1from zoundry.appframework.exceptions import ZAppFrameworkException
2from zoundry.appframework.messages import _extstr
3from zoundry.base.util.fileutil import getFileMetaData
4import os
5
6# ------------------------------------------------------------------------------
7# The interface for connection response information.  This object contains the
8# meta information about the connection response (code, message, headers).
9# ------------------------------------------------------------------------------
10class IZHttpConnectionRespInfo:
11    
12    def getURL(self):
13        u"""getURL() -> string
14        Returns the URL that this response represents.""" #$NON-NLS-1$
15    # end getURL()
16    
17    def getCode(self):
18        u"""getCode() -> int
19        Returns the HTTP response code.""" #$NON-NLS-1$
20    # end getCode()
21    
22    def getMessage(self):
23        u"""getMessage() -> string
24        Returns the HTTP response message.""" #$NON-NLS-1$
25    # end getMessage()
26    
27    def getHeaders(self):
28        u"""getHeaders() -> map<string, string>
29        Returns a map of HTTP response header (map of header
30        key to header value).""" #$NON-NLS-1$
31    # end getHeaders()
32    
33    def getHeader(self, name):
34        u"""getHeader(string) -> string
35        Returns the value of the header with the given name.""" #$NON-NLS-1$
36    # end getHeader()
37    
38    def getContentType(self):
39        u"""getContentType() -> string
40        Returns the content type of the response.""" #$NON-NLS-1$
41    # end getContentType()
42    
43    def getContentLength(self):
44        u"""getContentLength() -> int
45        Returns the content length of the response.""" #$NON-NLS-1$
46    # end getContentLength()
47
48# end IZHttpConnectionRespInfo
49
50
51# ------------------------------------------------------------------------------
52# Extends the IZHttpConnectionRespInfo by adding accessors to get at the actual
53# response content.
54# ------------------------------------------------------------------------------
55class IZHttpConnectionResp(IZHttpConnectionRespInfo):
56    
57    def getContentStream(self):
58        u"""getContentStream() -> stream
59        Returns a file-like object for the content for this
60        response.""" #$NON-NLS-1$
61    # end getContentStream
62    
63    def getContent(self):
64        u"""getContent() -> data[]
65        Returns the content data for this response.""" #$NON-NLS-1$
66    # end getContent()
67    
68    def getContentFilename(self):
69        u"""getContentFilename() -> string
70        Returns the filename where the content for this response
71        is stored.""" #$NON-NLS-1$
72    # end getContentFilename()
73
74# end IZHttpConnectionResp
75
76
77# ------------------------------------------------------------------------------
78# A simple implementation of a connection response info.
79# ------------------------------------------------------------------------------
80class ZHttpConnectionRespInfo(IZHttpConnectionRespInfo):
81
82    def __init__(self, url, code, message, headers):
83        self.url = url
84        self.code = code
85        self.message = message
86        self.headers = headers
87    # end __init__()
88    
89    def getURL(self):
90        return self.url
91    # end getURL()
92
93    def getCode(self):
94        return self.code
95    # end getCode()
96    
97    def getMessage(self):
98        return self.message
99    # end getMessage()
100    
101    def getHeaders(self):
102        return self.headers
103    # end getHeaders()
104    
105    def getHeader(self, name):
106        if name in self.headers:
107            return self.headers[name]
108        return None
109    # end getHeader()
110    
111    def getContentType(self):
112        return self.getHeader(u"content-type") #$NON-NLS-1$
113    # end getContentType()
114    
115    def getContentLength(self):
116        cl = self.getHeader(u"content-length") #$NON-NLS-1$
117        if cl is not None:
118            return long(cl)
119        return None
120    # end getContentLength()
121
122# end ZHttpConnectionRespInfo
123
124
125# ------------------------------------------------------------------------------
126# A simple implementation of a connection response.
127# ------------------------------------------------------------------------------
128class ZHttpConnectionResp(ZHttpConnectionRespInfo, IZHttpConnectionResp):
129
130    def __init__(self, url, code, message, headers, contentFilename):
131        ZHttpConnectionRespInfo.__init__(self, url, code, message, headers)
132
133        self.contentFilename = contentFilename
134    # end __init__()
135
136    def getContentLength(self):
137        if os.path.isfile(self.contentFilename):
138            return getFileMetaData(self.contentFilename)[2]
139
140        return ZHttpConnectionRespInfo.getContentLength(self)
141    # end getContentLength()
142
143    def getContentStream(self):
144        if not os.path.isfile(self.contentFilename):
145            raise ZAppFrameworkException(u"%s: '%s'." % (_extstr(u"connresp.NoContentFoundError"), self.contentFilename)) #$NON-NLS-1$ #$NON-NLS-2$
146        return open(self.contentFilename)
147    # end getContentStream
148    
149    def getContent(self):
150        file = self.getContentStream()
151        try:
152            return file.read()
153        finally:
154            file.close()
155    # end getContent()
156    
157    def getContentFilename(self):
158        return self.contentFilename
159    # end getContentFilename()
160
161# end ZHttpConnectionResp
162
Full Screen

questions.js

Source: questions.js Github

copy
1const state = {
2	quizStarted: false,
3	quizCompleted: false,
4	scrollHeight: 0,
5	questions: [
6		// PERSONALITY QUESTIONS
7		{
8			content: "You’re bored. Pick a smartphone activity / app:",
9			isAnswered: false,
10			illustrationRef: 0,
11			ingredientRef: 0,
12			answers: [
13				{
14					content: "Instagram",
15					value: 0
16				},
17				{
18					content: "Facebook",
19					value: 3
20				},
21				{
22					content: "Twitter",
23					value: 1
24				},
25				{
26					content: "Reminders",
27					value: 2
28				},
29				{
30					content: "Camera",
31					value: 4
32				},
33				{
34					content: "Pinterest",
35					value: 5
36				}
37			]
38		},
39		{
40			content: "Pick an experience:",
41			isAnswered: false,
42			illustrationRef: 0,
43			ingredientRef: 0,
44			answers: [
45				{
46					content: "Concert",
47					value: 0
48				},
49				{
50					content: "Game Night",
51					value: 1
52				},
53				{
54					content: "Staycation",
55					value: 3
56				},
57				{
58					content: "Burning Man",
59					value: 4
60				},
61				{
62					content: "Try a new restaurant",
63					value: 5
64				},
65				{
66					content: "A workshop, class or conference",
67					value: 2
68				}
69			]
70		},
71		{
72			content: "Of the following, which is most important to you?",
73			isAnswered: false,
74			illustrationRef: 0,
75			ingredientRef: 0,
76			answers: [
77				{
78					content: "Expression",
79					value: 4
80				},
81				{
82					content: "Efficiency",
83					value: 1
84				},
85				{
86					content: "Harmony",
87					value: 2
88				},
89				{
90					content: "Open Mindedness",
91					value: 5
92				},
93				{
94					content: "Peace",
95					value: 3
96				},
97				{
98					content: "Current Events",
99					value: 0
100				}
101			]
102		},
103		{
104			content: "Which best represents you?",
105			isAnswered: false,
106			illustrationRef: 0,
107			ingredientRef: 0,
108			answers: [
109				{
110					content: "The roof",
111					value: 3
112				},
113				{
114					content: "The door",
115					value: 5
116				},
117				{
118					content: "The window",
119					value: 4
120				},
121				{
122					content: "The living room",
123					value: 0
124				},
125				{
126					content: "The space",
127					value: 5
128				},
129				{
130					content: "The pillars",
131					value: 2
132				}
133			]
134		},
135		{
136			content: "You get a bonus day off. What do you do with it?",
137			isAnswered: false,
138			illustrationRef: 0,
139			ingredientRef: 0,
140			answers: [
141				{
142					content: "Make plans to go out",
143					value: 0
144				},
145				{
146					content: "Indulge in a hobby",
147					value: 5
148				},
149				{
150					content: "Dive into a book",
151					value: 1
152				},
153				{
154					content: "Catch up on some work",
155					value: 2
156				},
157				{
158					content: "Spend time with the fam",
159					value: 3
160				},
161				{
162					content: "Do something spontaneous",
163					value: 4
164				}
165			]
166		},
167		{
168			content: "My Kryptonite:",
169			isAnswered: false,
170			illustrationRef: 0,
171			ingredientRef: 0,
172			answers: [
173				{
174					content: "Emotions",
175					value: 1
176				},
177				{
178					content: "Too Much Pressure",
179					value: 3
180				},
181				{
182					content: "Rigid Rules & Institutions",
183					value: 4
184				},
185				{
186					content: "Analysis Paralysis",
187					value: 2
188				},
189				{
190					content: "Micromanagement",
191					value: 5
192				},
193				{
194					content: "Unwanted Change",
195					value: 0
196				}
197			]
198		},
199		{
200			content: "Zombie apocalypse! Pick a thing",
201			isAnswered: false,
202			illustrationRef: 0,
203			ingredientRef: 0,
204			answers: [
205				{
206					content: "Armor",
207					value: 3
208				},
209				{
210					content: "Crossbow",
211					value: 2
212				},
213				{
214					content: "Cool outfit",
215					value: 0
216				},
217				{
218					content: "Laboratory",
219					value: 1
220				},
221				{
222					content: "Abandoned Mall",
223					value: 4
224				},
225				{
226					content: "Car",
227					value: 5
228				}
229			]
230		},
231		// MOOD QUESTIONS
232		{
233			content: "How’s it going?",
234			isAnswered: false,
235			illustrationRef: 1,
236			ingredientRef: 1,
237			answers: [
238				{
239					content: "Meh",
240					value: 2
241				},
242				{
243					content: "Everything stay cherry",
244					value: 4
245				},
246				{
247					content: "I can’t sit still",
248					value: 3
249				},
250				{
251					content: "I’m doing amazing!",
252					value: 0
253				},
254				{
255					content: "Grr!!",
256					value: 5
257				},
258				{
259					content: "There’s a lot on my mind",
260					value: 1
261				}
262			]
263		},
264		{
265			content: "Quickly — pick a spirit animal",
266			isAnswered: false,
267			illustrationRef: 1,
268			ingredientRef: 1,
269			answers: [
270				{
271					content: "Turtle",
272					value: 4
273				},
274				{
275					content: "Puppy",
276					value: 0
277				},
278				{
279					content: "Whale",
280					value: 1
281				},
282				{
283					content: "Sloth",
284					value: 2
285				},
286				{
287					content: "Squirrel",
288					value: 3
289				},
290				{
291					content: "Cactus",
292					value: 5
293				}
294			]
295		},
296		{
297			content: "Pick a color",
298			isAnswered: false,
299			illustrationRef: 1,
300			ingredientRef: 1,
301			answers: [
302				{
303					content: "Yellow",
304					value: 0
305				},
306				{
307					content: "Blue",
308					value: 1
309				},
310				{
311					content: "Red",
312					value: 5
313				},
314				{
315					content: "White",
316					value: 3
317				},
318				{
319					content: "Gray",
320					value: 2
321				},
322				{
323					content: "Green",
324					value: 4
325				}
326			]
327		},
328		{
329			content: "Pick an emoji",
330			isAnswered: false,
331			illustrationRef: 1,
332			ingredientRef: 1,
333			answers: [
334				{
335					content: "T_T",
336					value: 1
337				},
338				{
339					content: "-_-",
340					value: 2
341				},
342				{
343					content: "(╯°Д°)╯︵ ┻━┻",
344					value: 5
345				},
346				{
347					content: "^____^",
348					value: 0
349				},
350				{
351					// eslint-disable-next-line no-useless-escape
352					content: "¯\\_(ツ)_/¯",
353					value: 4
354				},
355				{
356					content: "(ï¼ _ï¼ )",
357					value: 3
358				}
359			]
360		},
361		{
362			content: "You usually sleep",
363			isAnswered: false,
364			illustrationRef: 1,
365			ingredientRef: 1,
366			answers: [
367				{
368					content: "Full Fetal",
369					value: 3
370				},
371				{
372					content: "Half fetal",
373					value: 1
374				},
375				{
376					content: "Vampire",
377					value: 0
378				},
379				{
380					content: "Starfish",
381					value: 4
382				},
383				{
384					content: "Stomach",
385					value: 5
386				},
387				{
388					content: "Log",
389					value: 2
390				}
391			]
392		},
393		{
394			content: "Choose a Bob",
395			isAnswered: false,
396			illustrationRef: 2,
397			ingredientRef: 2,
398			answers: [
399				{
400					content: "Ross",
401					value: 1
402				},
403				{
404					content: "Marley",
405					value: 3
406				},
407				{
408					content: "Sponge",
409					value: 4
410				},
411				{
412					content: "Dylan",
413					value: 0
414				},
415				{
416					content: "‘s BBQ",
417					value: 2
418				},
419				{
420					content: "Julia Roberts",
421					value: 5
422				}
423			]
424		},
425		{
426			content: "Which object best represents you?",
427			isAnswered: false,
428			illustrationRef: 2,
429			ingredientRef: 2,
430			answers: [
431				{
432					content: "Sponge",
433					value: 3
434				},
435				{
436					content: "Tree",
437					value: 0
438				},
439				{
440					content: "Book",
441					value: 1
442				},
443				{
444					content: "Gem",
445					value: 5
446				},
447				{
448					content: "Ball",
449					value: 4
450				},
451				{
452					content: "Slippah",
453					value: 2
454				}
455			]
456		},
457		{
458			content: "Choose a condiment or topping",
459			isAnswered: false,
460			illustrationRef: 2,
461			ingredientRef: 2,
462			answers: [
463				{
464					content: "Kikkoman Shoyu",
465					value: 2
466				},
467				{
468					content: "Scallions",
469					value: 5
470				},
471				{
472					content: "Nori",
473					value: 1
474				},
475				{
476					content: "Tobiko",
477					value: 4
478				},
479				{
480					content: "Microgreens",
481					value: 0
482				},
483				{
484					content: "Yuzu Kosho",
485					value: 3
486				}
487			]
488		}
489	]
490};
491
492const getters = {
493	allQuestions: state => state.questions,
494	quizCompleted: state => state.quizCompleted,
495	quizStarted: state => state.quizStarted,
496	scrollHeight: state => state.scrollHeight
497};
498
499const actions = {};
500
501const mutations = {
502	setScrollHeight(state, { value }) {
503		state.scrollHeight = value;
504	},
505	setQuestionsToAnswered(state, { selected, value }) {
506		state.questions.map(question => {
507			if (question.content == selected) {
508				question.isAnswered = value;
509			}
510		});
511	},
512	setQuizStatus(state, { result }) {
513		state.quizCompleted = result;
514	},
515	setQuizStarted(state, { value }) {
516		state.quizStarted = value;
517	},
518	resetAllAnsweredStatus(state) {
519		state.questions.map(question => {
520			question.isAnswered = false;
521		});
522	}
523};
524
525export default {
526	state,
527	getters,
528	actions,
529	mutations
530};
531
Full Screen

movieData.js

Source: movieData.js Github

copy
1module.exports = [{
2  cover: '/images/collectionsImg/movieImg.png',
3  name: '《双子杀手》',
4  intro: '美国国防情报局特工亨利(威尔·史密斯饰),准备退休之际意外遭到一名神秘杀手的追杀,在两人的激烈较量中,他发现这名杀手竟然是年轻了20多岁的自己,一场我与我的对决旋即展开,而背后的真相也逐渐浮出水面。',
5  create_time: '2019-09-23',
6  creator: {
7    _id: '123',
8    username: '系统命名',
9  },
10  //包含的句子
11  posts: [{
12      _id: '5eb3d3027655001700f92fdc',
13      content:'世界上这么多人来追杀我,为什么派你来?'
14    }, {
15      _id: '5eb3d3617655001700f92fdd',
16      content:'我知道他为什么跟你一样厉害,他就是你。'
17    }, {
18      _id: '5eb3d4c17655001700f92fde',
19      content:'你是谁? 他是你的复制人。'
20    },
21    {
22      _id: '5eb3d4da7655001700f92fdf',
23      content:`“我是最厉害的” 
24               “然而你并不是最厉害的”`
25    }, {
26      _id: '5eb3d4f37655001700f92fe0',
27      content:'我们一切的努力危在旦夕,没有人能除掉他,双子杀手会搞定他。'
28    }, {
29      _id: '5eb3d5037655001700f92fe1',
30      content:'你看不出来我很痛苦吗? 别怀疑你自己。'
31    }]
32},{
33  cover: '/images/collectionsImg/movieImg.png',
34  name: '《双子杀手》',
35  intro: '美国国防情报局特工亨利(威尔·史密斯饰),准备退休之际意外遭到一名神秘杀手的追杀,在两人的激烈较量中,他发现这名杀手竟然是年轻了20多岁的自己,一场我与我的对决旋即展开,而背后的真相也逐渐浮出水面。',
36  create_time: '2019-09-23',
37  creator: {
38    _id: '123',
39    username: '系统命名',
40  },
41  //包含的句子
42  posts: [{
43      _id: '5eb3d3027655001700f92fdc',
44      content:'世界上这么多人来追杀我,为什么派你来?'
45    }, {
46      _id: '5eb3d3617655001700f92fdd',
47      content:'我知道他为什么跟你一样厉害,他就是你。'
48    }, {
49      _id: '5eb3d4c17655001700f92fde',
50      content:'你是谁? 他是你的复制人。'
51    },
52    {
53      _id: '5eb3d4da7655001700f92fdf',
54      content:`“我是最厉害的” 
55               “然而你并不是最厉害的”`
56    }, {
57      _id: '5eb3d4f37655001700f92fe0',
58      content:'我们一切的努力危在旦夕,没有人能除掉他,双子杀手会搞定他。'
59    }, {
60      _id: '5eb3d5037655001700f92fe1',
61      content:'你看不出来我很痛苦吗? 别怀疑你自己。'
62    }]
63},{
64  cover: '/images/collectionsImg/movieImg.png',
65  name: '《双子杀手》',
66  intro: '美国国防情报局特工亨利(威尔·史密斯饰),准备退休之际意外遭到一名神秘杀手的追杀,在两人的激烈较量中,他发现这名杀手竟然是年轻了20多岁的自己,一场我与我的对决旋即展开,而背后的真相也逐渐浮出水面。',
67  create_time: '2019-09-23',
68  creator: {
69    _id: '123',
70    username: '系统命名',
71  },
72  //包含的句子
73  posts: [{
74      _id: '5eb3d3027655001700f92fdc',
75      content:'世界上这么多人来追杀我,为什么派你来?'
76    }, {
77      _id: '5eb3d3617655001700f92fdd',
78      content:'我知道他为什么跟你一样厉害,他就是你。'
79    }, {
80      _id: '5eb3d4c17655001700f92fde',
81      content:'你是谁? 他是你的复制人。'
82    },
83    {
84      _id: '5eb3d4da7655001700f92fdf',
85      content:`“我是最厉害的” 
86               “然而你并不是最厉害的”`
87    }, {
88      _id: '5eb3d4f37655001700f92fe0',
89      content:'我们一切的努力危在旦夕,没有人能除掉他,双子杀手会搞定他。'
90    }, {
91      _id: '5eb3d5037655001700f92fe1',
92      content:'你看不出来我很痛苦吗? 别怀疑你自己。'
93    }]
94},{
95  cover: '/images/collectionsImg/movieImg.png',
96  name: '《双子杀手》',
97  intro: '美国国防情报局特工亨利(威尔·史密斯饰),准备退休之际意外遭到一名神秘杀手的追杀,在两人的激烈较量中,他发现这名杀手竟然是年轻了20多岁的自己,一场我与我的对决旋即展开,而背后的真相也逐渐浮出水面。',
98  create_time: '2019-09-23',
99  creator: {
100    _id: '123',
101    username: '系统命名',
102  },
103  //包含的句子
104  posts: [{
105      _id: '5eb3d3027655001700f92fdc',
106      content:'世界上这么多人来追杀我,为什么派你来?'
107    }, {
108      _id: '5eb3d3617655001700f92fdd',
109      content:'我知道他为什么跟你一样厉害,他就是你。'
110    }, {
111      _id: '5eb3d4c17655001700f92fde',
112      content:'你是谁? 他是你的复制人。'
113    },
114    {
115      _id: '5eb3d4da7655001700f92fdf',
116      content:`“我是最厉害的” 
117               “然而你并不是最厉害的”`
118    }, {
119      _id: '5eb3d4f37655001700f92fe0',
120      content:'我们一切的努力危在旦夕,没有人能除掉他,双子杀手会搞定他。'
121    }, {
122      _id: '5eb3d5037655001700f92fe1',
123      content:'你看不出来我很痛苦吗? 别怀疑你自己。'
124    }]
125},{
126  cover: '/images/collectionsImg/movieImg.png',
127  name: '《双子杀手》',
128  intro: '美国国防情报局特工亨利(威尔·史密斯饰),准备退休之际意外遭到一名神秘杀手的追杀,在两人的激烈较量中,他发现这名杀手竟然是年轻了20多岁的自己,一场我与我的对决旋即展开,而背后的真相也逐渐浮出水面。',
129  create_time: '2019-09-23',
130  creator: {
131    _id: '123',
132    username: '系统命名',
133  },
134  //包含的句子
135  posts: [{
136      _id: '5eb3d3027655001700f92fdc',
137      content:'世界上这么多人来追杀我,为什么派你来?'
138    }, {
139      _id: '5eb3d3617655001700f92fdd',
140      content:'我知道他为什么跟你一样厉害,他就是你。'
141    }, {
142      _id: '5eb3d4c17655001700f92fde',
143      content:'你是谁? 他是你的复制人。'
144    },
145    {
146      _id: '5eb3d4da7655001700f92fdf',
147      content:`“我是最厉害的” 
148               “然而你并不是最厉害的”`
149    }, {
150      _id: '5eb3d4f37655001700f92fe0',
151      content:'我们一切的努力危在旦夕,没有人能除掉他,双子杀手会搞定他。'
152    }, {
153      _id: '5eb3d5037655001700f92fe1',
154      content:'你看不出来我很痛苦吗? 别怀疑你自己。'
155    }]
156},{
157  cover: '/images/collectionsImg/movieImg.png',
158  name: '《双子杀手》',
159  intro: '美国国防情报局特工亨利(威尔·史密斯饰),准备退休之际意外遭到一名神秘杀手的追杀,在两人的激烈较量中,他发现这名杀手竟然是年轻了20多岁的自己,一场我与我的对决旋即展开,而背后的真相也逐渐浮出水面。',
160  create_time: '2019-09-23',
161  creator: {
162    _id: '123',
163    username: '系统命名',
164  },
165  //包含的句子
166  posts: [{
167      _id: '5eb3d3027655001700f92fdc',
168      content:'世界上这么多人来追杀我,为什么派你来?'
169    }, {
170      _id: '5eb3d3617655001700f92fdd',
171      content:'我知道他为什么跟你一样厉害,他就是你。'
172    }, {
173      _id: '5eb3d4c17655001700f92fde',
174      content:'你是谁? 他是你的复制人。'
175    },
176    {
177      _id: '5eb3d4da7655001700f92fdf',
178      content:`“我是最厉害的” 
179               “然而你并不是最厉害的”`
180    }, {
181      _id: '5eb3d4f37655001700f92fe0',
182      content:'我们一切的努力危在旦夕,没有人能除掉他,双子杀手会搞定他。'
183    }, {
184      _id: '5eb3d5037655001700f92fe1',
185      content:'你看不出来我很痛苦吗? 别怀疑你自己。'
186    }]
187},{
188  cover: '/images/collectionsImg/movieImg.png',
189  name: '《双子杀手》',
190  intro: '美国国防情报局特工亨利(威尔·史密斯饰),准备退休之际意外遭到一名神秘杀手的追杀,在两人的激烈较量中,他发现这名杀手竟然是年轻了20多岁的自己,一场我与我的对决旋即展开,而背后的真相也逐渐浮出水面。',
191  create_time: '2019-09-23',
192  creator: {
193    _id: '123',
194    username: '系统命名',
195  },
196  //包含的句子
197  posts: [{
198      _id: '5eb3d3027655001700f92fdc',
199      content:'世界上这么多人来追杀我,为什么派你来?'
200    }, {
201      _id: '5eb3d3617655001700f92fdd',
202