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