1 # -*- encoding: utf-8 -*-
2 #
3 # Copyright (c) 2001 Richard Jones, richard@bofh.asn.au.
4 # This module is free software, and you may redistribute it and/or modify
5 # under the same terms as Python, so long as this copyright message and
6 # disclaimer are retained in their original form.
7 #
8 # This module is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 #
12 # $Id: test_mailgw.py,v 1.96 2008-08-19 01:40:59 richard Exp $
14 # TODO: test bcc
16 import unittest, tempfile, os, shutil, errno, imp, sys, difflib, rfc822, time
18 from cStringIO import StringIO
20 if not os.environ.has_key('SENDMAILDEBUG'):
21 os.environ['SENDMAILDEBUG'] = 'mail-test.log'
22 SENDMAILDEBUG = os.environ['SENDMAILDEBUG']
24 from roundup.mailgw import MailGW, Unauthorized, uidFromAddress, \
25 parseContent, IgnoreLoop, IgnoreBulk, MailUsageError, MailUsageHelp
26 from roundup import init, instance, password, rfc2822, __version__
27 from roundup.anypy.sets_ import set
29 import db_test_base
31 class Message(rfc822.Message):
32 """String-based Message class with equivalence test."""
33 def __init__(self, s):
34 rfc822.Message.__init__(self, StringIO(s.strip()))
36 def __eq__(self, other):
37 return (self.dict == other.dict and
38 self.fp.read() == other.fp.read())
40 class DiffHelper:
41 def compareMessages(self, new, old):
42 """Compare messages for semantic equivalence."""
43 new, old = Message(new), Message(old)
45 # all Roundup-generated messages have "Precedence: bulk"
46 old['Precedence'] = 'bulk'
48 # don't try to compare the date
49 del new['date'], old['date']
51 if not new == old:
52 res = []
54 replace = {}
55 for key in new.keys():
56 if key.startswith('from '):
57 # skip the unix from line
58 continue
59 if key.lower() == 'x-roundup-version':
60 # version changes constantly, so handle it specially
61 if new[key] != __version__:
62 res.append(' %s: %r != %r' % (key, __version__,
63 new[key]))
64 elif key.lower() == 'content-type' and 'boundary=' in new[key]:
65 # handle mime messages
66 newmime = new[key].split('=',1)[-1].strip('"')
67 oldmime = old.get(key, '').split('=',1)[-1].strip('"')
68 replace ['--' + newmime] = '--' + oldmime
69 replace ['--' + newmime + '--'] = '--' + oldmime + '--'
70 elif new.get(key, '') != old.get(key, ''):
71 res.append(' %s: %r != %r' % (key, old.get(key, ''),
72 new.get(key, '')))
74 body_diff = self.compareStrings(new.fp.read(), old.fp.read(),
75 replace=replace)
76 if body_diff:
77 res.append('')
78 res.extend(body_diff)
80 if res:
81 res.insert(0, 'Generated message not correct (diff follows):')
82 raise AssertionError, '\n'.join(res)
84 def compareStrings(self, s2, s1, replace={}):
85 '''Note the reversal of s2 and s1 - difflib.SequenceMatcher wants
86 the first to be the "original" but in the calls in this file,
87 the second arg is the original. Ho hum.
88 Do replacements over the replace dict -- used for mime boundary
89 '''
90 l1 = s1.strip().split('\n')
91 l2 = [replace.get(i,i) for i in s2.strip().split('\n')]
92 if l1 == l2:
93 return
94 s = difflib.SequenceMatcher(None, l1, l2)
95 res = []
96 for value, s1s, s1e, s2s, s2e in s.get_opcodes():
97 if value == 'equal':
98 for i in range(s1s, s1e):
99 res.append(' %s'%l1[i])
100 elif value == 'delete':
101 for i in range(s1s, s1e):
102 res.append('- %s'%l1[i])
103 elif value == 'insert':
104 for i in range(s2s, s2e):
105 res.append('+ %s'%l2[i])
106 elif value == 'replace':
107 for i, j in zip(range(s1s, s1e), range(s2s, s2e)):
108 res.append('- %s'%l1[i])
109 res.append('+ %s'%l2[j])
111 return res
113 class MailgwTestCase(unittest.TestCase, DiffHelper):
114 count = 0
115 schema = 'classic'
116 def setUp(self):
117 MailgwTestCase.count = MailgwTestCase.count + 1
118 self.dirname = '_test_mailgw_%s'%self.count
119 # set up and open a tracker
120 self.instance = db_test_base.setupTracker(self.dirname)
122 # and open the database
123 self.db = self.instance.open('admin')
124 self.chef_id = self.db.user.create(username='Chef',
125 address='chef@bork.bork.bork', realname='Bork, Chef', roles='User')
126 self.richard_id = self.db.user.create(username='richard',
127 address='richard@test.test', roles='User')
128 self.mary_id = self.db.user.create(username='mary',
129 address='mary@test.test', roles='User', realname='Contrary, Mary')
130 self.john_id = self.db.user.create(username='john',
131 address='john@test.test', roles='User', realname='John Doe',
132 alternate_addresses='jondoe@test.test\njohn.doe@test.test')
134 def tearDown(self):
135 if os.path.exists(SENDMAILDEBUG):
136 os.remove(SENDMAILDEBUG)
137 self.db.close()
138 try:
139 shutil.rmtree(self.dirname)
140 except OSError, error:
141 if error.errno not in (errno.ENOENT, errno.ESRCH): raise
143 def _handle_mail(self, message):
144 # handler will open a new db handle. On single-threaded
145 # databases we'll have to close our current connection
146 self.db.commit()
147 self.db.close()
148 handler = self.instance.MailGW(self.instance)
149 handler.trapExceptions = 0
150 ret = handler.main(StringIO(message))
151 # handler had its own database, open new connection
152 self.db = self.instance.open('admin')
153 return ret
155 def _get_mail(self):
156 f = open(SENDMAILDEBUG)
157 try:
158 return f.read()
159 finally:
160 f.close()
162 def testEmptyMessage(self):
163 nodeid = self._handle_mail('''Content-Type: text/plain;
164 charset="iso-8859-1"
165 From: Chef <chef@bork.bork.bork>
166 To: issue_tracker@your.tracker.email.domain.example
167 Cc: richard@test.test
168 Reply-To: chef@bork.bork.bork
169 Message-Id: <dummy_test_message_id>
170 Subject: [issue] Testing...
172 ''')
173 assert not os.path.exists(SENDMAILDEBUG)
174 self.assertEqual(self.db.issue.get(nodeid, 'title'), 'Testing...')
176 def doNewIssue(self):
177 nodeid = self._handle_mail('''Content-Type: text/plain;
178 charset="iso-8859-1"
179 From: Chef <chef@bork.bork.bork>
180 To: issue_tracker@your.tracker.email.domain.example
181 Cc: richard@test.test
182 Message-Id: <dummy_test_message_id>
183 Subject: [issue] Testing...
185 This is a test submission of a new issue.
186 ''')
187 assert not os.path.exists(SENDMAILDEBUG)
188 l = self.db.issue.get(nodeid, 'nosy')
189 l.sort()
190 self.assertEqual(l, [self.chef_id, self.richard_id])
191 return nodeid
193 def testNewIssue(self):
194 self.doNewIssue()
196 def testNewIssueNosy(self):
197 self.instance.config.ADD_AUTHOR_TO_NOSY = 'yes'
198 nodeid = self._handle_mail('''Content-Type: text/plain;
199 charset="iso-8859-1"
200 From: Chef <chef@bork.bork.bork>
201 To: issue_tracker@your.tracker.email.domain.example
202 Cc: richard@test.test
203 Message-Id: <dummy_test_message_id>
204 Subject: [issue] Testing...
206 This is a test submission of a new issue.
207 ''')
208 assert not os.path.exists(SENDMAILDEBUG)
209 l = self.db.issue.get(nodeid, 'nosy')
210 l.sort()
211 self.assertEqual(l, [self.chef_id, self.richard_id])
213 def testAlternateAddress(self):
214 self._handle_mail('''Content-Type: text/plain;
215 charset="iso-8859-1"
216 From: John Doe <john.doe@test.test>
217 To: issue_tracker@your.tracker.email.domain.example
218 Message-Id: <dummy_test_message_id>
219 Subject: [issue] Testing...
221 This is a test submission of a new issue.
222 ''')
223 userlist = self.db.user.list()
224 assert not os.path.exists(SENDMAILDEBUG)
225 self.assertEqual(userlist, self.db.user.list(),
226 "user created when it shouldn't have been")
228 def testNewIssueNoClass(self):
229 self._handle_mail('''Content-Type: text/plain;
230 charset="iso-8859-1"
231 From: Chef <chef@bork.bork.bork>
232 To: issue_tracker@your.tracker.email.domain.example
233 Cc: richard@test.test
234 Message-Id: <dummy_test_message_id>
235 Subject: Testing...
237 This is a test submission of a new issue.
238 ''')
239 assert not os.path.exists(SENDMAILDEBUG)
241 def testNewIssueAuthMsg(self):
242 # TODO: fix the damn config - this is apalling
243 self.db.config.MESSAGES_TO_AUTHOR = 'yes'
244 self._handle_mail('''Content-Type: text/plain;
245 charset="iso-8859-1"
246 From: Chef <chef@bork.bork.bork>
247 To: issue_tracker@your.tracker.email.domain.example
248 Message-Id: <dummy_test_message_id>
249 Subject: [issue] Testing... [nosy=mary; assignedto=richard]
251 This is a test submission of a new issue.
252 ''')
253 self.compareMessages(self._get_mail(),
254 '''FROM: roundup-admin@your.tracker.email.domain.example
255 TO: chef@bork.bork.bork, mary@test.test, richard@test.test
256 Content-Type: text/plain; charset="utf-8"
257 Subject: [issue1] Testing...
258 To: chef@bork.bork.bork, mary@test.test, richard@test.test
259 From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
260 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
261 MIME-Version: 1.0
262 Message-Id: <dummy_test_message_id>
263 X-Roundup-Name: Roundup issue tracker
264 X-Roundup-Loop: hello
265 X-Roundup-Issue-Status: unread
266 Content-Transfer-Encoding: quoted-printable
269 New submission from Bork, Chef <chef@bork.bork.bork>:
271 This is a test submission of a new issue.
273 ----------
274 assignedto: richard
275 messages: 1
276 nosy: Chef, mary, richard
277 status: unread
278 title: Testing...
280 _______________________________________________________________________
281 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
282 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
283 _______________________________________________________________________
284 ''')
286 def testNewIssueNoAuthorInfo(self):
287 self.db.config.MAIL_ADD_AUTHORINFO = 'no'
288 self._handle_mail('''Content-Type: text/plain;
289 charset="iso-8859-1"
290 From: Chef <chef@bork.bork.bork>
291 To: issue_tracker@your.tracker.email.domain.example
292 Message-Id: <dummy_test_message_id>
293 Subject: [issue] Testing... [nosy=mary; assignedto=richard]
295 This is a test submission of a new issue.
296 ''')
297 self.compareMessages(self._get_mail(),
298 '''FROM: roundup-admin@your.tracker.email.domain.example
299 TO: chef@bork.bork.bork, mary@test.test, richard@test.test
300 Content-Type: text/plain; charset="utf-8"
301 Subject: [issue1] Testing...
302 To: mary@test.test, richard@test.test
303 From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
304 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
305 MIME-Version: 1.0
306 Message-Id: <dummy_test_message_id>
307 X-Roundup-Name: Roundup issue tracker
308 X-Roundup-Loop: hello
309 X-Roundup-Issue-Status: unread
310 Content-Transfer-Encoding: quoted-printable
312 This is a test submission of a new issue.
314 ----------
315 assignedto: richard
316 messages: 1
317 nosy: Chef, mary, richard
318 status: unread
319 title: Testing...
321 _______________________________________________________________________
322 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
323 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
324 _______________________________________________________________________
325 ''')
327 def testNewIssueNoAuthorEmail(self):
328 self.db.config.MAIL_ADD_AUTHOREMAIL = 'no'
329 self._handle_mail('''Content-Type: text/plain;
330 charset="iso-8859-1"
331 From: Chef <chef@bork.bork.bork>
332 To: issue_tracker@your.tracker.email.domain.example
333 Message-Id: <dummy_test_message_id>
334 Subject: [issue] Testing... [nosy=mary; assignedto=richard]
336 This is a test submission of a new issue.
337 ''')
338 self.compareMessages(self._get_mail(),
339 '''FROM: roundup-admin@your.tracker.email.domain.example
340 TO: chef@bork.bork.bork, mary@test.test, richard@test.test
341 Content-Type: text/plain; charset="utf-8"
342 Subject: [issue1] Testing...
343 To: mary@test.test, richard@test.test
344 From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
345 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
346 MIME-Version: 1.0
347 Message-Id: <dummy_test_message_id>
348 X-Roundup-Name: Roundup issue tracker
349 X-Roundup-Loop: hello
350 X-Roundup-Issue-Status: unread
351 Content-Transfer-Encoding: quoted-printable
353 New submission from Bork, Chef:
355 This is a test submission of a new issue.
357 ----------
358 assignedto: richard
359 messages: 1
360 nosy: Chef, mary, richard
361 status: unread
362 title: Testing...
364 _______________________________________________________________________
365 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
366 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
367 _______________________________________________________________________
368 ''')
370 multipart_msg = '''From: mary <mary@test.test>
371 To: issue_tracker@your.tracker.email.domain.example
372 Message-Id: <followup_dummy_id>
373 In-Reply-To: <dummy_test_message_id>
374 Subject: [issue1] Testing...
375 Content-Type: multipart/mixed; boundary="bxyzzy"
376 Content-Disposition: inline
379 --bxyzzy
380 Content-Type: multipart/alternative; boundary="bCsyhTFzCvuiizWE"
381 Content-Disposition: inline
383 --bCsyhTFzCvuiizWE
384 Content-Type: text/plain; charset=us-ascii
385 Content-Disposition: inline
387 test attachment first text/plain
389 --bCsyhTFzCvuiizWE
390 Content-Type: application/octet-stream
391 Content-Disposition: attachment; filename="first.dvi"
392 Content-Transfer-Encoding: base64
394 SnVzdCBhIHRlc3QgAQo=
396 --bCsyhTFzCvuiizWE
397 Content-Type: text/plain; charset=us-ascii
398 Content-Disposition: inline
400 test attachment second text/plain
402 --bCsyhTFzCvuiizWE
403 Content-Type: text/html
404 Content-Disposition: inline
406 <html>
407 to be ignored.
408 </html>
410 --bCsyhTFzCvuiizWE--
412 --bxyzzy
413 Content-Type: multipart/alternative; boundary="bCsyhTFzCvuiizWF"
414 Content-Disposition: inline
416 --bCsyhTFzCvuiizWF
417 Content-Type: text/plain; charset=us-ascii
418 Content-Disposition: inline
420 test attachment third text/plain
422 --bCsyhTFzCvuiizWF
423 Content-Type: application/octet-stream
424 Content-Disposition: attachment; filename="second.dvi"
425 Content-Transfer-Encoding: base64
427 SnVzdCBhIHRlc3QK
429 --bCsyhTFzCvuiizWF--
431 --bxyzzy--
432 '''
434 def testMultipartKeepAlternatives(self):
435 self.doNewIssue()
436 self._handle_mail(self.multipart_msg)
437 messages = self.db.issue.get('1', 'messages')
438 messages.sort()
439 msg = self.db.msg.getnode (messages[-1])
440 assert(len(msg.files) == 5)
441 names = {0 : 'first.dvi', 4 : 'second.dvi'}
442 content = {3 : 'test attachment third text/plain\n',
443 4 : 'Just a test\n'}
444 for n, id in enumerate (msg.files):
445 f = self.db.file.getnode (id)
446 self.assertEqual(f.name, names.get (n, 'unnamed'))
447 if n in content :
448 self.assertEqual(f.content, content [n])
449 self.assertEqual(msg.content, 'test attachment second text/plain')
451 def testMultipartDropAlternatives(self):
452 self.doNewIssue()
453 self.db.config.MAILGW_IGNORE_ALTERNATIVES = True
454 self._handle_mail(self.multipart_msg)
455 messages = self.db.issue.get('1', 'messages')
456 messages.sort()
457 msg = self.db.msg.getnode (messages[-1])
458 assert(len(msg.files) == 2)
459 names = {1 : 'second.dvi'}
460 content = {0 : 'test attachment third text/plain\n',
461 1 : 'Just a test\n'}
462 for n, id in enumerate (msg.files):
463 f = self.db.file.getnode (id)
464 self.assertEqual(f.name, names.get (n, 'unnamed'))
465 if n in content :
466 self.assertEqual(f.content, content [n])
467 self.assertEqual(msg.content, 'test attachment second text/plain')
469 def testSimpleFollowup(self):
470 self.doNewIssue()
471 self._handle_mail('''Content-Type: text/plain;
472 charset="iso-8859-1"
473 From: mary <mary@test.test>
474 To: issue_tracker@your.tracker.email.domain.example
475 Message-Id: <followup_dummy_id>
476 In-Reply-To: <dummy_test_message_id>
477 Subject: [issue1] Testing...
479 This is a second followup
480 ''')
481 self.compareMessages(self._get_mail(),
482 '''FROM: roundup-admin@your.tracker.email.domain.example
483 TO: chef@bork.bork.bork, richard@test.test
484 Content-Type: text/plain; charset="utf-8"
485 Subject: [issue1] Testing...
486 To: chef@bork.bork.bork, richard@test.test
487 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
488 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
489 MIME-Version: 1.0
490 Message-Id: <followup_dummy_id>
491 In-Reply-To: <dummy_test_message_id>
492 X-Roundup-Name: Roundup issue tracker
493 X-Roundup-Loop: hello
494 X-Roundup-Issue-Status: chatting
495 Content-Transfer-Encoding: quoted-printable
498 Contrary, Mary <mary@test.test> added the comment:
500 This is a second followup
502 ----------
503 status: unread -> chatting
505 _______________________________________________________________________
506 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
507 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
508 _______________________________________________________________________
509 ''')
511 def testFollowup(self):
512 self.doNewIssue()
514 self._handle_mail('''Content-Type: text/plain;
515 charset="iso-8859-1"
516 From: richard <richard@test.test>
517 To: issue_tracker@your.tracker.email.domain.example
518 Message-Id: <followup_dummy_id>
519 In-Reply-To: <dummy_test_message_id>
520 Subject: [issue1] Testing... [assignedto=mary; nosy=+john]
522 This is a followup
523 ''')
524 l = self.db.issue.get('1', 'nosy')
525 l.sort()
526 self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id,
527 self.john_id])
529 self.compareMessages(self._get_mail(),
530 '''FROM: roundup-admin@your.tracker.email.domain.example
531 TO: chef@bork.bork.bork, john@test.test, mary@test.test
532 Content-Type: text/plain; charset="utf-8"
533 Subject: [issue1] Testing...
534 To: chef@bork.bork.bork, john@test.test, mary@test.test
535 From: richard <issue_tracker@your.tracker.email.domain.example>
536 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
537 MIME-Version: 1.0
538 Message-Id: <followup_dummy_id>
539 In-Reply-To: <dummy_test_message_id>
540 X-Roundup-Name: Roundup issue tracker
541 X-Roundup-Loop: hello
542 X-Roundup-Issue-Status: chatting
543 Content-Transfer-Encoding: quoted-printable
546 richard <richard@test.test> added the comment:
548 This is a followup
550 ----------
551 assignedto: -> mary
552 nosy: +john, mary
553 status: unread -> chatting
555 _______________________________________________________________________
556 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
557 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
558 _______________________________________________________________________
559 ''')
561 def testPropertyChangeOnly(self):
562 self.doNewIssue()
563 oldvalues = self.db.getnode('issue', '1').copy()
564 oldvalues['assignedto'] = None
565 # reconstruct old behaviour: This would reuse the
566 # database-handle from the doNewIssue above which has committed
567 # as user "Chef". So we close and reopen the db as that user.
568 self.db.close()
569 self.db = self.instance.open('Chef')
570 self.db.issue.set('1', assignedto=self.chef_id)
571 self.db.commit()
572 self.db.issue.nosymessage('1', None, oldvalues)
574 new_mail = ""
575 for line in self._get_mail().split("\n"):
576 if "Message-Id: " in line:
577 continue
578 if "Date: " in line:
579 continue
580 new_mail += line+"\n"
582 self.compareMessages(new_mail, """
583 FROM: roundup-admin@your.tracker.email.domain.example
584 TO: chef@bork.bork.bork, richard@test.test
585 Content-Type: text/plain; charset="utf-8"
586 Subject: [issue1] Testing...
587 To: chef@bork.bork.bork, richard@test.test
588 From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
589 X-Roundup-Name: Roundup issue tracker
590 X-Roundup-Loop: hello
591 X-Roundup-Issue-Status: unread
592 X-Roundup-Version: 1.3.3
593 MIME-Version: 1.0
594 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
595 Content-Transfer-Encoding: quoted-printable
598 Change by Bork, Chef <chef@bork.bork.bork>:
601 ----------
602 assignedto: -> Chef
604 _______________________________________________________________________
605 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
606 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
607 _______________________________________________________________________
608 """)
611 #
612 # FOLLOWUP TITLE MATCH
613 #
614 def testFollowupTitleMatch(self):
615 self.doNewIssue()
616 self._handle_mail('''Content-Type: text/plain;
617 charset="iso-8859-1"
618 From: richard <richard@test.test>
619 To: issue_tracker@your.tracker.email.domain.example
620 Message-Id: <followup_dummy_id>
621 Subject: Re: Testing... [assignedto=mary; nosy=+john]
623 This is a followup
624 ''')
625 self.compareMessages(self._get_mail(),
626 '''FROM: roundup-admin@your.tracker.email.domain.example
627 TO: chef@bork.bork.bork, john@test.test, mary@test.test
628 Content-Type: text/plain; charset="utf-8"
629 Subject: [issue1] Testing...
630 To: chef@bork.bork.bork, john@test.test, mary@test.test
631 From: richard <issue_tracker@your.tracker.email.domain.example>
632 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
633 MIME-Version: 1.0
634 Message-Id: <followup_dummy_id>
635 In-Reply-To: <dummy_test_message_id>
636 X-Roundup-Name: Roundup issue tracker
637 X-Roundup-Loop: hello
638 X-Roundup-Issue-Status: chatting
639 Content-Transfer-Encoding: quoted-printable
642 richard <richard@test.test> added the comment:
644 This is a followup
646 ----------
647 assignedto: -> mary
648 nosy: +john, mary
649 status: unread -> chatting
651 _______________________________________________________________________
652 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
653 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
654 _______________________________________________________________________
655 ''')
657 def testFollowupTitleMatchMultiRe(self):
658 nodeid1 = self.doNewIssue()
659 nodeid2 = self._handle_mail('''Content-Type: text/plain;
660 charset="iso-8859-1"
661 From: richard <richard@test.test>
662 To: issue_tracker@your.tracker.email.domain.example
663 Message-Id: <followup_dummy_id>
664 Subject: Re: Testing... [assignedto=mary; nosy=+john]
666 This is a followup
667 ''')
669 nodeid3 = self._handle_mail('''Content-Type: text/plain;
670 charset="iso-8859-1"
671 From: richard <richard@test.test>
672 To: issue_tracker@your.tracker.email.domain.example
673 Message-Id: <followup2_dummy_id>
674 Subject: Ang: Re: Testing...
676 This is a followup
677 ''')
678 self.assertEqual(nodeid1, nodeid2)
679 self.assertEqual(nodeid1, nodeid3)
681 def testFollowupTitleMatchNever(self):
682 nodeid = self.doNewIssue()
683 self.db.config.MAILGW_SUBJECT_CONTENT_MATCH = 'never'
684 self.assertNotEqual(self._handle_mail('''Content-Type: text/plain;
685 charset="iso-8859-1"
686 From: richard <richard@test.test>
687 To: issue_tracker@your.tracker.email.domain.example
688 Message-Id: <followup_dummy_id>
689 Subject: Re: Testing...
691 This is a followup
692 '''), nodeid)
694 def testFollowupTitleMatchNeverInterval(self):
695 nodeid = self.doNewIssue()
696 # force failure of the interval
697 time.sleep(2)
698 self.db.config.MAILGW_SUBJECT_CONTENT_MATCH = 'creation 00:00:01'
699 self.assertNotEqual(self._handle_mail('''Content-Type: text/plain;
700 charset="iso-8859-1"
701 From: richard <richard@test.test>
702 To: issue_tracker@your.tracker.email.domain.example
703 Message-Id: <followup_dummy_id>
704 Subject: Re: Testing...
706 This is a followup
707 '''), nodeid)
710 def testFollowupTitleMatchInterval(self):
711 nodeid = self.doNewIssue()
712 self.db.config.MAILGW_SUBJECT_CONTENT_MATCH = 'creation +1d'
713 self.assertEqual(self._handle_mail('''Content-Type: text/plain;
714 charset="iso-8859-1"
715 From: richard <richard@test.test>
716 To: issue_tracker@your.tracker.email.domain.example
717 Message-Id: <followup_dummy_id>
718 Subject: Re: Testing...
720 This is a followup
721 '''), nodeid)
724 def testFollowupNosyAuthor(self):
725 self.doNewIssue()
726 self.db.config.ADD_AUTHOR_TO_NOSY = 'yes'
727 self._handle_mail('''Content-Type: text/plain;
728 charset="iso-8859-1"
729 From: john@test.test
730 To: issue_tracker@your.tracker.email.domain.example
731 Message-Id: <followup_dummy_id>
732 In-Reply-To: <dummy_test_message_id>
733 Subject: [issue1] Testing...
735 This is a followup
736 ''')
738 self.compareMessages(self._get_mail(),
739 '''FROM: roundup-admin@your.tracker.email.domain.example
740 TO: chef@bork.bork.bork, richard@test.test
741 Content-Type: text/plain; charset="utf-8"
742 Subject: [issue1] Testing...
743 To: chef@bork.bork.bork, richard@test.test
744 From: John Doe <issue_tracker@your.tracker.email.domain.example>
745 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
746 MIME-Version: 1.0
747 Message-Id: <followup_dummy_id>
748 In-Reply-To: <dummy_test_message_id>
749 X-Roundup-Name: Roundup issue tracker
750 X-Roundup-Loop: hello
751 X-Roundup-Issue-Status: chatting
752 Content-Transfer-Encoding: quoted-printable
755 John Doe <john@test.test> added the comment:
757 This is a followup
759 ----------
760 nosy: +john
761 status: unread -> chatting
763 _______________________________________________________________________
764 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
765 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
766 _______________________________________________________________________
768 ''')
770 def testFollowupNosyRecipients(self):
771 self.doNewIssue()
772 self.db.config.ADD_RECIPIENTS_TO_NOSY = 'yes'
773 self._handle_mail('''Content-Type: text/plain;
774 charset="iso-8859-1"
775 From: richard@test.test
776 To: issue_tracker@your.tracker.email.domain.example
777 Cc: john@test.test
778 Message-Id: <followup_dummy_id>
779 In-Reply-To: <dummy_test_message_id>
780 Subject: [issue1] Testing...
782 This is a followup
783 ''')
784 self.compareMessages(self._get_mail(),
785 '''FROM: roundup-admin@your.tracker.email.domain.example
786 TO: chef@bork.bork.bork
787 Content-Type: text/plain; charset="utf-8"
788 Subject: [issue1] Testing...
789 To: chef@bork.bork.bork
790 From: richard <issue_tracker@your.tracker.email.domain.example>
791 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
792 MIME-Version: 1.0
793 Message-Id: <followup_dummy_id>
794 In-Reply-To: <dummy_test_message_id>
795 X-Roundup-Name: Roundup issue tracker
796 X-Roundup-Loop: hello
797 X-Roundup-Issue-Status: chatting
798 Content-Transfer-Encoding: quoted-printable
801 richard <richard@test.test> added the comment:
803 This is a followup
805 ----------
806 nosy: +john
807 status: unread -> chatting
809 _______________________________________________________________________
810 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
811 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
812 _______________________________________________________________________
814 ''')
816 def testFollowupNosyAuthorAndCopy(self):
817 self.doNewIssue()
818 self.db.config.ADD_AUTHOR_TO_NOSY = 'yes'
819 self.db.config.MESSAGES_TO_AUTHOR = 'yes'
820 self._handle_mail('''Content-Type: text/plain;
821 charset="iso-8859-1"
822 From: john@test.test
823 To: issue_tracker@your.tracker.email.domain.example
824 Message-Id: <followup_dummy_id>
825 In-Reply-To: <dummy_test_message_id>
826 Subject: [issue1] Testing...
828 This is a followup
829 ''')
830 self.compareMessages(self._get_mail(),
831 '''FROM: roundup-admin@your.tracker.email.domain.example
832 TO: chef@bork.bork.bork, john@test.test, richard@test.test
833 Content-Type: text/plain; charset="utf-8"
834 Subject: [issue1] Testing...
835 To: chef@bork.bork.bork, john@test.test, richard@test.test
836 From: John Doe <issue_tracker@your.tracker.email.domain.example>
837 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
838 MIME-Version: 1.0
839 Message-Id: <followup_dummy_id>
840 In-Reply-To: <dummy_test_message_id>
841 X-Roundup-Name: Roundup issue tracker
842 X-Roundup-Loop: hello
843 X-Roundup-Issue-Status: chatting
844 Content-Transfer-Encoding: quoted-printable
847 John Doe <john@test.test> added the comment:
849 This is a followup
851 ----------
852 nosy: +john
853 status: unread -> chatting
855 _______________________________________________________________________
856 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
857 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
858 _______________________________________________________________________
860 ''')
862 def testFollowupNoNosyAuthor(self):
863 self.doNewIssue()
864 self.instance.config.ADD_AUTHOR_TO_NOSY = 'no'
865 self._handle_mail('''Content-Type: text/plain;
866 charset="iso-8859-1"
867 From: john@test.test
868 To: issue_tracker@your.tracker.email.domain.example
869 Message-Id: <followup_dummy_id>
870 In-Reply-To: <dummy_test_message_id>
871 Subject: [issue1] Testing...
873 This is a followup
874 ''')
875 self.compareMessages(self._get_mail(),
876 '''FROM: roundup-admin@your.tracker.email.domain.example
877 TO: chef@bork.bork.bork, richard@test.test
878 Content-Type: text/plain; charset="utf-8"
879 Subject: [issue1] Testing...
880 To: chef@bork.bork.bork, richard@test.test
881 From: John Doe <issue_tracker@your.tracker.email.domain.example>
882 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
883 MIME-Version: 1.0
884 Message-Id: <followup_dummy_id>
885 In-Reply-To: <dummy_test_message_id>
886 X-Roundup-Name: Roundup issue tracker
887 X-Roundup-Loop: hello
888 X-Roundup-Issue-Status: chatting
889 Content-Transfer-Encoding: quoted-printable
892 John Doe <john@test.test> added the comment:
894 This is a followup
896 ----------
897 status: unread -> chatting
899 _______________________________________________________________________
900 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
901 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
902 _______________________________________________________________________
904 ''')
906 def testFollowupNoNosyRecipients(self):
907 self.doNewIssue()
908 self.instance.config.ADD_RECIPIENTS_TO_NOSY = 'no'
909 self._handle_mail('''Content-Type: text/plain;
910 charset="iso-8859-1"
911 From: richard@test.test
912 To: issue_tracker@your.tracker.email.domain.example
913 Cc: john@test.test
914 Message-Id: <followup_dummy_id>
915 In-Reply-To: <dummy_test_message_id>
916 Subject: [issue1] Testing...
918 This is a followup
919 ''')
920 self.compareMessages(self._get_mail(),
921 '''FROM: roundup-admin@your.tracker.email.domain.example
922 TO: chef@bork.bork.bork
923 Content-Type: text/plain; charset="utf-8"
924 Subject: [issue1] Testing...
925 To: chef@bork.bork.bork
926 From: richard <issue_tracker@your.tracker.email.domain.example>
927 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
928 MIME-Version: 1.0
929 Message-Id: <followup_dummy_id>
930 In-Reply-To: <dummy_test_message_id>
931 X-Roundup-Name: Roundup issue tracker
932 X-Roundup-Loop: hello
933 X-Roundup-Issue-Status: chatting
934 Content-Transfer-Encoding: quoted-printable
937 richard <richard@test.test> added the comment:
939 This is a followup
941 ----------
942 status: unread -> chatting
944 _______________________________________________________________________
945 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
946 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
947 _______________________________________________________________________
949 ''')
951 def testFollowupEmptyMessage(self):
952 self.doNewIssue()
954 self._handle_mail('''Content-Type: text/plain;
955 charset="iso-8859-1"
956 From: richard <richard@test.test>
957 To: issue_tracker@your.tracker.email.domain.example
958 Message-Id: <followup_dummy_id>
959 In-Reply-To: <dummy_test_message_id>
960 Subject: [issue1] Testing... [assignedto=mary; nosy=+john]
962 ''')
963 l = self.db.issue.get('1', 'nosy')
964 l.sort()
965 self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id,
966 self.john_id])
968 # should be no file created (ie. no message)
969 assert not os.path.exists(SENDMAILDEBUG)
971 def testFollowupEmptyMessageNoSubject(self):
972 self.doNewIssue()
974 self._handle_mail('''Content-Type: text/plain;
975 charset="iso-8859-1"
976 From: richard <richard@test.test>
977 To: issue_tracker@your.tracker.email.domain.example
978 Message-Id: <followup_dummy_id>
979 In-Reply-To: <dummy_test_message_id>
980 Subject: [issue1] [assignedto=mary; nosy=+john]
982 ''')
983 l = self.db.issue.get('1', 'nosy')
984 l.sort()
985 self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id,
986 self.john_id])
988 # should be no file created (ie. no message)
989 assert not os.path.exists(SENDMAILDEBUG)
991 def testNosyRemove(self):
992 self.doNewIssue()
994 self._handle_mail('''Content-Type: text/plain;
995 charset="iso-8859-1"
996 From: richard <richard@test.test>
997 To: issue_tracker@your.tracker.email.domain.example
998 Message-Id: <followup_dummy_id>
999 In-Reply-To: <dummy_test_message_id>
1000 Subject: [issue1] Testing... [nosy=-richard]
1002 ''')
1003 l = self.db.issue.get('1', 'nosy')
1004 l.sort()
1005 self.assertEqual(l, [self.chef_id])
1007 # NO NOSY MESSAGE SHOULD BE SENT!
1008 assert not os.path.exists(SENDMAILDEBUG)
1010 def testNewUserAuthor(self):
1012 l = self.db.user.list()
1013 l.sort()
1014 message = '''Content-Type: text/plain;
1015 charset="iso-8859-1"
1016 From: fubar <fubar@bork.bork.bork>
1017 To: issue_tracker@your.tracker.email.domain.example
1018 Message-Id: <dummy_test_message_id>
1019 Subject: [issue] Testing...
1021 This is a test submission of a new issue.
1022 '''
1023 def hook (db, **kw):
1024 ''' set up callback for db open '''
1025 db.security.role['anonymous'].permissions=[]
1026 anonid = db.user.lookup('anonymous')
1027 db.user.set(anonid, roles='Anonymous')
1028 self.instance.schema_hook = hook
1029 try:
1030 self._handle_mail(message)
1031 except Unauthorized, value:
1032 body_diff = self.compareMessages(str(value), """
1033 You are not a registered user.
1035 Unknown address: fubar@bork.bork.bork
1036 """)
1038 assert not body_diff, body_diff
1040 else:
1041 raise AssertionError, "Unathorized not raised when handling mail"
1044 def hook (db, **kw):
1045 ''' set up callback for db open '''
1046 # Add Web Access role to anonymous, and try again to make sure
1047 # we get a "please register at:" message this time.
1048 p = [
1049 db.security.getPermission('Create', 'user'),
1050 db.security.getPermission('Web Access', None),
1051 ]
1052 db.security.role['anonymous'].permissions=p
1053 self.instance.schema_hook = hook
1054 try:
1055 self._handle_mail(message)
1056 except Unauthorized, value:
1057 body_diff = self.compareMessages(str(value), """
1058 You are not a registered user. Please register at:
1060 http://tracker.example/cgi-bin/roundup.cgi/bugs/user?template=register
1062 ...before sending mail to the tracker.
1064 Unknown address: fubar@bork.bork.bork
1065 """)
1067 assert not body_diff, body_diff
1069 else:
1070 raise AssertionError, "Unathorized not raised when handling mail"
1072 # Make sure list of users is the same as before.
1073 m = self.db.user.list()
1074 m.sort()
1075 self.assertEqual(l, m)
1077 def hook (db, **kw):
1078 ''' set up callback for db open '''
1079 # now with the permission
1080 p = [
1081 db.security.getPermission('Create', 'user'),
1082 db.security.getPermission('Email Access', None),
1083 ]
1084 db.security.role['anonymous'].permissions=p
1085 self.instance.schema_hook = hook
1086 self._handle_mail(message)
1087 m = self.db.user.list()
1088 m.sort()
1089 self.assertNotEqual(l, m)
1091 def testNewUserAuthorHighBit(self):
1092 l = set(self.db.user.list())
1093 # From: name has Euro symbol in it
1094 message = '''Content-Type: text/plain;
1095 charset="iso-8859-1"
1096 From: =?utf8?b?SOKCrGxsbw==?= <fubar@bork.bork.bork>
1097 To: issue_tracker@your.tracker.email.domain.example
1098 Message-Id: <dummy_test_message_id>
1099 Subject: [issue] Testing...
1101 This is a test submission of a new issue.
1102 '''
1103 def hook (db, **kw):
1104 ''' set up callback for db open '''
1105 p = [
1106 db.security.getPermission('Create', 'user'),
1107 db.security.getPermission('Email Access', None),
1108 ]
1109 db.security.role['anonymous'].permissions=p
1110 self.instance.schema_hook = hook
1111 self._handle_mail(message)
1112 m = set(self.db.user.list())
1113 new = list(m - l)[0]
1114 name = self.db.user.get(new, 'realname')
1115 self.assertEquals(name, 'H€llo')
1117 def testUnknownUser(self):
1118 l = set(self.db.user.list())
1119 message = '''Content-Type: text/plain;
1120 charset="iso-8859-1"
1121 From: Nonexisting User <nonexisting@bork.bork.bork>
1122 To: issue_tracker@your.tracker.email.domain.example
1123 Message-Id: <dummy_test_message_id>
1124 Subject: [issue] Testing nonexisting user...
1126 This is a test submission of a new issue.
1127 '''
1128 self.db.close()
1129 handler = self.instance.MailGW(self.instance)
1130 # we want a bounce message:
1131 handler.trapExceptions = 1
1132 ret = handler.main(StringIO(message))
1133 self.compareMessages(self._get_mail(),
1134 '''FROM: Roundup issue tracker <roundup-admin@your.tracker.email.domain.example>
1135 TO: nonexisting@bork.bork.bork
1136 From nobody Tue Jul 14 12:04:11 2009
1137 Content-Type: multipart/mixed; boundary="===============0639262320=="
1138 MIME-Version: 1.0
1139 Subject: Failed issue tracker submission
1140 To: nonexisting@bork.bork.bork
1141 From: Roundup issue tracker <roundup-admin@your.tracker.email.domain.example>
1142 Date: Tue, 14 Jul 2009 12:04:11 +0000
1143 Precedence: bulk
1144 X-Roundup-Name: Roundup issue tracker
1145 X-Roundup-Loop: hello
1146 X-Roundup-Version: 1.4.8
1147 MIME-Version: 1.0
1149 --===============0639262320==
1150 Content-Type: text/plain; charset="us-ascii"
1151 MIME-Version: 1.0
1152 Content-Transfer-Encoding: 7bit
1156 You are not a registered user.
1158 Unknown address: nonexisting@bork.bork.bork
1160 --===============0639262320==
1161 Content-Type: text/plain; charset="us-ascii"
1162 MIME-Version: 1.0
1163 Content-Transfer-Encoding: 7bit
1165 Content-Type: text/plain;
1166 charset="iso-8859-1"
1167 From: Nonexisting User <nonexisting@bork.bork.bork>
1168 To: issue_tracker@your.tracker.email.domain.example
1169 Message-Id: <dummy_test_message_id>
1170 Subject: [issue] Testing nonexisting user...
1172 This is a test submission of a new issue.
1174 --===============0639262320==--
1175 ''')
1177 def testEnc01(self):
1178 self.doNewIssue()
1179 self._handle_mail('''Content-Type: text/plain;
1180 charset="iso-8859-1"
1181 From: mary <mary@test.test>
1182 To: issue_tracker@your.tracker.email.domain.example
1183 Message-Id: <followup_dummy_id>
1184 In-Reply-To: <dummy_test_message_id>
1185 Subject: [issue1] Testing...
1186 Content-Type: text/plain;
1187 charset="iso-8859-1"
1188 Content-Transfer-Encoding: quoted-printable
1190 A message with encoding (encoded oe =F6)
1192 ''')
1193 self.compareMessages(self._get_mail(),
1194 '''FROM: roundup-admin@your.tracker.email.domain.example
1195 TO: chef@bork.bork.bork, richard@test.test
1196 Content-Type: text/plain; charset="utf-8"
1197 Subject: [issue1] Testing...
1198 To: chef@bork.bork.bork, richard@test.test
1199 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
1200 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1201 MIME-Version: 1.0
1202 Message-Id: <followup_dummy_id>
1203 In-Reply-To: <dummy_test_message_id>
1204 X-Roundup-Name: Roundup issue tracker
1205 X-Roundup-Loop: hello
1206 X-Roundup-Issue-Status: chatting
1207 Content-Transfer-Encoding: quoted-printable
1210 Contrary, Mary <mary@test.test> added the comment:
1212 A message with encoding (encoded oe =C3=B6)
1214 ----------
1215 status: unread -> chatting
1217 _______________________________________________________________________
1218 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1219 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1220 _______________________________________________________________________
1221 ''')
1223 def testEncNonUTF8(self):
1224 self.doNewIssue()
1225 self.instance.config.EMAIL_CHARSET = 'iso-8859-1'
1226 self._handle_mail('''Content-Type: text/plain;
1227 charset="iso-8859-1"
1228 From: mary <mary@test.test>
1229 To: issue_tracker@your.tracker.email.domain.example
1230 Message-Id: <followup_dummy_id>
1231 In-Reply-To: <dummy_test_message_id>
1232 Subject: [issue1] Testing...
1233 Content-Type: text/plain;
1234 charset="iso-8859-1"
1235 Content-Transfer-Encoding: quoted-printable
1237 A message with encoding (encoded oe =F6)
1239 ''')
1240 self.compareMessages(self._get_mail(),
1241 '''FROM: roundup-admin@your.tracker.email.domain.example
1242 TO: chef@bork.bork.bork, richard@test.test
1243 Content-Type: text/plain; charset="iso-8859-1"
1244 Subject: [issue1] Testing...
1245 To: chef@bork.bork.bork, richard@test.test
1246 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
1247 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1248 MIME-Version: 1.0
1249 Message-Id: <followup_dummy_id>
1250 In-Reply-To: <dummy_test_message_id>
1251 X-Roundup-Name: Roundup issue tracker
1252 X-Roundup-Loop: hello
1253 X-Roundup-Issue-Status: chatting
1254 Content-Transfer-Encoding: quoted-printable
1257 Contrary, Mary <mary@test.test> added the comment:
1259 A message with encoding (encoded oe =F6)
1261 ----------
1262 status: unread -> chatting
1264 _______________________________________________________________________
1265 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1266 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1267 _______________________________________________________________________
1268 ''')
1271 def testMultipartEnc01(self):
1272 self.doNewIssue()
1273 self._handle_mail('''Content-Type: text/plain;
1274 charset="iso-8859-1"
1275 From: mary <mary@test.test>
1276 To: issue_tracker@your.tracker.email.domain.example
1277 Message-Id: <followup_dummy_id>
1278 In-Reply-To: <dummy_test_message_id>
1279 Subject: [issue1] Testing...
1280 Content-Type: multipart/mixed;
1281 boundary="----_=_NextPart_000_01"
1283 This message is in MIME format. Since your mail reader does not understand
1284 this format, some or all of this message may not be legible.
1286 ------_=_NextPart_000_01
1287 Content-Type: text/plain;
1288 charset="iso-8859-1"
1289 Content-Transfer-Encoding: quoted-printable
1291 A message with first part encoded (encoded oe =F6)
1293 ''')
1294 self.compareMessages(self._get_mail(),
1295 '''FROM: roundup-admin@your.tracker.email.domain.example
1296 TO: chef@bork.bork.bork, richard@test.test
1297 Content-Type: text/plain; charset="utf-8"
1298 Subject: [issue1] Testing...
1299 To: chef@bork.bork.bork, richard@test.test
1300 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
1301 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1302 MIME-Version: 1.0
1303 Message-Id: <followup_dummy_id>
1304 In-Reply-To: <dummy_test_message_id>
1305 X-Roundup-Name: Roundup issue tracker
1306 X-Roundup-Loop: hello
1307 X-Roundup-Issue-Status: chatting
1308 Content-Transfer-Encoding: quoted-printable
1311 Contrary, Mary <mary@test.test> added the comment:
1313 A message with first part encoded (encoded oe =C3=B6)
1315 ----------
1316 status: unread -> chatting
1318 _______________________________________________________________________
1319 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1320 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1321 _______________________________________________________________________
1322 ''')
1324 def testContentDisposition(self):
1325 self.doNewIssue()
1326 self._handle_mail('''Content-Type: text/plain;
1327 charset="iso-8859-1"
1328 From: mary <mary@test.test>
1329 To: issue_tracker@your.tracker.email.domain.example
1330 Message-Id: <followup_dummy_id>
1331 In-Reply-To: <dummy_test_message_id>
1332 Subject: [issue1] Testing...
1333 Content-Type: multipart/mixed; boundary="bCsyhTFzCvuiizWE"
1334 Content-Disposition: inline
1337 --bCsyhTFzCvuiizWE
1338 Content-Type: text/plain; charset=us-ascii
1339 Content-Disposition: inline
1341 test attachment binary
1343 --bCsyhTFzCvuiizWE
1344 Content-Type: application/octet-stream
1345 Content-Disposition: attachment; filename="main.dvi"
1346 Content-Transfer-Encoding: base64
1348 SnVzdCBhIHRlc3QgAQo=
1350 --bCsyhTFzCvuiizWE--
1351 ''')
1352 messages = self.db.issue.get('1', 'messages')
1353 messages.sort()
1354 file = self.db.file.getnode (self.db.msg.get(messages[-1], 'files')[0])
1355 self.assertEqual(file.name, 'main.dvi')
1356 self.assertEqual(file.content, 'Just a test \001\n')
1358 def testFollowupStupidQuoting(self):
1359 self.doNewIssue()
1361 self._handle_mail('''Content-Type: text/plain;
1362 charset="iso-8859-1"
1363 From: richard <richard@test.test>
1364 To: issue_tracker@your.tracker.email.domain.example
1365 Message-Id: <followup_dummy_id>
1366 In-Reply-To: <dummy_test_message_id>
1367 Subject: Re: "[issue1] Testing... "
1369 This is a followup
1370 ''')
1371 self.compareMessages(self._get_mail(),
1372 '''FROM: roundup-admin@your.tracker.email.domain.example
1373 TO: chef@bork.bork.bork
1374 Content-Type: text/plain; charset="utf-8"
1375 Subject: [issue1] Testing...
1376 To: chef@bork.bork.bork
1377 From: richard <issue_tracker@your.tracker.email.domain.example>
1378 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1379 MIME-Version: 1.0
1380 Message-Id: <followup_dummy_id>
1381 In-Reply-To: <dummy_test_message_id>
1382 X-Roundup-Name: Roundup issue tracker
1383 X-Roundup-Loop: hello
1384 X-Roundup-Issue-Status: chatting
1385 Content-Transfer-Encoding: quoted-printable
1388 richard <richard@test.test> added the comment:
1390 This is a followup
1392 ----------
1393 status: unread -> chatting
1395 _______________________________________________________________________
1396 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1397 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1398 _______________________________________________________________________
1399 ''')
1401 def testEmailQuoting(self):
1402 self.instance.config.EMAIL_KEEP_QUOTED_TEXT = 'no'
1403 self.innerTestQuoting('''This is a followup
1404 ''')
1406 def testEmailQuotingRemove(self):
1407 self.instance.config.EMAIL_KEEP_QUOTED_TEXT = 'yes'
1408 self.innerTestQuoting('''Blah blah wrote:
1409 > Blah bklaskdfj sdf asdf jlaskdf skj sdkfjl asdf
1410 > skdjlkjsdfalsdkfjasdlfkj dlfksdfalksd fj
1411 >
1413 This is a followup
1414 ''')
1416 def innerTestQuoting(self, expect):
1417 nodeid = self.doNewIssue()
1419 messages = self.db.issue.get(nodeid, 'messages')
1421 self._handle_mail('''Content-Type: text/plain;
1422 charset="iso-8859-1"
1423 From: richard <richard@test.test>
1424 To: issue_tracker@your.tracker.email.domain.example
1425 Message-Id: <followup_dummy_id>
1426 In-Reply-To: <dummy_test_message_id>
1427 Subject: Re: [issue1] Testing...
1429 Blah blah wrote:
1430 > Blah bklaskdfj sdf asdf jlaskdf skj sdkfjl asdf
1431 > skdjlkjsdfalsdkfjasdlfkj dlfksdfalksd fj
1432 >
1434 This is a followup
1435 ''')
1436 # figure the new message id
1437 newmessages = self.db.issue.get(nodeid, 'messages')
1438 for msg in messages:
1439 newmessages.remove(msg)
1440 messageid = newmessages[0]
1442 self.compareMessages(self.db.msg.get(messageid, 'content'), expect)
1444 def testUserLookup(self):
1445 i = self.db.user.create(username='user1', address='user1@foo.com')
1446 self.assertEqual(uidFromAddress(self.db, ('', 'user1@foo.com'), 0), i)
1447 self.assertEqual(uidFromAddress(self.db, ('', 'USER1@foo.com'), 0), i)
1448 i = self.db.user.create(username='user2', address='USER2@foo.com')
1449 self.assertEqual(uidFromAddress(self.db, ('', 'USER2@foo.com'), 0), i)
1450 self.assertEqual(uidFromAddress(self.db, ('', 'user2@foo.com'), 0), i)
1452 def testUserAlternateLookup(self):
1453 i = self.db.user.create(username='user1', address='user1@foo.com',
1454 alternate_addresses='user1@bar.com')
1455 self.assertEqual(uidFromAddress(self.db, ('', 'user1@bar.com'), 0), i)
1456 self.assertEqual(uidFromAddress(self.db, ('', 'USER1@bar.com'), 0), i)
1458 def testUserCreate(self):
1459 i = uidFromAddress(self.db, ('', 'user@foo.com'), 1)
1460 self.assertNotEqual(uidFromAddress(self.db, ('', 'user@bar.com'), 1), i)
1462 def testRFC2822(self):
1463 ascii_header = "[issue243] This is a \"test\" - with 'quotation' marks"
1464 unicode_header = '[issue244] \xd0\xb0\xd0\xbd\xd0\xb4\xd1\x80\xd0\xb5\xd0\xb9'
1465 unicode_encoded = '=?utf-8?q?[issue244]_=D0=B0=D0=BD=D0=B4=D1=80=D0=B5=D0=B9?='
1466 self.assertEqual(rfc2822.encode_header(ascii_header), ascii_header)
1467 self.assertEqual(rfc2822.encode_header(unicode_header), unicode_encoded)
1469 def testRegistrationConfirmation(self):
1470 otk = "Aj4euk4LZSAdwePohj90SME5SpopLETL"
1471 self.db.getOTKManager().set(otk, username='johannes')
1472 self._handle_mail('''Content-Type: text/plain;
1473 charset="iso-8859-1"
1474 From: Chef <chef@bork.bork.bork>
1475 To: issue_tracker@your.tracker.email.domain.example
1476 Cc: richard@test.test
1477 Message-Id: <dummy_test_message_id>
1478 Subject: Re: Complete your registration to Roundup issue tracker
1479 -- key %s
1481 This is a test confirmation of registration.
1482 ''' % otk)
1483 self.db.user.lookup('johannes')
1485 def testFollowupOnNonIssue(self):
1486 self.db.keyword.create(name='Foo')
1487 self._handle_mail('''Content-Type: text/plain;
1488 charset="iso-8859-1"
1489 From: richard <richard@test.test>
1490 To: issue_tracker@your.tracker.email.domain.example
1491 Message-Id: <followup_dummy_id>
1492 In-Reply-To: <dummy_test_message_id>
1493 Subject: [keyword1] Testing... [name=Bar]
1495 ''')
1496 self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
1498 def testResentFrom(self):
1499 nodeid = self._handle_mail('''Content-Type: text/plain;
1500 charset="iso-8859-1"
1501 From: Chef <chef@bork.bork.bork>
1502 Resent-From: mary <mary@test.test>
1503 To: issue_tracker@your.tracker.email.domain.example
1504 Cc: richard@test.test
1505 Message-Id: <dummy_test_message_id>
1506 Subject: [issue] Testing...
1508 This is a test submission of a new issue.
1509 ''')
1510 assert not os.path.exists(SENDMAILDEBUG)
1511 l = self.db.issue.get(nodeid, 'nosy')
1512 l.sort()
1513 self.assertEqual(l, [self.richard_id, self.mary_id])
1514 return nodeid
1516 def testDejaVu(self):
1517 self.assertRaises(IgnoreLoop, self._handle_mail,
1518 '''Content-Type: text/plain;
1519 charset="iso-8859-1"
1520 From: Chef <chef@bork.bork.bork>
1521 X-Roundup-Loop: hello
1522 To: issue_tracker@your.tracker.email.domain.example
1523 Cc: richard@test.test
1524 Message-Id: <dummy_test_message_id>
1525 Subject: Re: [issue] Testing...
1527 Hi, I've been mis-configured to loop messages back to myself.
1528 ''')
1530 def testItsBulkStupid(self):
1531 self.assertRaises(IgnoreBulk, self._handle_mail,
1532 '''Content-Type: text/plain;
1533 charset="iso-8859-1"
1534 From: Chef <chef@bork.bork.bork>
1535 Precedence: bulk
1536 To: issue_tracker@your.tracker.email.domain.example
1537 Cc: richard@test.test
1538 Message-Id: <dummy_test_message_id>
1539 Subject: Re: [issue] Testing...
1541 Hi, I'm on holidays, and this is a dumb auto-responder.
1542 ''')
1544 def testAutoReplyEmailsAreIgnored(self):
1545 self.assertRaises(IgnoreBulk, self._handle_mail,
1546 '''Content-Type: text/plain;
1547 charset="iso-8859-1"
1548 From: Chef <chef@bork.bork.bork>
1549 To: issue_tracker@your.tracker.email.domain.example
1550 Cc: richard@test.test
1551 Message-Id: <dummy_test_message_id>
1552 Subject: Re: [issue] Out of office AutoReply: Back next week
1554 Hi, I am back in the office next week
1555 ''')
1557 def testNoSubject(self):
1558 self.assertRaises(MailUsageError, self._handle_mail,
1559 '''Content-Type: text/plain;
1560 charset="iso-8859-1"
1561 From: Chef <chef@bork.bork.bork>
1562 To: issue_tracker@your.tracker.email.domain.example
1563 Cc: richard@test.test
1564 Reply-To: chef@bork.bork.bork
1565 Message-Id: <dummy_test_message_id>
1567 ''')
1569 #
1570 # TEST FOR INVALID DESIGNATOR HANDLING
1571 #
1572 def testInvalidDesignator(self):
1573 self.assertRaises(MailUsageError, self._handle_mail,
1574 '''Content-Type: text/plain;
1575 charset="iso-8859-1"
1576 From: Chef <chef@bork.bork.bork>
1577 To: issue_tracker@your.tracker.email.domain.example
1578 Subject: [frobulated] testing
1579 Cc: richard@test.test
1580 Reply-To: chef@bork.bork.bork
1581 Message-Id: <dummy_test_message_id>
1583 ''')
1584 self.assertRaises(MailUsageError, self._handle_mail,
1585 '''Content-Type: text/plain;
1586 charset="iso-8859-1"
1587 From: Chef <chef@bork.bork.bork>
1588 To: issue_tracker@your.tracker.email.domain.example
1589 Subject: [issue12345] testing
1590 Cc: richard@test.test
1591 Reply-To: chef@bork.bork.bork
1592 Message-Id: <dummy_test_message_id>
1594 ''')
1596 def testInvalidClassLoose(self):
1597 self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
1598 nodeid = self._handle_mail('''Content-Type: text/plain;
1599 charset="iso-8859-1"
1600 From: Chef <chef@bork.bork.bork>
1601 To: issue_tracker@your.tracker.email.domain.example
1602 Subject: [frobulated] testing
1603 Cc: richard@test.test
1604 Reply-To: chef@bork.bork.bork
1605 Message-Id: <dummy_test_message_id>
1607 ''')
1608 assert not os.path.exists(SENDMAILDEBUG)
1609 self.assertEqual(self.db.issue.get(nodeid, 'title'),
1610 '[frobulated] testing')
1612 def testInvalidClassLooseReply(self):
1613 self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
1614 nodeid = self._handle_mail('''Content-Type: text/plain;
1615 charset="iso-8859-1"
1616 From: Chef <chef@bork.bork.bork>
1617 To: issue_tracker@your.tracker.email.domain.example
1618 Subject: Re: [frobulated] testing
1619 Cc: richard@test.test
1620 Reply-To: chef@bork.bork.bork
1621 Message-Id: <dummy_test_message_id>
1623 ''')
1624 assert not os.path.exists(SENDMAILDEBUG)
1625 self.assertEqual(self.db.issue.get(nodeid, 'title'),
1626 '[frobulated] testing')
1628 def testInvalidClassLoose(self):
1629 self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
1630 nodeid = self._handle_mail('''Content-Type: text/plain;
1631 charset="iso-8859-1"
1632 From: Chef <chef@bork.bork.bork>
1633 To: issue_tracker@your.tracker.email.domain.example
1634 Subject: [issue1234] testing
1635 Cc: richard@test.test
1636 Reply-To: chef@bork.bork.bork
1637 Message-Id: <dummy_test_message_id>
1639 ''')
1640 assert not os.path.exists(SENDMAILDEBUG)
1641 self.assertEqual(self.db.issue.get(nodeid, 'title'),
1642 '[issue1234] testing')
1644 def testClassLooseOK(self):
1645 self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
1646 self.db.keyword.create(name='Foo')
1647 nodeid = self._handle_mail('''Content-Type: text/plain;
1648 charset="iso-8859-1"
1649 From: Chef <chef@bork.bork.bork>
1650 To: issue_tracker@your.tracker.email.domain.example
1651 Subject: [keyword1] Testing... [name=Bar]
1652 Cc: richard@test.test
1653 Reply-To: chef@bork.bork.bork
1654 Message-Id: <dummy_test_message_id>
1656 ''')
1657 assert not os.path.exists(SENDMAILDEBUG)
1658 self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
1660 def testClassStrictInvalid(self):
1661 self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'strict'
1662 self.instance.config.MAILGW_DEFAULT_CLASS = ''
1664 message = '''Content-Type: text/plain;
1665 charset="iso-8859-1"
1666 From: Chef <chef@bork.bork.bork>
1667 To: issue_tracker@your.tracker.email.domain.example
1668 Subject: Testing...
1669 Cc: richard@test.test
1670 Reply-To: chef@bork.bork.bork
1671 Message-Id: <dummy_test_message_id>
1673 '''
1674 self.assertRaises(MailUsageError, self._handle_mail, message)
1676 def testClassStrictValid(self):
1677 self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'strict'
1678 self.instance.config.MAILGW_DEFAULT_CLASS = ''
1680 nodeid = self._handle_mail('''Content-Type: text/plain;
1681 charset="iso-8859-1"
1682 From: Chef <chef@bork.bork.bork>
1683 To: issue_tracker@your.tracker.email.domain.example
1684 Subject: [issue] Testing...
1685 Cc: richard@test.test
1686 Reply-To: chef@bork.bork.bork
1687 Message-Id: <dummy_test_message_id>
1689 ''')
1691 assert not os.path.exists(SENDMAILDEBUG)
1692 self.assertEqual(self.db.issue.get(nodeid, 'title'), 'Testing...')
1694 #
1695 # TEST FOR INVALID COMMANDS HANDLING
1696 #
1697 def testInvalidCommands(self):
1698 self.assertRaises(MailUsageError, self._handle_mail,
1699 '''Content-Type: text/plain;
1700 charset="iso-8859-1"
1701 From: Chef <chef@bork.bork.bork>
1702 To: issue_tracker@your.tracker.email.domain.example
1703 Subject: testing [frobulated]
1704 Cc: richard@test.test
1705 Reply-To: chef@bork.bork.bork
1706 Message-Id: <dummy_test_message_id>
1708 ''')
1710 def testInvalidCommandPassthrough(self):
1711 self.instance.config.MAILGW_SUBJECT_SUFFIX_PARSING = 'none'
1712 nodeid = self._handle_mail('''Content-Type: text/plain;
1713 charset="iso-8859-1"
1714 From: Chef <chef@bork.bork.bork>
1715 To: issue_tracker@your.tracker.email.domain.example
1716 Subject: testing [frobulated]
1717 Cc: richard@test.test
1718 Reply-To: chef@bork.bork.bork
1719 Message-Id: <dummy_test_message_id>
1721 ''')
1722 assert not os.path.exists(SENDMAILDEBUG)
1723 self.assertEqual(self.db.issue.get(nodeid, 'title'),
1724 'testing [frobulated]')
1726 def testInvalidCommandPassthroughLoose(self):
1727 self.instance.config.MAILGW_SUBJECT_SUFFIX_PARSING = 'loose'
1728 nodeid = self._handle_mail('''Content-Type: text/plain;
1729 charset="iso-8859-1"
1730 From: Chef <chef@bork.bork.bork>
1731 To: issue_tracker@your.tracker.email.domain.example
1732 Subject: testing [frobulated]
1733 Cc: richard@test.test
1734 Reply-To: chef@bork.bork.bork
1735 Message-Id: <dummy_test_message_id>
1737 ''')
1738 assert not os.path.exists(SENDMAILDEBUG)
1739 self.assertEqual(self.db.issue.get(nodeid, 'title'),
1740 'testing [frobulated]')
1742 def testInvalidCommandPassthroughLooseOK(self):
1743 self.instance.config.MAILGW_SUBJECT_SUFFIX_PARSING = 'loose'
1744 nodeid = self._handle_mail('''Content-Type: text/plain;
1745 charset="iso-8859-1"
1746 From: Chef <chef@bork.bork.bork>
1747 To: issue_tracker@your.tracker.email.domain.example
1748 Subject: testing [assignedto=mary]
1749 Cc: richard@test.test
1750 Reply-To: chef@bork.bork.bork
1751 Message-Id: <dummy_test_message_id>
1753 ''')
1754 assert not os.path.exists(SENDMAILDEBUG)
1755 self.assertEqual(self.db.issue.get(nodeid, 'title'), 'testing')
1756 self.assertEqual(self.db.issue.get(nodeid, 'assignedto'), self.mary_id)
1758 def testCommandDelimiters(self):
1759 self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '{}'
1760 nodeid = self._handle_mail('''Content-Type: text/plain;
1761 charset="iso-8859-1"
1762 From: Chef <chef@bork.bork.bork>
1763 To: issue_tracker@your.tracker.email.domain.example
1764 Subject: testing {assignedto=mary}
1765 Cc: richard@test.test
1766 Reply-To: chef@bork.bork.bork
1767 Message-Id: <dummy_test_message_id>
1769 ''')
1770 assert not os.path.exists(SENDMAILDEBUG)
1771 self.assertEqual(self.db.issue.get(nodeid, 'title'), 'testing')
1772 self.assertEqual(self.db.issue.get(nodeid, 'assignedto'), self.mary_id)
1774 def testPrefixDelimiters(self):
1775 self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '{}'
1776 self.db.keyword.create(name='Foo')
1777 self._handle_mail('''Content-Type: text/plain;
1778 charset="iso-8859-1"
1779 From: richard <richard@test.test>
1780 To: issue_tracker@your.tracker.email.domain.example
1781 Message-Id: <followup_dummy_id>
1782 In-Reply-To: <dummy_test_message_id>
1783 Subject: {keyword1} Testing... {name=Bar}
1785 ''')
1786 assert not os.path.exists(SENDMAILDEBUG)
1787 self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
1789 def testCommandDelimitersIgnore(self):
1790 self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '{}'
1791 nodeid = self._handle_mail('''Content-Type: text/plain;
1792 charset="iso-8859-1"
1793 From: Chef <chef@bork.bork.bork>
1794 To: issue_tracker@your.tracker.email.domain.example
1795 Subject: testing [assignedto=mary]
1796 Cc: richard@test.test
1797 Reply-To: chef@bork.bork.bork
1798 Message-Id: <dummy_test_message_id>
1800 ''')
1801 assert not os.path.exists(SENDMAILDEBUG)
1802 self.assertEqual(self.db.issue.get(nodeid, 'title'),
1803 'testing [assignedto=mary]')
1804 self.assertEqual(self.db.issue.get(nodeid, 'assignedto'), None)
1806 def testReplytoMatch(self):
1807 self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
1808 nodeid = self.doNewIssue()
1809 nodeid2 = self._handle_mail('''Content-Type: text/plain;
1810 charset="iso-8859-1"
1811 From: Chef <chef@bork.bork.bork>
1812 To: issue_tracker@your.tracker.email.domain.example
1813 Message-Id: <dummy_test_message_id2>
1814 In-Reply-To: <dummy_test_message_id>
1815 Subject: Testing...
1817 Followup message.
1818 ''')
1820 nodeid3 = self._handle_mail('''Content-Type: text/plain;
1821 charset="iso-8859-1"
1822 From: Chef <chef@bork.bork.bork>
1823 To: issue_tracker@your.tracker.email.domain.example
1824 Message-Id: <dummy_test_message_id3>
1825 In-Reply-To: <dummy_test_message_id2>
1826 Subject: Testing...
1828 Yet another message in the same thread/issue.
1829 ''')
1831 self.assertEqual(nodeid, nodeid2)
1832 self.assertEqual(nodeid, nodeid3)
1834 def testHelpSubject(self):
1835 message = '''Content-Type: text/plain;
1836 charset="iso-8859-1"
1837 From: Chef <chef@bork.bork.bork>
1838 To: issue_tracker@your.tracker.email.domain.example
1839 Message-Id: <dummy_test_message_id2>
1840 In-Reply-To: <dummy_test_message_id>
1841 Subject: hElp
1844 '''
1845 self.assertRaises(MailUsageHelp, self._handle_mail, message)
1847 def testMaillistSubject(self):
1848 self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '[]'
1849 self.db.keyword.create(name='Foo')
1850 self._handle_mail('''Content-Type: text/plain;
1851 charset="iso-8859-1"
1852 From: Chef <chef@bork.bork.bork>
1853 To: issue_tracker@your.tracker.email.domain.example
1854 Subject: [mailinglist-name] [keyword1] Testing.. [name=Bar]
1855 Cc: richard@test.test
1856 Reply-To: chef@bork.bork.bork
1857 Message-Id: <dummy_test_message_id>
1859 ''')
1861 assert not os.path.exists(SENDMAILDEBUG)
1862 self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
1864 def testUnknownPrefixSubject(self):
1865 self.db.keyword.create(name='Foo')
1866 self._handle_mail('''Content-Type: text/plain;
1867 charset="iso-8859-1"
1868 From: Chef <chef@bork.bork.bork>
1869 To: issue_tracker@your.tracker.email.domain.example
1870 Subject: VeryStrangeRe: [keyword1] Testing.. [name=Bar]
1871 Cc: richard@test.test
1872 Reply-To: chef@bork.bork.bork
1873 Message-Id: <dummy_test_message_id>
1875 ''')
1877 assert not os.path.exists(SENDMAILDEBUG)
1878 self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
1880 def testIssueidLast(self):
1881 nodeid1 = self.doNewIssue()
1882 nodeid2 = self._handle_mail('''Content-Type: text/plain;
1883 charset="iso-8859-1"
1884 From: mary <mary@test.test>
1885 To: issue_tracker@your.tracker.email.domain.example
1886 Message-Id: <followup_dummy_id>
1887 In-Reply-To: <dummy_test_message_id>
1888 Subject: New title [issue1]
1890 This is a second followup
1891 ''')
1893 assert nodeid1 == nodeid2
1894 self.assertEqual(self.db.issue.get(nodeid2, 'title'), "Testing...")
1897 def test_suite():
1898 suite = unittest.TestSuite()
1899 suite.addTest(unittest.makeSuite(MailgwTestCase))
1900 return suite
1902 if __name__ == '__main__':
1903 runner = unittest.TextTestRunner()
1904 unittest.main(testRunner=runner)
1906 # vim: set filetype=python sts=4 sw=4 et si :