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
17 import gpgmelib
18 from email.parser import FeedParser
21 try:
22 import pyme, pyme.core
23 except ImportError:
24 pyme = None
27 from cStringIO import StringIO
29 if not os.environ.has_key('SENDMAILDEBUG'):
30 os.environ['SENDMAILDEBUG'] = 'mail-test.log'
31 SENDMAILDEBUG = os.environ['SENDMAILDEBUG']
33 from roundup import mailgw, i18n, roundupdb
34 from roundup.mailgw import MailGW, Unauthorized, uidFromAddress, \
35 parseContent, IgnoreLoop, IgnoreBulk, MailUsageError, MailUsageHelp
36 from roundup import init, instance, password, rfc2822, __version__
37 from roundup.anypy.sets_ import set
39 #import db_test_base
40 import memorydb
42 class Message(rfc822.Message):
43 """String-based Message class with equivalence test."""
44 def __init__(self, s):
45 rfc822.Message.__init__(self, StringIO(s.strip()))
47 def __eq__(self, other):
48 return (self.dict == other.dict and
49 self.fp.read() == other.fp.read())
51 class Tracker(object):
52 def open(self, journaltag):
53 return self.db
55 class DiffHelper:
56 def compareMessages(self, new, old):
57 """Compare messages for semantic equivalence."""
58 new, old = Message(new), Message(old)
60 # all Roundup-generated messages have "Precedence: bulk"
61 old['Precedence'] = 'bulk'
63 # don't try to compare the date
64 del new['date'], old['date']
66 if not new == old:
67 res = []
69 replace = {}
70 for key in new.keys():
71 if key.startswith('from '):
72 # skip the unix from line
73 continue
74 if key.lower() == 'x-roundup-version':
75 # version changes constantly, so handle it specially
76 if new[key] != __version__:
77 res.append(' %s: %r != %r' % (key, __version__,
78 new[key]))
79 elif key.lower() == 'content-type' and 'boundary=' in new[key]:
80 # handle mime messages
81 newmime = new[key].split('=',1)[-1].strip('"')
82 oldmime = old.get(key, '').split('=',1)[-1].strip('"')
83 replace ['--' + newmime] = '--' + oldmime
84 replace ['--' + newmime + '--'] = '--' + oldmime + '--'
85 elif new.get(key, '') != old.get(key, ''):
86 res.append(' %s: %r != %r' % (key, old.get(key, ''),
87 new.get(key, '')))
89 body_diff = self.compareStrings(new.fp.read(), old.fp.read(),
90 replace=replace)
91 if body_diff:
92 res.append('')
93 res.extend(body_diff)
95 if res:
96 res.insert(0, 'Generated message not correct (diff follows, expected vs. actual):')
97 raise AssertionError, '\n'.join(res)
99 def compareStrings(self, s2, s1, replace={}):
100 '''Note the reversal of s2 and s1 - difflib.SequenceMatcher wants
101 the first to be the "original" but in the calls in this file,
102 the second arg is the original. Ho hum.
103 Do replacements over the replace dict -- used for mime boundary
104 '''
105 l1 = s1.strip().split('\n')
106 l2 = [replace.get(i,i) for i in s2.strip().split('\n')]
107 if l1 == l2:
108 return
109 s = difflib.SequenceMatcher(None, l1, l2)
110 res = []
111 for value, s1s, s1e, s2s, s2e in s.get_opcodes():
112 if value == 'equal':
113 for i in range(s1s, s1e):
114 res.append(' %s'%l1[i])
115 elif value == 'delete':
116 for i in range(s1s, s1e):
117 res.append('- %s'%l1[i])
118 elif value == 'insert':
119 for i in range(s2s, s2e):
120 res.append('+ %s'%l2[i])
121 elif value == 'replace':
122 for i, j in zip(range(s1s, s1e), range(s2s, s2e)):
123 res.append('- %s'%l1[i])
124 res.append('+ %s'%l2[j])
126 return res
128 class MailgwTestAbstractBase(unittest.TestCase, DiffHelper):
129 count = 0
130 schema = 'classic'
131 def setUp(self):
132 self.old_translate_ = mailgw._
133 roundupdb._ = mailgw._ = i18n.get_translation(language='C').gettext
134 self.__class__.count = self.__class__.count + 1
136 # and open the database / "instance"
137 self.db = memorydb.create('admin')
138 self.instance = Tracker()
139 self.instance.db = self.db
140 self.instance.config = self.db.config
141 self.instance.MailGW = MailGW
143 self.chef_id = self.db.user.create(username='Chef',
144 address='chef@bork.bork.bork', realname='Bork, Chef', roles='User')
145 self.richard_id = self.db.user.create(username='richard',
146 address='richard@test.test', roles='User')
147 self.mary_id = self.db.user.create(username='mary',
148 address='mary@test.test', roles='User', realname='Contrary, Mary')
149 self.john_id = self.db.user.create(username='john',
150 address='john@test.test', roles='User', realname='John Doe',
151 alternate_addresses='jondoe@test.test\njohn.doe@test.test')
152 self.rgg_id = self.db.user.create(username='rgg',
153 address='rgg@test.test', roles='User')
155 def tearDown(self):
156 roundupdb._ = mailgw._ = self.old_translate_
157 if os.path.exists(SENDMAILDEBUG):
158 os.remove(SENDMAILDEBUG)
159 self.db.close()
161 def _create_mailgw(self, message, args=()):
162 class MailGW(self.instance.MailGW):
163 def handle_message(self, message):
164 return self._handle_message(message)
165 handler = MailGW(self.instance, args)
166 handler.db = self.db
167 return handler
169 def _handle_mail(self, message, args=(), trap_exc=0):
170 handler = self._create_mailgw(message, args)
171 handler.trapExceptions = trap_exc
172 return handler.main(StringIO(message))
174 def _get_mail(self):
175 f = open(SENDMAILDEBUG)
176 try:
177 return f.read()
178 finally:
179 f.close()
181 # Normal test-case used for both non-pgp test and a test while pgp
182 # is enabled, so this test is run in both test suites.
183 def testEmptyMessage(self):
184 nodeid = self._handle_mail('''Content-Type: text/plain;
185 charset="iso-8859-1"
186 From: Chef <chef@bork.bork.bork>
187 To: issue_tracker@your.tracker.email.domain.example
188 Cc: richard@test.test
189 Reply-To: chef@bork.bork.bork
190 Message-Id: <dummy_test_message_id>
191 Subject: [issue] Testing...
193 ''')
194 assert not os.path.exists(SENDMAILDEBUG)
195 self.assertEqual(self.db.issue.get(nodeid, 'title'), 'Testing...')
198 class MailgwTestCase(MailgwTestAbstractBase):
200 def testMessageWithFromInIt(self):
201 nodeid = self._handle_mail('''Content-Type: text/plain;
202 charset="iso-8859-1"
203 From: Chef <chef@bork.bork.bork>
204 To: issue_tracker@your.tracker.email.domain.example
205 Cc: richard@test.test
206 Reply-To: chef@bork.bork.bork
207 Message-Id: <dummy_test_message_id>
208 Subject: [issue] Testing...
210 From here to there!
211 ''')
212 assert not os.path.exists(SENDMAILDEBUG)
213 msgid = self.db.issue.get(nodeid, 'messages')[0]
214 self.assertEqual(self.db.msg.get(msgid, 'content'), 'From here to there!')
216 def testNoMessageId(self):
217 self.instance.config['MAIL_DOMAIN'] = 'example.com'
218 nodeid = self._handle_mail('''Content-Type: text/plain;
219 charset="iso-8859-1"
220 From: Chef <chef@bork.bork.bork>
221 To: issue_tracker@your.tracker.email.domain.example
222 Cc: richard@test.test
223 Reply-To: chef@bork.bork.bork
224 Subject: [issue] Testing...
226 Hi there!
227 ''')
228 assert not os.path.exists(SENDMAILDEBUG)
229 msgid = self.db.issue.get(nodeid, 'messages')[0]
230 messageid = self.db.msg.get(msgid, 'messageid')
231 x1, x2 = messageid.split('@')
232 self.assertEqual(x2, 'example.com>')
233 x = x1.split('.')[-1]
234 self.assertEqual(x, 'issueNone')
235 nodeid = self._handle_mail('''Content-Type: text/plain;
236 charset="iso-8859-1"
237 From: Chef <chef@bork.bork.bork>
238 To: issue_tracker@your.tracker.email.domain.example
239 Subject: [issue%(nodeid)s] Testing...
241 Just a test reply
242 '''%locals())
243 msgid = self.db.issue.get(nodeid, 'messages')[-1]
244 messageid = self.db.msg.get(msgid, 'messageid')
245 x1, x2 = messageid.split('@')
246 self.assertEqual(x2, 'example.com>')
247 x = x1.split('.')[-1]
248 self.assertEqual(x, "issue%s"%nodeid)
250 def testOptions(self):
251 nodeid = self._handle_mail('''Content-Type: text/plain;
252 charset="iso-8859-1"
253 From: Chef <chef@bork.bork.bork>
254 To: issue_tracker@your.tracker.email.domain.example
255 Message-Id: <dummy_test_message_id>
256 Reply-To: chef@bork.bork.bork
257 Subject: [issue] Testing...
259 Hi there!
260 ''', (('-C', 'issue'), ('-S', 'status=chatting;priority=critical')))
261 self.assertEqual(self.db.issue.get(nodeid, 'status'), '3')
262 self.assertEqual(self.db.issue.get(nodeid, 'priority'), '1')
264 def testOptionsMulti(self):
265 nodeid = self._handle_mail('''Content-Type: text/plain;
266 charset="iso-8859-1"
267 From: Chef <chef@bork.bork.bork>
268 To: issue_tracker@your.tracker.email.domain.example
269 Message-Id: <dummy_test_message_id>
270 Reply-To: chef@bork.bork.bork
271 Subject: [issue] Testing...
273 Hi there!
274 ''', (('-C', 'issue'), ('-S', 'status=chatting'), ('-S', 'priority=critical')))
275 self.assertEqual(self.db.issue.get(nodeid, 'status'), '3')
276 self.assertEqual(self.db.issue.get(nodeid, 'priority'), '1')
278 def testOptionClass(self):
279 nodeid = self._handle_mail('''Content-Type: text/plain;
280 charset="iso-8859-1"
281 From: Chef <chef@bork.bork.bork>
282 To: issue_tracker@your.tracker.email.domain.example
283 Message-Id: <dummy_test_message_id>
284 Reply-To: chef@bork.bork.bork
285 Subject: [issue] Testing... [status=chatting;priority=critical]
287 Hi there!
288 ''', (('-c', 'issue'),))
289 self.assertEqual(self.db.issue.get(nodeid, 'title'), 'Testing...')
290 self.assertEqual(self.db.issue.get(nodeid, 'status'), '3')
291 self.assertEqual(self.db.issue.get(nodeid, 'priority'), '1')
293 def doNewIssue(self):
294 nodeid = self._handle_mail('''Content-Type: text/plain;
295 charset="iso-8859-1"
296 From: Chef <chef@bork.bork.bork>
297 To: issue_tracker@your.tracker.email.domain.example
298 Cc: richard@test.test
299 Message-Id: <dummy_test_message_id>
300 Subject: [issue] Testing...
302 This is a test submission of a new issue.
303 ''')
304 assert not os.path.exists(SENDMAILDEBUG)
305 l = self.db.issue.get(nodeid, 'nosy')
306 l.sort()
307 self.assertEqual(l, [self.chef_id, self.richard_id])
308 return nodeid
310 def testNewIssue(self):
311 self.doNewIssue()
313 def testNewIssueNosy(self):
314 self.instance.config.ADD_AUTHOR_TO_NOSY = 'yes'
315 nodeid = self._handle_mail('''Content-Type: text/plain;
316 charset="iso-8859-1"
317 From: Chef <chef@bork.bork.bork>
318 To: issue_tracker@your.tracker.email.domain.example
319 Cc: richard@test.test
320 Message-Id: <dummy_test_message_id>
321 Subject: [issue] Testing...
323 This is a test submission of a new issue.
324 ''')
325 assert not os.path.exists(SENDMAILDEBUG)
326 l = self.db.issue.get(nodeid, 'nosy')
327 l.sort()
328 self.assertEqual(l, [self.chef_id, self.richard_id])
330 def testAlternateAddress(self):
331 self._handle_mail('''Content-Type: text/plain;
332 charset="iso-8859-1"
333 From: John Doe <john.doe@test.test>
334 To: issue_tracker@your.tracker.email.domain.example
335 Message-Id: <dummy_test_message_id>
336 Subject: [issue] Testing...
338 This is a test submission of a new issue.
339 ''')
340 userlist = self.db.user.list()
341 assert not os.path.exists(SENDMAILDEBUG)
342 self.assertEqual(userlist, self.db.user.list(),
343 "user created when it shouldn't have been")
345 def testNewIssueNoClass(self):
346 self._handle_mail('''Content-Type: text/plain;
347 charset="iso-8859-1"
348 From: Chef <chef@bork.bork.bork>
349 To: issue_tracker@your.tracker.email.domain.example
350 Cc: richard@test.test
351 Message-Id: <dummy_test_message_id>
352 Subject: Testing...
354 This is a test submission of a new issue.
355 ''')
356 assert not os.path.exists(SENDMAILDEBUG)
358 def testNewIssueAuthMsg(self):
359 # TODO: fix the damn config - this is apalling
360 self.db.config.MESSAGES_TO_AUTHOR = 'yes'
361 self._handle_mail('''Content-Type: text/plain;
362 charset="iso-8859-1"
363 From: Chef <chef@bork.bork.bork>
364 To: issue_tracker@your.tracker.email.domain.example
365 Message-Id: <dummy_test_message_id>
366 Subject: [issue] Testing... [nosy=mary; assignedto=richard]
368 This is a test submission of a new issue.
369 ''')
370 self.compareMessages(self._get_mail(),
371 '''FROM: roundup-admin@your.tracker.email.domain.example
372 TO: chef@bork.bork.bork, mary@test.test, richard@test.test
373 Content-Type: text/plain; charset="utf-8"
374 Subject: [issue1] Testing...
375 To: chef@bork.bork.bork, mary@test.test, richard@test.test
376 From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
377 Reply-To: Roundup issue tracker
378 <issue_tracker@your.tracker.email.domain.example>
379 MIME-Version: 1.0
380 Message-Id: <dummy_test_message_id>
381 X-Roundup-Name: Roundup issue tracker
382 X-Roundup-Loop: hello
383 X-Roundup-Issue-Status: unread
384 Content-Transfer-Encoding: quoted-printable
387 New submission from Bork, Chef <chef@bork.bork.bork>:
389 This is a test submission of a new issue.
391 ----------
392 assignedto: richard
393 messages: 1
394 nosy: Chef, mary, richard
395 status: unread
396 title: Testing...
398 _______________________________________________________________________
399 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
400 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
401 _______________________________________________________________________
402 ''')
404 def testNewIssueNoAuthorInfo(self):
405 self.db.config.MAIL_ADD_AUTHORINFO = 'no'
406 self._handle_mail('''Content-Type: text/plain;
407 charset="iso-8859-1"
408 From: Chef <chef@bork.bork.bork>
409 To: issue_tracker@your.tracker.email.domain.example
410 Message-Id: <dummy_test_message_id>
411 Subject: [issue] Testing... [nosy=mary; assignedto=richard]
413 This is a test submission of a new issue.
414 ''')
415 self.compareMessages(self._get_mail(),
416 '''FROM: roundup-admin@your.tracker.email.domain.example
417 TO: chef@bork.bork.bork, mary@test.test, richard@test.test
418 Content-Type: text/plain; charset="utf-8"
419 Subject: [issue1] Testing...
420 To: mary@test.test, richard@test.test
421 From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
422 Reply-To: Roundup issue tracker
423 <issue_tracker@your.tracker.email.domain.example>
424 MIME-Version: 1.0
425 Message-Id: <dummy_test_message_id>
426 X-Roundup-Name: Roundup issue tracker
427 X-Roundup-Loop: hello
428 X-Roundup-Issue-Status: unread
429 Content-Transfer-Encoding: quoted-printable
431 This is a test submission of a new issue.
433 ----------
434 assignedto: richard
435 messages: 1
436 nosy: Chef, mary, richard
437 status: unread
438 title: Testing...
440 _______________________________________________________________________
441 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
442 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
443 _______________________________________________________________________
444 ''')
446 def testNewIssueNoAuthorEmail(self):
447 self.db.config.MAIL_ADD_AUTHOREMAIL = 'no'
448 self._handle_mail('''Content-Type: text/plain;
449 charset="iso-8859-1"
450 From: Chef <chef@bork.bork.bork>
451 To: issue_tracker@your.tracker.email.domain.example
452 Message-Id: <dummy_test_message_id>
453 Subject: [issue] Testing... [nosy=mary; assignedto=richard]
455 This is a test submission of a new issue.
456 ''')
457 self.compareMessages(self._get_mail(),
458 '''FROM: roundup-admin@your.tracker.email.domain.example
459 TO: chef@bork.bork.bork, mary@test.test, richard@test.test
460 Content-Type: text/plain; charset="utf-8"
461 Subject: [issue1] Testing...
462 To: mary@test.test, richard@test.test
463 From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
464 Reply-To: Roundup issue tracker
465 <issue_tracker@your.tracker.email.domain.example>
466 MIME-Version: 1.0
467 Message-Id: <dummy_test_message_id>
468 X-Roundup-Name: Roundup issue tracker
469 X-Roundup-Loop: hello
470 X-Roundup-Issue-Status: unread
471 Content-Transfer-Encoding: quoted-printable
473 New submission from Bork, Chef:
475 This is a test submission of a new issue.
477 ----------
478 assignedto: richard
479 messages: 1
480 nosy: Chef, mary, richard
481 status: unread
482 title: Testing...
484 _______________________________________________________________________
485 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
486 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
487 _______________________________________________________________________
488 ''')
490 multipart_msg = '''From: mary <mary@test.test>
491 To: issue_tracker@your.tracker.email.domain.example
492 Message-Id: <followup_dummy_id>
493 In-Reply-To: <dummy_test_message_id>
494 Subject: [issue1] Testing...
495 Content-Type: multipart/mixed; boundary="bxyzzy"
496 Content-Disposition: inline
499 --bxyzzy
500 Content-Type: multipart/alternative; boundary="bCsyhTFzCvuiizWE"
501 Content-Disposition: inline
503 --bCsyhTFzCvuiizWE
504 Content-Type: text/plain; charset=us-ascii
505 Content-Disposition: inline
507 test attachment first text/plain
509 --bCsyhTFzCvuiizWE
510 Content-Type: application/octet-stream
511 Content-Disposition: attachment; filename="first.dvi"
512 Content-Transfer-Encoding: base64
514 SnVzdCBhIHRlc3QgAQo=
516 --bCsyhTFzCvuiizWE
517 Content-Type: text/plain; charset=us-ascii
518 Content-Disposition: inline
520 test attachment second text/plain
522 --bCsyhTFzCvuiizWE
523 Content-Type: text/html
524 Content-Disposition: inline
526 <html>
527 to be ignored.
528 </html>
530 --bCsyhTFzCvuiizWE--
532 --bxyzzy
533 Content-Type: multipart/alternative; boundary="bCsyhTFzCvuiizWF"
534 Content-Disposition: inline
536 --bCsyhTFzCvuiizWF
537 Content-Type: text/plain; charset=us-ascii
538 Content-Disposition: inline
540 test attachment third text/plain
542 --bCsyhTFzCvuiizWF
543 Content-Type: application/octet-stream
544 Content-Disposition: attachment; filename="second.dvi"
545 Content-Transfer-Encoding: base64
547 SnVzdCBhIHRlc3QK
549 --bCsyhTFzCvuiizWF--
551 --bxyzzy--
552 '''
554 multipart_msg_latin1 = '''From: mary <mary@test.test>
555 To: issue_tracker@your.tracker.email.domain.example
556 Message-Id: <followup_dummy_id>
557 In-Reply-To: <dummy_test_message_id>
558 Subject: [issue1] Testing...
559 Content-Type: multipart/alternative; boundary=001485f339f8f361fb049188dbba
562 --001485f339f8f361fb049188dbba
563 Content-Type: text/plain; charset=ISO-8859-1
564 Content-Transfer-Encoding: quoted-printable
566 umlaut =E4=F6=FC=C4=D6=DC=DF
568 --001485f339f8f361fb049188dbba
569 Content-Type: text/html; charset=ISO-8859-1
570 Content-Transfer-Encoding: quoted-printable
572 <html>umlaut =E4=F6=FC=C4=D6=DC=DF</html>
574 --001485f339f8f361fb049188dbba--
575 '''
577 multipart_msg_rfc822 = '''From: mary <mary@test.test>
578 To: issue_tracker@your.tracker.email.domain.example
579 Message-Id: <followup_dummy_id>
580 In-Reply-To: <dummy_test_message_id>
581 Subject: [issue1] Testing...
582 Content-Type: multipart/mixed; boundary=001485f339f8f361fb049188dbba
584 This is a multi-part message in MIME format.
585 --001485f339f8f361fb049188dbba
586 Content-Type: text/plain; charset=ISO-8859-15
587 Content-Transfer-Encoding: 7bit
589 First part: Text
591 --001485f339f8f361fb049188dbba
592 Content-Type: message/rfc822; name="Fwd: Original email subject.eml"
593 Content-Transfer-Encoding: 7bit
594 Content-Disposition: attachment; filename="Fwd: Original email subject.eml"
596 Message-Id: <followup_dummy_id_2>
597 In-Reply-To: <dummy_test_message_id_2>
598 MIME-Version: 1.0
599 Subject: Fwd: Original email subject
600 Date: Mon, 23 Aug 2010 08:23:33 +0200
601 Content-Type: multipart/alternative; boundary="090500050101020406060002"
603 This is a multi-part message in MIME format.
604 --090500050101020406060002
605 Content-Type: text/plain; charset=ISO-8859-15; format=flowed
606 Content-Transfer-Encoding: 7bit
608 some text in inner email
609 ========================
611 --090500050101020406060002
612 Content-Type: text/html; charset=ISO-8859-15
613 Content-Transfer-Encoding: 7bit
615 <html>
616 some text in inner email
617 ========================
618 </html>
620 --090500050101020406060002--
622 --001485f339f8f361fb049188dbba--
623 '''
625 def testMultipartKeepAlternatives(self):
626 self.doNewIssue()
627 self._handle_mail(self.multipart_msg)
628 messages = self.db.issue.get('1', 'messages')
629 messages.sort()
630 msg = self.db.msg.getnode (messages[-1])
631 assert(len(msg.files) == 5)
632 names = {0 : 'first.dvi', 4 : 'second.dvi'}
633 content = {3 : 'test attachment third text/plain\n',
634 4 : 'Just a test\n'}
635 for n, id in enumerate (msg.files):
636 f = self.db.file.getnode (id)
637 self.assertEqual(f.name, names.get (n, 'unnamed'))
638 if n in content :
639 self.assertEqual(f.content, content [n])
640 self.assertEqual(msg.content, 'test attachment second text/plain')
642 def testMultipartSeveralAttachmentMessages(self):
643 self.doNewIssue()
644 self._handle_mail(self.multipart_msg)
645 messages = self.db.issue.get('1', 'messages')
646 messages.sort()
647 self.assertEqual(messages[-1], '2')
648 msg = self.db.msg.getnode (messages[-1])
649 self.assertEqual(len(msg.files), 5)
650 issue = self.db.issue.getnode ('1')
651 self.assertEqual(len(issue.files), 5)
652 names = {0 : 'first.dvi', 4 : 'second.dvi'}
653 content = {3 : 'test attachment third text/plain\n',
654 4 : 'Just a test\n'}
655 for n, id in enumerate (msg.files):
656 f = self.db.file.getnode (id)
657 self.assertEqual(f.name, names.get (n, 'unnamed'))
658 if n in content :
659 self.assertEqual(f.content, content [n])
660 self.assertEqual(msg.content, 'test attachment second text/plain')
661 self.assertEqual(msg.files, ['1', '2', '3', '4', '5'])
662 self.assertEqual(issue.files, ['1', '2', '3', '4', '5'])
664 self._handle_mail(self.multipart_msg)
665 issue = self.db.issue.getnode ('1')
666 self.assertEqual(len(issue.files), 10)
667 messages = self.db.issue.get('1', 'messages')
668 messages.sort()
669 self.assertEqual(messages[-1], '3')
670 msg = self.db.msg.getnode (messages[-1])
671 self.assertEqual(issue.files, [str(i+1) for i in range(10)])
672 self.assertEqual(msg.files, ['6', '7', '8', '9', '10'])
674 def testMultipartKeepFiles(self):
675 self.doNewIssue()
676 self._handle_mail(self.multipart_msg)
677 messages = self.db.issue.get('1', 'messages')
678 messages.sort()
679 msg = self.db.msg.getnode (messages[-1])
680 self.assertEqual(len(msg.files), 5)
681 issue = self.db.issue.getnode ('1')
682 self.assertEqual(len(issue.files), 5)
683 names = {0 : 'first.dvi', 4 : 'second.dvi'}
684 content = {3 : 'test attachment third text/plain\n',
685 4 : 'Just a test\n'}
686 for n, id in enumerate (msg.files):
687 f = self.db.file.getnode (id)
688 self.assertEqual(f.name, names.get (n, 'unnamed'))
689 if n in content :
690 self.assertEqual(f.content, content [n])
691 self.assertEqual(msg.content, 'test attachment second text/plain')
692 self._handle_mail('''From: mary <mary@test.test>
693 To: issue_tracker@your.tracker.email.domain.example
694 Message-Id: <followup_dummy_id2>
695 In-Reply-To: <dummy_test_message_id>
696 Subject: [issue1] Testing...
698 This ist a message without attachment
699 ''')
700 issue = self.db.issue.getnode ('1')
701 self.assertEqual(len(issue.files), 5)
702 self.assertEqual(issue.files, ['1', '2', '3', '4', '5'])
704 def testMultipartDropAlternatives(self):
705 self.doNewIssue()
706 self.db.config.MAILGW_IGNORE_ALTERNATIVES = True
707 self._handle_mail(self.multipart_msg)
708 messages = self.db.issue.get('1', 'messages')
709 messages.sort()
710 msg = self.db.msg.getnode (messages[-1])
711 self.assertEqual(len(msg.files), 2)
712 names = {1 : 'second.dvi'}
713 content = {0 : 'test attachment third text/plain\n',
714 1 : 'Just a test\n'}
715 for n, id in enumerate (msg.files):
716 f = self.db.file.getnode (id)
717 self.assertEqual(f.name, names.get (n, 'unnamed'))
718 if n in content :
719 self.assertEqual(f.content, content [n])
720 self.assertEqual(msg.content, 'test attachment second text/plain')
722 def testMultipartCharsetUTF8NoAttach(self):
723 c = 'umlaut \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f'
724 self.doNewIssue()
725 self.db.config.NOSY_MAX_ATTACHMENT_SIZE = 0
726 self._handle_mail(self.multipart_msg_latin1)
727 messages = self.db.issue.get('1', 'messages')
728 messages.sort()
729 msg = self.db.msg.getnode (messages[-1])
730 self.assertEqual(len(msg.files), 1)
731 name = 'unnamed'
732 content = '<html>' + c + '</html>\n'
733 for n, id in enumerate (msg.files):
734 f = self.db.file.getnode (id)
735 self.assertEqual(f.name, name)
736 self.assertEqual(f.content, content)
737 self.assertEqual(msg.content, c)
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: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
745 Reply-To: Roundup issue tracker
746 <issue_tracker@your.tracker.email.domain.example>
747 MIME-Version: 1.0
748 Message-Id: <followup_dummy_id>
749 In-Reply-To: <dummy_test_message_id>
750 X-Roundup-Name: Roundup issue tracker
751 X-Roundup-Loop: hello
752 X-Roundup-Issue-Status: chatting
753 X-Roundup-Issue-Files: unnamed
754 Content-Transfer-Encoding: quoted-printable
757 Contrary, Mary <mary@test.test> added the comment:
759 umlaut =C3=A4=C3=B6=C3=BC=C3=84=C3=96=C3=9C=C3=9F
760 File 'unnamed' not attached - you can download it from http://tracker.examp=
761 le/cgi-bin/roundup.cgi/bugs/file1.
763 ----------
764 status: unread -> chatting
766 _______________________________________________________________________
767 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
768 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
769 _______________________________________________________________________
770 ''')
772 def testMultipartCharsetLatin1NoAttach(self):
773 c = 'umlaut \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f'
774 self.doNewIssue()
775 self.db.config.NOSY_MAX_ATTACHMENT_SIZE = 0
776 self.db.config.MAIL_CHARSET = 'iso-8859-1'
777 self._handle_mail(self.multipart_msg_latin1)
778 messages = self.db.issue.get('1', 'messages')
779 messages.sort()
780 msg = self.db.msg.getnode (messages[-1])
781 self.assertEqual(len(msg.files), 1)
782 name = 'unnamed'
783 content = '<html>' + c + '</html>\n'
784 for n, id in enumerate (msg.files):
785 f = self.db.file.getnode (id)
786 self.assertEqual(f.name, name)
787 self.assertEqual(f.content, content)
788 self.assertEqual(msg.content, c)
789 self.compareMessages(self._get_mail(),
790 '''FROM: roundup-admin@your.tracker.email.domain.example
791 TO: chef@bork.bork.bork, richard@test.test
792 Content-Type: text/plain; charset="iso-8859-1"
793 Subject: [issue1] Testing...
794 To: chef@bork.bork.bork, richard@test.test
795 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
796 Reply-To: Roundup issue tracker
797 <issue_tracker@your.tracker.email.domain.example>
798 MIME-Version: 1.0
799 Message-Id: <followup_dummy_id>
800 In-Reply-To: <dummy_test_message_id>
801 X-Roundup-Name: Roundup issue tracker
802 X-Roundup-Loop: hello
803 X-Roundup-Issue-Status: chatting
804 X-Roundup-Issue-Files: unnamed
805 Content-Transfer-Encoding: quoted-printable
808 Contrary, Mary <mary@test.test> added the comment:
810 umlaut =E4=F6=FC=C4=D6=DC=DF
811 File 'unnamed' not attached - you can download it from http://tracker.examp=
812 le/cgi-bin/roundup.cgi/bugs/file1.
814 ----------
815 status: unread -> chatting
817 _______________________________________________________________________
818 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
819 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
820 _______________________________________________________________________
821 ''')
823 def testMultipartCharsetUTF8AttachFile(self):
824 c = 'umlaut \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f'
825 self.doNewIssue()
826 self._handle_mail(self.multipart_msg_latin1)
827 messages = self.db.issue.get('1', 'messages')
828 messages.sort()
829 msg = self.db.msg.getnode (messages[-1])
830 self.assertEqual(len(msg.files), 1)
831 name = 'unnamed'
832 content = '<html>' + c + '</html>\n'
833 for n, id in enumerate (msg.files):
834 f = self.db.file.getnode (id)
835 self.assertEqual(f.name, name)
836 self.assertEqual(f.content, content)
837 self.assertEqual(msg.content, c)
838 self.compareMessages(self._get_mail(),
839 '''FROM: roundup-admin@your.tracker.email.domain.example
840 TO: chef@bork.bork.bork, richard@test.test
841 Content-Type: multipart/mixed; boundary="utf-8"
842 Subject: [issue1] Testing...
843 To: chef@bork.bork.bork, richard@test.test
844 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
845 Reply-To: Roundup issue tracker
846 <issue_tracker@your.tracker.email.domain.example>
847 MIME-Version: 1.0
848 Message-Id: <followup_dummy_id>
849 In-Reply-To: <dummy_test_message_id>
850 X-Roundup-Name: Roundup issue tracker
851 X-Roundup-Loop: hello
852 X-Roundup-Issue-Status: chatting
853 X-Roundup-Issue-Files: unnamed
854 Content-Transfer-Encoding: quoted-printable
857 --utf-8
858 MIME-Version: 1.0
859 Content-Type: text/plain; charset="utf-8"
860 Content-Transfer-Encoding: quoted-printable
863 Contrary, Mary <mary@test.test> added the comment:
865 umlaut =C3=A4=C3=B6=C3=BC=C3=84=C3=96=C3=9C=C3=9F
867 ----------
868 status: unread -> chatting
870 _______________________________________________________________________
871 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
872 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
873 _______________________________________________________________________
874 --utf-8
875 Content-Type: text/html
876 MIME-Version: 1.0
877 Content-Transfer-Encoding: base64
878 Content-Disposition: attachment;
879 filename="unnamed"
881 PGh0bWw+dW1sYXV0IMOkw7bDvMOEw5bDnMOfPC9odG1sPgo=
883 --utf-8--
884 ''')
886 def testMultipartCharsetLatin1AttachFile(self):
887 c = 'umlaut \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f'
888 self.doNewIssue()
889 self.db.config.MAIL_CHARSET = 'iso-8859-1'
890 self._handle_mail(self.multipart_msg_latin1)
891 messages = self.db.issue.get('1', 'messages')
892 messages.sort()
893 msg = self.db.msg.getnode (messages[-1])
894 self.assertEqual(len(msg.files), 1)
895 name = 'unnamed'
896 content = '<html>' + c + '</html>\n'
897 for n, id in enumerate (msg.files):
898 f = self.db.file.getnode (id)
899 self.assertEqual(f.name, name)
900 self.assertEqual(f.content, content)
901 self.assertEqual(msg.content, c)
902 self.compareMessages(self._get_mail(),
903 '''FROM: roundup-admin@your.tracker.email.domain.example
904 TO: chef@bork.bork.bork, richard@test.test
905 Content-Type: multipart/mixed; boundary="utf-8"
906 Subject: [issue1] Testing...
907 To: chef@bork.bork.bork, richard@test.test
908 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
909 Reply-To: Roundup issue tracker
910 <issue_tracker@your.tracker.email.domain.example>
911 MIME-Version: 1.0
912 Message-Id: <followup_dummy_id>
913 In-Reply-To: <dummy_test_message_id>
914 X-Roundup-Name: Roundup issue tracker
915 X-Roundup-Loop: hello
916 X-Roundup-Issue-Status: chatting
917 X-Roundup-Issue-Files: unnamed
918 Content-Transfer-Encoding: quoted-printable
921 --utf-8
922 MIME-Version: 1.0
923 Content-Type: text/plain; charset="iso-8859-1"
924 Content-Transfer-Encoding: quoted-printable
927 Contrary, Mary <mary@test.test> added the comment:
929 umlaut =E4=F6=FC=C4=D6=DC=DF
931 ----------
932 status: unread -> chatting
934 _______________________________________________________________________
935 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
936 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
937 _______________________________________________________________________
938 --utf-8
939 Content-Type: text/html
940 MIME-Version: 1.0
941 Content-Transfer-Encoding: base64
942 Content-Disposition: attachment;
943 filename="unnamed"
945 PGh0bWw+dW1sYXV0IMOkw7bDvMOEw5bDnMOfPC9odG1sPgo=
947 --utf-8--
948 ''')
950 def testMultipartRFC822(self):
951 self.doNewIssue()
952 self._handle_mail(self.multipart_msg_rfc822)
953 messages = self.db.issue.get('1', 'messages')
954 messages.sort()
955 msg = self.db.msg.getnode (messages[-1])
956 self.assertEqual(len(msg.files), 1)
957 name = "Fwd: Original email subject.eml"
958 for n, id in enumerate (msg.files):
959 f = self.db.file.getnode (id)
960 self.assertEqual(f.name, name)
961 self.assertEqual(msg.content, 'First part: Text')
962 self.compareMessages(self._get_mail(),
963 '''TO: chef@bork.bork.bork, richard@test.test
964 Content-Type: text/plain; charset="utf-8"
965 Subject: [issue1] Testing...
966 To: chef@bork.bork.bork, richard@test.test
967 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
968 Reply-To: Roundup issue tracker
969 <issue_tracker@your.tracker.email.domain.example>
970 MIME-Version: 1.0
971 Message-Id: <followup_dummy_id>
972 In-Reply-To: <dummy_test_message_id>
973 X-Roundup-Name: Roundup issue tracker
974 X-Roundup-Loop: hello
975 X-Roundup-Issue-Status: chatting
976 X-Roundup-Issue-Files: Fwd: Original email subject.eml
977 Content-Transfer-Encoding: quoted-printable
980 --utf-8
981 MIME-Version: 1.0
982 Content-Type: text/plain; charset="utf-8"
983 Content-Transfer-Encoding: quoted-printable
986 Contrary, Mary <mary@test.test> added the comment:
988 First part: Text
990 ----------
991 status: unread -> chatting
993 _______________________________________________________________________
994 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
995 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
996 _______________________________________________________________________
997 --utf-8
998 Content-Type: message/rfc822
999 MIME-Version: 1.0
1000 Content-Disposition: attachment;
1001 filename="Fwd: Original email subject.eml"
1003 Message-Id: <followup_dummy_id_2>
1004 In-Reply-To: <dummy_test_message_id_2>
1005 MIME-Version: 1.0
1006 Subject: Fwd: Original email subject
1007 Date: Mon, 23 Aug 2010 08:23:33 +0200
1008 Content-Type: multipart/alternative; boundary="090500050101020406060002"
1010 This is a multi-part message in MIME format.
1011 --090500050101020406060002
1012 Content-Type: text/plain; charset=ISO-8859-15; format=flowed
1013 Content-Transfer-Encoding: 7bit
1015 some text in inner email
1016 ========================
1018 --090500050101020406060002
1019 Content-Type: text/html; charset=ISO-8859-15
1020 Content-Transfer-Encoding: 7bit
1022 <html>
1023 some text in inner email
1024 ========================
1025 </html>
1027 --090500050101020406060002--
1029 --utf-8--
1030 ''')
1032 def testMultipartRFC822Unpack(self):
1033 self.doNewIssue()
1034 self.db.config.MAILGW_UNPACK_RFC822 = True
1035 self._handle_mail(self.multipart_msg_rfc822)
1036 messages = self.db.issue.get('1', 'messages')
1037 messages.sort()
1038 msg = self.db.msg.getnode (messages[-1])
1039 self.assertEqual(len(msg.files), 2)
1040 t = 'some text in inner email\n========================\n'
1041 content = {0 : t, 1 : '<html>\n' + t + '</html>\n'}
1042 for n, id in enumerate (msg.files):
1043 f = self.db.file.getnode (id)
1044 self.assertEqual(f.name, 'unnamed')
1045 if n in content :
1046 self.assertEqual(f.content, content [n])
1047 self.assertEqual(msg.content, 'First part: Text')
1049 def testSimpleFollowup(self):
1050 self.doNewIssue()
1051 self._handle_mail('''Content-Type: text/plain;
1052 charset="iso-8859-1"
1053 From: mary <mary@test.test>
1054 To: issue_tracker@your.tracker.email.domain.example
1055 Message-Id: <followup_dummy_id>
1056 In-Reply-To: <dummy_test_message_id>
1057 Subject: [issue1] Testing...
1059 This is a second followup
1060 ''')
1061 self.compareMessages(self._get_mail(),
1062 '''FROM: roundup-admin@your.tracker.email.domain.example
1063 TO: chef@bork.bork.bork, richard@test.test
1064 Content-Type: text/plain; charset="utf-8"
1065 Subject: [issue1] Testing...
1066 To: chef@bork.bork.bork, richard@test.test
1067 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
1068 Reply-To: Roundup issue tracker
1069 <issue_tracker@your.tracker.email.domain.example>
1070 MIME-Version: 1.0
1071 Message-Id: <followup_dummy_id>
1072 In-Reply-To: <dummy_test_message_id>
1073 X-Roundup-Name: Roundup issue tracker
1074 X-Roundup-Loop: hello
1075 X-Roundup-Issue-Status: chatting
1076 Content-Transfer-Encoding: quoted-printable
1079 Contrary, Mary <mary@test.test> added the comment:
1081 This is a second followup
1083 ----------
1084 status: unread -> chatting
1086 _______________________________________________________________________
1087 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1088 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1089 _______________________________________________________________________
1090 ''')
1092 def testFollowup(self):
1093 self.doNewIssue()
1095 self._handle_mail('''Content-Type: text/plain;
1096 charset="iso-8859-1"
1097 From: richard <richard@test.test>
1098 To: issue_tracker@your.tracker.email.domain.example
1099 Message-Id: <followup_dummy_id>
1100 In-Reply-To: <dummy_test_message_id>
1101 Subject: [issue1] Testing... [assignedto=mary; nosy=+john]
1103 This is a followup
1104 ''')
1105 l = self.db.issue.get('1', 'nosy')
1106 l.sort()
1107 self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id,
1108 self.john_id])
1110 self.compareMessages(self._get_mail(),
1111 '''FROM: roundup-admin@your.tracker.email.domain.example
1112 TO: chef@bork.bork.bork, john@test.test, mary@test.test
1113 Content-Type: text/plain; charset="utf-8"
1114 Subject: [issue1] Testing...
1115 To: chef@bork.bork.bork, john@test.test, mary@test.test
1116 From: richard <issue_tracker@your.tracker.email.domain.example>
1117 Reply-To: Roundup issue tracker
1118 <issue_tracker@your.tracker.email.domain.example>
1119 MIME-Version: 1.0
1120 Message-Id: <followup_dummy_id>
1121 In-Reply-To: <dummy_test_message_id>
1122 X-Roundup-Name: Roundup issue tracker
1123 X-Roundup-Loop: hello
1124 X-Roundup-Issue-Status: chatting
1125 Content-Transfer-Encoding: quoted-printable
1128 richard <richard@test.test> added the comment:
1130 This is a followup
1132 ----------
1133 assignedto: -> mary
1134 nosy: +john, mary
1135 status: unread -> chatting
1137 _______________________________________________________________________
1138 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1139 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1140 _______________________________________________________________________
1141 ''')
1143 def testFollowupNoSubjectChange(self):
1144 self.db.config.MAILGW_SUBJECT_UPDATES_TITLE = 'no'
1145 self.doNewIssue()
1147 self._handle_mail('''Content-Type: text/plain;
1148 charset="iso-8859-1"
1149 From: richard <richard@test.test>
1150 To: issue_tracker@your.tracker.email.domain.example
1151 Message-Id: <followup_dummy_id>
1152 In-Reply-To: <dummy_test_message_id>
1153 Subject: [issue1] Wrzlbrmft... [assignedto=mary; nosy=+john]
1155 This is a followup
1156 ''')
1157 l = self.db.issue.get('1', 'nosy')
1158 l.sort()
1159 self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id,
1160 self.john_id])
1162 self.compareMessages(self._get_mail(),
1163 '''FROM: roundup-admin@your.tracker.email.domain.example
1164 TO: chef@bork.bork.bork, john@test.test, mary@test.test
1165 Content-Type: text/plain; charset="utf-8"
1166 Subject: [issue1] Testing...
1167 To: chef@bork.bork.bork, john@test.test, mary@test.test
1168 From: richard <issue_tracker@your.tracker.email.domain.example>
1169 Reply-To: Roundup issue tracker
1170 <issue_tracker@your.tracker.email.domain.example>
1171 MIME-Version: 1.0
1172 Message-Id: <followup_dummy_id>
1173 In-Reply-To: <dummy_test_message_id>
1174 X-Roundup-Name: Roundup issue tracker
1175 X-Roundup-Loop: hello
1176 X-Roundup-Issue-Status: chatting
1177 Content-Transfer-Encoding: quoted-printable
1180 richard <richard@test.test> added the comment:
1182 This is a followup
1184 ----------
1185 assignedto: -> mary
1186 nosy: +john, mary
1187 status: unread -> chatting
1189 _______________________________________________________________________
1190 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1191 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1192 _______________________________________________________________________
1193 ''')
1194 self.assertEqual(self.db.issue.get('1','title'), 'Testing...')
1196 def testFollowupExplicitSubjectChange(self):
1197 self.doNewIssue()
1199 self._handle_mail('''Content-Type: text/plain;
1200 charset="iso-8859-1"
1201 From: richard <richard@test.test>
1202 To: issue_tracker@your.tracker.email.domain.example
1203 Message-Id: <followup_dummy_id>
1204 In-Reply-To: <dummy_test_message_id>
1205 Subject: [issue1] Wrzlbrmft... [assignedto=mary; nosy=+john; title=new title]
1207 This is a followup
1208 ''')
1209 l = self.db.issue.get('1', 'nosy')
1210 l.sort()
1211 self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id,
1212 self.john_id])
1214 self.compareMessages(self._get_mail(),
1215 '''FROM: roundup-admin@your.tracker.email.domain.example
1216 TO: chef@bork.bork.bork, john@test.test, mary@test.test
1217 Content-Type: text/plain; charset="utf-8"
1218 Subject: [issue1] new title
1219 To: chef@bork.bork.bork, john@test.test, mary@test.test
1220 From: richard <issue_tracker@your.tracker.email.domain.example>
1221 Reply-To: Roundup issue tracker
1222 <issue_tracker@your.tracker.email.domain.example>
1223 MIME-Version: 1.0
1224 Message-Id: <followup_dummy_id>
1225 In-Reply-To: <dummy_test_message_id>
1226 X-Roundup-Name: Roundup issue tracker
1227 X-Roundup-Loop: hello
1228 X-Roundup-Issue-Status: chatting
1229 Content-Transfer-Encoding: quoted-printable
1232 richard <richard@test.test> added the comment:
1234 This is a followup
1236 ----------
1237 assignedto: -> mary
1238 nosy: +john, mary
1239 status: unread -> chatting
1240 title: Testing... -> new title
1242 _______________________________________________________________________
1243 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1244 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1245 _______________________________________________________________________
1246 ''')
1248 def testNosyGeneration(self):
1249 self.db.issue.create(title='test')
1251 # create a nosy message
1252 msg = self.db.msg.create(content='This is a test',
1253 author=self.richard_id, messageid='<dummy_test_message_id>')
1254 self.db.journaltag = 'richard'
1255 l = self.db.issue.create(title='test', messages=[msg],
1256 nosy=[self.chef_id, self.mary_id, self.john_id])
1258 self.compareMessages(self._get_mail(),
1259 '''FROM: roundup-admin@your.tracker.email.domain.example
1260 TO: chef@bork.bork.bork, john@test.test, mary@test.test
1261 Content-Type: text/plain; charset="utf-8"
1262 Subject: [issue2] test
1263 To: chef@bork.bork.bork, john@test.test, mary@test.test
1264 From: richard <issue_tracker@your.tracker.email.domain.example>
1265 Reply-To: Roundup issue tracker
1266 <issue_tracker@your.tracker.email.domain.example>
1267 MIME-Version: 1.0
1268 Message-Id: <dummy_test_message_id>
1269 X-Roundup-Name: Roundup issue tracker
1270 X-Roundup-Loop: hello
1271 X-Roundup-Issue-Status: unread
1272 Content-Transfer-Encoding: quoted-printable
1275 New submission from richard <richard@test.test>:
1277 This is a test
1279 ----------
1280 messages: 1
1281 nosy: Chef, john, mary, richard
1282 status: unread
1283 title: test
1285 _______________________________________________________________________
1286 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1287 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue2>
1288 _______________________________________________________________________
1289 ''')
1291 def testPropertyChangeOnly(self):
1292 self.doNewIssue()
1293 oldvalues = self.db.getnode('issue', '1').copy()
1294 oldvalues['assignedto'] = None
1295 # reconstruct old behaviour: This would reuse the
1296 # database-handle from the doNewIssue above which has committed
1297 # as user "Chef". So we close and reopen the db as that user.
1298 #self.db.close() actually don't close 'cos this empties memorydb
1299 self.db = self.instance.open('Chef')
1300 self.db.issue.set('1', assignedto=self.chef_id)
1301 self.db.commit()
1302 self.db.issue.nosymessage('1', None, oldvalues)
1304 new_mail = ""
1305 for line in self._get_mail().split("\n"):
1306 if "Message-Id: " in line:
1307 continue
1308 if "Date: " in line:
1309 continue
1310 new_mail += line+"\n"
1312 self.compareMessages(new_mail, """
1313 FROM: roundup-admin@your.tracker.email.domain.example
1314 TO: chef@bork.bork.bork, richard@test.test
1315 Content-Type: text/plain; charset="utf-8"
1316 Subject: [issue1] Testing...
1317 To: chef@bork.bork.bork, richard@test.test
1318 From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
1319 X-Roundup-Name: Roundup issue tracker
1320 X-Roundup-Loop: hello
1321 X-Roundup-Issue-Status: unread
1322 X-Roundup-Version: 1.3.3
1323 In-Reply-To: <dummy_test_message_id>
1324 MIME-Version: 1.0
1325 Reply-To: Roundup issue tracker
1326 <issue_tracker@your.tracker.email.domain.example>
1327 Content-Transfer-Encoding: quoted-printable
1330 Change by Bork, Chef <chef@bork.bork.bork>:
1333 ----------
1334 assignedto: -> Chef
1336 _______________________________________________________________________
1337 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1338 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1339 _______________________________________________________________________
1340 """)
1343 #
1344 # FOLLOWUP TITLE MATCH
1345 #
1346 def testFollowupTitleMatch(self):
1347 self.doNewIssue()
1348 self._handle_mail('''Content-Type: text/plain;
1349 charset="iso-8859-1"
1350 From: richard <richard@test.test>
1351 To: issue_tracker@your.tracker.email.domain.example
1352 Message-Id: <followup_dummy_id>
1353 Subject: Re: Testing... [assignedto=mary; nosy=+john]
1355 This is a followup
1356 ''')
1357 self.compareMessages(self._get_mail(),
1358 '''FROM: roundup-admin@your.tracker.email.domain.example
1359 TO: chef@bork.bork.bork, john@test.test, mary@test.test
1360 Content-Type: text/plain; charset="utf-8"
1361 Subject: [issue1] Testing...
1362 To: chef@bork.bork.bork, john@test.test, mary@test.test
1363 From: richard <issue_tracker@your.tracker.email.domain.example>
1364 Reply-To: Roundup issue tracker
1365 <issue_tracker@your.tracker.email.domain.example>
1366 MIME-Version: 1.0
1367 Message-Id: <followup_dummy_id>
1368 In-Reply-To: <dummy_test_message_id>
1369 X-Roundup-Name: Roundup issue tracker
1370 X-Roundup-Loop: hello
1371 X-Roundup-Issue-Status: chatting
1372 Content-Transfer-Encoding: quoted-printable
1375 richard <richard@test.test> added the comment:
1377 This is a followup
1379 ----------
1380 assignedto: -> mary
1381 nosy: +john, mary
1382 status: unread -> chatting
1384 _______________________________________________________________________
1385 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1386 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1387 _______________________________________________________________________
1388 ''')
1390 def testFollowupTitleMatchMultiRe(self):
1391 nodeid1 = self.doNewIssue()
1392 nodeid2 = self._handle_mail('''Content-Type: text/plain;
1393 charset="iso-8859-1"
1394 From: richard <richard@test.test>
1395 To: issue_tracker@your.tracker.email.domain.example
1396 Message-Id: <followup_dummy_id>
1397 Subject: Re: Testing... [assignedto=mary; nosy=+john]
1399 This is a followup
1400 ''')
1402 nodeid3 = self._handle_mail('''Content-Type: text/plain;
1403 charset="iso-8859-1"
1404 From: richard <richard@test.test>
1405 To: issue_tracker@your.tracker.email.domain.example
1406 Message-Id: <followup2_dummy_id>
1407 Subject: Ang: Re: Testing...
1409 This is a followup
1410 ''')
1411 self.assertEqual(nodeid1, nodeid2)
1412 self.assertEqual(nodeid1, nodeid3)
1414 def testFollowupTitleMatchNever(self):
1415 nodeid = self.doNewIssue()
1416 self.db.config.MAILGW_SUBJECT_CONTENT_MATCH = 'never'
1417 self.assertNotEqual(self._handle_mail('''Content-Type: text/plain;
1418 charset="iso-8859-1"
1419 From: richard <richard@test.test>
1420 To: issue_tracker@your.tracker.email.domain.example
1421 Message-Id: <followup_dummy_id>
1422 Subject: Re: Testing...
1424 This is a followup
1425 '''), nodeid)
1427 def testFollowupTitleMatchNeverInterval(self):
1428 nodeid = self.doNewIssue()
1429 # force failure of the interval
1430 time.sleep(2)
1431 self.db.config.MAILGW_SUBJECT_CONTENT_MATCH = 'creation 00:00:01'
1432 self.assertNotEqual(self._handle_mail('''Content-Type: text/plain;
1433 charset="iso-8859-1"
1434 From: richard <richard@test.test>
1435 To: issue_tracker@your.tracker.email.domain.example
1436 Message-Id: <followup_dummy_id>
1437 Subject: Re: Testing...
1439 This is a followup
1440 '''), nodeid)
1443 def testFollowupTitleMatchInterval(self):
1444 nodeid = self.doNewIssue()
1445 self.db.config.MAILGW_SUBJECT_CONTENT_MATCH = 'creation +1d'
1446 self.assertEqual(self._handle_mail('''Content-Type: text/plain;
1447 charset="iso-8859-1"
1448 From: richard <richard@test.test>
1449 To: issue_tracker@your.tracker.email.domain.example
1450 Message-Id: <followup_dummy_id>
1451 Subject: Re: Testing...
1453 This is a followup
1454 '''), nodeid)
1457 def testFollowupNosyAuthor(self):
1458 self.doNewIssue()
1459 self.db.config.ADD_AUTHOR_TO_NOSY = 'yes'
1460 self._handle_mail('''Content-Type: text/plain;
1461 charset="iso-8859-1"
1462 From: john@test.test
1463 To: issue_tracker@your.tracker.email.domain.example
1464 Message-Id: <followup_dummy_id>
1465 In-Reply-To: <dummy_test_message_id>
1466 Subject: [issue1] Testing...
1468 This is a followup
1469 ''')
1471 self.compareMessages(self._get_mail(),
1472 '''FROM: roundup-admin@your.tracker.email.domain.example
1473 TO: chef@bork.bork.bork, richard@test.test
1474 Content-Type: text/plain; charset="utf-8"
1475 Subject: [issue1] Testing...
1476 To: chef@bork.bork.bork, richard@test.test
1477 From: John Doe <issue_tracker@your.tracker.email.domain.example>
1478 Reply-To: Roundup issue tracker
1479 <issue_tracker@your.tracker.email.domain.example>
1480 MIME-Version: 1.0
1481 Message-Id: <followup_dummy_id>
1482 In-Reply-To: <dummy_test_message_id>
1483 X-Roundup-Name: Roundup issue tracker
1484 X-Roundup-Loop: hello
1485 X-Roundup-Issue-Status: chatting
1486 Content-Transfer-Encoding: quoted-printable
1489 John Doe <john@test.test> added the comment:
1491 This is a followup
1493 ----------
1494 nosy: +john
1495 status: unread -> chatting
1497 _______________________________________________________________________
1498 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1499 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1500 _______________________________________________________________________
1502 ''')
1504 def testFollowupNosyRecipients(self):
1505 self.doNewIssue()
1506 self.db.config.ADD_RECIPIENTS_TO_NOSY = 'yes'
1507 self._handle_mail('''Content-Type: text/plain;
1508 charset="iso-8859-1"
1509 From: richard@test.test
1510 To: issue_tracker@your.tracker.email.domain.example
1511 Cc: john@test.test
1512 Message-Id: <followup_dummy_id>
1513 In-Reply-To: <dummy_test_message_id>
1514 Subject: [issue1] Testing...
1516 This is a followup
1517 ''')
1518 self.compareMessages(self._get_mail(),
1519 '''FROM: roundup-admin@your.tracker.email.domain.example
1520 TO: chef@bork.bork.bork
1521 Content-Type: text/plain; charset="utf-8"
1522 Subject: [issue1] Testing...
1523 To: chef@bork.bork.bork
1524 From: richard <issue_tracker@your.tracker.email.domain.example>
1525 Reply-To: Roundup issue tracker
1526 <issue_tracker@your.tracker.email.domain.example>
1527 MIME-Version: 1.0
1528 Message-Id: <followup_dummy_id>
1529 In-Reply-To: <dummy_test_message_id>
1530 X-Roundup-Name: Roundup issue tracker
1531 X-Roundup-Loop: hello
1532 X-Roundup-Issue-Status: chatting
1533 Content-Transfer-Encoding: quoted-printable
1536 richard <richard@test.test> added the comment:
1538 This is a followup
1540 ----------
1541 nosy: +john
1542 status: unread -> chatting
1544 _______________________________________________________________________
1545 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1546 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1547 _______________________________________________________________________
1549 ''')
1551 def testFollowupNosyAuthorAndCopy(self):
1552 self.doNewIssue()
1553 self.db.config.ADD_AUTHOR_TO_NOSY = 'yes'
1554 self.db.config.MESSAGES_TO_AUTHOR = 'yes'
1555 self._handle_mail('''Content-Type: text/plain;
1556 charset="iso-8859-1"
1557 From: john@test.test
1558 To: issue_tracker@your.tracker.email.domain.example
1559 Message-Id: <followup_dummy_id>
1560 In-Reply-To: <dummy_test_message_id>
1561 Subject: [issue1] Testing...
1563 This is a followup
1564 ''')
1565 self.compareMessages(self._get_mail(),
1566 '''FROM: roundup-admin@your.tracker.email.domain.example
1567 TO: chef@bork.bork.bork, john@test.test, richard@test.test
1568 Content-Type: text/plain; charset="utf-8"
1569 Subject: [issue1] Testing...
1570 To: chef@bork.bork.bork, john@test.test, richard@test.test
1571 From: John Doe <issue_tracker@your.tracker.email.domain.example>
1572 Reply-To: Roundup issue tracker
1573 <issue_tracker@your.tracker.email.domain.example>
1574 MIME-Version: 1.0
1575 Message-Id: <followup_dummy_id>
1576 In-Reply-To: <dummy_test_message_id>
1577 X-Roundup-Name: Roundup issue tracker
1578 X-Roundup-Loop: hello
1579 X-Roundup-Issue-Status: chatting
1580 Content-Transfer-Encoding: quoted-printable
1583 John Doe <john@test.test> added the comment:
1585 This is a followup
1587 ----------
1588 nosy: +john
1589 status: unread -> chatting
1591 _______________________________________________________________________
1592 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1593 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1594 _______________________________________________________________________
1596 ''')
1598 def testFollowupNoNosyAuthor(self):
1599 self.doNewIssue()
1600 self.instance.config.ADD_AUTHOR_TO_NOSY = 'no'
1601 self._handle_mail('''Content-Type: text/plain;
1602 charset="iso-8859-1"
1603 From: john@test.test
1604 To: issue_tracker@your.tracker.email.domain.example
1605 Message-Id: <followup_dummy_id>
1606 In-Reply-To: <dummy_test_message_id>
1607 Subject: [issue1] Testing...
1609 This is a followup
1610 ''')
1611 self.compareMessages(self._get_mail(),
1612 '''FROM: roundup-admin@your.tracker.email.domain.example
1613 TO: chef@bork.bork.bork, richard@test.test
1614 Content-Type: text/plain; charset="utf-8"
1615 Subject: [issue1] Testing...
1616 To: chef@bork.bork.bork, richard@test.test
1617 From: John Doe <issue_tracker@your.tracker.email.domain.example>
1618 Reply-To: Roundup issue tracker
1619 <issue_tracker@your.tracker.email.domain.example>
1620 MIME-Version: 1.0
1621 Message-Id: <followup_dummy_id>
1622 In-Reply-To: <dummy_test_message_id>
1623 X-Roundup-Name: Roundup issue tracker
1624 X-Roundup-Loop: hello
1625 X-Roundup-Issue-Status: chatting
1626 Content-Transfer-Encoding: quoted-printable
1629 John Doe <john@test.test> added the comment:
1631 This is a followup
1633 ----------
1634 status: unread -> chatting
1636 _______________________________________________________________________
1637 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1638 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1639 _______________________________________________________________________
1641 ''')
1643 def testFollowupNoNosyRecipients(self):
1644 self.doNewIssue()
1645 self.instance.config.ADD_RECIPIENTS_TO_NOSY = 'no'
1646 self._handle_mail('''Content-Type: text/plain;
1647 charset="iso-8859-1"
1648 From: richard@test.test
1649 To: issue_tracker@your.tracker.email.domain.example
1650 Cc: john@test.test
1651 Message-Id: <followup_dummy_id>
1652 In-Reply-To: <dummy_test_message_id>
1653 Subject: [issue1] Testing...
1655 This is a followup
1656 ''')
1657 self.compareMessages(self._get_mail(),
1658 '''FROM: roundup-admin@your.tracker.email.domain.example
1659 TO: chef@bork.bork.bork
1660 Content-Type: text/plain; charset="utf-8"
1661 Subject: [issue1] Testing...
1662 To: chef@bork.bork.bork
1663 From: richard <issue_tracker@your.tracker.email.domain.example>
1664 Reply-To: Roundup issue tracker
1665 <issue_tracker@your.tracker.email.domain.example>
1666 MIME-Version: 1.0
1667 Message-Id: <followup_dummy_id>
1668 In-Reply-To: <dummy_test_message_id>
1669 X-Roundup-Name: Roundup issue tracker
1670 X-Roundup-Loop: hello
1671 X-Roundup-Issue-Status: chatting
1672 Content-Transfer-Encoding: quoted-printable
1675 richard <richard@test.test> added the comment:
1677 This is a followup
1679 ----------
1680 status: unread -> chatting
1682 _______________________________________________________________________
1683 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1684 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1685 _______________________________________________________________________
1687 ''')
1689 def testFollowupEmptyMessage(self):
1690 self.doNewIssue()
1692 self._handle_mail('''Content-Type: text/plain;
1693 charset="iso-8859-1"
1694 From: richard <richard@test.test>
1695 To: issue_tracker@your.tracker.email.domain.example
1696 Message-Id: <followup_dummy_id>
1697 In-Reply-To: <dummy_test_message_id>
1698 Subject: [issue1] Testing... [assignedto=mary; nosy=+john]
1700 ''')
1701 l = self.db.issue.get('1', 'nosy')
1702 l.sort()
1703 self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id,
1704 self.john_id])
1706 # should be no file created (ie. no message)
1707 assert not os.path.exists(SENDMAILDEBUG)
1709 def testFollowupEmptyMessageNoSubject(self):
1710 self.doNewIssue()
1712 self._handle_mail('''Content-Type: text/plain;
1713 charset="iso-8859-1"
1714 From: richard <richard@test.test>
1715 To: issue_tracker@your.tracker.email.domain.example
1716 Message-Id: <followup_dummy_id>
1717 In-Reply-To: <dummy_test_message_id>
1718 Subject: [issue1] [assignedto=mary; nosy=+john]
1720 ''')
1721 l = self.db.issue.get('1', 'nosy')
1722 l.sort()
1723 self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id,
1724 self.john_id])
1726 # should be no file created (ie. no message)
1727 assert not os.path.exists(SENDMAILDEBUG)
1729 def testNosyRemove(self):
1730 self.doNewIssue()
1732 self._handle_mail('''Content-Type: text/plain;
1733 charset="iso-8859-1"
1734 From: richard <richard@test.test>
1735 To: issue_tracker@your.tracker.email.domain.example
1736 Message-Id: <followup_dummy_id>
1737 In-Reply-To: <dummy_test_message_id>
1738 Subject: [issue1] Testing... [nosy=-richard]
1740 ''')
1741 l = self.db.issue.get('1', 'nosy')
1742 l.sort()
1743 self.assertEqual(l, [self.chef_id])
1745 # NO NOSY MESSAGE SHOULD BE SENT!
1746 assert not os.path.exists(SENDMAILDEBUG)
1748 def testNewUserAuthor(self):
1749 self.db.commit()
1750 l = self.db.user.list()
1751 l.sort()
1752 message = '''Content-Type: text/plain;
1753 charset="iso-8859-1"
1754 From: fubar <fubar@bork.bork.bork>
1755 To: issue_tracker@your.tracker.email.domain.example
1756 Message-Id: <dummy_test_message_id>
1757 Subject: [issue] Testing...
1759 This is a test submission of a new issue.
1760 '''
1761 self.db.security.role['anonymous'].permissions=[]
1762 anonid = self.db.user.lookup('anonymous')
1763 self.db.user.set(anonid, roles='Anonymous')
1764 try:
1765 self._handle_mail(message)
1766 except Unauthorized, value:
1767 body_diff = self.compareMessages(str(value), """
1768 You are not a registered user.
1770 Unknown address: fubar@bork.bork.bork
1771 """)
1772 assert not body_diff, body_diff
1773 else:
1774 raise AssertionError, "Unathorized not raised when handling mail"
1776 # Add Web Access role to anonymous, and try again to make sure
1777 # we get a "please register at:" message this time.
1778 p = [
1779 self.db.security.getPermission('Register', 'user'),
1780 self.db.security.getPermission('Web Access', None),
1781 ]
1782 self.db.security.role['anonymous'].permissions=p
1783 try:
1784 self._handle_mail(message)
1785 except Unauthorized, value:
1786 body_diff = self.compareMessages(str(value), """
1787 You are not a registered user. Please register at:
1789 http://tracker.example/cgi-bin/roundup.cgi/bugs/user?template=register
1791 ...before sending mail to the tracker.
1793 Unknown address: fubar@bork.bork.bork
1794 """)
1795 assert not body_diff, body_diff
1796 else:
1797 raise AssertionError, "Unauthorized not raised when handling mail"
1799 # Make sure list of users is the same as before.
1800 m = self.db.user.list()
1801 m.sort()
1802 self.assertEqual(l, m)
1804 # now with the permission
1805 p = [
1806 self.db.security.getPermission('Register', 'user'),
1807 self.db.security.getPermission('Email Access', None),
1808 ]
1809 self.db.security.role['anonymous'].permissions=p
1810 self._handle_mail(message)
1811 m = self.db.user.list()
1812 m.sort()
1813 self.assertNotEqual(l, m)
1815 def testNewUserAuthorEncodedName(self):
1816 l = set(self.db.user.list())
1817 # From: name has Euro symbol in it
1818 message = '''Content-Type: text/plain;
1819 charset="iso-8859-1"
1820 From: =?utf8?b?SOKCrGxsbw==?= <fubar@bork.bork.bork>
1821 To: issue_tracker@your.tracker.email.domain.example
1822 Message-Id: <dummy_test_message_id>
1823 Subject: [issue] Testing...
1825 This is a test submission of a new issue.
1826 '''
1827 p = [
1828 self.db.security.getPermission('Register', 'user'),
1829 self.db.security.getPermission('Email Access', None),
1830 self.db.security.getPermission('Create', 'issue'),
1831 self.db.security.getPermission('Create', 'msg'),
1832 ]
1833 self.db.security.role['anonymous'].permissions = p
1834 self._handle_mail(message)
1835 m = set(self.db.user.list())
1836 new = list(m - l)[0]
1837 name = self.db.user.get(new, 'realname')
1838 self.assertEquals(name, 'H€llo')
1840 def testNewUserAuthorMixedEncodedName(self):
1841 l = set(self.db.user.list())
1842 # From: name has Euro symbol in it
1843 message = '''Content-Type: text/plain;
1844 charset="iso-8859-1"
1845 From: Firstname =?utf-8?b?w6TDtsOf?= Last <fubar@bork.bork.bork>
1846 To: issue_tracker@your.tracker.email.domain.example
1847 Message-Id: <dummy_test_message_id>
1848 Subject: [issue] Test =?utf-8?b?w4TDlsOc?= umlauts
1849 X1
1850 X2
1852 This is a test submission of a new issue.
1853 '''
1854 p = [
1855 self.db.security.getPermission('Register', 'user'),
1856 self.db.security.getPermission('Email Access', None),
1857 self.db.security.getPermission('Create', 'issue'),
1858 self.db.security.getPermission('Create', 'msg'),
1859 ]
1860 self.db.security.role['anonymous'].permissions = p
1861 self._handle_mail(message)
1862 title = self.db.issue.get('1', 'title')
1863 self.assertEquals(title, 'Test \xc3\x84\xc3\x96\xc3\x9c umlauts X1 X2')
1864 m = set(self.db.user.list())
1865 new = list(m - l)[0]
1866 name = self.db.user.get(new, 'realname')
1867 self.assertEquals(name, 'Firstname \xc3\xa4\xc3\xb6\xc3\x9f Last')
1869 def testUnknownUser(self):
1870 l = set(self.db.user.list())
1871 message = '''Content-Type: text/plain;
1872 charset="iso-8859-1"
1873 From: Nonexisting User <nonexisting@bork.bork.bork>
1874 To: issue_tracker@your.tracker.email.domain.example
1875 Message-Id: <dummy_test_message_id>
1876 Subject: [issue] Testing nonexisting user...
1878 This is a test submission of a new issue.
1879 '''
1880 # trap_exc=1: we want a bounce message:
1881 ret = self._handle_mail(message, trap_exc=1)
1882 self.compareMessages(self._get_mail(),
1883 '''FROM: Roundup issue tracker <roundup-admin@your.tracker.email.domain.example>
1884 TO: nonexisting@bork.bork.bork
1885 From nobody Tue Jul 14 12:04:11 2009
1886 Content-Type: multipart/mixed; boundary="===============0639262320=="
1887 MIME-Version: 1.0
1888 Subject: Failed issue tracker submission
1889 To: nonexisting@bork.bork.bork
1890 From: Roundup issue tracker <roundup-admin@your.tracker.email.domain.example>
1891 Date: Tue, 14 Jul 2009 12:04:11 +0000
1892 Precedence: bulk
1893 X-Roundup-Name: Roundup issue tracker
1894 X-Roundup-Loop: hello
1895 X-Roundup-Version: 1.4.8
1896 MIME-Version: 1.0
1898 --===============0639262320==
1899 Content-Type: text/plain; charset="us-ascii"
1900 MIME-Version: 1.0
1901 Content-Transfer-Encoding: 7bit
1905 You are not a registered user. Please register at:
1907 http://tracker.example/cgi-bin/roundup.cgi/bugs/user?template=register
1909 ...before sending mail to the tracker.
1911 Unknown address: nonexisting@bork.bork.bork
1913 --===============0639262320==
1914 Content-Type: text/plain; charset="us-ascii"
1915 MIME-Version: 1.0
1916 Content-Transfer-Encoding: 7bit
1918 Content-Type: text/plain;
1919 charset="iso-8859-1"
1920 From: Nonexisting User <nonexisting@bork.bork.bork>
1921 To: issue_tracker@your.tracker.email.domain.example
1922 Message-Id: <dummy_test_message_id>
1923 Subject: [issue] Testing nonexisting user...
1925 This is a test submission of a new issue.
1927 --===============0639262320==--
1928 ''')
1930 def testEnc01(self):
1931 self.db.user.set(self.mary_id,
1932 realname='\xe4\xf6\xfc\xc4\xd6\xdc\xdf, Mary'.decode
1933 ('latin-1').encode('utf-8'))
1934 self.doNewIssue()
1935 self._handle_mail('''Content-Type: text/plain;
1936 charset="iso-8859-1"
1937 From: mary <mary@test.test>
1938 To: issue_tracker@your.tracker.email.domain.example
1939 Message-Id: <followup_dummy_id>
1940 In-Reply-To: <dummy_test_message_id>
1941 Subject: [issue1] Testing...
1942 Content-Type: text/plain;
1943 charset="iso-8859-1"
1944 Content-Transfer-Encoding: quoted-printable
1946 A message with encoding (encoded oe =F6)
1948 ''')
1949 self.compareMessages(self._get_mail(),
1950 '''FROM: roundup-admin@your.tracker.email.domain.example
1951 TO: chef@bork.bork.bork, richard@test.test
1952 Content-Type: text/plain; charset="utf-8"
1953 Subject: [issue1] Testing...
1954 To: chef@bork.bork.bork, richard@test.test
1955 From: =?utf-8?b?w6TDtsO8w4TDlsOcw58sIE1hcnk=?=
1956 <issue_tracker@your.tracker.email.domain.example>
1957 Reply-To: Roundup issue tracker
1958 <issue_tracker@your.tracker.email.domain.example>
1959 MIME-Version: 1.0
1960 Message-Id: <followup_dummy_id>
1961 In-Reply-To: <dummy_test_message_id>
1962 X-Roundup-Name: Roundup issue tracker
1963 X-Roundup-Loop: hello
1964 X-Roundup-Issue-Status: chatting
1965 Content-Transfer-Encoding: quoted-printable
1968 =C3=A4=C3=B6=C3=BC=C3=84=C3=96=C3=9C=C3=9F, Mary <mary@test.test> added the=
1969 comment:
1971 A message with encoding (encoded oe =C3=B6)
1973 ----------
1974 status: unread -> chatting
1976 _______________________________________________________________________
1977 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1978 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1979 _______________________________________________________________________
1980 ''')
1982 def testEncNonUTF8(self):
1983 self.doNewIssue()
1984 self.instance.config.EMAIL_CHARSET = 'iso-8859-1'
1985 self._handle_mail('''Content-Type: text/plain;
1986 charset="iso-8859-1"
1987 From: mary <mary@test.test>
1988 To: issue_tracker@your.tracker.email.domain.example
1989 Message-Id: <followup_dummy_id>
1990 In-Reply-To: <dummy_test_message_id>
1991 Subject: [issue1] Testing...
1992 Content-Type: text/plain;
1993 charset="iso-8859-1"
1994 Content-Transfer-Encoding: quoted-printable
1996 A message with encoding (encoded oe =F6)
1998 ''')
1999 self.compareMessages(self._get_mail(),
2000 '''FROM: roundup-admin@your.tracker.email.domain.example
2001 TO: chef@bork.bork.bork, richard@test.test
2002 Content-Type: text/plain; charset="iso-8859-1"
2003 Subject: [issue1] Testing...
2004 To: chef@bork.bork.bork, richard@test.test
2005 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
2006 Reply-To: Roundup issue tracker
2007 <issue_tracker@your.tracker.email.domain.example>
2008 MIME-Version: 1.0
2009 Message-Id: <followup_dummy_id>
2010 In-Reply-To: <dummy_test_message_id>
2011 X-Roundup-Name: Roundup issue tracker
2012 X-Roundup-Loop: hello
2013 X-Roundup-Issue-Status: chatting
2014 Content-Transfer-Encoding: quoted-printable
2017 Contrary, Mary <mary@test.test> added the comment:
2019 A message with encoding (encoded oe =F6)
2021 ----------
2022 status: unread -> chatting
2024 _______________________________________________________________________
2025 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
2026 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
2027 _______________________________________________________________________
2028 ''')
2031 def testMultipartEnc01(self):
2032 self.doNewIssue()
2033 self._handle_mail('''Content-Type: text/plain;
2034 charset="iso-8859-1"
2035 From: mary <mary@test.test>
2036 To: issue_tracker@your.tracker.email.domain.example
2037 Message-Id: <followup_dummy_id>
2038 In-Reply-To: <dummy_test_message_id>
2039 Subject: [issue1] Testing...
2040 Content-Type: multipart/mixed;
2041 boundary="----_=_NextPart_000_01"
2043 This message is in MIME format. Since your mail reader does not understand
2044 this format, some or all of this message may not be legible.
2046 ------_=_NextPart_000_01
2047 Content-Type: text/plain;
2048 charset="iso-8859-1"
2049 Content-Transfer-Encoding: quoted-printable
2051 A message with first part encoded (encoded oe =F6)
2053 ''')
2054 self.compareMessages(self._get_mail(),
2055 '''FROM: roundup-admin@your.tracker.email.domain.example
2056 TO: chef@bork.bork.bork, richard@test.test
2057 Content-Type: text/plain; charset="utf-8"
2058 Subject: [issue1] Testing...
2059 To: chef@bork.bork.bork, richard@test.test
2060 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
2061 Reply-To: Roundup issue tracker
2062 <issue_tracker@your.tracker.email.domain.example>
2063 MIME-Version: 1.0
2064 Message-Id: <followup_dummy_id>
2065 In-Reply-To: <dummy_test_message_id>
2066 X-Roundup-Name: Roundup issue tracker
2067 X-Roundup-Loop: hello
2068 X-Roundup-Issue-Status: chatting
2069 Content-Transfer-Encoding: quoted-printable
2072 Contrary, Mary <mary@test.test> added the comment:
2074 A message with first part encoded (encoded oe =C3=B6)
2076 ----------
2077 status: unread -> chatting
2079 _______________________________________________________________________
2080 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
2081 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
2082 _______________________________________________________________________
2083 ''')
2085 def testContentDisposition(self):
2086 self.doNewIssue()
2087 self._handle_mail('''Content-Type: text/plain;
2088 charset="iso-8859-1"
2089 From: mary <mary@test.test>
2090 To: issue_tracker@your.tracker.email.domain.example
2091 Message-Id: <followup_dummy_id>
2092 In-Reply-To: <dummy_test_message_id>
2093 Subject: [issue1] Testing...
2094 Content-Type: multipart/mixed; boundary="bCsyhTFzCvuiizWE"
2095 Content-Disposition: inline
2098 --bCsyhTFzCvuiizWE
2099 Content-Type: text/plain; charset=us-ascii
2100 Content-Disposition: inline
2102 test attachment binary
2104 --bCsyhTFzCvuiizWE
2105 Content-Type: application/octet-stream
2106 Content-Disposition: attachment; filename="main.dvi"
2107 Content-Transfer-Encoding: base64
2109 SnVzdCBhIHRlc3QgAQo=
2111 --bCsyhTFzCvuiizWE--
2112 ''')
2113 messages = self.db.issue.get('1', 'messages')
2114 messages.sort()
2115 file = self.db.file.getnode (self.db.msg.get(messages[-1], 'files')[0])
2116 self.assertEqual(file.name, 'main.dvi')
2117 self.assertEqual(file.content, 'Just a test \001\n')
2119 def testFollowupStupidQuoting(self):
2120 self.doNewIssue()
2122 self._handle_mail('''Content-Type: text/plain;
2123 charset="iso-8859-1"
2124 From: richard <richard@test.test>
2125 To: issue_tracker@your.tracker.email.domain.example
2126 Message-Id: <followup_dummy_id>
2127 In-Reply-To: <dummy_test_message_id>
2128 Subject: Re: "[issue1] Testing... "
2130 This is a followup
2131 ''')
2132 self.compareMessages(self._get_mail(),
2133 '''FROM: roundup-admin@your.tracker.email.domain.example
2134 TO: chef@bork.bork.bork
2135 Content-Type: text/plain; charset="utf-8"
2136 Subject: [issue1] Testing...
2137 To: chef@bork.bork.bork
2138 From: richard <issue_tracker@your.tracker.email.domain.example>
2139 Reply-To: Roundup issue tracker
2140 <issue_tracker@your.tracker.email.domain.example>
2141 MIME-Version: 1.0
2142 Message-Id: <followup_dummy_id>
2143 In-Reply-To: <dummy_test_message_id>
2144 X-Roundup-Name: Roundup issue tracker
2145 X-Roundup-Loop: hello
2146 X-Roundup-Issue-Status: chatting
2147 Content-Transfer-Encoding: quoted-printable
2150 richard <richard@test.test> added the comment:
2152 This is a followup
2154 ----------
2155 status: unread -> chatting
2157 _______________________________________________________________________
2158 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
2159 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
2160 _______________________________________________________________________
2161 ''')
2163 def testEmailQuoting(self):
2164 self.instance.config.EMAIL_KEEP_QUOTED_TEXT = 'no'
2165 self.innerTestQuoting('''This is a followup
2166 ''')
2168 def testEmailQuotingRemove(self):
2169 self.instance.config.EMAIL_KEEP_QUOTED_TEXT = 'yes'
2170 self.innerTestQuoting('''Blah blah wrote:
2171 > Blah bklaskdfj sdf asdf jlaskdf skj sdkfjl asdf
2172 > skdjlkjsdfalsdkfjasdlfkj dlfksdfalksd fj
2173 >
2175 This is a followup
2176 ''')
2178 def innerTestQuoting(self, expect):
2179 nodeid = self.doNewIssue()
2181 messages = self.db.issue.get(nodeid, 'messages')
2183 self._handle_mail('''Content-Type: text/plain;
2184 charset="iso-8859-1"
2185 From: richard <richard@test.test>
2186 To: issue_tracker@your.tracker.email.domain.example
2187 Message-Id: <followup_dummy_id>
2188 In-Reply-To: <dummy_test_message_id>
2189 Subject: Re: [issue1] Testing...
2191 Blah blah wrote:
2192 > Blah bklaskdfj sdf asdf jlaskdf skj sdkfjl asdf
2193 > skdjlkjsdfalsdkfjasdlfkj dlfksdfalksd fj
2194 >
2196 This is a followup
2197 ''')
2198 # figure the new message id
2199 newmessages = self.db.issue.get(nodeid, 'messages')
2200 for msg in messages:
2201 newmessages.remove(msg)
2202 messageid = newmessages[0]
2204 self.compareMessages(self.db.msg.get(messageid, 'content'), expect)
2206 def testUserLookup(self):
2207 i = self.db.user.create(username='user1', address='user1@foo.com')
2208 self.assertEqual(uidFromAddress(self.db, ('', 'user1@foo.com'), 0), i)
2209 self.assertEqual(uidFromAddress(self.db, ('', 'USER1@foo.com'), 0), i)
2210 i = self.db.user.create(username='user2', address='USER2@foo.com')
2211 self.assertEqual(uidFromAddress(self.db, ('', 'USER2@foo.com'), 0), i)
2212 self.assertEqual(uidFromAddress(self.db, ('', 'user2@foo.com'), 0), i)
2214 def testUserAlternateLookup(self):
2215 i = self.db.user.create(username='user1', address='user1@foo.com',
2216 alternate_addresses='user1@bar.com')
2217 self.assertEqual(uidFromAddress(self.db, ('', 'user1@bar.com'), 0), i)
2218 self.assertEqual(uidFromAddress(self.db, ('', 'USER1@bar.com'), 0), i)
2220 def testUserAlternateSubstringNomatch(self):
2221 i = self.db.user.create(username='user1', address='user1@foo.com',
2222 alternate_addresses='x-user1@bar.com')
2223 self.assertEqual(uidFromAddress(self.db, ('', 'user1@bar.com'), 0), 0)
2224 self.assertEqual(uidFromAddress(self.db, ('', 'USER1@bar.com'), 0), 0)
2226 def testUserCreate(self):
2227 i = uidFromAddress(self.db, ('', 'user@foo.com'), 1)
2228 self.assertNotEqual(uidFromAddress(self.db, ('', 'user@bar.com'), 1), i)
2230 def testRFC2822(self):
2231 ascii_header = "[issue243] This is a \"test\" - with 'quotation' marks"
2232 unicode_header = '[issue244] \xd0\xb0\xd0\xbd\xd0\xb4\xd1\x80\xd0\xb5\xd0\xb9'
2233 unicode_encoded = '=?utf-8?q?[issue244]_=D0=B0=D0=BD=D0=B4=D1=80=D0=B5=D0=B9?='
2234 self.assertEqual(rfc2822.encode_header(ascii_header), ascii_header)
2235 self.assertEqual(rfc2822.encode_header(unicode_header), unicode_encoded)
2237 def testRegistrationConfirmation(self):
2238 otk = "Aj4euk4LZSAdwePohj90SME5SpopLETL"
2239 self.db.getOTKManager().set(otk, username='johannes')
2240 self._handle_mail('''Content-Type: text/plain;
2241 charset="iso-8859-1"
2242 From: Chef <chef@bork.bork.bork>
2243 To: issue_tracker@your.tracker.email.domain.example
2244 Cc: richard@test.test
2245 Message-Id: <dummy_test_message_id>
2246 Subject: Re: Complete your registration to Roundup issue tracker
2247 -- key %s
2249 This is a test confirmation of registration.
2250 ''' % otk)
2251 self.db.user.lookup('johannes')
2253 def testFollowupOnNonIssue(self):
2254 self.db.keyword.create(name='Foo')
2255 self._handle_mail('''Content-Type: text/plain;
2256 charset="iso-8859-1"
2257 From: richard <richard@test.test>
2258 To: issue_tracker@your.tracker.email.domain.example
2259 Message-Id: <followup_dummy_id>
2260 In-Reply-To: <dummy_test_message_id>
2261 Subject: [keyword1] Testing... [name=Bar]
2263 ''')
2264 self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
2266 def testResentFrom(self):
2267 nodeid = self._handle_mail('''Content-Type: text/plain;
2268 charset="iso-8859-1"
2269 From: Chef <chef@bork.bork.bork>
2270 Resent-From: mary <mary@test.test>
2271 To: issue_tracker@your.tracker.email.domain.example
2272 Cc: richard@test.test
2273 Message-Id: <dummy_test_message_id>
2274 Subject: [issue] Testing...
2276 This is a test submission of a new issue.
2277 ''')
2278 assert not os.path.exists(SENDMAILDEBUG)
2279 l = self.db.issue.get(nodeid, 'nosy')
2280 l.sort()
2281 self.assertEqual(l, [self.richard_id, self.mary_id])
2282 return nodeid
2284 def testDejaVu(self):
2285 self.assertRaises(IgnoreLoop, self._handle_mail,
2286 '''Content-Type: text/plain;
2287 charset="iso-8859-1"
2288 From: Chef <chef@bork.bork.bork>
2289 X-Roundup-Loop: hello
2290 To: issue_tracker@your.tracker.email.domain.example
2291 Cc: richard@test.test
2292 Message-Id: <dummy_test_message_id>
2293 Subject: Re: [issue] Testing...
2295 Hi, I've been mis-configured to loop messages back to myself.
2296 ''')
2298 def testItsBulkStupid(self):
2299 self.assertRaises(IgnoreBulk, self._handle_mail,
2300 '''Content-Type: text/plain;
2301 charset="iso-8859-1"
2302 From: Chef <chef@bork.bork.bork>
2303 Precedence: bulk
2304 To: issue_tracker@your.tracker.email.domain.example
2305 Cc: richard@test.test
2306 Message-Id: <dummy_test_message_id>
2307 Subject: Re: [issue] Testing...
2309 Hi, I'm on holidays, and this is a dumb auto-responder.
2310 ''')
2312 def testAutoReplyEmailsAreIgnored(self):
2313 self.assertRaises(IgnoreBulk, self._handle_mail,
2314 '''Content-Type: text/plain;
2315 charset="iso-8859-1"
2316 From: Chef <chef@bork.bork.bork>
2317 To: issue_tracker@your.tracker.email.domain.example
2318 Cc: richard@test.test
2319 Message-Id: <dummy_test_message_id>
2320 Subject: Re: [issue] Out of office AutoReply: Back next week
2322 Hi, I am back in the office next week
2323 ''')
2325 def testNoSubject(self):
2326 self.assertRaises(MailUsageError, self._handle_mail,
2327 '''Content-Type: text/plain;
2328 charset="iso-8859-1"
2329 From: Chef <chef@bork.bork.bork>
2330 To: issue_tracker@your.tracker.email.domain.example
2331 Cc: richard@test.test
2332 Reply-To: chef@bork.bork.bork
2333 Message-Id: <dummy_test_message_id>
2335 ''')
2337 #
2338 # TEST FOR INVALID DESIGNATOR HANDLING
2339 #
2340 def testInvalidDesignator(self):
2341 self.assertRaises(MailUsageError, self._handle_mail,
2342 '''Content-Type: text/plain;
2343 charset="iso-8859-1"
2344 From: Chef <chef@bork.bork.bork>
2345 To: issue_tracker@your.tracker.email.domain.example
2346 Subject: [frobulated] testing
2347 Cc: richard@test.test
2348 Reply-To: chef@bork.bork.bork
2349 Message-Id: <dummy_test_message_id>
2351 ''')
2352 self.assertRaises(MailUsageError, self._handle_mail,
2353 '''Content-Type: text/plain;
2354 charset="iso-8859-1"
2355 From: Chef <chef@bork.bork.bork>
2356 To: issue_tracker@your.tracker.email.domain.example
2357 Subject: [issue12345] testing
2358 Cc: richard@test.test
2359 Reply-To: chef@bork.bork.bork
2360 Message-Id: <dummy_test_message_id>
2362 ''')
2364 def testInvalidClassLoose(self):
2365 self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
2366 nodeid = self._handle_mail('''Content-Type: text/plain;
2367 charset="iso-8859-1"
2368 From: Chef <chef@bork.bork.bork>
2369 To: issue_tracker@your.tracker.email.domain.example
2370 Subject: [frobulated] testing
2371 Cc: richard@test.test
2372 Reply-To: chef@bork.bork.bork
2373 Message-Id: <dummy_test_message_id>
2375 ''')
2376 assert not os.path.exists(SENDMAILDEBUG)
2377 self.assertEqual(self.db.issue.get(nodeid, 'title'),
2378 '[frobulated] testing')
2380 def testInvalidClassLooseReply(self):
2381 self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
2382 nodeid = self._handle_mail('''Content-Type: text/plain;
2383 charset="iso-8859-1"
2384 From: Chef <chef@bork.bork.bork>
2385 To: issue_tracker@your.tracker.email.domain.example
2386 Subject: Re: [frobulated] testing
2387 Cc: richard@test.test
2388 Reply-To: chef@bork.bork.bork
2389 Message-Id: <dummy_test_message_id>
2391 ''')
2392 assert not os.path.exists(SENDMAILDEBUG)
2393 self.assertEqual(self.db.issue.get(nodeid, 'title'),
2394 '[frobulated] testing')
2396 def testInvalidClassLoose(self):
2397 self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
2398 nodeid = self._handle_mail('''Content-Type: text/plain;
2399 charset="iso-8859-1"
2400 From: Chef <chef@bork.bork.bork>
2401 To: issue_tracker@your.tracker.email.domain.example
2402 Subject: [issue1234] testing
2403 Cc: richard@test.test
2404 Reply-To: chef@bork.bork.bork
2405 Message-Id: <dummy_test_message_id>
2407 ''')
2408 assert not os.path.exists(SENDMAILDEBUG)
2409 self.assertEqual(self.db.issue.get(nodeid, 'title'),
2410 '[issue1234] testing')
2412 def testClassLooseOK(self):
2413 self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
2414 self.db.keyword.create(name='Foo')
2415 nodeid = self._handle_mail('''Content-Type: text/plain;
2416 charset="iso-8859-1"
2417 From: Chef <chef@bork.bork.bork>
2418 To: issue_tracker@your.tracker.email.domain.example
2419 Subject: [keyword1] Testing... [name=Bar]
2420 Cc: richard@test.test
2421 Reply-To: chef@bork.bork.bork
2422 Message-Id: <dummy_test_message_id>
2424 ''')
2425 assert not os.path.exists(SENDMAILDEBUG)
2426 self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
2428 def testClassStrictInvalid(self):
2429 self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'strict'
2430 self.instance.config.MAILGW_DEFAULT_CLASS = ''
2432 message = '''Content-Type: text/plain;
2433 charset="iso-8859-1"
2434 From: Chef <chef@bork.bork.bork>
2435 To: issue_tracker@your.tracker.email.domain.example
2436 Subject: Testing...
2437 Cc: richard@test.test
2438 Reply-To: chef@bork.bork.bork
2439 Message-Id: <dummy_test_message_id>
2441 '''
2442 self.assertRaises(MailUsageError, self._handle_mail, message)
2444 def testClassStrictValid(self):
2445 self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'strict'
2446 self.instance.config.MAILGW_DEFAULT_CLASS = ''
2448 nodeid = self._handle_mail('''Content-Type: text/plain;
2449 charset="iso-8859-1"
2450 From: Chef <chef@bork.bork.bork>
2451 To: issue_tracker@your.tracker.email.domain.example
2452 Subject: [issue] Testing...
2453 Cc: richard@test.test
2454 Reply-To: chef@bork.bork.bork
2455 Message-Id: <dummy_test_message_id>
2457 ''')
2459 assert not os.path.exists(SENDMAILDEBUG)
2460 self.assertEqual(self.db.issue.get(nodeid, 'title'), 'Testing...')
2462 #
2463 # TEST FOR INVALID COMMANDS HANDLING
2464 #
2465 def testInvalidCommands(self):
2466 self.assertRaises(MailUsageError, self._handle_mail,
2467 '''Content-Type: text/plain;
2468 charset="iso-8859-1"
2469 From: Chef <chef@bork.bork.bork>
2470 To: issue_tracker@your.tracker.email.domain.example
2471 Subject: testing [frobulated]
2472 Cc: richard@test.test
2473 Reply-To: chef@bork.bork.bork
2474 Message-Id: <dummy_test_message_id>
2476 ''')
2478 def testInvalidCommandPassthrough(self):
2479 self.instance.config.MAILGW_SUBJECT_SUFFIX_PARSING = 'none'
2480 nodeid = self._handle_mail('''Content-Type: text/plain;
2481 charset="iso-8859-1"
2482 From: Chef <chef@bork.bork.bork>
2483 To: issue_tracker@your.tracker.email.domain.example
2484 Subject: testing [frobulated]
2485 Cc: richard@test.test
2486 Reply-To: chef@bork.bork.bork
2487 Message-Id: <dummy_test_message_id>
2489 ''')
2490 assert not os.path.exists(SENDMAILDEBUG)
2491 self.assertEqual(self.db.issue.get(nodeid, 'title'),
2492 'testing [frobulated]')
2494 def testInvalidCommandPassthroughLoose(self):
2495 self.instance.config.MAILGW_SUBJECT_SUFFIX_PARSING = 'loose'
2496 nodeid = self._handle_mail('''Content-Type: text/plain;
2497 charset="iso-8859-1"
2498 From: Chef <chef@bork.bork.bork>
2499 To: issue_tracker@your.tracker.email.domain.example
2500 Subject: testing [frobulated]
2501 Cc: richard@test.test
2502 Reply-To: chef@bork.bork.bork
2503 Message-Id: <dummy_test_message_id>
2505 ''')
2506 assert not os.path.exists(SENDMAILDEBUG)
2507 self.assertEqual(self.db.issue.get(nodeid, 'title'),
2508 'testing [frobulated]')
2510 def testInvalidCommandPassthroughLooseOK(self):
2511 self.instance.config.MAILGW_SUBJECT_SUFFIX_PARSING = 'loose'
2512 nodeid = self._handle_mail('''Content-Type: text/plain;
2513 charset="iso-8859-1"
2514 From: Chef <chef@bork.bork.bork>
2515 To: issue_tracker@your.tracker.email.domain.example
2516 Subject: testing [assignedto=mary]
2517 Cc: richard@test.test
2518 Reply-To: chef@bork.bork.bork
2519 Message-Id: <dummy_test_message_id>
2521 ''')
2522 assert not os.path.exists(SENDMAILDEBUG)
2523 self.assertEqual(self.db.issue.get(nodeid, 'title'), 'testing')
2524 self.assertEqual(self.db.issue.get(nodeid, 'assignedto'), self.mary_id)
2526 def testCommandDelimiters(self):
2527 self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '{}'
2528 nodeid = self._handle_mail('''Content-Type: text/plain;
2529 charset="iso-8859-1"
2530 From: Chef <chef@bork.bork.bork>
2531 To: issue_tracker@your.tracker.email.domain.example
2532 Subject: testing {assignedto=mary}
2533 Cc: richard@test.test
2534 Reply-To: chef@bork.bork.bork
2535 Message-Id: <dummy_test_message_id>
2537 ''')
2538 assert not os.path.exists(SENDMAILDEBUG)
2539 self.assertEqual(self.db.issue.get(nodeid, 'title'), 'testing')
2540 self.assertEqual(self.db.issue.get(nodeid, 'assignedto'), self.mary_id)
2542 def testPrefixDelimiters(self):
2543 self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '{}'
2544 self.db.keyword.create(name='Foo')
2545 self._handle_mail('''Content-Type: text/plain;
2546 charset="iso-8859-1"
2547 From: richard <richard@test.test>
2548 To: issue_tracker@your.tracker.email.domain.example
2549 Message-Id: <followup_dummy_id>
2550 In-Reply-To: <dummy_test_message_id>
2551 Subject: {keyword1} Testing... {name=Bar}
2553 ''')
2554 assert not os.path.exists(SENDMAILDEBUG)
2555 self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
2557 def testCommandDelimitersIgnore(self):
2558 self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '{}'
2559 nodeid = self._handle_mail('''Content-Type: text/plain;
2560 charset="iso-8859-1"
2561 From: Chef <chef@bork.bork.bork>
2562 To: issue_tracker@your.tracker.email.domain.example
2563 Subject: testing [assignedto=mary]
2564 Cc: richard@test.test
2565 Reply-To: chef@bork.bork.bork
2566 Message-Id: <dummy_test_message_id>
2568 ''')
2569 assert not os.path.exists(SENDMAILDEBUG)
2570 self.assertEqual(self.db.issue.get(nodeid, 'title'),
2571 'testing [assignedto=mary]')
2572 self.assertEqual(self.db.issue.get(nodeid, 'assignedto'), None)
2574 def testReplytoMatch(self):
2575 self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
2576 nodeid = self.doNewIssue()
2577 nodeid2 = self._handle_mail('''Content-Type: text/plain;
2578 charset="iso-8859-1"
2579 From: Chef <chef@bork.bork.bork>
2580 To: issue_tracker@your.tracker.email.domain.example
2581 Message-Id: <dummy_test_message_id2>
2582 In-Reply-To: <dummy_test_message_id>
2583 Subject: Testing...
2585 Followup message.
2586 ''')
2588 nodeid3 = self._handle_mail('''Content-Type: text/plain;
2589 charset="iso-8859-1"
2590 From: Chef <chef@bork.bork.bork>
2591 To: issue_tracker@your.tracker.email.domain.example
2592 Message-Id: <dummy_test_message_id3>
2593 In-Reply-To: <dummy_test_message_id2>
2594 Subject: Testing...
2596 Yet another message in the same thread/issue.
2597 ''')
2599 self.assertEqual(nodeid, nodeid2)
2600 self.assertEqual(nodeid, nodeid3)
2602 def testHelpSubject(self):
2603 message = '''Content-Type: text/plain;
2604 charset="iso-8859-1"
2605 From: Chef <chef@bork.bork.bork>
2606 To: issue_tracker@your.tracker.email.domain.example
2607 Message-Id: <dummy_test_message_id2>
2608 In-Reply-To: <dummy_test_message_id>
2609 Subject: hElp
2612 '''
2613 self.assertRaises(MailUsageHelp, self._handle_mail, message)
2615 def testMaillistSubject(self):
2616 self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '[]'
2617 self.db.keyword.create(name='Foo')
2618 self._handle_mail('''Content-Type: text/plain;
2619 charset="iso-8859-1"
2620 From: Chef <chef@bork.bork.bork>
2621 To: issue_tracker@your.tracker.email.domain.example
2622 Subject: [mailinglist-name] [keyword1] Testing.. [name=Bar]
2623 Cc: richard@test.test
2624 Reply-To: chef@bork.bork.bork
2625 Message-Id: <dummy_test_message_id>
2627 ''')
2629 assert not os.path.exists(SENDMAILDEBUG)
2630 self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
2632 def testUnknownPrefixSubject(self):
2633 self.db.keyword.create(name='Foo')
2634 self._handle_mail('''Content-Type: text/plain;
2635 charset="iso-8859-1"
2636 From: Chef <chef@bork.bork.bork>
2637 To: issue_tracker@your.tracker.email.domain.example
2638 Subject: VeryStrangeRe: [keyword1] Testing.. [name=Bar]
2639 Cc: richard@test.test
2640 Reply-To: chef@bork.bork.bork
2641 Message-Id: <dummy_test_message_id>
2643 ''')
2645 assert not os.path.exists(SENDMAILDEBUG)
2646 self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
2648 def testOneCharSubject(self):
2649 message = '''Content-Type: text/plain;
2650 charset="iso-8859-1"
2651 From: Chef <chef@bork.bork.bork>
2652 To: issue_tracker@your.tracker.email.domain.example
2653 Subject: b
2654 Cc: richard@test.test
2655 Reply-To: chef@bork.bork.bork
2656 Message-Id: <dummy_test_message_id>
2658 '''
2659 try:
2660 self._handle_mail(message)
2661 except MailUsageError:
2662 self.fail('MailUsageError raised')
2664 def testIssueidLast(self):
2665 nodeid1 = self.doNewIssue()
2666 nodeid2 = self._handle_mail('''Content-Type: text/plain;
2667 charset="iso-8859-1"
2668 From: mary <mary@test.test>
2669 To: issue_tracker@your.tracker.email.domain.example
2670 Message-Id: <followup_dummy_id>
2671 In-Reply-To: <dummy_test_message_id>
2672 Subject: New title [issue1]
2674 This is a second followup
2675 ''')
2677 assert nodeid1 == nodeid2
2678 self.assertEqual(self.db.issue.get(nodeid2, 'title'), "Testing...")
2680 def testSecurityMessagePermissionContent(self):
2681 id = self.doNewIssue()
2682 issue = self.db.issue.getnode (id)
2683 self.db.security.addRole(name='Nomsg')
2684 self.db.security.addPermissionToRole('Nomsg', 'Email Access')
2685 for cl in 'issue', 'file', 'keyword':
2686 for p in 'View', 'Edit', 'Create':
2687 self.db.security.addPermissionToRole('Nomsg', p, cl)
2688 self.db.user.set(self.mary_id, roles='Nomsg')
2689 nodeid = self._handle_mail('''Content-Type: text/plain;
2690 charset="iso-8859-1"
2691 From: Chef <chef@bork.bork.bork>
2692 To: issue_tracker@your.tracker.email.domain.example
2693 Message-Id: <dummy_test_message_id_2>
2694 Subject: [issue%(id)s] Testing... [nosy=+mary]
2696 Just a test reply
2697 '''%locals())
2698 assert os.path.exists(SENDMAILDEBUG)
2699 self.compareMessages(self._get_mail(),
2700 '''FROM: roundup-admin@your.tracker.email.domain.example
2701 TO: chef@bork.bork.bork, richard@test.test
2702 Content-Type: text/plain; charset="utf-8"
2703 Subject: [issue1] Testing...
2704 To: richard@test.test
2705 From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
2706 Reply-To: Roundup issue tracker
2707 <issue_tracker@your.tracker.email.domain.example>
2708 MIME-Version: 1.0
2709 Message-Id: <dummy_test_message_id_2>
2710 In-Reply-To: <dummy_test_message_id>
2711 X-Roundup-Name: Roundup issue tracker
2712 X-Roundup-Loop: hello
2713 X-Roundup-Issue-Status: chatting
2714 Content-Transfer-Encoding: quoted-printable
2717 Bork, Chef <chef@bork.bork.bork> added the comment:
2719 Just a test reply
2721 ----------
2722 nosy: +mary
2723 status: unread -> chatting
2725 _______________________________________________________________________
2726 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
2727 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
2728 _______________________________________________________________________
2729 ''')
2731 def testOutlookAttachment(self):
2732 message = '''X-MimeOLE: Produced By Microsoft Exchange V6.5
2733 Content-class: urn:content-classes:message
2734 MIME-Version: 1.0
2735 Content-Type: multipart/mixed;
2736 boundary="----_=_NextPart_001_01CACA65.40A51CBC"
2737 Subject: Example of a failed outlook attachment e-mail
2738 Date: Tue, 23 Mar 2010 01:43:44 -0700
2739 Message-ID: <CA37F17219784343816CA6613D2E339205E7D0F9@nrcwstexb1.nrc.ca>
2740 X-MS-Has-Attach: yes
2741 X-MS-TNEF-Correlator:
2742 Thread-Topic: Example of a failed outlook attachment e-mail
2743 Thread-Index: AcrKJo/t3pUBBwTpSwWNE3LE67UBDQ==
2744 From: "Hugh" <richard@test.test>
2745 To: <richard@test.test>
2746 X-OriginalArrivalTime: 23 Mar 2010 08:45:57.0350 (UTC) FILETIME=[41893860:01CACA65]
2748 This is a multi-part message in MIME format.
2750 ------_=_NextPart_001_01CACA65.40A51CBC
2751 Content-Type: multipart/alternative;
2752 boundary="----_=_NextPart_002_01CACA65.40A51CBC"
2755 ------_=_NextPart_002_01CACA65.40A51CBC
2756 Content-Type: text/plain;
2757 charset="us-ascii"
2758 Content-Transfer-Encoding: quoted-printable
2761 Hi Richard,
2763 I suppose this isn't the exact message that was sent but is a resend of
2764 one of my trial messages that failed. For your benefit I changed the
2765 subject line and am adding these words to the message body. Should
2766 still be as problematic, but if you like I can resend an exact copy of a
2767 failed message changing nothing except putting your address instead of
2768 our tracker.
2770 Thanks very much for taking time to look into this. Much appreciated.
2772 <<battery backup>>=20
2774 ------_=_NextPart_002_01CACA65.40A51CBC
2775 Content-Type: text/html;
2776 charset="us-ascii"
2777 Content-Transfer-Encoding: quoted-printable
2779 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
2780 <HTML>
2781 <HEAD>
2782 <META HTTP-EQUIV=3D"Content-Type" CONTENT=3D"text/html; =
2783 charset=3Dus-ascii">
2784 <META NAME=3D"Generator" CONTENT=3D"MS Exchange Server version =
2785 6.5.7654.12">
2786 <TITLE>Example of a failed outlook attachment e-mail</TITLE>
2787 </HEAD>
2788 <BODY>
2789 <!-- Converted from text/rtf format -->
2790 <BR>
2792 <P><FONT SIZE=3D2 FACE=3D"Arial">Hi Richard,</FONT>
2793 </P>
2795 <P><FONT SIZE=3D2 FACE=3D"Arial">I suppose this isn't the exact message =
2796 that was sent but is a resend of one of my trial messages that =
2797 failed. For your benefit I changed the subject line and am adding =
2798 these words to the message body. Should still be as problematic, =
2799 but if you like I can resend an exact copy of a failed message changing =
2800 nothing except putting your address instead of our tracker.</FONT></P>
2802 <P><FONT SIZE=3D2 FACE=3D"Arial">Thanks very much for taking time to =
2803 look into this. Much appreciated.</FONT>
2804 </P>
2805 <BR>
2807 <P><FONT FACE=3D"Arial" SIZE=3D2 COLOR=3D"#000000"> <<battery =
2808 backup>> </FONT>
2809 </P>
2811 </BODY>
2812 </HTML>
2813 ------_=_NextPart_002_01CACA65.40A51CBC--
2815 ------_=_NextPart_001_01CACA65.40A51CBC
2816 Content-Type: message/rfc822
2817 Content-Transfer-Encoding: 7bit
2819 X-MimeOLE: Produced By Microsoft Exchange V6.5
2820 MIME-Version: 1.0
2821 Content-Type: multipart/alternative;
2822 boundary="----_=_NextPart_003_01CAC15A.29717800"
2823 X-OriginalArrivalTime: 11 Mar 2010 20:33:51.0249 (UTC) FILETIME=[28FEE010:01CAC15A]
2824 Content-class: urn:content-classes:message
2825 Subject: battery backup
2826 Date: Thu, 11 Mar 2010 13:33:43 -0700
2827 Message-ID: <p06240809c7bf02f9624c@[128.114.22.203]>
2828 X-MS-Has-Attach:
2829 X-MS-TNEF-Correlator:
2830 Thread-Topic: battery backup
2831 Thread-Index: AcrBWimtulTrSvBdQ2CcfZ8lyQdxmQ==
2832 From: "Jerry" <jerry@test.test>
2833 To: "Hugh" <hugh@test.test>
2835 This is a multi-part message in MIME format.
2837 ------_=_NextPart_003_01CAC15A.29717800
2838 Content-Type: text/plain;
2839 charset="iso-8859-1"
2840 Content-Transfer-Encoding: quoted-printable
2842 Dear Hugh,
2843 A car batter has an energy capacity of ~ 500Wh. A UPS=20
2844 battery is worse than this.
2846 if we need to provied 100kW for 30 minutes that will take 100 car=20
2847 batteries. This seems like an awful lot of batteries.
2849 Of course I like your idea of making the time 1 minute, so we get to=20
2850 a more modest number of batteries
2852 Jerry
2855 ------_=_NextPart_003_01CAC15A.29717800
2856 Content-Type: text/html;
2857 charset="iso-8859-1"
2858 Content-Transfer-Encoding: quoted-printable
2860 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
2861 <HTML>
2862 <HEAD>
2863 <META HTTP-EQUIV=3D"Content-Type" CONTENT=3D"text/html; =
2864 charset=3Diso-8859-1">
2865 <META NAME=3D"Generator" CONTENT=3D"MS Exchange Server version =
2866 6.5.7654.12">
2867 <TITLE>battery backup</TITLE>
2868 </HEAD>
2869 <BODY>
2870 <!-- Converted from text/plain format -->
2872 <P><FONT SIZE=3D2>Dear Hugh,</FONT>
2874 <BR> <FONT SIZE=3D2>A car =
2875 batter has an energy capacity of ~ 500Wh. A UPS </FONT>
2877 <BR><FONT SIZE=3D2>battery is worse than this.</FONT>
2878 </P>
2880 <P><FONT SIZE=3D2>if we need to provied 100kW for 30 minutes that will =
2881 take 100 car </FONT>
2883 <BR><FONT SIZE=3D2>batteries. This seems like an awful lot of =
2884 batteries.</FONT>
2885 </P>
2887 <P><FONT SIZE=3D2>Of course I like your idea of making the time 1 =
2888 minute, so we get to </FONT>
2890 <BR><FONT SIZE=3D2>a more modest number of batteries</FONT>
2891 </P>
2893 <P><FONT SIZE=3D2>Jerry</FONT>
2894 </P>
2896 </BODY>
2897 </HTML>
2898 ------_=_NextPart_003_01CAC15A.29717800--
2900 ------_=_NextPart_001_01CACA65.40A51CBC--
2901 '''
2902 nodeid = self._handle_mail(message)
2903 assert not os.path.exists(SENDMAILDEBUG)
2904 msgid = self.db.issue.get(nodeid, 'messages')[0]
2905 self.assert_(self.db.msg.get(msgid, 'content').startswith('Hi Richard'))
2906 self.assertEqual(self.db.msg.get(msgid, 'files'), ['1', '2'])
2907 fileid = self.db.msg.get(msgid, 'files')[0]
2908 self.assertEqual(self.db.file.get(fileid, 'type'), 'text/html')
2909 fileid = self.db.msg.get(msgid, 'files')[1]
2910 self.assertEqual(self.db.file.get(fileid, 'type'), 'message/rfc822')
2912 def testForwardedMessageAttachment(self):
2913 message = '''Return-Path: <rgg@test.test>
2914 Received: from localhost(127.0.0.1), claiming to be "[115.130.26.69]"
2915 via SMTP by localhost, id smtpdAAApLaWrq; Tue Apr 13 23:10:05 2010
2916 Message-ID: <4BC4F9C7.50409@test.test>
2917 Date: Wed, 14 Apr 2010 09:09:59 +1000
2918 From: Rupert Goldie <rgg@test.test>
2919 User-Agent: Thunderbird 2.0.0.24 (Windows/20100228)
2920 MIME-Version: 1.0
2921 To: ekit issues <issues@test.test>
2922 Subject: [Fwd: PHP ERROR (fb)] post limit reached
2923 Content-Type: multipart/mixed; boundary="------------000807090608060304010403"
2925 This is a multi-part message in MIME format.
2926 --------------000807090608060304010403
2927 Content-Type: text/plain; charset=ISO-8859-1; format=flowed
2928 Content-Transfer-Encoding: 7bit
2930 Catch this exception and log it without emailing.
2932 --------------000807090608060304010403
2933 Content-Type: message/rfc822; name="PHP ERROR (fb).eml"
2934 Content-Transfer-Encoding: 7bit
2935 Content-Disposition: inline; filename="PHP ERROR (fb).eml"
2937 Return-Path: <ektravj@test.test>
2938 X-Sieve: CMU Sieve 2.2
2939 via SMTP by crown.off.ekorp.com, id smtpdAAA1JaW1o; Tue Apr 13 23:01:04 2010
2940 X-Virus-Scanned: by amavisd-new at ekit.com
2941 To: facebook-errors@test.test
2942 From: ektravj@test.test
2943 Subject: PHP ERROR (fb)
2944 Message-Id: <20100413230100.D601D27E84@mail2.elax3.ekorp.com>
2945 Date: Tue, 13 Apr 2010 23:01:00 +0000 (UTC)
2947 [13-Apr-2010 22:49:02] PHP Fatal error: Uncaught exception 'Exception' with message 'Facebook Error Message: Feed action request limit reached' in /app/01/www/virtual/fb.ekit.com/htdocs/includes/functions.php:280
2948 Stack trace:
2949 #0 /app/01/www/virtual/fb.ekit.com/htdocs/gateway/ekit/feed/index.php(178): fb_exceptions(Object(FacebookRestClientException))
2950 #1 {main}
2951 thrown in /app/01/www/virtual/fb.ekit.com/htdocs/includes/functions.php on line 280
2954 --------------000807090608060304010403--
2955 '''
2956 nodeid = self._handle_mail(message)
2957 assert not os.path.exists(SENDMAILDEBUG)
2958 msgid = self.db.issue.get(nodeid, 'messages')[0]
2959 self.assertEqual(self.db.msg.get(msgid, 'content'),
2960 'Catch this exception and log it without emailing.')
2961 self.assertEqual(self.db.msg.get(msgid, 'files'), ['1'])
2962 fileid = self.db.msg.get(msgid, 'files')[0]
2963 self.assertEqual(self.db.file.get(fileid, 'type'), 'message/rfc822')
2965 pgp_test_key = """
2966 -----BEGIN PGP PRIVATE KEY BLOCK-----
2967 Version: GnuPG v1.4.10 (GNU/Linux)
2969 lQOYBE6NqtsBCADG3UUMYxjwUOpDDVvr0Y8qkvKsgdF79en1zfHtRYlmZc+EJxg8
2970 53CCFGReQWJwOjyP3/SLJwJqfiPR7MAYAqJsm/4U2lxF7sIlEnlrRpFuvB625KOQ
2971 oedCkI4nLa+4QAXHxVX2qLx7es3r2JAoitZLX7ZtUB7qGSRh98DmdAgCY3CFN7iZ
2972 w6xpvIU+LNbsHSo1sf8VP6z7NHQFacgrVvLyRJ4C5lTPU42iM5E6HKxYFExNV3Rn
2973 +2G0bsuiifHV6nJQD73onjwcC6tU97W779dllHlhG3SSP0KlnwmCCvPMlQvROk0A
2974 rLyzKWcUpZwK1aLRYByjFMH9WYXRkhf08bkDABEBAAEAB/9dcmSb6YUyiBNM5t4m
2975 9hZcXykBvw79PRVvmBLy+BYUtArLgsN0+xx3Q7XWRMtJCVSkFw0GxpHwEM4sOyAZ
2976 KEPC3ZqLmgB6LDO2z/OWYVa9vlCAiPgDYtEVCnCCIInN/ue4dBZtDeVj8NUK2n0D
2977 UBpa2OMUgu3D+4SJNK7EnAmXdOaP6yfe6SXwcQfti8UoSFMJRkQkbY1rm/6iPfON
2978 t2RBAc7jW4eRzdciWCfvJfMSj9cqxTBQWz5vVadeY9Bm/IKw1HiKNBrJratq2v+D
2979 VGr0EkE9oOa5zbgZt2CFvknE4YhGmv81xFdK5GXr8L7nluZrePMblWbkI2ICTbV0
2980 RKLhBADYLvyDFX3cCoFzWmCl5L32G6LLfTt0yU0eUHcAzXd7QjOZN289HWYEmdVi
2981 kpxQPDxhWz+m8qt0HJGFl2+BKpZJBaT/L5AcqTBODxarxCSBTIVhCjD/46XvLY0h
2982 b2ZnG8HSLyFdRj07vk+qTvcF58qUuYFSLIF2t2imTCR/PwR/LwQA632vn2/7KIHj
2983 DR0O+G9eccTtAfX4TN4Q4Ua3WByClLZu/LSAenCLZ1CHVABEH6dwwjEARLeNUdLi
2984 Xy5KKlpr2vkoh96fnw0r2yg7dlBXq4yQKjJBXwNaKpuvqgzd8en0zJGLXxzt0NT3
2985 H+QNIP2WZMJSDQcDh3HhQrH0IeNdDm0D/iyJgSMXvqjm+KhYIa3xiloQsCRlDNm+
2986 XC7Eo5hsjvBaIKba6o9oL9oEiSVUFryPWKWIpi0P7/F5voJL6KFSZTor3x3o9CcC
2987 qHyqMHfNL23EAVJulySfPYLC7S3QB+tCBLXmKxb/YXCSLVi/UDzVgvWN6KIknZg2
2988 6uDLUzPbzDGjOZ20K1JvdW5kdXAgVGVzdGtleSA8cm91bmR1cC1hZG1pbkBleGFt
2989 cGxlLmNvbT6JATgEEwECACIFAk6NqtsCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4B
2990 AheAAAoJEFrc/VYxw4dBG7oIAMCU9sRjK0dS7z/IGJ8KcCOQNN674AooJLn+J9Ew
2991 BT6/WxMY13nm/iK0uX2sOGnnXdg1PJ15IvD8zB5wXLbe25t6oRl5G58vmeKEyjc8
2992 QTB43/c8EsqY1ob7EVcuhrJCSS/JM8ApzQyXrh2QNmS+mBCJcx74MeipE6mNVT9j
2993 VscizixdFjqvJLkbW1kGac3Wj+c3ICNUIp0lbwb+Ve2rXlU+iXHEDqaVJDMEppme
2994 gDiZl+bKYrqljhZkH9Slv55uqqXUSg1SmTm/2orHUdAmDc6Y6azKNqQEqD2B0JyT
2995 jTJQJVMl5Oln63aZDCTxHkoqn8q06OjLJRD4on7jlanZEladA5gETo2q2wEIALEF
2996 poVkZrnqme2M8FObrQyVB+ZYT2mox56WLyInbxVFDg20qqIvQfVE0P69Yuf1OXkj
2997 q7bNI03Jvo+uzxpztOKPDo7tnbQ7bXbOmq3n4wUoN29NMrYNg6tF1ubEv1WwYUMw
2998 7LfF4BLMETXpT0JElV1+awfP9rrGiyWkH4enG612HT+1OoA0R0nNH0kslD6OhdoR
2999 VDqkyiCmdY9x176EhzhL3vCoN6ywRVTfFbAJiMv9UDzxs0SStmVOK/l5XLfWQO6f
3000 9boAHihpnxEfPIJhsD+FpVKVf3g85qWAjh2BfuzdW79vjLBdTHJQxg4HdhliWbXg
3001 PjjrVEgWEFVc+NDlNb0AEQEAAQAH/A1a6sbniI8q3DVoIP19zN7FI5UaQSuB2Jrl
3002 +Q+vlUQv3dvk2cwQmqj2vyRo2gcRS3u7LYpGDGLNqfshv22JyzId2YWo9vE7sTTP
3003 E4EJRz8CsLlMmVsoxoVBE0cnvXOpMef6z0ZyFEdMGVmi4iA9bQi3r+V6qBehQQA0
3004 U034VTCPN4yvWyq6TWsABesOx48nkQ5TlduIq2ZGNCR8Vd1fe6vGM7YXyQWxy5ke
3005 guqmph73H2bOB6hSuUnyBFKtinrF9MbCGA0PqheUVqy0p7og6x/pEoAVkKBJ9Ki+
3006 ePuQtBl5h9e3SbiN+r7aa6T0Ygx/7igl4eWPfvJYIXYXc4aKiwEEANEa5rBoN7Ta
3007 ED+R47Rg9w/EW3VDQ6R3Szy1rvIKjC6JlDyKlGgTeWEFjDeTwCB4xU7YtxVpt6bk
3008 b7RBtDkRck2+DwnscutA7Uxn267UxzNUd1IxhUccRFRfRS7OEnmlVmaLUnOeHHwe
3009 OrZyRSiNVnh0QABEJnwNjX4m139v6YD9BADYuM5XCawI63pYa3/l7UX9H5EH95OZ
3010 G9Hw7pXQ/YJYerSxRx+2q0+koRcdoby1TVaRrdDC+kOm3PI7e66S5rnaZ1FZeYQP
3011 nVYzyGqNnsvncs24kYBL8DYaDDfdm7vfzSEqia0VNqZ4TMbwJLk5f5Ys4WOF791G
3012 LPJgrAPG1jgDwQQAovKbw0u6blIUKsUYOLsviaLCyFC9DwaHqIZwjy8omnh7MaKE
3013 7+MXxJpfcVqFifj3CmqMdSmTfkgbKQPAI46Q1OKWvkvUxEvi7WATo4taEXupRFL5
3014 jnL8c4h46z8UpMX2CMwWU0k1Et/zlBoYy7gNON7tF2/uuN18zWFBlD72HuM9HIkB
3015 HwQYAQIACQUCTo2q2wIbDAAKCRBa3P1WMcOHQYI+CACDXJf1e695LpcsrVxKgiQr
3016 9fTbNJYB+tjbnd9vas92Gz1wZcQV9RjLkYQeEbOpWQud/1UeLRsFECMj7kbgAEqz
3017 7fIO4SeN8hFEvvZ+lI0AoBi4XvuUcCm5kvAodvmF8M9kQiUzF1gm+R9QQeJFDLpW
3018 8Gg7J3V3qM+N0FuXrypYcsEv7n/RJ1n+lhTW5hFzKBlNL4WrAhY/QsXEbmdsa478
3019 tzuHlETtjMm4g4DgppUdlCMegcpjjC9zKsN5xFOQmNMTO/6rPFUqk3k3T6I0LV4O
3020 zm4xNC+wwAA69ibnbrY1NR019et7RYW+qBudGbpJB1ABzkf/NsaCj6aTaubt7PZP
3021 =3uFZ
3022 -----END PGP PRIVATE KEY BLOCK-----
3023 """
3025 john_doe_key = """
3026 -----BEGIN PGP PRIVATE KEY BLOCK-----
3027 Version: GnuPG v1.4.10 (GNU/Linux)
3029 lQHYBE6NwvABBACxg7QqV2qHywwM3wae6HAHJVEo7EeYA6Lv0pZlW3Aw4CCCnpgJ
3030 jA7CekGFcmGmoCaN9ezuVAPTgUlK4yt8a7P6cT0vw1q341Om9IEKAu59RpNZN/H9
3031 6GfZ95bU51W/hdTFysH1DRwbCR3MowvLeA6Pk4cZlPsYHD0SD3De2i1BewARAQAB
3032 AAP+IRi4L6jKwPS3k3LFrj0SHhL0Fdgv5QTQjTxLNCyfN02iYhglqqoFWncm3jWc
3033 RU/YwGEYwrrBV97kBmVihzkhfgFRsxynE9PMGKKEAuRcAl21RPJDFA6Dlnp6M2No
3034 rR6eoAhrlZ8+KsK9JaXSMalzO/Yh4u3mOinq3f3XL96wAEkCAMAxeZMF5pnXARNR
3035 Y7u2clhNNnLuf+BzpENCFMaWzWPyTcvbf4xNK7ZHPxFVZpX5/qAPJ8rnTaOTHxnN
3036 5PgqbO8CAOxyrTw/muakTJLg+FXdn8BgxZGJXMT7KmkU9SReefjo7c1WlnZxKIAy
3037 6vLIG8WMGpdfCFDve0YLr/GGyDtOjDUB/RN3gn6qnAJThBnVk2wESZVx41fihbIF
3038 ACCKc9heFskzwurtvvp+bunM3quwrSH1hWvxiWJlDmGSn8zQFypGChifgLQZSm9o
3039 biBEb2UgPGpvaG5AdGVzdC50ZXN0Poi4BBMBAgAiBQJOjcLwAhsDBgsJCAcDAgYV
3040 CAIJCgsEFgIDAQIeAQIXgAAKCRC/z7qg+FujnPWiA/9T5SOGraRNIVVIyvJvYwkG
3041 OTAfQ0K3QMlLoQMPmaEbx9Q+isF15M9sOMcl1XGO4UNWuCPIIN8z/y/OLgAB0ZuL
3042 GlnAPPOOZ+MlaUXiMYo8oi416QZrMDf2H/Nkc10csiXm+zMl8RqeIQBEeljNyJ+t
3043 MG1EWn/PHTwFTd/VePuQdJ0B2AROjcLwAQQApw+72jKy0/wqg5SAtnVSkA1F3Jna
3044 /OG+ufz5dX57jkMFRvFoksWIWqHmiCjdE5QV8j+XTnjElhLsmrgjl7aAFveb30R6
3045 ImmcpKMN31vAp4RZlnyYbYUCY4IXFuz3n1CaUL+mRx5yNJykrZNfpWNf2pwozkZq
3046 lcDI69ymIW5acXUAEQEAAQAD/R7Jdf98l1scngMYo228ikYUxBqm2eX/fiQNXDWM
3047 ZR2u+TJ9O53MvFejfXX7Pd6lTDQUBwDFncjgXO0YYSrMzabhqpqoKLqOIpZmBuWC
3048 Hh1lvcFoIYoDR2LkiJ9EPBUEVUBDsUO8ajkILEE3G+DDpCaf9Vo82lCVyhDESqyt
3049 v4lxAgDOLpoq1Whv5Ejr6FifTWytCiQjH2P1SmePlQmy6oEJRUYA1t4zYrzCJUX8
3050 VAvPjh9JXilP6mhDbyQArWllewV9AgDPbVOf75ktRwfhje26tZsukqWYJCc1XvoH
3051 3PTzA7vH1HZZq7dvxa87PiSnkOLEsIAsI+4jpeMxpPlQRxUvHf1ZAf9rK3v3HMJ/
3052 2xVzwK24Oaj+g2O7D/fdqtLFGe5S5JobnTyp9xArDAhaZ/AKfDMYjUIKMP+bdNAf
3053 y8fQUtuawFltm1GInwQYAQIACQUCTo3C8AIbDAAKCRC/z7qg+FujnDzYA/9EU6Pv
3054 Ci1+DCtxjnq7IOvOjqExhFNGvN9Dw17Tl8HcyW3if9v5RxeSWYKl0DhzVdzMQgH/
3055 78q4F4W1q2IkB7SCpXizHLIc3eh8iZkbWZE+CGPvTpqyF03Yi16qhxpAbkGs2Yhq
3056 jTx5oJ4CL5fybBOZLg+BTlK4HIee6xEcbNoq+A==
3057 =ZKBW
3058 -----END PGP PRIVATE KEY BLOCK-----
3059 """
3061 ownertrust = """
3062 723762CD5A5FECB76DC72DF85ADCFD5631C38741:6:
3063 2940C247A1FBAD508A1AF24BBFCFBAA0F85BA39C:6:
3064 """
3066 class MailgwPGPTestCase(MailgwTestAbstractBase):
3067 pgphome = gpgmelib.pgphome
3068 def setUp(self):
3069 MailgwTestAbstractBase.setUp(self)
3070 self.db.security.addRole(name = 'pgp', description = 'PGP Role')
3071 self.instance.config['PGP_HOMEDIR'] = self.pgphome
3072 self.instance.config['PGP_ROLES'] = 'pgp'
3073 self.instance.config['PGP_ENABLE'] = True
3074 self.instance.config['MAIL_DOMAIN'] = 'example.com'
3075 self.instance.config['ADMIN_EMAIL'] = 'roundup-admin@example.com'
3076 self.db.user.set(self.john_id, roles='User,pgp')
3077 gpgmelib.setUpPGP()
3079 def tearDown(self):
3080 MailgwTestAbstractBase.tearDown(self)
3081 gpgmelib.tearDownPGP()
3083 def testPGPUnsignedMessage(self):
3084 self.assertRaises(MailUsageError, self._handle_mail,
3085 '''Content-Type: text/plain;
3086 charset="iso-8859-1"
3087 From: John Doe <john@test.test>
3088 To: issue_tracker@your.tracker.email.domain.example
3089 Message-Id: <dummy_test_message_id>
3090 Subject: [issue] Testing non-signed message...
3092 This is no pgp signed message.
3093 ''')
3095 signed_msg = '''Content-Disposition: inline
3096 From: John Doe <john@test.test>
3097 To: issue_tracker@your.tracker.email.domain.example
3098 Subject: [issue] Testing signed message...
3099 Content-Type: multipart/signed; micalg=pgp-sha1;
3100 protocol="application/pgp-signature"; boundary="cWoXeonUoKmBZSoM"
3103 --cWoXeonUoKmBZSoM
3104 Content-Type: text/plain; charset=us-ascii
3105 Content-Disposition: inline
3107 This is a pgp signed message.
3109 --cWoXeonUoKmBZSoM
3110 Content-Type: application/pgp-signature; name="signature.asc"
3111 Content-Description: Digital signature
3112 Content-Disposition: inline
3114 -----BEGIN PGP SIGNATURE-----
3115 Version: GnuPG v1.4.10 (GNU/Linux)
3117 iJwEAQECAAYFAk6N4A4ACgkQv8+6oPhbo5x5nAP/d7R7SxTvLoVESI+1r7eDXp1J
3118 LvBVU2EF3YFYKBHMLcWmjG92fNjnHX6NENTEhTeBynba5IPEwUfITC+7PmgPmQkA
3119 VXnFZnwraHxsYgyFsVFN1kkTSbwRUlWl9+nTEsr0yBLTpZN0QSIDcwu+i/xVcg+t
3120 ZQ4K6R3m3AOw7BLdvZs=
3121 =wpYk
3122 -----END PGP SIGNATURE-----
3124 --cWoXeonUoKmBZSoM--
3125 '''
3127 def testPGPSignedMessage(self):
3128 nodeid = self._handle_mail(self.signed_msg)
3129 m = self.db.issue.get(nodeid, 'messages')[0]
3130 self.assertEqual(self.db.msg.get(m, 'content'),
3131 'This is a pgp signed message.')
3133 def testPGPSignedMessageFail(self):
3134 # require both, signing and encryption
3135 self.instance.config['PGP_REQUIRE_INCOMING'] = 'both'
3136 self.assertRaises(MailUsageError, self._handle_mail, self.signed_msg)
3138 encrypted_msg = '''Content-Disposition: inline
3139 From: John Doe <john@test.test>
3140 To: roundup-admin@example.com
3141 Subject: [issue] Testing encrypted message...
3142 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";
3143 boundary="d6Gm4EdcadzBjdND"
3145 --d6Gm4EdcadzBjdND
3146 Content-Type: application/pgp-encrypted
3147 Content-Disposition: attachment
3149 Version: 1
3151 --d6Gm4EdcadzBjdND
3152 Content-Type: application/octet-stream
3153 Content-Disposition: inline; filename="msg.asc"
3155 -----BEGIN PGP MESSAGE-----
3156 Version: GnuPG v1.4.10 (GNU/Linux)
3158 hQEMAzfeQttq+Q2YAQf9FxCtZVgC7jAy6UkeAJ1imCpnh9DgKA5w40OFtrY4mVAp
3159 cL7kCkvGvJCW7uQZrmSgIiYaZGLI3GS42XutORC6E6PzBEW0fJUMIXYmoSd0OFeY
3160 3H2+854qu37W/uCOWM9OnPFIH8g8q8DgYy88i0goM+Ot9Q96yFfJ7QymanOZJgVa
3161 MNC+oKDiIZKiE3PCwtGr+8CHZN/9J6O4FeJijBlr09C5LXc+Nif5T0R0nt17MAns
3162 9g2UvGxW8U24NAS1mOg868U05hquLPIcFz9jGZGknJu7HBpOkQ9GjKqkzN8pgZVN
3163 VbN8IdDqi0QtRKE44jtWQlyNlESMjv6GtC2V9F6qKNK8AfHtBexDhyv4G9cPFFNO
3164 afQ6e4dPi89RYIQyydtwiqao8fj6jlAy2Z1cbr7YxwBG7BeUZv9yis7ShaAIo78S
3165 82MrCYpSjfHNwKiSfC5yITw22Uv4wWgixVdAsaSdtBqEKXJPG9LNey18ArsBjSM1
3166 P81iDOWUp/uyIe5ZfvNI38BBxEYslPTUlDk2GB8J2Vun7IWHoj9a4tY3IotC9jBr
3167 5Qnigzqrt7cJZX6OrN0c+wnOjXbMGYXmgSs4jeM=
3168 =XX5Q
3169 -----END PGP MESSAGE-----
3171 --d6Gm4EdcadzBjdND--
3172 '''
3173 def testPGPEncryptedUnsignedMessageError(self):
3174 self.assertRaises(MailUsageError, self._handle_mail, self.encrypted_msg)
3176 def testPGPEncryptedUnsignedMessage(self):
3177 # no error if we don't require a signature:
3178 self.instance.config['PGP_REQUIRE_INCOMING'] = 'encrypted'
3179 nodeid = self._handle_mail (self.encrypted_msg)
3180 m = self.db.issue.get(nodeid, 'messages')[0]
3181 self.assertEqual(self.db.msg.get(m, 'content'),
3182 'This is the text to be encrypted')
3184 def testPGPEncryptedUnsignedMessageFromNonPGPUser(self):
3185 msg = self.encrypted_msg.replace('John Doe <john@test.test>',
3186 '"Contrary, Mary" <mary@test.test>')
3187 nodeid = self._handle_mail (msg)
3188 m = self.db.issue.get(nodeid, 'messages')[0]
3189 self.assertEqual(self.db.msg.get(m, 'content'),
3190 'This is the text to be encrypted')
3191 self.assertEqual(self.db.msg.get(m, 'author'), self.mary_id)
3193 # check that a bounce-message that is triggered *after*
3194 # decrypting is properly encrypted:
3195 def testPGPEncryptedUnsignedMessageCheckBounce(self):
3196 # allow non-signed msg
3197 self.instance.config['PGP_REQUIRE_INCOMING'] = 'encrypted'
3198 # don't allow creation of message, trigger error *after* decrypt
3199 self.db.user.set(self.john_id, roles='pgp')
3200 self.db.security.addPermissionToRole('pgp', 'Email Access')
3201 self.db.security.addPermissionToRole('pgp', 'Create', 'issue')
3202 # trap_exc=1: we want a bounce message:
3203 self._handle_mail(self.encrypted_msg, trap_exc=1)
3204 m = self._get_mail()
3205 fp = FeedParser()
3206 fp.feed(m)
3207 parts = fp.close().get_payload()
3208 self.assertEqual(len(parts),2)
3209 self.assertEqual(parts[0].get_payload().strip(), 'Version: 1')
3210 crypt = pyme.core.Data(parts[1].get_payload())
3211 plain = pyme.core.Data()
3212 ctx = pyme.core.Context()
3213 res = ctx.op_decrypt(crypt, plain)
3214 self.assertEqual(res, None)
3215 plain.seek(0,0)
3216 fp = FeedParser()
3217 fp.feed(plain.read())
3218 parts = fp.close().get_payload()
3219 self.assertEqual(len(parts),2)
3220 self.assertEqual(parts[0].get_payload().strip(),
3221 'You are not permitted to create messages.')
3222 self.assertEqual(parts[1].get_payload().strip(),
3223 '''Content-Type: text/plain; charset=us-ascii
3224 Content-Disposition: inline
3226 This is the text to be encrypted''')
3229 def testPGPEncryptedSignedMessage(self):
3230 # require both, signing and encryption
3231 self.instance.config['PGP_REQUIRE_INCOMING'] = 'both'
3232 nodeid = self._handle_mail('''Content-Disposition: inline
3233 From: John Doe <john@test.test>
3234 To: roundup-admin@example.com
3235 Subject: Testing encrypted and signed message
3236 MIME-Version: 1.0
3237 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";
3238 boundary="ReaqsoxgOBHFXBhH"
3240 --ReaqsoxgOBHFXBhH
3241 Content-Type: application/pgp-encrypted
3242 Content-Disposition: attachment
3244 Version: 1
3246 --ReaqsoxgOBHFXBhH
3247 Content-Type: application/octet-stream
3248 Content-Disposition: inline; filename="msg.asc"
3250 -----BEGIN PGP MESSAGE-----
3251 Version: GnuPG v1.4.10 (GNU/Linux)
3253 hQEMAzfeQttq+Q2YAQf+NaC3r8qBURQqxHH9IAP4vg0QAP2yj3n0v6guo1lRf5BA
3254 EUfTQ3jc3chxLvzTgoUIuMOvhlNroqR1lgLwhfSTCyuKWDZa+aVNiSgsB2MD44Xd
3255 mAkKKmnmOGLmfbICbPQZxl4xNhCMTHiAy1xQE6mTj/+pEAq5XxjJUwn/gJ3O1Wmd
3256 NyWtJY2N+TRbxUVB2WhG1j9J1D2sjhG26TciE8JeuLDZzaiVNOW9YlX2Lw5KtlkR
3257 Hkgw6Xme06G0XXZUcm9JuBU/7oFP/tSrC1tBsnVlq1pZYf6AygIBdXWb9gD/WmXh
3258 7Eu/xCKrw4RFnXnTgmBz/NHRfVDkfdSscZqexnG1D9LAwQHSuVf8sxDPNesv0W+8
3259 e49loVjvU+Y0BCFQAbWSW4iOEUYZpW/ITRE4+wIqMXZbAraeBV0KPZ4hAa3qSmf+
3260 oZBRcbzssL163Odx/OHRuK2J2CHC654+crrlTBnxd/RUKgRbSUKwrZzB2G6OPcGv
3261 wfiqXsY+XvSZtTbWuvUJxePh8vhhhjpuo1JtlrYc3hZ9OYgoCoV1JiLl5c60U5Es
3262 oUT9GDl1Qsgb4dF4TJ1IBj+riYiocYpJxPhxzsy6liSLNy2OA6VEjG0FGk53+Ok9
3263 7UzOA+WaHJHSXafZzrdP1TWJUFlOMA+dOgTKpH69eL1+IRfywOjEwp1UNSbLnJpc
3264 D0QQLwIFttplKvYkn0DZByJCVnIlGkl4s5LM5rnc8iecX8Jad0iRIlPV6CVM+Nso
3265 WdARUfyJfXAmz8uk4f2sVfeMu1gdMySdjvxwlgHDJdBPIG51r2b8L/NCTiC57YjF
3266 zGhS06FLl3V1xx6gBlpqQHjut3efrAGpXGBVpnTJMOcgYAk=
3267 =jt/n
3268 -----END PGP MESSAGE-----
3270 --ReaqsoxgOBHFXBhH--
3271 ''')
3272 m = self.db.issue.get(nodeid, 'messages')[0]
3273 self.assertEqual(self.db.msg.get(m, 'content'),
3274 'This is the text of a signed and encrypted email.')
3277 def test_suite():
3278 suite = unittest.TestSuite()
3279 suite.addTest(unittest.makeSuite(MailgwTestCase))
3280 if pyme is not None:
3281 suite.addTest(unittest.makeSuite(MailgwPGPTestCase))
3282 else:
3283 print "Skipping PGP tests"
3284 return suite
3286 if __name__ == '__main__':
3287 runner = unittest.TextTestRunner()
3288 unittest.main(testRunner=runner)
3290 # vim: set filetype=python sts=4 sw=4 et si :