blob: dd7cca3005fd44869c3c4ed856870c08e5806e8c [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# Copyright 2016 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style
3# license that can be found in the LICENSE file or at
4# https://developers.google.com/open-source/licenses/bsd
5
6"""Tests for monorail.framework.emailfmt."""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import mock
12import unittest
13
14from google.appengine.ext import testbed
15
16import settings
17from framework import emailfmt
18from framework import framework_views
19from proto import project_pb2
20from testing import testing_helpers
21
22from google.appengine.api import apiproxy_stub_map
23
24
25class EmailFmtTest(unittest.TestCase):
26
27 def setUp(self):
28 self.testbed = testbed.Testbed()
29 self.testbed.activate()
30 self.testbed.init_datastore_v3_stub()
31 self.testbed.init_memcache_stub()
32
33 def tearDown(self):
34 self.testbed.deactivate()
35
36 def testValidateReferencesHeader(self):
37 project = project_pb2.Project()
38 project.project_name = 'open-open'
39 subject = 'slipped disk'
40 expected = emailfmt.MakeMessageID(
41 'jrobbins@gmail.com', subject,
42 '%s@%s' % (project.project_name, emailfmt.MailDomain()))
43 self.assertTrue(
44 emailfmt.ValidateReferencesHeader(
45 expected, project, 'jrobbins@gmail.com', subject))
46
47 self.assertFalse(
48 emailfmt.ValidateReferencesHeader(
49 expected, project, 'jrobbins@gmail.com', 'something else'))
50
51 self.assertFalse(
52 emailfmt.ValidateReferencesHeader(
53 expected, project, 'someoneelse@gmail.com', subject))
54
55 project.project_name = 'other-project'
56 self.assertFalse(
57 emailfmt.ValidateReferencesHeader(
58 expected, project, 'jrobbins@gmail.com', subject))
59
60 def testParseEmailMessage(self):
61 msg = testing_helpers.MakeMessage(testing_helpers.HEADER_LINES, 'awesome!')
62
63 (from_addr, to_addrs, cc_addrs, references, incident_id,
64 subject, body) = emailfmt.ParseEmailMessage(msg)
65
66 self.assertEqual('user@example.com', from_addr)
67 self.assertEqual(['proj@monorail.example.com'], to_addrs)
68 self.assertEqual(['ningerso@chromium.org'], cc_addrs)
69 # Expected msg-id was generated from a previous known-good test run.
70 self.assertEqual(['<0=969704940193871313=13442892928193434663='
71 'proj@monorail.example.com>'],
72 references)
73 self.assertEqual('', incident_id)
74 self.assertEqual('Issue 123 in proj: broken link', subject)
75 self.assertEqual('awesome!', body)
76
77 references_header = ('References', '<1234@foo.com> <5678@bar.com>')
78 msg = testing_helpers.MakeMessage(
79 testing_helpers.HEADER_LINES + [references_header], 'awesome!')
80 (from_addr, to_addrs, cc_addrs, references, incident_id, subject,
81 body) = emailfmt.ParseEmailMessage(msg)
82 self.assertItemsEqual(
83 ['<5678@bar.com>',
84 '<0=969704940193871313=13442892928193434663='
85 'proj@monorail.example.com>',
86 '<1234@foo.com>'],
87 references)
88
89 def testParseEmailMessage_Bulk(self):
90 for precedence in ['Bulk', 'Junk']:
91 msg = testing_helpers.MakeMessage(
92 testing_helpers.HEADER_LINES + [('Precedence', precedence)],
93 'I am on vacation!')
94
95 (from_addr, to_addrs, cc_addrs, references, incident_id, subject,
96 body) = emailfmt.ParseEmailMessage(msg)
97
98 self.assertEqual('', from_addr)
99 self.assertEqual([], to_addrs)
100 self.assertEqual([], cc_addrs)
101 self.assertEqual('', references)
102 self.assertEqual('', incident_id)
103 self.assertEqual('', subject)
104 self.assertEqual('', body)
105
106 def testExtractAddrs(self):
107 header_val = ''
108 self.assertEqual(
109 [], emailfmt._ExtractAddrs(header_val))
110
111 header_val = 'J. Robbins <a@b.com>, c@d.com,\n Nick "Name" Dude <e@f.com>'
112 self.assertEqual(
113 ['a@b.com', 'c@d.com', 'e@f.com'],
114 emailfmt._ExtractAddrs(header_val))
115
116 header_val = ('hot: J. O\'Robbins <a@b.com>; '
117 'cool: "friendly" <e.g-h@i-j.k-L.com>')
118 self.assertEqual(
119 ['a@b.com', 'e.g-h@i-j.k-L.com'],
120 emailfmt._ExtractAddrs(header_val))
121
122 def CheckIdentifiedValues(
123 self, project_addr, subject, expected_project_name, expected_local_id,
124 expected_verb=None, expected_label=None):
125 """Testing helper function to check 3 results against expected values."""
126 project_name, verb, label = emailfmt.IdentifyProjectVerbAndLabel(
127 project_addr)
128 local_id = emailfmt.IdentifyIssue(project_name, subject)
129 self.assertEqual(expected_project_name, project_name)
130 self.assertEqual(expected_local_id, local_id)
131 self.assertEqual(expected_verb, verb)
132 self.assertEqual(expected_label, label)
133
134 def testIdentifyProjectAndIssues_Normal(self):
135 """Parse normal issue notification subject lines."""
136 self.CheckIdentifiedValues(
137 'proj@monorail.example.com',
138 'Issue 123 in proj: the dogs wont eat the dogfood',
139 'proj', 123)
140
141 self.CheckIdentifiedValues(
142 'Proj@MonoRail.Example.Com',
143 'Issue 123 in proj: the dogs wont eat the dogfood',
144 'proj', 123)
145
146 self.CheckIdentifiedValues(
147 'proj-4-u@test-example3.com',
148 'Issue 123 in proj-4-u: this one goes to: 11',
149 'proj-4-u', 123)
150
151 self.CheckIdentifiedValues(
152 'night@monorail.example.com',
153 'Issue 451 in day: something is fishy',
154 'night', None)
155
156 def testIdentifyProjectAndIssues_Compact(self):
157 """Parse compact subject lines."""
158 self.CheckIdentifiedValues(
159 'proj@monorail.example.com',
160 'proj:123: the dogs wont eat the dogfood',
161 'proj', 123)
162
163 self.CheckIdentifiedValues(
164 'Proj@MonoRail.Example.Com',
165 'proj:123: the dogs wont eat the dogfood',
166 'proj', 123)
167
168 self.CheckIdentifiedValues(
169 'proj-4-u@test-example3.com',
170 'proj-4-u:123: this one goes to: 11',
171 'proj-4-u', 123)
172
173 self.CheckIdentifiedValues(
174 'night@monorail.example.com',
175 'day:451: something is fishy',
176 'night', None)
177
178 def testIdentifyProjectAndIssues_NotAMatch(self):
179 """These subject lines do not match the ones we send."""
180 self.CheckIdentifiedValues(
181 'no_reply@chromium.org',
182 'Issue 234 in project foo: ignore this one',
183 None, None)
184
185 self.CheckIdentifiedValues(
186 'no_reply@chromium.org',
187 'foo-234: ignore this one',
188 None, None)
189
190 def testStripSubjectPrefixes(self):
191 self.assertEqual(
192 '',
193 emailfmt._StripSubjectPrefixes(''))
194
195 self.assertEqual(
196 'this is it',
197 emailfmt._StripSubjectPrefixes('this is it'))
198
199 self.assertEqual(
200 'this is it',
201 emailfmt._StripSubjectPrefixes('re: this is it'))
202
203 self.assertEqual(
204 'this is it',
205 emailfmt._StripSubjectPrefixes('Re: Fwd: aw:this is it'))
206
207 self.assertEqual(
208 'This - . IS it',
209 emailfmt._StripSubjectPrefixes('This - . IS it'))
210
211
212class MailDomainTest(unittest.TestCase):
213
214 def testTrivialCases(self):
215 self.assertEqual(
216 'testbed-test.appspotmail.com',
217 emailfmt.MailDomain())
218
219
220class NoReplyAddressTest(unittest.TestCase):
221
222 def testNoCommenter(self):
223 self.assertEqual(
224 'no_reply@testbed-test.appspotmail.com',
225 emailfmt.NoReplyAddress())
226
227 def testWithCommenter(self):
228 commenter_view = framework_views.StuffUserView(
229 111, 'user@example.com', True)
230 self.assertEqual(
231 'user via monorail '
232 '<no_reply+v2.111@testbed-test.appspotmail.com>',
233 emailfmt.NoReplyAddress(
234 commenter_view=commenter_view, reveal_addr=True))
235
236 def testObscuredCommenter(self):
237 commenter_view = framework_views.StuffUserView(
238 111, 'user@example.com', True)
239 self.assertEqual(
240 u'u\u2026 via monorail '
241 '<no_reply+v2.111@testbed-test.appspotmail.com>',
242 emailfmt.NoReplyAddress(
243 commenter_view=commenter_view, reveal_addr=False))
244
245
246class FormatFromAddrTest(unittest.TestCase):
247
248 def setUp(self):
249 self.project = project_pb2.Project(project_name='monorail')
250 self.old_send_email_as_format = settings.send_email_as_format
251 settings.send_email_as_format = 'monorail@%(domain)s'
252 self.old_send_noreply_email_as_format = (
253 settings.send_noreply_email_as_format)
254 settings.send_noreply_email_as_format = 'monorail+noreply@%(domain)s'
255
256 def tearDown(self):
257 self.old_send_email_as_format = settings.send_email_as_format
258 self.old_send_noreply_email_as_format = (
259 settings.send_noreply_email_as_format)
260
261 def testNoCommenter(self):
262 self.assertEqual('monorail@chromium.org',
263 emailfmt.FormatFromAddr(self.project))
264
265 @mock.patch('settings.branded_domains',
266 {'monorail': 'bugs.branded.com', '*': 'bugs.chromium.org'})
267 def testNoCommenter_Branded(self):
268 self.assertEqual('monorail@branded.com',
269 emailfmt.FormatFromAddr(self.project))
270
271 def testNoCommenterWithNoReply(self):
272 self.assertEqual('monorail+noreply@chromium.org',
273 emailfmt.FormatFromAddr(self.project, can_reply_to=False))
274
275 @mock.patch('settings.branded_domains',
276 {'monorail': 'bugs.branded.com', '*': 'bugs.chromium.org'})
277 def testNoCommenterWithNoReply_Branded(self):
278 self.assertEqual('monorail+noreply@branded.com',
279 emailfmt.FormatFromAddr(self.project, can_reply_to=False))
280
281 def testWithCommenter(self):
282 commenter_view = framework_views.StuffUserView(
283 111, 'user@example.com', True)
284 self.assertEqual(
285 u'user via monorail <monorail+v2.111@chromium.org>',
286 emailfmt.FormatFromAddr(
287 self.project, commenter_view=commenter_view, reveal_addr=True))
288
289 @mock.patch('settings.branded_domains',
290 {'monorail': 'bugs.branded.com', '*': 'bugs.chromium.org'})
291 def testWithCommenter_Branded(self):
292 commenter_view = framework_views.StuffUserView(
293 111, 'user@example.com', True)
294 self.assertEqual(
295 u'user via monorail <monorail+v2.111@branded.com>',
296 emailfmt.FormatFromAddr(
297 self.project, commenter_view=commenter_view, reveal_addr=True))
298
299 def testObscuredCommenter(self):
300 commenter_view = framework_views.StuffUserView(
301 111, 'user@example.com', True)
302 self.assertEqual(
303 u'u\u2026 via monorail <monorail+v2.111@chromium.org>',
304 emailfmt.FormatFromAddr(
305 self.project, commenter_view=commenter_view, reveal_addr=False))
306
307 def testServiceAccountCommenter(self):
308 johndoe_bot = '123456789@developer.gserviceaccount.com'
309 commenter_view = framework_views.StuffUserView(
310 111, johndoe_bot, True)
311 self.assertEqual(
312 ('johndoe via monorail <monorail+v2.111@chromium.org>'),
313 emailfmt.FormatFromAddr(
314 self.project, commenter_view=commenter_view, reveal_addr=False))
315
316
317class NormalizeHeaderWhitespaceTest(unittest.TestCase):
318
319 def testTrivialCases(self):
320 self.assertEqual(
321 '',
322 emailfmt.NormalizeHeader(''))
323
324 self.assertEqual(
325 '',
326 emailfmt.NormalizeHeader(' \t\n'))
327
328 self.assertEqual(
329 'a',
330 emailfmt.NormalizeHeader('a'))
331
332 self.assertEqual(
333 'a b',
334 emailfmt.NormalizeHeader(' a b '))
335
336 def testLongSummary(self):
337 big_string = 'x' * 500
338 self.assertEqual(
339 big_string[:emailfmt.MAX_HEADER_CHARS_CONSIDERED],
340 emailfmt.NormalizeHeader(big_string))
341
342 big_string = 'x y ' * 500
343 self.assertEqual(
344 big_string[:emailfmt.MAX_HEADER_CHARS_CONSIDERED],
345 emailfmt.NormalizeHeader(big_string))
346
347 big_string = 'x ' * 100
348 self.assertEqual(
349 'x ' * 99 + 'x',
350 emailfmt.NormalizeHeader(big_string))
351
352 def testNormalCase(self):
353 self.assertEqual(
354 '[a] b: c d',
355 emailfmt.NormalizeHeader('[a] b:\tc\n\td'))
356
357
358class MakeMessageIDTest(unittest.TestCase):
359
360 def setUp(self):
361 self.testbed = testbed.Testbed()
362 self.testbed.activate()
363 self.testbed.init_datastore_v3_stub()
364 self.testbed.init_memcache_stub()
365
366 def tearDown(self):
367 self.testbed.deactivate()
368
369 def testMakeMessageIDTest(self):
370 message_id = emailfmt.MakeMessageID(
371 'to@to.com', 'subject', 'from@from.com')
372 self.assertTrue(message_id.startswith('<0='))
373 self.assertEqual('testbed-test.appspotmail.com>',
374 message_id.split('@')[-1])
375
376 settings.mail_domain = None
377 message_id = emailfmt.MakeMessageID(
378 'to@to.com', 'subject', 'from@from.com')
379 self.assertTrue(message_id.startswith('<0='))
380 self.assertEqual('testbed-test.appspotmail.com>',
381 message_id.split('@')[-1])
382
383 message_id = emailfmt.MakeMessageID(
384 'to@to.com', 'subject', 'from@from.com')
385 self.assertTrue(message_id.startswith('<0='))
386 self.assertEqual('testbed-test.appspotmail.com>',
387 message_id.split('@')[-1])
388
389 message_id_ws_1 = emailfmt.MakeMessageID(
390 'to@to.com',
391 'this is a very long subject that is sure to be wordwrapped by gmail',
392 'from@from.com')
393 message_id_ws_2 = emailfmt.MakeMessageID(
394 'to@to.com',
395 'this is a very long subject that \n\tis sure to be '
396 'wordwrapped \t\tby gmail',
397 'from@from.com')
398 self.assertEqual(message_id_ws_1, message_id_ws_2)
399
400
401class GetReferencesTest(unittest.TestCase):
402
403 def setUp(self):
404 self.testbed = testbed.Testbed()
405 self.testbed.activate()
406 self.testbed.init_datastore_v3_stub()
407 self.testbed.init_memcache_stub()
408
409 def tearDown(self):
410 self.testbed.deactivate()
411
412 def testNotPartOfThread(self):
413 refs = emailfmt.GetReferences(
414 'a@a.com', 'hi', None, emailfmt.NoReplyAddress())
415 self.assertEqual(0, len(refs))
416
417 def testAnywhereInThread(self):
418 refs = emailfmt.GetReferences(
419 'a@a.com', 'hi', 0, emailfmt.NoReplyAddress())
420 self.assertTrue(len(refs))
421 self.assertTrue(refs.startswith('<0='))
422
423
424class StripQuotedTextTest(unittest.TestCase):
425
426 def CheckExpected(self, expected_output, test_input):
427 actual_output = emailfmt.StripQuotedText(test_input)
428 self.assertEqual(expected_output, actual_output)
429
430 def testAllNewText(self):
431 self.CheckExpected('', '')
432 self.CheckExpected('', '\n')
433 self.CheckExpected('', '\n\n')
434 self.CheckExpected('new', 'new')
435 self.CheckExpected('new', '\nnew\n')
436 self.CheckExpected('new\ntext', '\nnew\ntext\n')
437 self.CheckExpected('new\n\ntext', '\nnew\n\ntext\n')
438
439 def testQuotedLines(self):
440 self.CheckExpected(
441 ('new\n'
442 'text'),
443 ('new\n'
444 'text\n'
445 '\n'
446 '> something you said\n'
447 '> that took two lines'))
448
449 self.CheckExpected(
450 ('new\n'
451 'text'),
452 ('new\n'
453 'text\n'
454 '\n'
455 '> something you said\n'
456 '> that took two lines'))
457
458 self.CheckExpected(
459 ('new\n'
460 'text'),
461 ('> something you said\n'
462 '> that took two lines\n'
463 'new\n'
464 'text\n'
465 '\n'))
466
467 self.CheckExpected(
468 ('newtext'),
469 ('> something you said\n'
470 '> that took two lines\n'
471 'newtext'))
472
473 self.CheckExpected(
474 ('new\n'
475 'text'),
476 ('new\n'
477 'text\n'
478 '\n'
479 '> something you said\n'
480 '> > in response to some other junk'))
481
482 self.CheckExpected(
483 ('new\n'
484 '\n'
485 'text'),
486 ('new\n'
487 '\n'
488 '> something you said\n'
489 '> > in response to some other junk\n'
490 '\n'
491 'text\n'))
492
493 self.CheckExpected(
494 ('new\n'
495 '\n'
496 'text'),
497 ('new\n'
498 'On Mon, Jan 1, 2023, So-and-so <so@and-so.com> Wrote:\n'
499 '> something you said\n'
500 '> > in response to some other junk\n'
501 '\n'
502 'text\n'))
503
504 self.CheckExpected(
505 ('new\n'
506 '\n'
507 'text'),
508 ('new\n'
509 'On Mon, Jan 1, 2023, So-and-so <so@and-so.com> Wrote:\n'
510 '\n'
511 '> something you said\n'
512 '> > in response to some other junk\n'
513 '\n'
514 'text\n'))
515
516 self.CheckExpected(
517 ('new\n'
518 '\n'
519 'text'),
520 ('new\n'
521 'On Mon, Jan 1, 2023, user@example.com via Monorail\n'
522 '<monorail@chromium.com> Wrote:\n'
523 '\n'
524 '> something you said\n'
525 '> > in response to some other junk\n'
526 '\n'
527 'text\n'))
528
529 self.CheckExpected(
530 ('new\n'
531 '\n'
532 'text'),
533 ('new\n'
534 'On Jan 14, 2016 6:19 AM, "user@example.com via Monorail" <\n'
535 'monorail@chromium.com> Wrote:\n'
536 '\n'
537 '> something you said\n'
538 '> > in response to some other junk\n'
539 '\n'
540 'text\n'))
541
542 self.CheckExpected(
543 ('new\n'
544 '\n'
545 'text'),
546 ('new\n'
547 'On Jan 14, 2016 6:19 AM, "user@example.com via Monorail" <\n'
548 'monorail@monorail-prod.appspotmail.com> wrote:\n'
549 '\n'
550 '> something you said\n'
551 '> > in response to some other junk\n'
552 '\n'
553 'text\n'))
554
555 self.CheckExpected(
556 ('new\n'
557 '\n'
558 'text'),
559 ('new\n'
560 'On Mon, Jan 1, 2023, So-and-so so@and-so.com wrote:\n'
561 '\n'
562 '> something you said\n'
563 '> > in response to some other junk\n'
564 '\n'
565 'text\n'))
566
567 self.CheckExpected(
568 ('new\n'
569 '\n'
570 'text'),
571 ('new\n'
572 'On Wed, Sep 8, 2010 at 6:56 PM, So =AND= <so@gmail.com>wrote:\n'
573 '\n'
574 '> something you said\n'
575 '> > in response to some other junk\n'
576 '\n'
577 'text\n'))
578
579 self.CheckExpected(
580 ('new\n'
581 '\n'
582 'text'),
583 ('new\n'
584 'On Mon, Jan 1, 2023, So-and-so <so@and-so.com> Wrote:\n'
585 '\n'
586 '> something you said\n'
587 '> > in response to some other junk\n'
588 '\n'
589 'text\n'))
590
591 self.CheckExpected(
592 ('new\n'
593 '\n'
594 'text'),
595 ('new\n'
596 'project-name@testbed-test.appspotmail.com wrote:\n'
597 '\n'
598 '> something you said\n'
599 '> > in response to some other junk\n'
600 '\n'
601 'text\n'))
602
603 self.CheckExpected(
604 ('new\n'
605 '\n'
606 'text'),
607 ('new\n'
608 'project-name@testbed-test.appspotmail.com a \xc3\xa9crit :\n'
609 '\n'
610 '> something you said\n'
611 '> > in response to some other junk\n'
612 '\n'
613 'text\n'))
614
615 self.CheckExpected(
616 ('new\n'
617 '\n'
618 'text'),
619 ('new\n'
620 'project.domain.com@testbed-test.appspotmail.com a \xc3\xa9crit :\n'
621 '\n'
622 '> something you said\n'
623 '> > in response to some other junk\n'
624 '\n'
625 'text\n'))
626
627 self.CheckExpected(
628 ('new\n'
629 '\n'
630 'text'),
631 ('new\n'
632 '2023/01/4 <so@and-so.com>\n'
633 '\n'
634 '> something you said\n'
635 '> > in response to some other junk\n'
636 '\n'
637 'text\n'))
638
639 self.CheckExpected(
640 ('new\n'
641 '\n'
642 'text'),
643 ('new\n'
644 '2023/01/4 <so-and@so.com>\n'
645 '\n'
646 '> something you said\n'
647 '> > in response to some other junk\n'
648 '\n'
649 'text\n'))
650
651 def testBoundaryLines(self):
652
653 self.CheckExpected(
654 ('new'),
655 ('new\n'
656 '---- forwarded message ======\n'
657 '\n'
658 'something you said\n'
659 '> in response to some other junk\n'
660 '\n'
661 'text\n'))
662
663 self.CheckExpected(
664 ('new'),
665 ('new\n'
666 '-----Original Message-----\n'
667 '\n'
668 'something you said\n'
669 '> in response to some other junk\n'
670 '\n'
671 'text\n'))
672
673 self.CheckExpected(
674 ('new'),
675 ('new\n'
676 '\n'
677 'Updates:\n'
678 '\tStatus: Fixed\n'
679 '\n'
680 'notification text\n'))
681
682 self.CheckExpected(
683 ('new'),
684 ('new\n'
685 '\n'
686 'Comment #1 on issue 9 by username: Is there ...'
687 'notification text\n'))
688
689 def testSignatures(self):
690
691 self.CheckExpected(
692 ('new\n'
693 'text'),
694 ('new\n'
695 'text\n'
696 '-- \n'
697 'Name\n'
698 'phone\n'
699 'funny quote, or legal disclaimers\n'))
700
701 self.CheckExpected(
702 ('new\n'
703 'text'),
704 ('new\n'
705 'text\n'
706 '--\n'
707 'Name\n'
708 'phone\n'
709 'funny quote, or legal disclaimers\n'))
710
711 self.CheckExpected(
712 ('new\n'
713 'text'),
714 ('new\n'
715 'text\n'
716 '--\n'
717 'Name\n'
718 'ginormous signature\n'
719 'phone\n'
720 'address\n'
721 'address\n'
722 'address\n'
723 'homepage\n'
724 'social network A\n'
725 'social network B\n'
726 'social network C\n'
727 'funny quote\n'
728 '4 lines about why email should be short\n'
729 'legal disclaimers\n'))
730
731 self.CheckExpected(
732 ('new\n'
733 'text'),
734 ('new\n'
735 'text\n'
736 '_______________\n'
737 'Name\n'
738 'phone\n'
739 'funny quote, or legal disclaimers\n'))
740
741 self.CheckExpected(
742 ('new\n'
743 'text'),
744 ('new\n'
745 'text\n'
746 '\n'
747 'Thanks,\n'
748 'Name\n'
749 '\n'
750 '_______________\n'
751 'Name\n'
752 'phone\n'
753 'funny quote, or legal disclaimers\n'))
754
755 self.CheckExpected(
756 ('new\n'
757 'text'),
758 ('new\n'
759 'text\n'
760 '\n'
761 'Thanks,\n'
762 'Name'))
763
764 self.CheckExpected(
765 ('new\n'
766 'text'),
767 ('new\n'
768 'text\n'
769 '\n'
770 'Cheers,\n'
771 'Name'))
772
773 self.CheckExpected(
774 ('new\n'
775 'text'),
776 ('new\n'
777 'text\n'
778 '\n'
779 'Regards\n'
780 'Name'))
781
782 self.CheckExpected(
783 ('new\n'
784 'text'),
785 ('new\n'
786 'text\n'
787 '\n'
788 'best regards'))
789
790 self.CheckExpected(
791 ('new\n'
792 'text'),
793 ('new\n'
794 'text\n'
795 '\n'
796 'THX'))
797
798 self.CheckExpected(
799 ('new\n'
800 'text'),
801 ('new\n'
802 'text\n'
803 '\n'
804 'Thank you,\n'
805 'Name'))
806
807 self.CheckExpected(
808 ('new\n'
809 'text'),
810 ('new\n'
811 'text\n'
812 '\n'
813 'Sent from my iPhone'))
814
815 self.CheckExpected(
816 ('new\n'
817 'text'),
818 ('new\n'
819 'text\n'
820 '\n'
821 'Sent from my iPod'))