Code

e0789ae2d015f6b2ab51ae3172605e22ea4ff89e
[roundup.git] / test / test_mailgw.py
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 from email.parser import FeedParser
20 try:
21     import pyme, pyme.core
22 except ImportError:
23     pyme = None
26 from cStringIO import StringIO
28 if not os.environ.has_key('SENDMAILDEBUG'):
29     os.environ['SENDMAILDEBUG'] = 'mail-test.log'
30 SENDMAILDEBUG = os.environ['SENDMAILDEBUG']
32 from roundup import mailgw, i18n, roundupdb
33 from roundup.mailgw import MailGW, Unauthorized, uidFromAddress, \
34     parseContent, IgnoreLoop, IgnoreBulk, MailUsageError, MailUsageHelp
35 from roundup import init, instance, password, rfc2822, __version__
36 from roundup.anypy.sets_ import set
38 #import db_test_base
39 import memorydb
41 class Message(rfc822.Message):
42     """String-based Message class with equivalence test."""
43     def __init__(self, s):
44         rfc822.Message.__init__(self, StringIO(s.strip()))
46     def __eq__(self, other):
47         return (self.dict == other.dict and
48                 self.fp.read() == other.fp.read())
50 class Tracker(object):
51     def open(self, journaltag):
52         return self.db
54 class DiffHelper:
55     def compareMessages(self, new, old):
56         """Compare messages for semantic equivalence."""
57         new, old = Message(new), Message(old)
59         # all Roundup-generated messages have "Precedence: bulk"
60         old['Precedence'] = 'bulk'
62         # don't try to compare the date
63         del new['date'], old['date']
65         if not new == old:
66             res = []
68             replace = {}
69             for key in new.keys():
70                 if key.startswith('from '):
71                     # skip the unix from line
72                     continue
73                 if key.lower() == 'x-roundup-version':
74                     # version changes constantly, so handle it specially
75                     if new[key] != __version__:
76                         res.append('  %s: %r != %r' % (key, __version__,
77                             new[key]))
78                 elif key.lower() == 'content-type' and 'boundary=' in new[key]:
79                     # handle mime messages
80                     newmime = new[key].split('=',1)[-1].strip('"')
81                     oldmime = old.get(key, '').split('=',1)[-1].strip('"')
82                     replace ['--' + newmime] = '--' + oldmime
83                     replace ['--' + newmime + '--'] = '--' + oldmime + '--'
84                 elif new.get(key, '') != old.get(key, ''):
85                     res.append('  %s: %r != %r' % (key, old.get(key, ''),
86                         new.get(key, '')))
88             body_diff = self.compareStrings(new.fp.read(), old.fp.read(),
89                 replace=replace)
90             if body_diff:
91                 res.append('')
92                 res.extend(body_diff)
94             if res:
95                 res.insert(0, 'Generated message not correct (diff follows, expected vs. actual):')
96                 raise AssertionError, '\n'.join(res)
98     def compareStrings(self, s2, s1, replace={}):
99         '''Note the reversal of s2 and s1 - difflib.SequenceMatcher wants
100            the first to be the "original" but in the calls in this file,
101            the second arg is the original. Ho hum.
102            Do replacements over the replace dict -- used for mime boundary
103         '''
104         l1 = s1.strip().split('\n')
105         l2 = [replace.get(i,i) for i in s2.strip().split('\n')]
106         if l1 == l2:
107             return
108         s = difflib.SequenceMatcher(None, l1, l2)
109         res = []
110         for value, s1s, s1e, s2s, s2e in s.get_opcodes():
111             if value == 'equal':
112                 for i in range(s1s, s1e):
113                     res.append('  %s'%l1[i])
114             elif value == 'delete':
115                 for i in range(s1s, s1e):
116                     res.append('- %s'%l1[i])
117             elif value == 'insert':
118                 for i in range(s2s, s2e):
119                     res.append('+ %s'%l2[i])
120             elif value == 'replace':
121                 for i, j in zip(range(s1s, s1e), range(s2s, s2e)):
122                     res.append('- %s'%l1[i])
123                     res.append('+ %s'%l2[j])
125         return res
127 class MailgwTestAbstractBase(unittest.TestCase, DiffHelper):
128     count = 0
129     schema = 'classic'
130     def setUp(self):
131         self.old_translate_ = mailgw._
132         roundupdb._ = mailgw._ = i18n.get_translation(language='C').gettext
133         self.__class__.count = self.__class__.count + 1
135         # and open the database / "instance"
136         self.db = memorydb.create('admin')
137         self.instance = Tracker()
138         self.instance.db = self.db
139         self.instance.config = self.db.config
140         self.instance.MailGW = MailGW
142         self.chef_id = self.db.user.create(username='Chef',
143             address='chef@bork.bork.bork', realname='Bork, Chef', roles='User')
144         self.richard_id = self.db.user.create(username='richard',
145             address='richard@test.test', roles='User')
146         self.mary_id = self.db.user.create(username='mary',
147             address='mary@test.test', roles='User', realname='Contrary, Mary')
148         self.john_id = self.db.user.create(username='john',
149             address='john@test.test', roles='User', realname='John Doe',
150             alternate_addresses='jondoe@test.test\njohn.doe@test.test')
151         self.rgg_id = self.db.user.create(username='rgg',
152             address='rgg@test.test', roles='User')
154     def tearDown(self):
155         roundupdb._ = mailgw._ = self.old_translate_
156         if os.path.exists(SENDMAILDEBUG):
157             os.remove(SENDMAILDEBUG)
158         self.db.close()
160     def _create_mailgw(self, message, args=()):
161         class MailGW(self.instance.MailGW):
162             def handle_message(self, message):
163                 return self._handle_message(message)
164         handler = MailGW(self.instance, args)
165         handler.db = self.db
166         return handler
168     def _handle_mail(self, message, args=(), trap_exc=0):
169         handler = self._create_mailgw(message, args)
170         handler.trapExceptions = trap_exc
171         return handler.main(StringIO(message))
173     def _get_mail(self):
174         f = open(SENDMAILDEBUG)
175         try:
176             return f.read()
177         finally:
178             f.close()
180     # Normal test-case used for both non-pgp test and a test while pgp
181     # is enabled, so this test is run in both test suites.
182     def testEmptyMessage(self):
183         nodeid = self._handle_mail('''Content-Type: text/plain;
184   charset="iso-8859-1"
185 From: Chef <chef@bork.bork.bork>
186 To: issue_tracker@your.tracker.email.domain.example
187 Cc: richard@test.test
188 Reply-To: chef@bork.bork.bork
189 Message-Id: <dummy_test_message_id>
190 Subject: [issue] Testing...
192 ''')
193         assert not os.path.exists(SENDMAILDEBUG)
194         self.assertEqual(self.db.issue.get(nodeid, 'title'), 'Testing...')
197 class MailgwTestCase(MailgwTestAbstractBase):
199     def testMessageWithFromInIt(self):
200         nodeid = self._handle_mail('''Content-Type: text/plain;
201   charset="iso-8859-1"
202 From: Chef <chef@bork.bork.bork>
203 To: issue_tracker@your.tracker.email.domain.example
204 Cc: richard@test.test
205 Reply-To: chef@bork.bork.bork
206 Message-Id: <dummy_test_message_id>
207 Subject: [issue] Testing...
209 From here to there!
210 ''')
211         assert not os.path.exists(SENDMAILDEBUG)
212         msgid = self.db.issue.get(nodeid, 'messages')[0]
213         self.assertEqual(self.db.msg.get(msgid, 'content'), 'From here to there!')
215     def testNoMessageId(self):
216         self.instance.config['MAIL_DOMAIN'] = 'example.com'
217         nodeid = self._handle_mail('''Content-Type: text/plain;
218   charset="iso-8859-1"
219 From: Chef <chef@bork.bork.bork>
220 To: issue_tracker@your.tracker.email.domain.example
221 Cc: richard@test.test
222 Reply-To: chef@bork.bork.bork
223 Subject: [issue] Testing...
225 Hi there!
226 ''')
227         assert not os.path.exists(SENDMAILDEBUG)
228         msgid = self.db.issue.get(nodeid, 'messages')[0]
229         messageid = self.db.msg.get(msgid, 'messageid')
230         x1, x2 = messageid.split('@')
231         self.assertEqual(x2, 'example.com>')
232         x = x1.split('.')[-1]
233         self.assertEqual(x, 'issueNone')
234         nodeid = self._handle_mail('''Content-Type: text/plain;
235   charset="iso-8859-1"
236 From: Chef <chef@bork.bork.bork>
237 To: issue_tracker@your.tracker.email.domain.example
238 Subject: [issue%(nodeid)s] Testing...
240 Just a test reply
241 '''%locals())
242         msgid = self.db.issue.get(nodeid, 'messages')[-1]
243         messageid = self.db.msg.get(msgid, 'messageid')
244         x1, x2 = messageid.split('@')
245         self.assertEqual(x2, 'example.com>')
246         x = x1.split('.')[-1]
247         self.assertEqual(x, "issue%s"%nodeid)
249     def testOptions(self):
250         nodeid = self._handle_mail('''Content-Type: text/plain;
251   charset="iso-8859-1"
252 From: Chef <chef@bork.bork.bork>
253 To: issue_tracker@your.tracker.email.domain.example
254 Message-Id: <dummy_test_message_id>
255 Reply-To: chef@bork.bork.bork
256 Subject: [issue] Testing...
258 Hi there!
259 ''', (('-C', 'issue'), ('-S', 'status=chatting;priority=critical')))
260         self.assertEqual(self.db.issue.get(nodeid, 'status'), '3')
261         self.assertEqual(self.db.issue.get(nodeid, 'priority'), '1')
263     def testOptionsMulti(self):
264         nodeid = self._handle_mail('''Content-Type: text/plain;
265   charset="iso-8859-1"
266 From: Chef <chef@bork.bork.bork>
267 To: issue_tracker@your.tracker.email.domain.example
268 Message-Id: <dummy_test_message_id>
269 Reply-To: chef@bork.bork.bork
270 Subject: [issue] Testing...
272 Hi there!
273 ''', (('-C', 'issue'), ('-S', 'status=chatting'), ('-S', 'priority=critical')))
274         self.assertEqual(self.db.issue.get(nodeid, 'status'), '3')
275         self.assertEqual(self.db.issue.get(nodeid, 'priority'), '1')
277     def testOptionClass(self):
278         nodeid = self._handle_mail('''Content-Type: text/plain;
279   charset="iso-8859-1"
280 From: Chef <chef@bork.bork.bork>
281 To: issue_tracker@your.tracker.email.domain.example
282 Message-Id: <dummy_test_message_id>
283 Reply-To: chef@bork.bork.bork
284 Subject: [issue] Testing... [status=chatting;priority=critical]
286 Hi there!
287 ''', (('-c', 'issue'),))
288         self.assertEqual(self.db.issue.get(nodeid, 'title'), 'Testing...')
289         self.assertEqual(self.db.issue.get(nodeid, 'status'), '3')
290         self.assertEqual(self.db.issue.get(nodeid, 'priority'), '1')
292     def doNewIssue(self):
293         nodeid = self._handle_mail('''Content-Type: text/plain;
294   charset="iso-8859-1"
295 From: Chef <chef@bork.bork.bork>
296 To: issue_tracker@your.tracker.email.domain.example
297 Cc: richard@test.test
298 Message-Id: <dummy_test_message_id>
299 Subject: [issue] Testing...
301 This is a test submission of a new issue.
302 ''')
303         assert not os.path.exists(SENDMAILDEBUG)
304         l = self.db.issue.get(nodeid, 'nosy')
305         l.sort()
306         self.assertEqual(l, [self.chef_id, self.richard_id])
307         return nodeid
309     def testNewIssue(self):
310         self.doNewIssue()
312     def testNewIssueNosy(self):
313         self.instance.config.ADD_AUTHOR_TO_NOSY = 'yes'
314         nodeid = self._handle_mail('''Content-Type: text/plain;
315   charset="iso-8859-1"
316 From: Chef <chef@bork.bork.bork>
317 To: issue_tracker@your.tracker.email.domain.example
318 Cc: richard@test.test
319 Message-Id: <dummy_test_message_id>
320 Subject: [issue] Testing...
322 This is a test submission of a new issue.
323 ''')
324         assert not os.path.exists(SENDMAILDEBUG)
325         l = self.db.issue.get(nodeid, 'nosy')
326         l.sort()
327         self.assertEqual(l, [self.chef_id, self.richard_id])
329     def testAlternateAddress(self):
330         self._handle_mail('''Content-Type: text/plain;
331   charset="iso-8859-1"
332 From: John Doe <john.doe@test.test>
333 To: issue_tracker@your.tracker.email.domain.example
334 Message-Id: <dummy_test_message_id>
335 Subject: [issue] Testing...
337 This is a test submission of a new issue.
338 ''')
339         userlist = self.db.user.list()
340         assert not os.path.exists(SENDMAILDEBUG)
341         self.assertEqual(userlist, self.db.user.list(),
342             "user created when it shouldn't have been")
344     def testNewIssueNoClass(self):
345         self._handle_mail('''Content-Type: text/plain;
346   charset="iso-8859-1"
347 From: Chef <chef@bork.bork.bork>
348 To: issue_tracker@your.tracker.email.domain.example
349 Cc: richard@test.test
350 Message-Id: <dummy_test_message_id>
351 Subject: Testing...
353 This is a test submission of a new issue.
354 ''')
355         assert not os.path.exists(SENDMAILDEBUG)
357     def testNewIssueAuthMsg(self):
358         # TODO: fix the damn config - this is apalling
359         self.db.config.MESSAGES_TO_AUTHOR = 'yes'
360         self._handle_mail('''Content-Type: text/plain;
361   charset="iso-8859-1"
362 From: Chef <chef@bork.bork.bork>
363 To: issue_tracker@your.tracker.email.domain.example
364 Message-Id: <dummy_test_message_id>
365 Subject: [issue] Testing... [nosy=mary; assignedto=richard]
367 This is a test submission of a new issue.
368 ''')
369         self.compareMessages(self._get_mail(),
370 '''FROM: roundup-admin@your.tracker.email.domain.example
371 TO: chef@bork.bork.bork, mary@test.test, richard@test.test
372 Content-Type: text/plain; charset="utf-8"
373 Subject: [issue1] Testing...
374 To: chef@bork.bork.bork, mary@test.test, richard@test.test
375 From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
376 Reply-To: Roundup issue tracker
377  <issue_tracker@your.tracker.email.domain.example>
378 MIME-Version: 1.0
379 Message-Id: <dummy_test_message_id>
380 X-Roundup-Name: Roundup issue tracker
381 X-Roundup-Loop: hello
382 X-Roundup-Issue-Status: unread
383 Content-Transfer-Encoding: quoted-printable
386 New submission from Bork, Chef <chef@bork.bork.bork>:
388 This is a test submission of a new issue.
390 ----------
391 assignedto: richard
392 messages: 1
393 nosy: Chef, mary, richard
394 status: unread
395 title: Testing...
397 _______________________________________________________________________
398 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
399 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
400 _______________________________________________________________________
401 ''')
403     def testNewIssueNoAuthorInfo(self):
404         self.db.config.MAIL_ADD_AUTHORINFO = 'no'
405         self._handle_mail('''Content-Type: text/plain;
406   charset="iso-8859-1"
407 From: Chef <chef@bork.bork.bork>
408 To: issue_tracker@your.tracker.email.domain.example
409 Message-Id: <dummy_test_message_id>
410 Subject: [issue] Testing... [nosy=mary; assignedto=richard]
412 This is a test submission of a new issue.
413 ''')
414         self.compareMessages(self._get_mail(),
415 '''FROM: roundup-admin@your.tracker.email.domain.example
416 TO: chef@bork.bork.bork, mary@test.test, richard@test.test
417 Content-Type: text/plain; charset="utf-8"
418 Subject: [issue1] Testing...
419 To: mary@test.test, richard@test.test
420 From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
421 Reply-To: Roundup issue tracker
422  <issue_tracker@your.tracker.email.domain.example>
423 MIME-Version: 1.0
424 Message-Id: <dummy_test_message_id>
425 X-Roundup-Name: Roundup issue tracker
426 X-Roundup-Loop: hello
427 X-Roundup-Issue-Status: unread
428 Content-Transfer-Encoding: quoted-printable
430 This is a test submission of a new issue.
432 ----------
433 assignedto: richard
434 messages: 1
435 nosy: Chef, mary, richard
436 status: unread
437 title: Testing...
439 _______________________________________________________________________
440 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
441 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
442 _______________________________________________________________________
443 ''')
445     def testNewIssueNoAuthorEmail(self):
446         self.db.config.MAIL_ADD_AUTHOREMAIL = 'no'
447         self._handle_mail('''Content-Type: text/plain;
448   charset="iso-8859-1"
449 From: Chef <chef@bork.bork.bork>
450 To: issue_tracker@your.tracker.email.domain.example
451 Message-Id: <dummy_test_message_id>
452 Subject: [issue] Testing... [nosy=mary; assignedto=richard]
454 This is a test submission of a new issue.
455 ''')
456         self.compareMessages(self._get_mail(),
457 '''FROM: roundup-admin@your.tracker.email.domain.example
458 TO: chef@bork.bork.bork, mary@test.test, richard@test.test
459 Content-Type: text/plain; charset="utf-8"
460 Subject: [issue1] Testing...
461 To: mary@test.test, richard@test.test
462 From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
463 Reply-To: Roundup issue tracker
464  <issue_tracker@your.tracker.email.domain.example>
465 MIME-Version: 1.0
466 Message-Id: <dummy_test_message_id>
467 X-Roundup-Name: Roundup issue tracker
468 X-Roundup-Loop: hello
469 X-Roundup-Issue-Status: unread
470 Content-Transfer-Encoding: quoted-printable
472 New submission from Bork, Chef:
474 This is a test submission of a new issue.
476 ----------
477 assignedto: richard
478 messages: 1
479 nosy: Chef, mary, richard
480 status: unread
481 title: Testing...
483 _______________________________________________________________________
484 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
485 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
486 _______________________________________________________________________
487 ''')
489     multipart_msg = '''From: mary <mary@test.test>
490 To: issue_tracker@your.tracker.email.domain.example
491 Message-Id: <followup_dummy_id>
492 In-Reply-To: <dummy_test_message_id>
493 Subject: [issue1] Testing...
494 Content-Type: multipart/mixed; boundary="bxyzzy"
495 Content-Disposition: inline
498 --bxyzzy
499 Content-Type: multipart/alternative; boundary="bCsyhTFzCvuiizWE"
500 Content-Disposition: inline
502 --bCsyhTFzCvuiizWE
503 Content-Type: text/plain; charset=us-ascii
504 Content-Disposition: inline
506 test attachment first text/plain
508 --bCsyhTFzCvuiizWE
509 Content-Type: application/octet-stream
510 Content-Disposition: attachment; filename="first.dvi"
511 Content-Transfer-Encoding: base64
513 SnVzdCBhIHRlc3QgAQo=
515 --bCsyhTFzCvuiizWE
516 Content-Type: text/plain; charset=us-ascii
517 Content-Disposition: inline
519 test attachment second text/plain
521 --bCsyhTFzCvuiizWE
522 Content-Type: text/html
523 Content-Disposition: inline
525 <html>
526 to be ignored.
527 </html>
529 --bCsyhTFzCvuiizWE--
531 --bxyzzy
532 Content-Type: multipart/alternative; boundary="bCsyhTFzCvuiizWF"
533 Content-Disposition: inline
535 --bCsyhTFzCvuiizWF
536 Content-Type: text/plain; charset=us-ascii
537 Content-Disposition: inline
539 test attachment third text/plain
541 --bCsyhTFzCvuiizWF
542 Content-Type: application/octet-stream
543 Content-Disposition: attachment; filename="second.dvi"
544 Content-Transfer-Encoding: base64
546 SnVzdCBhIHRlc3QK
548 --bCsyhTFzCvuiizWF--
550 --bxyzzy--
551 '''
553     multipart_msg_latin1 = '''From: mary <mary@test.test>
554 To: issue_tracker@your.tracker.email.domain.example
555 Message-Id: <followup_dummy_id>
556 In-Reply-To: <dummy_test_message_id>
557 Subject: [issue1] Testing...
558 Content-Type: multipart/alternative; boundary=001485f339f8f361fb049188dbba
561 --001485f339f8f361fb049188dbba
562 Content-Type: text/plain; charset=ISO-8859-1
563 Content-Transfer-Encoding: quoted-printable
565 umlaut =E4=F6=FC=C4=D6=DC=DF
567 --001485f339f8f361fb049188dbba
568 Content-Type: text/html; charset=ISO-8859-1
569 Content-Transfer-Encoding: quoted-printable
571 <html>umlaut =E4=F6=FC=C4=D6=DC=DF</html>
573 --001485f339f8f361fb049188dbba--
574 '''
576     multipart_msg_rfc822 = '''From: mary <mary@test.test>
577 To: issue_tracker@your.tracker.email.domain.example
578 Message-Id: <followup_dummy_id>
579 In-Reply-To: <dummy_test_message_id>
580 Subject: [issue1] Testing...
581 Content-Type: multipart/mixed; boundary=001485f339f8f361fb049188dbba
583 This is a multi-part message in MIME format.
584 --001485f339f8f361fb049188dbba
585 Content-Type: text/plain; charset=ISO-8859-15
586 Content-Transfer-Encoding: 7bit
588 First part: Text
590 --001485f339f8f361fb049188dbba
591 Content-Type: message/rfc822; name="Fwd: Original email subject.eml"
592 Content-Transfer-Encoding: 7bit
593 Content-Disposition: attachment; filename="Fwd: Original email subject.eml"
595 Message-Id: <followup_dummy_id_2>
596 In-Reply-To: <dummy_test_message_id_2>
597 MIME-Version: 1.0
598 Subject: Fwd: Original email subject
599 Date: Mon, 23 Aug 2010 08:23:33 +0200
600 Content-Type: multipart/alternative; boundary="090500050101020406060002"
602 This is a multi-part message in MIME format.
603 --090500050101020406060002
604 Content-Type: text/plain; charset=ISO-8859-15; format=flowed
605 Content-Transfer-Encoding: 7bit
607 some text in inner email
608 ========================
610 --090500050101020406060002
611 Content-Type: text/html; charset=ISO-8859-15
612 Content-Transfer-Encoding: 7bit
614 <html>
615 some text in inner email
616 ========================
617 </html>
619 --090500050101020406060002--
621 --001485f339f8f361fb049188dbba--
622 '''
624     def testMultipartKeepAlternatives(self):
625         self.doNewIssue()
626         self._handle_mail(self.multipart_msg)
627         messages = self.db.issue.get('1', 'messages')
628         messages.sort()
629         msg = self.db.msg.getnode (messages[-1])
630         assert(len(msg.files) == 5)
631         names = {0 : 'first.dvi', 4 : 'second.dvi'}
632         content = {3 : 'test attachment third text/plain\n',
633                    4 : 'Just a test\n'}
634         for n, id in enumerate (msg.files):
635             f = self.db.file.getnode (id)
636             self.assertEqual(f.name, names.get (n, 'unnamed'))
637             if n in content :
638                 self.assertEqual(f.content, content [n])
639         self.assertEqual(msg.content, 'test attachment second text/plain')
641     def testMultipartSeveralAttachmentMessages(self):
642         self.doNewIssue()
643         self._handle_mail(self.multipart_msg)
644         messages = self.db.issue.get('1', 'messages')
645         messages.sort()
646         self.assertEqual(messages[-1], '2')
647         msg = self.db.msg.getnode (messages[-1])
648         self.assertEqual(len(msg.files), 5)
649         issue = self.db.issue.getnode ('1')
650         self.assertEqual(len(issue.files), 5)
651         names = {0 : 'first.dvi', 4 : 'second.dvi'}
652         content = {3 : 'test attachment third text/plain\n',
653                    4 : 'Just a test\n'}
654         for n, id in enumerate (msg.files):
655             f = self.db.file.getnode (id)
656             self.assertEqual(f.name, names.get (n, 'unnamed'))
657             if n in content :
658                 self.assertEqual(f.content, content [n])
659         self.assertEqual(msg.content, 'test attachment second text/plain')
660         self.assertEqual(msg.files, ['1', '2', '3', '4', '5'])
661         self.assertEqual(issue.files, ['1', '2', '3', '4', '5'])
663         self._handle_mail(self.multipart_msg)
664         issue = self.db.issue.getnode ('1')
665         self.assertEqual(len(issue.files), 10)
666         messages = self.db.issue.get('1', 'messages')
667         messages.sort()
668         self.assertEqual(messages[-1], '3')
669         msg = self.db.msg.getnode (messages[-1])
670         self.assertEqual(issue.files, [str(i+1) for i in range(10)])
671         self.assertEqual(msg.files, ['6', '7', '8', '9', '10'])
673     def testMultipartKeepFiles(self):
674         self.doNewIssue()
675         self._handle_mail(self.multipart_msg)
676         messages = self.db.issue.get('1', 'messages')
677         messages.sort()
678         msg = self.db.msg.getnode (messages[-1])
679         self.assertEqual(len(msg.files), 5)
680         issue = self.db.issue.getnode ('1')
681         self.assertEqual(len(issue.files), 5)
682         names = {0 : 'first.dvi', 4 : 'second.dvi'}
683         content = {3 : 'test attachment third text/plain\n',
684                    4 : 'Just a test\n'}
685         for n, id in enumerate (msg.files):
686             f = self.db.file.getnode (id)
687             self.assertEqual(f.name, names.get (n, 'unnamed'))
688             if n in content :
689                 self.assertEqual(f.content, content [n])
690         self.assertEqual(msg.content, 'test attachment second text/plain')
691         self._handle_mail('''From: mary <mary@test.test>
692 To: issue_tracker@your.tracker.email.domain.example
693 Message-Id: <followup_dummy_id2>
694 In-Reply-To: <dummy_test_message_id>
695 Subject: [issue1] Testing...
697 This ist a message without attachment
698 ''')
699         issue = self.db.issue.getnode ('1')
700         self.assertEqual(len(issue.files), 5)
701         self.assertEqual(issue.files, ['1', '2', '3', '4', '5'])
703     def testMultipartDropAlternatives(self):
704         self.doNewIssue()
705         self.db.config.MAILGW_IGNORE_ALTERNATIVES = True
706         self._handle_mail(self.multipart_msg)
707         messages = self.db.issue.get('1', 'messages')
708         messages.sort()
709         msg = self.db.msg.getnode (messages[-1])
710         self.assertEqual(len(msg.files), 2)
711         names = {1 : 'second.dvi'}
712         content = {0 : 'test attachment third text/plain\n',
713                    1 : 'Just a test\n'}
714         for n, id in enumerate (msg.files):
715             f = self.db.file.getnode (id)
716             self.assertEqual(f.name, names.get (n, 'unnamed'))
717             if n in content :
718                 self.assertEqual(f.content, content [n])
719         self.assertEqual(msg.content, 'test attachment second text/plain')
721     def testMultipartCharsetUTF8NoAttach(self):
722         c = 'umlaut \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f'
723         self.doNewIssue()
724         self.db.config.NOSY_MAX_ATTACHMENT_SIZE = 0
725         self._handle_mail(self.multipart_msg_latin1)
726         messages = self.db.issue.get('1', 'messages')
727         messages.sort()
728         msg = self.db.msg.getnode (messages[-1])
729         self.assertEqual(len(msg.files), 1)
730         name = 'unnamed'
731         content = '<html>' + c + '</html>\n'
732         for n, id in enumerate (msg.files):
733             f = self.db.file.getnode (id)
734             self.assertEqual(f.name, name)
735             self.assertEqual(f.content, content)
736         self.assertEqual(msg.content, c)
737         self.compareMessages(self._get_mail(),
738 '''FROM: roundup-admin@your.tracker.email.domain.example
739 TO: chef@bork.bork.bork, richard@test.test
740 Content-Type: text/plain; charset="utf-8"
741 Subject: [issue1] Testing...
742 To: chef@bork.bork.bork, richard@test.test
743 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
744 Reply-To: Roundup issue tracker
745  <issue_tracker@your.tracker.email.domain.example>
746 MIME-Version: 1.0
747 Message-Id: <followup_dummy_id>
748 In-Reply-To: <dummy_test_message_id>
749 X-Roundup-Name: Roundup issue tracker
750 X-Roundup-Loop: hello
751 X-Roundup-Issue-Status: chatting
752 X-Roundup-Issue-Files: unnamed
753 Content-Transfer-Encoding: quoted-printable
756 Contrary, Mary <mary@test.test> added the comment:
758 umlaut =C3=A4=C3=B6=C3=BC=C3=84=C3=96=C3=9C=C3=9F
759 File 'unnamed' not attached - you can download it from http://tracker.examp=
760 le/cgi-bin/roundup.cgi/bugs/file1.
762 ----------
763 status: unread -> chatting
765 _______________________________________________________________________
766 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
767 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
768 _______________________________________________________________________
769 ''')
771     def testMultipartCharsetLatin1NoAttach(self):
772         c = 'umlaut \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f'
773         self.doNewIssue()
774         self.db.config.NOSY_MAX_ATTACHMENT_SIZE = 0
775         self.db.config.MAIL_CHARSET = 'iso-8859-1'
776         self._handle_mail(self.multipart_msg_latin1)
777         messages = self.db.issue.get('1', 'messages')
778         messages.sort()
779         msg = self.db.msg.getnode (messages[-1])
780         self.assertEqual(len(msg.files), 1)
781         name = 'unnamed'
782         content = '<html>' + c + '</html>\n'
783         for n, id in enumerate (msg.files):
784             f = self.db.file.getnode (id)
785             self.assertEqual(f.name, name)
786             self.assertEqual(f.content, content)
787         self.assertEqual(msg.content, c)
788         self.compareMessages(self._get_mail(),
789 '''FROM: roundup-admin@your.tracker.email.domain.example
790 TO: chef@bork.bork.bork, richard@test.test
791 Content-Type: text/plain; charset="iso-8859-1"
792 Subject: [issue1] Testing...
793 To: chef@bork.bork.bork, richard@test.test
794 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
795 Reply-To: Roundup issue tracker
796  <issue_tracker@your.tracker.email.domain.example>
797 MIME-Version: 1.0
798 Message-Id: <followup_dummy_id>
799 In-Reply-To: <dummy_test_message_id>
800 X-Roundup-Name: Roundup issue tracker
801 X-Roundup-Loop: hello
802 X-Roundup-Issue-Status: chatting
803 X-Roundup-Issue-Files: unnamed
804 Content-Transfer-Encoding: quoted-printable
807 Contrary, Mary <mary@test.test> added the comment:
809 umlaut =E4=F6=FC=C4=D6=DC=DF
810 File 'unnamed' not attached - you can download it from http://tracker.examp=
811 le/cgi-bin/roundup.cgi/bugs/file1.
813 ----------
814 status: unread -> chatting
816 _______________________________________________________________________
817 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
818 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
819 _______________________________________________________________________
820 ''')
822     def testMultipartCharsetUTF8AttachFile(self):
823         c = 'umlaut \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f'
824         self.doNewIssue()
825         self._handle_mail(self.multipart_msg_latin1)
826         messages = self.db.issue.get('1', 'messages')
827         messages.sort()
828         msg = self.db.msg.getnode (messages[-1])
829         self.assertEqual(len(msg.files), 1)
830         name = 'unnamed'
831         content = '<html>' + c + '</html>\n'
832         for n, id in enumerate (msg.files):
833             f = self.db.file.getnode (id)
834             self.assertEqual(f.name, name)
835             self.assertEqual(f.content, content)
836         self.assertEqual(msg.content, c)
837         self.compareMessages(self._get_mail(),
838 '''FROM: roundup-admin@your.tracker.email.domain.example
839 TO: chef@bork.bork.bork, richard@test.test
840 Content-Type: multipart/mixed; boundary="utf-8"
841 Subject: [issue1] Testing...
842 To: chef@bork.bork.bork, richard@test.test
843 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
844 Reply-To: Roundup issue tracker
845  <issue_tracker@your.tracker.email.domain.example>
846 MIME-Version: 1.0
847 Message-Id: <followup_dummy_id>
848 In-Reply-To: <dummy_test_message_id>
849 X-Roundup-Name: Roundup issue tracker
850 X-Roundup-Loop: hello
851 X-Roundup-Issue-Status: chatting
852 X-Roundup-Issue-Files: unnamed
853 Content-Transfer-Encoding: quoted-printable
856 --utf-8
857 MIME-Version: 1.0
858 Content-Type: text/plain; charset="utf-8"
859 Content-Transfer-Encoding: quoted-printable
862 Contrary, Mary <mary@test.test> added the comment:
864 umlaut =C3=A4=C3=B6=C3=BC=C3=84=C3=96=C3=9C=C3=9F
866 ----------
867 status: unread -> chatting
869 _______________________________________________________________________
870 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
871 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
872 _______________________________________________________________________
873 --utf-8
874 Content-Type: text/html
875 MIME-Version: 1.0
876 Content-Transfer-Encoding: base64
877 Content-Disposition: attachment;
878  filename="unnamed"
880 PGh0bWw+dW1sYXV0IMOkw7bDvMOEw5bDnMOfPC9odG1sPgo=
882 --utf-8--
883 ''')
885     def testMultipartCharsetLatin1AttachFile(self):
886         c = 'umlaut \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f'
887         self.doNewIssue()
888         self.db.config.MAIL_CHARSET = 'iso-8859-1'
889         self._handle_mail(self.multipart_msg_latin1)
890         messages = self.db.issue.get('1', 'messages')
891         messages.sort()
892         msg = self.db.msg.getnode (messages[-1])
893         self.assertEqual(len(msg.files), 1)
894         name = 'unnamed'
895         content = '<html>' + c + '</html>\n'
896         for n, id in enumerate (msg.files):
897             f = self.db.file.getnode (id)
898             self.assertEqual(f.name, name)
899             self.assertEqual(f.content, content)
900         self.assertEqual(msg.content, c)
901         self.compareMessages(self._get_mail(),
902 '''FROM: roundup-admin@your.tracker.email.domain.example
903 TO: chef@bork.bork.bork, richard@test.test
904 Content-Type: multipart/mixed; boundary="utf-8"
905 Subject: [issue1] Testing...
906 To: chef@bork.bork.bork, richard@test.test
907 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
908 Reply-To: Roundup issue tracker
909  <issue_tracker@your.tracker.email.domain.example>
910 MIME-Version: 1.0
911 Message-Id: <followup_dummy_id>
912 In-Reply-To: <dummy_test_message_id>
913 X-Roundup-Name: Roundup issue tracker
914 X-Roundup-Loop: hello
915 X-Roundup-Issue-Status: chatting
916 X-Roundup-Issue-Files: unnamed
917 Content-Transfer-Encoding: quoted-printable
920 --utf-8
921 MIME-Version: 1.0
922 Content-Type: text/plain; charset="iso-8859-1"
923 Content-Transfer-Encoding: quoted-printable
926 Contrary, Mary <mary@test.test> added the comment:
928 umlaut =E4=F6=FC=C4=D6=DC=DF
930 ----------
931 status: unread -> chatting
933 _______________________________________________________________________
934 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
935 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
936 _______________________________________________________________________
937 --utf-8
938 Content-Type: text/html
939 MIME-Version: 1.0
940 Content-Transfer-Encoding: base64
941 Content-Disposition: attachment;
942  filename="unnamed"
944 PGh0bWw+dW1sYXV0IMOkw7bDvMOEw5bDnMOfPC9odG1sPgo=
946 --utf-8--
947 ''')
949     def testMultipartRFC822(self):
950         self.doNewIssue()
951         self._handle_mail(self.multipart_msg_rfc822)
952         messages = self.db.issue.get('1', 'messages')
953         messages.sort()
954         msg = self.db.msg.getnode (messages[-1])
955         self.assertEqual(len(msg.files), 1)
956         name = "Fwd: Original email subject.eml"
957         for n, id in enumerate (msg.files):
958             f = self.db.file.getnode (id)
959             self.assertEqual(f.name, name)
960         self.assertEqual(msg.content, 'First part: Text')
961         self.compareMessages(self._get_mail(),
962 '''TO: chef@bork.bork.bork, richard@test.test
963 Content-Type: text/plain; charset="utf-8"
964 Subject: [issue1] Testing...
965 To: chef@bork.bork.bork, richard@test.test
966 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
967 Reply-To: Roundup issue tracker
968  <issue_tracker@your.tracker.email.domain.example>
969 MIME-Version: 1.0
970 Message-Id: <followup_dummy_id>
971 In-Reply-To: <dummy_test_message_id>
972 X-Roundup-Name: Roundup issue tracker
973 X-Roundup-Loop: hello
974 X-Roundup-Issue-Status: chatting
975 X-Roundup-Issue-Files: Fwd: Original email subject.eml
976 Content-Transfer-Encoding: quoted-printable
979 --utf-8
980 MIME-Version: 1.0
981 Content-Type: text/plain; charset="utf-8"
982 Content-Transfer-Encoding: quoted-printable
985 Contrary, Mary <mary@test.test> added the comment:
987 First part: Text
989 ----------
990 status: unread -> chatting
992 _______________________________________________________________________
993 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
994 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
995 _______________________________________________________________________
996 --utf-8
997 Content-Type: message/rfc822
998 MIME-Version: 1.0
999 Content-Disposition: attachment;
1000  filename="Fwd: Original email subject.eml"
1002 Message-Id: <followup_dummy_id_2>
1003 In-Reply-To: <dummy_test_message_id_2>
1004 MIME-Version: 1.0
1005 Subject: Fwd: Original email subject
1006 Date: Mon, 23 Aug 2010 08:23:33 +0200
1007 Content-Type: multipart/alternative; boundary="090500050101020406060002"
1009 This is a multi-part message in MIME format.
1010 --090500050101020406060002
1011 Content-Type: text/plain; charset=ISO-8859-15; format=flowed
1012 Content-Transfer-Encoding: 7bit
1014 some text in inner email
1015 ========================
1017 --090500050101020406060002
1018 Content-Type: text/html; charset=ISO-8859-15
1019 Content-Transfer-Encoding: 7bit
1021 <html>
1022 some text in inner email
1023 ========================
1024 </html>
1026 --090500050101020406060002--
1028 --utf-8--
1029 ''')
1031     def testMultipartRFC822Unpack(self):
1032         self.doNewIssue()
1033         self.db.config.MAILGW_UNPACK_RFC822 = True
1034         self._handle_mail(self.multipart_msg_rfc822)
1035         messages = self.db.issue.get('1', 'messages')
1036         messages.sort()
1037         msg = self.db.msg.getnode (messages[-1])
1038         self.assertEqual(len(msg.files), 2)
1039         t = 'some text in inner email\n========================\n'
1040         content = {0 : t, 1 : '<html>\n' + t + '</html>\n'}
1041         for n, id in enumerate (msg.files):
1042             f = self.db.file.getnode (id)
1043             self.assertEqual(f.name, 'unnamed')
1044             if n in content :
1045                 self.assertEqual(f.content, content [n])
1046         self.assertEqual(msg.content, 'First part: Text')
1048     def testSimpleFollowup(self):
1049         self.doNewIssue()
1050         self._handle_mail('''Content-Type: text/plain;
1051   charset="iso-8859-1"
1052 From: mary <mary@test.test>
1053 To: issue_tracker@your.tracker.email.domain.example
1054 Message-Id: <followup_dummy_id>
1055 In-Reply-To: <dummy_test_message_id>
1056 Subject: [issue1] Testing...
1058 This is a second followup
1059 ''')
1060         self.compareMessages(self._get_mail(),
1061 '''FROM: roundup-admin@your.tracker.email.domain.example
1062 TO: chef@bork.bork.bork, richard@test.test
1063 Content-Type: text/plain; charset="utf-8"
1064 Subject: [issue1] Testing...
1065 To: chef@bork.bork.bork, richard@test.test
1066 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
1067 Reply-To: Roundup issue tracker
1068  <issue_tracker@your.tracker.email.domain.example>
1069 MIME-Version: 1.0
1070 Message-Id: <followup_dummy_id>
1071 In-Reply-To: <dummy_test_message_id>
1072 X-Roundup-Name: Roundup issue tracker
1073 X-Roundup-Loop: hello
1074 X-Roundup-Issue-Status: chatting
1075 Content-Transfer-Encoding: quoted-printable
1078 Contrary, Mary <mary@test.test> added the comment:
1080 This is a second followup
1082 ----------
1083 status: unread -> chatting
1085 _______________________________________________________________________
1086 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1087 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1088 _______________________________________________________________________
1089 ''')
1091     def testFollowup(self):
1092         self.doNewIssue()
1094         self._handle_mail('''Content-Type: text/plain;
1095   charset="iso-8859-1"
1096 From: richard <richard@test.test>
1097 To: issue_tracker@your.tracker.email.domain.example
1098 Message-Id: <followup_dummy_id>
1099 In-Reply-To: <dummy_test_message_id>
1100 Subject: [issue1] Testing... [assignedto=mary; nosy=+john]
1102 This is a followup
1103 ''')
1104         l = self.db.issue.get('1', 'nosy')
1105         l.sort()
1106         self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id,
1107             self.john_id])
1109         self.compareMessages(self._get_mail(),
1110 '''FROM: roundup-admin@your.tracker.email.domain.example
1111 TO: chef@bork.bork.bork, john@test.test, mary@test.test
1112 Content-Type: text/plain; charset="utf-8"
1113 Subject: [issue1] Testing...
1114 To: chef@bork.bork.bork, john@test.test, mary@test.test
1115 From: richard <issue_tracker@your.tracker.email.domain.example>
1116 Reply-To: Roundup issue tracker
1117  <issue_tracker@your.tracker.email.domain.example>
1118 MIME-Version: 1.0
1119 Message-Id: <followup_dummy_id>
1120 In-Reply-To: <dummy_test_message_id>
1121 X-Roundup-Name: Roundup issue tracker
1122 X-Roundup-Loop: hello
1123 X-Roundup-Issue-Status: chatting
1124 Content-Transfer-Encoding: quoted-printable
1127 richard <richard@test.test> added the comment:
1129 This is a followup
1131 ----------
1132 assignedto:  -> mary
1133 nosy: +john, mary
1134 status: unread -> chatting
1136 _______________________________________________________________________
1137 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1138 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1139 _______________________________________________________________________
1140 ''')
1142     def testFollowupNoSubjectChange(self):
1143         self.db.config.MAILGW_SUBJECT_UPDATES_TITLE = 'no'
1144         self.doNewIssue()
1146         self._handle_mail('''Content-Type: text/plain;
1147   charset="iso-8859-1"
1148 From: richard <richard@test.test>
1149 To: issue_tracker@your.tracker.email.domain.example
1150 Message-Id: <followup_dummy_id>
1151 In-Reply-To: <dummy_test_message_id>
1152 Subject: [issue1] Wrzlbrmft... [assignedto=mary; nosy=+john]
1154 This is a followup
1155 ''')
1156         l = self.db.issue.get('1', 'nosy')
1157         l.sort()
1158         self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id,
1159             self.john_id])
1161         self.compareMessages(self._get_mail(),
1162 '''FROM: roundup-admin@your.tracker.email.domain.example
1163 TO: chef@bork.bork.bork, john@test.test, mary@test.test
1164 Content-Type: text/plain; charset="utf-8"
1165 Subject: [issue1] Testing...
1166 To: chef@bork.bork.bork, john@test.test, mary@test.test
1167 From: richard <issue_tracker@your.tracker.email.domain.example>
1168 Reply-To: Roundup issue tracker
1169  <issue_tracker@your.tracker.email.domain.example>
1170 MIME-Version: 1.0
1171 Message-Id: <followup_dummy_id>
1172 In-Reply-To: <dummy_test_message_id>
1173 X-Roundup-Name: Roundup issue tracker
1174 X-Roundup-Loop: hello
1175 X-Roundup-Issue-Status: chatting
1176 Content-Transfer-Encoding: quoted-printable
1179 richard <richard@test.test> added the comment:
1181 This is a followup
1183 ----------
1184 assignedto:  -> mary
1185 nosy: +john, mary
1186 status: unread -> chatting
1188 _______________________________________________________________________
1189 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1190 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1191 _______________________________________________________________________
1192 ''')
1193         self.assertEqual(self.db.issue.get('1','title'), 'Testing...')
1195     def testFollowupExplicitSubjectChange(self):
1196         self.doNewIssue()
1198         self._handle_mail('''Content-Type: text/plain;
1199   charset="iso-8859-1"
1200 From: richard <richard@test.test>
1201 To: issue_tracker@your.tracker.email.domain.example
1202 Message-Id: <followup_dummy_id>
1203 In-Reply-To: <dummy_test_message_id>
1204 Subject: [issue1] Wrzlbrmft... [assignedto=mary; nosy=+john; title=new title]
1206 This is a followup
1207 ''')
1208         l = self.db.issue.get('1', 'nosy')
1209         l.sort()
1210         self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id,
1211             self.john_id])
1213         self.compareMessages(self._get_mail(),
1214 '''FROM: roundup-admin@your.tracker.email.domain.example
1215 TO: chef@bork.bork.bork, john@test.test, mary@test.test
1216 Content-Type: text/plain; charset="utf-8"
1217 Subject: [issue1] new title
1218 To: chef@bork.bork.bork, john@test.test, mary@test.test
1219 From: richard <issue_tracker@your.tracker.email.domain.example>
1220 Reply-To: Roundup issue tracker
1221  <issue_tracker@your.tracker.email.domain.example>
1222 MIME-Version: 1.0
1223 Message-Id: <followup_dummy_id>
1224 In-Reply-To: <dummy_test_message_id>
1225 X-Roundup-Name: Roundup issue tracker
1226 X-Roundup-Loop: hello
1227 X-Roundup-Issue-Status: chatting
1228 Content-Transfer-Encoding: quoted-printable
1231 richard <richard@test.test> added the comment:
1233 This is a followup
1235 ----------
1236 assignedto:  -> mary
1237 nosy: +john, mary
1238 status: unread -> chatting
1239 title: Testing... -> new title
1241 _______________________________________________________________________
1242 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1243 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1244 _______________________________________________________________________
1245 ''')
1247     def testNosyGeneration(self):
1248         self.db.issue.create(title='test')
1250         # create a nosy message
1251         msg = self.db.msg.create(content='This is a test',
1252             author=self.richard_id, messageid='<dummy_test_message_id>')
1253         self.db.journaltag = 'richard'
1254         l = self.db.issue.create(title='test', messages=[msg],
1255             nosy=[self.chef_id, self.mary_id, self.john_id])
1257         self.compareMessages(self._get_mail(),
1258 '''FROM: roundup-admin@your.tracker.email.domain.example
1259 TO: chef@bork.bork.bork, john@test.test, mary@test.test
1260 Content-Type: text/plain; charset="utf-8"
1261 Subject: [issue2] test
1262 To: chef@bork.bork.bork, john@test.test, mary@test.test
1263 From: richard <issue_tracker@your.tracker.email.domain.example>
1264 Reply-To: Roundup issue tracker
1265  <issue_tracker@your.tracker.email.domain.example>
1266 MIME-Version: 1.0
1267 Message-Id: <dummy_test_message_id>
1268 X-Roundup-Name: Roundup issue tracker
1269 X-Roundup-Loop: hello
1270 X-Roundup-Issue-Status: unread
1271 Content-Transfer-Encoding: quoted-printable
1274 New submission from richard <richard@test.test>:
1276 This is a test
1278 ----------
1279 messages: 1
1280 nosy: Chef, john, mary, richard
1281 status: unread
1282 title: test
1284 _______________________________________________________________________
1285 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1286 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue2>
1287 _______________________________________________________________________
1288 ''')
1290     def testPropertyChangeOnly(self):
1291         self.doNewIssue()
1292         oldvalues = self.db.getnode('issue', '1').copy()
1293         oldvalues['assignedto'] = None
1294         # reconstruct old behaviour: This would reuse the
1295         # database-handle from the doNewIssue above which has committed
1296         # as user "Chef". So we close and reopen the db as that user.
1297         #self.db.close() actually don't close 'cos this empties memorydb
1298         self.db = self.instance.open('Chef')
1299         self.db.issue.set('1', assignedto=self.chef_id)
1300         self.db.commit()
1301         self.db.issue.nosymessage('1', None, oldvalues)
1303         new_mail = ""
1304         for line in self._get_mail().split("\n"):
1305             if "Message-Id: " in line:
1306                 continue
1307             if "Date: " in line:
1308                 continue
1309             new_mail += line+"\n"
1311         self.compareMessages(new_mail, """
1312 FROM: roundup-admin@your.tracker.email.domain.example
1313 TO: chef@bork.bork.bork, richard@test.test
1314 Content-Type: text/plain; charset="utf-8"
1315 Subject: [issue1] Testing...
1316 To: chef@bork.bork.bork, richard@test.test
1317 From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
1318 X-Roundup-Name: Roundup issue tracker
1319 X-Roundup-Loop: hello
1320 X-Roundup-Issue-Status: unread
1321 X-Roundup-Version: 1.3.3
1322 In-Reply-To: <dummy_test_message_id>
1323 MIME-Version: 1.0
1324 Reply-To: Roundup issue tracker
1325  <issue_tracker@your.tracker.email.domain.example>
1326 Content-Transfer-Encoding: quoted-printable
1329 Change by Bork, Chef <chef@bork.bork.bork>:
1332 ----------
1333 assignedto:  -> Chef
1335 _______________________________________________________________________
1336 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1337 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1338 _______________________________________________________________________
1339 """)
1342     #
1343     # FOLLOWUP TITLE MATCH
1344     #
1345     def testFollowupTitleMatch(self):
1346         self.doNewIssue()
1347         self._handle_mail('''Content-Type: text/plain;
1348   charset="iso-8859-1"
1349 From: richard <richard@test.test>
1350 To: issue_tracker@your.tracker.email.domain.example
1351 Message-Id: <followup_dummy_id>
1352 Subject: Re: Testing... [assignedto=mary; nosy=+john]
1354 This is a followup
1355 ''')
1356         self.compareMessages(self._get_mail(),
1357 '''FROM: roundup-admin@your.tracker.email.domain.example
1358 TO: chef@bork.bork.bork, john@test.test, mary@test.test
1359 Content-Type: text/plain; charset="utf-8"
1360 Subject: [issue1] Testing...
1361 To: chef@bork.bork.bork, john@test.test, mary@test.test
1362 From: richard <issue_tracker@your.tracker.email.domain.example>
1363 Reply-To: Roundup issue tracker
1364  <issue_tracker@your.tracker.email.domain.example>
1365 MIME-Version: 1.0
1366 Message-Id: <followup_dummy_id>
1367 In-Reply-To: <dummy_test_message_id>
1368 X-Roundup-Name: Roundup issue tracker
1369 X-Roundup-Loop: hello
1370 X-Roundup-Issue-Status: chatting
1371 Content-Transfer-Encoding: quoted-printable
1374 richard <richard@test.test> added the comment:
1376 This is a followup
1378 ----------
1379 assignedto:  -> mary
1380 nosy: +john, mary
1381 status: unread -> chatting
1383 _______________________________________________________________________
1384 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1385 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1386 _______________________________________________________________________
1387 ''')
1389     def testFollowupTitleMatchMultiRe(self):
1390         nodeid1 = self.doNewIssue()
1391         nodeid2 = self._handle_mail('''Content-Type: text/plain;
1392   charset="iso-8859-1"
1393 From: richard <richard@test.test>
1394 To: issue_tracker@your.tracker.email.domain.example
1395 Message-Id: <followup_dummy_id>
1396 Subject: Re: Testing... [assignedto=mary; nosy=+john]
1398 This is a followup
1399 ''')
1401         nodeid3 = self._handle_mail('''Content-Type: text/plain;
1402   charset="iso-8859-1"
1403 From: richard <richard@test.test>
1404 To: issue_tracker@your.tracker.email.domain.example
1405 Message-Id: <followup2_dummy_id>
1406 Subject: Ang: Re: Testing...
1408 This is a followup
1409 ''')
1410         self.assertEqual(nodeid1, nodeid2)
1411         self.assertEqual(nodeid1, nodeid3)
1413     def testFollowupTitleMatchNever(self):
1414         nodeid = self.doNewIssue()
1415         self.db.config.MAILGW_SUBJECT_CONTENT_MATCH = 'never'
1416         self.assertNotEqual(self._handle_mail('''Content-Type: text/plain;
1417   charset="iso-8859-1"
1418 From: richard <richard@test.test>
1419 To: issue_tracker@your.tracker.email.domain.example
1420 Message-Id: <followup_dummy_id>
1421 Subject: Re: Testing...
1423 This is a followup
1424 '''), nodeid)
1426     def testFollowupTitleMatchNeverInterval(self):
1427         nodeid = self.doNewIssue()
1428         # force failure of the interval
1429         time.sleep(2)
1430         self.db.config.MAILGW_SUBJECT_CONTENT_MATCH = 'creation 00:00:01'
1431         self.assertNotEqual(self._handle_mail('''Content-Type: text/plain;
1432   charset="iso-8859-1"
1433 From: richard <richard@test.test>
1434 To: issue_tracker@your.tracker.email.domain.example
1435 Message-Id: <followup_dummy_id>
1436 Subject: Re: Testing...
1438 This is a followup
1439 '''), nodeid)
1442     def testFollowupTitleMatchInterval(self):
1443         nodeid = self.doNewIssue()
1444         self.db.config.MAILGW_SUBJECT_CONTENT_MATCH = 'creation +1d'
1445         self.assertEqual(self._handle_mail('''Content-Type: text/plain;
1446   charset="iso-8859-1"
1447 From: richard <richard@test.test>
1448 To: issue_tracker@your.tracker.email.domain.example
1449 Message-Id: <followup_dummy_id>
1450 Subject: Re: Testing...
1452 This is a followup
1453 '''), nodeid)
1456     def testFollowupNosyAuthor(self):
1457         self.doNewIssue()
1458         self.db.config.ADD_AUTHOR_TO_NOSY = 'yes'
1459         self._handle_mail('''Content-Type: text/plain;
1460   charset="iso-8859-1"
1461 From: john@test.test
1462 To: issue_tracker@your.tracker.email.domain.example
1463 Message-Id: <followup_dummy_id>
1464 In-Reply-To: <dummy_test_message_id>
1465 Subject: [issue1] Testing...
1467 This is a followup
1468 ''')
1470         self.compareMessages(self._get_mail(),
1471 '''FROM: roundup-admin@your.tracker.email.domain.example
1472 TO: chef@bork.bork.bork, richard@test.test
1473 Content-Type: text/plain; charset="utf-8"
1474 Subject: [issue1] Testing...
1475 To: chef@bork.bork.bork, richard@test.test
1476 From: John Doe <issue_tracker@your.tracker.email.domain.example>
1477 Reply-To: Roundup issue tracker
1478  <issue_tracker@your.tracker.email.domain.example>
1479 MIME-Version: 1.0
1480 Message-Id: <followup_dummy_id>
1481 In-Reply-To: <dummy_test_message_id>
1482 X-Roundup-Name: Roundup issue tracker
1483 X-Roundup-Loop: hello
1484 X-Roundup-Issue-Status: chatting
1485 Content-Transfer-Encoding: quoted-printable
1488 John Doe <john@test.test> added the comment:
1490 This is a followup
1492 ----------
1493 nosy: +john
1494 status: unread -> chatting
1496 _______________________________________________________________________
1497 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1498 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1499 _______________________________________________________________________
1501 ''')
1503     def testFollowupNosyRecipients(self):
1504         self.doNewIssue()
1505         self.db.config.ADD_RECIPIENTS_TO_NOSY = 'yes'
1506         self._handle_mail('''Content-Type: text/plain;
1507   charset="iso-8859-1"
1508 From: richard@test.test
1509 To: issue_tracker@your.tracker.email.domain.example
1510 Cc: john@test.test
1511 Message-Id: <followup_dummy_id>
1512 In-Reply-To: <dummy_test_message_id>
1513 Subject: [issue1] Testing...
1515 This is a followup
1516 ''')
1517         self.compareMessages(self._get_mail(),
1518 '''FROM: roundup-admin@your.tracker.email.domain.example
1519 TO: chef@bork.bork.bork
1520 Content-Type: text/plain; charset="utf-8"
1521 Subject: [issue1] Testing...
1522 To: chef@bork.bork.bork
1523 From: richard <issue_tracker@your.tracker.email.domain.example>
1524 Reply-To: Roundup issue tracker
1525  <issue_tracker@your.tracker.email.domain.example>
1526 MIME-Version: 1.0
1527 Message-Id: <followup_dummy_id>
1528 In-Reply-To: <dummy_test_message_id>
1529 X-Roundup-Name: Roundup issue tracker
1530 X-Roundup-Loop: hello
1531 X-Roundup-Issue-Status: chatting
1532 Content-Transfer-Encoding: quoted-printable
1535 richard <richard@test.test> added the comment:
1537 This is a followup
1539 ----------
1540 nosy: +john
1541 status: unread -> chatting
1543 _______________________________________________________________________
1544 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1545 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1546 _______________________________________________________________________
1548 ''')
1550     def testFollowupNosyAuthorAndCopy(self):
1551         self.doNewIssue()
1552         self.db.config.ADD_AUTHOR_TO_NOSY = 'yes'
1553         self.db.config.MESSAGES_TO_AUTHOR = 'yes'
1554         self._handle_mail('''Content-Type: text/plain;
1555   charset="iso-8859-1"
1556 From: john@test.test
1557 To: issue_tracker@your.tracker.email.domain.example
1558 Message-Id: <followup_dummy_id>
1559 In-Reply-To: <dummy_test_message_id>
1560 Subject: [issue1] Testing...
1562 This is a followup
1563 ''')
1564         self.compareMessages(self._get_mail(),
1565 '''FROM: roundup-admin@your.tracker.email.domain.example
1566 TO: chef@bork.bork.bork, john@test.test, richard@test.test
1567 Content-Type: text/plain; charset="utf-8"
1568 Subject: [issue1] Testing...
1569 To: chef@bork.bork.bork, john@test.test, richard@test.test
1570 From: John Doe <issue_tracker@your.tracker.email.domain.example>
1571 Reply-To: Roundup issue tracker
1572  <issue_tracker@your.tracker.email.domain.example>
1573 MIME-Version: 1.0
1574 Message-Id: <followup_dummy_id>
1575 In-Reply-To: <dummy_test_message_id>
1576 X-Roundup-Name: Roundup issue tracker
1577 X-Roundup-Loop: hello
1578 X-Roundup-Issue-Status: chatting
1579 Content-Transfer-Encoding: quoted-printable
1582 John Doe <john@test.test> added the comment:
1584 This is a followup
1586 ----------
1587 nosy: +john
1588 status: unread -> chatting
1590 _______________________________________________________________________
1591 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1592 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1593 _______________________________________________________________________
1595 ''')
1597     def testFollowupNoNosyAuthor(self):
1598         self.doNewIssue()
1599         self.instance.config.ADD_AUTHOR_TO_NOSY = 'no'
1600         self._handle_mail('''Content-Type: text/plain;
1601   charset="iso-8859-1"
1602 From: john@test.test
1603 To: issue_tracker@your.tracker.email.domain.example
1604 Message-Id: <followup_dummy_id>
1605 In-Reply-To: <dummy_test_message_id>
1606 Subject: [issue1] Testing...
1608 This is a followup
1609 ''')
1610         self.compareMessages(self._get_mail(),
1611 '''FROM: roundup-admin@your.tracker.email.domain.example
1612 TO: chef@bork.bork.bork, richard@test.test
1613 Content-Type: text/plain; charset="utf-8"
1614 Subject: [issue1] Testing...
1615 To: chef@bork.bork.bork, richard@test.test
1616 From: John Doe <issue_tracker@your.tracker.email.domain.example>
1617 Reply-To: Roundup issue tracker
1618  <issue_tracker@your.tracker.email.domain.example>
1619 MIME-Version: 1.0
1620 Message-Id: <followup_dummy_id>
1621 In-Reply-To: <dummy_test_message_id>
1622 X-Roundup-Name: Roundup issue tracker
1623 X-Roundup-Loop: hello
1624 X-Roundup-Issue-Status: chatting
1625 Content-Transfer-Encoding: quoted-printable
1628 John Doe <john@test.test> added the comment:
1630 This is a followup
1632 ----------
1633 status: unread -> chatting
1635 _______________________________________________________________________
1636 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1637 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1638 _______________________________________________________________________
1640 ''')
1642     def testFollowupNoNosyRecipients(self):
1643         self.doNewIssue()
1644         self.instance.config.ADD_RECIPIENTS_TO_NOSY = 'no'
1645         self._handle_mail('''Content-Type: text/plain;
1646   charset="iso-8859-1"
1647 From: richard@test.test
1648 To: issue_tracker@your.tracker.email.domain.example
1649 Cc: john@test.test
1650 Message-Id: <followup_dummy_id>
1651 In-Reply-To: <dummy_test_message_id>
1652 Subject: [issue1] Testing...
1654 This is a followup
1655 ''')
1656         self.compareMessages(self._get_mail(),
1657 '''FROM: roundup-admin@your.tracker.email.domain.example
1658 TO: chef@bork.bork.bork
1659 Content-Type: text/plain; charset="utf-8"
1660 Subject: [issue1] Testing...
1661 To: chef@bork.bork.bork
1662 From: richard <issue_tracker@your.tracker.email.domain.example>
1663 Reply-To: Roundup issue tracker
1664  <issue_tracker@your.tracker.email.domain.example>
1665 MIME-Version: 1.0
1666 Message-Id: <followup_dummy_id>
1667 In-Reply-To: <dummy_test_message_id>
1668 X-Roundup-Name: Roundup issue tracker
1669 X-Roundup-Loop: hello
1670 X-Roundup-Issue-Status: chatting
1671 Content-Transfer-Encoding: quoted-printable
1674 richard <richard@test.test> added the comment:
1676 This is a followup
1678 ----------
1679 status: unread -> chatting
1681 _______________________________________________________________________
1682 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1683 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1684 _______________________________________________________________________
1686 ''')
1688     def testFollowupEmptyMessage(self):
1689         self.doNewIssue()
1691         self._handle_mail('''Content-Type: text/plain;
1692   charset="iso-8859-1"
1693 From: richard <richard@test.test>
1694 To: issue_tracker@your.tracker.email.domain.example
1695 Message-Id: <followup_dummy_id>
1696 In-Reply-To: <dummy_test_message_id>
1697 Subject: [issue1] Testing... [assignedto=mary; nosy=+john]
1699 ''')
1700         l = self.db.issue.get('1', 'nosy')
1701         l.sort()
1702         self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id,
1703             self.john_id])
1705         # should be no file created (ie. no message)
1706         assert not os.path.exists(SENDMAILDEBUG)
1708     def testFollowupEmptyMessageNoSubject(self):
1709         self.doNewIssue()
1711         self._handle_mail('''Content-Type: text/plain;
1712   charset="iso-8859-1"
1713 From: richard <richard@test.test>
1714 To: issue_tracker@your.tracker.email.domain.example
1715 Message-Id: <followup_dummy_id>
1716 In-Reply-To: <dummy_test_message_id>
1717 Subject: [issue1] [assignedto=mary; nosy=+john]
1719 ''')
1720         l = self.db.issue.get('1', 'nosy')
1721         l.sort()
1722         self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id,
1723             self.john_id])
1725         # should be no file created (ie. no message)
1726         assert not os.path.exists(SENDMAILDEBUG)
1728     def testNosyRemove(self):
1729         self.doNewIssue()
1731         self._handle_mail('''Content-Type: text/plain;
1732   charset="iso-8859-1"
1733 From: richard <richard@test.test>
1734 To: issue_tracker@your.tracker.email.domain.example
1735 Message-Id: <followup_dummy_id>
1736 In-Reply-To: <dummy_test_message_id>
1737 Subject: [issue1] Testing... [nosy=-richard]
1739 ''')
1740         l = self.db.issue.get('1', 'nosy')
1741         l.sort()
1742         self.assertEqual(l, [self.chef_id])
1744         # NO NOSY MESSAGE SHOULD BE SENT!
1745         assert not os.path.exists(SENDMAILDEBUG)
1747     def testNewUserAuthor(self):
1748         self.db.commit()
1749         l = self.db.user.list()
1750         l.sort()
1751         message = '''Content-Type: text/plain;
1752   charset="iso-8859-1"
1753 From: fubar <fubar@bork.bork.bork>
1754 To: issue_tracker@your.tracker.email.domain.example
1755 Message-Id: <dummy_test_message_id>
1756 Subject: [issue] Testing...
1758 This is a test submission of a new issue.
1759 '''
1760         self.db.security.role['anonymous'].permissions=[]
1761         anonid = self.db.user.lookup('anonymous')
1762         self.db.user.set(anonid, roles='Anonymous')
1763         try:
1764             self._handle_mail(message)
1765         except Unauthorized, value:
1766             body_diff = self.compareMessages(str(value), """
1767 You are not a registered user.
1769 Unknown address: fubar@bork.bork.bork
1770 """)
1771             assert not body_diff, body_diff
1772         else:
1773             raise AssertionError, "Unathorized not raised when handling mail"
1775         # Add Web Access role to anonymous, and try again to make sure
1776         # we get a "please register at:" message this time.
1777         p = [
1778             self.db.security.getPermission('Register', 'user'),
1779             self.db.security.getPermission('Web Access', None),
1780         ]
1781         self.db.security.role['anonymous'].permissions=p
1782         try:
1783             self._handle_mail(message)
1784         except Unauthorized, value:
1785             body_diff = self.compareMessages(str(value), """
1786 You are not a registered user. Please register at:
1788 http://tracker.example/cgi-bin/roundup.cgi/bugs/user?template=register
1790 ...before sending mail to the tracker.
1792 Unknown address: fubar@bork.bork.bork
1793 """)
1794             assert not body_diff, body_diff
1795         else:
1796             raise AssertionError, "Unauthorized not raised when handling mail"
1798         # Make sure list of users is the same as before.
1799         m = self.db.user.list()
1800         m.sort()
1801         self.assertEqual(l, m)
1803         # now with the permission
1804         p = [
1805             self.db.security.getPermission('Register', 'user'),
1806             self.db.security.getPermission('Email Access', None),
1807         ]
1808         self.db.security.role['anonymous'].permissions=p
1809         self._handle_mail(message)
1810         m = self.db.user.list()
1811         m.sort()
1812         self.assertNotEqual(l, m)
1814     def testNewUserAuthorEncodedName(self):
1815         l = set(self.db.user.list())
1816         # From: name has Euro symbol in it
1817         message = '''Content-Type: text/plain;
1818   charset="iso-8859-1"
1819 From: =?utf8?b?SOKCrGxsbw==?= <fubar@bork.bork.bork>
1820 To: issue_tracker@your.tracker.email.domain.example
1821 Message-Id: <dummy_test_message_id>
1822 Subject: [issue] Testing...
1824 This is a test submission of a new issue.
1825 '''
1826         p = [
1827             self.db.security.getPermission('Register', 'user'),
1828             self.db.security.getPermission('Email Access', None),
1829             self.db.security.getPermission('Create', 'issue'),
1830             self.db.security.getPermission('Create', 'msg'),
1831         ]
1832         self.db.security.role['anonymous'].permissions = p
1833         self._handle_mail(message)
1834         m = set(self.db.user.list())
1835         new = list(m - l)[0]
1836         name = self.db.user.get(new, 'realname')
1837         self.assertEquals(name, 'H€llo')
1839     def testNewUserAuthorMixedEncodedName(self):
1840         l = set(self.db.user.list())
1841         # From: name has Euro symbol in it
1842         message = '''Content-Type: text/plain;
1843   charset="iso-8859-1"
1844 From: Firstname =?utf-8?b?w6TDtsOf?= Last <fubar@bork.bork.bork>
1845 To: issue_tracker@your.tracker.email.domain.example
1846 Message-Id: <dummy_test_message_id>
1847 Subject: [issue] Test =?utf-8?b?w4TDlsOc?= umlauts
1848  X1
1849  X2
1851 This is a test submission of a new issue.
1852 '''
1853         p = [
1854             self.db.security.getPermission('Register', 'user'),
1855             self.db.security.getPermission('Email Access', None),
1856             self.db.security.getPermission('Create', 'issue'),
1857             self.db.security.getPermission('Create', 'msg'),
1858         ]
1859         self.db.security.role['anonymous'].permissions = p
1860         self._handle_mail(message)
1861         title = self.db.issue.get('1', 'title')
1862         self.assertEquals(title, 'Test \xc3\x84\xc3\x96\xc3\x9c umlauts X1 X2')
1863         m = set(self.db.user.list())
1864         new = list(m - l)[0]
1865         name = self.db.user.get(new, 'realname')
1866         self.assertEquals(name, 'Firstname \xc3\xa4\xc3\xb6\xc3\x9f Last')
1868     def testUnknownUser(self):
1869         l = set(self.db.user.list())
1870         message = '''Content-Type: text/plain;
1871   charset="iso-8859-1"
1872 From: Nonexisting User <nonexisting@bork.bork.bork>
1873 To: issue_tracker@your.tracker.email.domain.example
1874 Message-Id: <dummy_test_message_id>
1875 Subject: [issue] Testing nonexisting user...
1877 This is a test submission of a new issue.
1878 '''
1879         # trap_exc=1: we want a bounce message:
1880         ret = self._handle_mail(message, trap_exc=1)
1881         self.compareMessages(self._get_mail(),
1882 '''FROM: Roundup issue tracker <roundup-admin@your.tracker.email.domain.example>
1883 TO: nonexisting@bork.bork.bork
1884 From nobody Tue Jul 14 12:04:11 2009
1885 Content-Type: multipart/mixed; boundary="===============0639262320=="
1886 MIME-Version: 1.0
1887 Subject: Failed issue tracker submission
1888 To: nonexisting@bork.bork.bork
1889 From: Roundup issue tracker <roundup-admin@your.tracker.email.domain.example>
1890 Date: Tue, 14 Jul 2009 12:04:11 +0000
1891 Precedence: bulk
1892 X-Roundup-Name: Roundup issue tracker
1893 X-Roundup-Loop: hello
1894 X-Roundup-Version: 1.4.8
1895 MIME-Version: 1.0
1897 --===============0639262320==
1898 Content-Type: text/plain; charset="us-ascii"
1899 MIME-Version: 1.0
1900 Content-Transfer-Encoding: 7bit
1904 You are not a registered user. Please register at:
1906 http://tracker.example/cgi-bin/roundup.cgi/bugs/user?template=register
1908 ...before sending mail to the tracker.
1910 Unknown address: nonexisting@bork.bork.bork
1912 --===============0639262320==
1913 Content-Type: text/plain; charset="us-ascii"
1914 MIME-Version: 1.0
1915 Content-Transfer-Encoding: 7bit
1917 Content-Type: text/plain;
1918   charset="iso-8859-1"
1919 From: Nonexisting User <nonexisting@bork.bork.bork>
1920 To: issue_tracker@your.tracker.email.domain.example
1921 Message-Id: <dummy_test_message_id>
1922 Subject: [issue] Testing nonexisting user...
1924 This is a test submission of a new issue.
1926 --===============0639262320==--
1927 ''')
1929     def testEnc01(self):
1930         self.db.user.set(self.mary_id,
1931             realname='\xe4\xf6\xfc\xc4\xd6\xdc\xdf, Mary'.decode
1932             ('latin-1').encode('utf-8'))
1933         self.doNewIssue()
1934         self._handle_mail('''Content-Type: text/plain;
1935   charset="iso-8859-1"
1936 From: mary <mary@test.test>
1937 To: issue_tracker@your.tracker.email.domain.example
1938 Message-Id: <followup_dummy_id>
1939 In-Reply-To: <dummy_test_message_id>
1940 Subject: [issue1] Testing...
1941 Content-Type: text/plain;
1942         charset="iso-8859-1"
1943 Content-Transfer-Encoding: quoted-printable
1945 A message with encoding (encoded oe =F6)
1947 ''')
1948         self.compareMessages(self._get_mail(),
1949 '''FROM: roundup-admin@your.tracker.email.domain.example
1950 TO: chef@bork.bork.bork, richard@test.test
1951 Content-Type: text/plain; charset="utf-8"
1952 Subject: [issue1] Testing...
1953 To: chef@bork.bork.bork, richard@test.test
1954 From: =?utf-8?b?w6TDtsO8w4TDlsOcw58sIE1hcnk=?=
1955  <issue_tracker@your.tracker.email.domain.example>
1956 Reply-To: Roundup issue tracker
1957  <issue_tracker@your.tracker.email.domain.example>
1958 MIME-Version: 1.0
1959 Message-Id: <followup_dummy_id>
1960 In-Reply-To: <dummy_test_message_id>
1961 X-Roundup-Name: Roundup issue tracker
1962 X-Roundup-Loop: hello
1963 X-Roundup-Issue-Status: chatting
1964 Content-Transfer-Encoding: quoted-printable
1967 =C3=A4=C3=B6=C3=BC=C3=84=C3=96=C3=9C=C3=9F, Mary <mary@test.test> added the=
1968  comment:
1970 A message with encoding (encoded oe =C3=B6)
1972 ----------
1973 status: unread -> chatting
1975 _______________________________________________________________________
1976 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
1977 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
1978 _______________________________________________________________________
1979 ''')
1981     def testEncNonUTF8(self):
1982         self.doNewIssue()
1983         self.instance.config.EMAIL_CHARSET = 'iso-8859-1'
1984         self._handle_mail('''Content-Type: text/plain;
1985   charset="iso-8859-1"
1986 From: mary <mary@test.test>
1987 To: issue_tracker@your.tracker.email.domain.example
1988 Message-Id: <followup_dummy_id>
1989 In-Reply-To: <dummy_test_message_id>
1990 Subject: [issue1] Testing...
1991 Content-Type: text/plain;
1992         charset="iso-8859-1"
1993 Content-Transfer-Encoding: quoted-printable
1995 A message with encoding (encoded oe =F6)
1997 ''')
1998         self.compareMessages(self._get_mail(),
1999 '''FROM: roundup-admin@your.tracker.email.domain.example
2000 TO: chef@bork.bork.bork, richard@test.test
2001 Content-Type: text/plain; charset="iso-8859-1"
2002 Subject: [issue1] Testing...
2003 To: chef@bork.bork.bork, richard@test.test
2004 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
2005 Reply-To: Roundup issue tracker
2006  <issue_tracker@your.tracker.email.domain.example>
2007 MIME-Version: 1.0
2008 Message-Id: <followup_dummy_id>
2009 In-Reply-To: <dummy_test_message_id>
2010 X-Roundup-Name: Roundup issue tracker
2011 X-Roundup-Loop: hello
2012 X-Roundup-Issue-Status: chatting
2013 Content-Transfer-Encoding: quoted-printable
2016 Contrary, Mary <mary@test.test> added the comment:
2018 A message with encoding (encoded oe =F6)
2020 ----------
2021 status: unread -> chatting
2023 _______________________________________________________________________
2024 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
2025 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
2026 _______________________________________________________________________
2027 ''')
2030     def testMultipartEnc01(self):
2031         self.doNewIssue()
2032         self._handle_mail('''Content-Type: text/plain;
2033   charset="iso-8859-1"
2034 From: mary <mary@test.test>
2035 To: issue_tracker@your.tracker.email.domain.example
2036 Message-Id: <followup_dummy_id>
2037 In-Reply-To: <dummy_test_message_id>
2038 Subject: [issue1] Testing...
2039 Content-Type: multipart/mixed;
2040         boundary="----_=_NextPart_000_01"
2042 This message is in MIME format. Since your mail reader does not understand
2043 this format, some or all of this message may not be legible.
2045 ------_=_NextPart_000_01
2046 Content-Type: text/plain;
2047         charset="iso-8859-1"
2048 Content-Transfer-Encoding: quoted-printable
2050 A message with first part encoded (encoded oe =F6)
2052 ''')
2053         self.compareMessages(self._get_mail(),
2054 '''FROM: roundup-admin@your.tracker.email.domain.example
2055 TO: chef@bork.bork.bork, richard@test.test
2056 Content-Type: text/plain; charset="utf-8"
2057 Subject: [issue1] Testing...
2058 To: chef@bork.bork.bork, richard@test.test
2059 From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
2060 Reply-To: Roundup issue tracker
2061  <issue_tracker@your.tracker.email.domain.example>
2062 MIME-Version: 1.0
2063 Message-Id: <followup_dummy_id>
2064 In-Reply-To: <dummy_test_message_id>
2065 X-Roundup-Name: Roundup issue tracker
2066 X-Roundup-Loop: hello
2067 X-Roundup-Issue-Status: chatting
2068 Content-Transfer-Encoding: quoted-printable
2071 Contrary, Mary <mary@test.test> added the comment:
2073 A message with first part encoded (encoded oe =C3=B6)
2075 ----------
2076 status: unread -> chatting
2078 _______________________________________________________________________
2079 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
2080 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
2081 _______________________________________________________________________
2082 ''')
2084     def testContentDisposition(self):
2085         self.doNewIssue()
2086         self._handle_mail('''Content-Type: text/plain;
2087   charset="iso-8859-1"
2088 From: mary <mary@test.test>
2089 To: issue_tracker@your.tracker.email.domain.example
2090 Message-Id: <followup_dummy_id>
2091 In-Reply-To: <dummy_test_message_id>
2092 Subject: [issue1] Testing...
2093 Content-Type: multipart/mixed; boundary="bCsyhTFzCvuiizWE"
2094 Content-Disposition: inline
2097 --bCsyhTFzCvuiizWE
2098 Content-Type: text/plain; charset=us-ascii
2099 Content-Disposition: inline
2101 test attachment binary
2103 --bCsyhTFzCvuiizWE
2104 Content-Type: application/octet-stream
2105 Content-Disposition: attachment; filename="main.dvi"
2106 Content-Transfer-Encoding: base64
2108 SnVzdCBhIHRlc3QgAQo=
2110 --bCsyhTFzCvuiizWE--
2111 ''')
2112         messages = self.db.issue.get('1', 'messages')
2113         messages.sort()
2114         file = self.db.file.getnode (self.db.msg.get(messages[-1], 'files')[0])
2115         self.assertEqual(file.name, 'main.dvi')
2116         self.assertEqual(file.content, 'Just a test \001\n')
2118     def testFollowupStupidQuoting(self):
2119         self.doNewIssue()
2121         self._handle_mail('''Content-Type: text/plain;
2122   charset="iso-8859-1"
2123 From: richard <richard@test.test>
2124 To: issue_tracker@your.tracker.email.domain.example
2125 Message-Id: <followup_dummy_id>
2126 In-Reply-To: <dummy_test_message_id>
2127 Subject: Re: "[issue1] Testing... "
2129 This is a followup
2130 ''')
2131         self.compareMessages(self._get_mail(),
2132 '''FROM: roundup-admin@your.tracker.email.domain.example
2133 TO: chef@bork.bork.bork
2134 Content-Type: text/plain; charset="utf-8"
2135 Subject: [issue1] Testing...
2136 To: chef@bork.bork.bork
2137 From: richard <issue_tracker@your.tracker.email.domain.example>
2138 Reply-To: Roundup issue tracker
2139  <issue_tracker@your.tracker.email.domain.example>
2140 MIME-Version: 1.0
2141 Message-Id: <followup_dummy_id>
2142 In-Reply-To: <dummy_test_message_id>
2143 X-Roundup-Name: Roundup issue tracker
2144 X-Roundup-Loop: hello
2145 X-Roundup-Issue-Status: chatting
2146 Content-Transfer-Encoding: quoted-printable
2149 richard <richard@test.test> added the comment:
2151 This is a followup
2153 ----------
2154 status: unread -> chatting
2156 _______________________________________________________________________
2157 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
2158 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
2159 _______________________________________________________________________
2160 ''')
2162     def testEmailQuoting(self):
2163         self.instance.config.EMAIL_KEEP_QUOTED_TEXT = 'no'
2164         self.innerTestQuoting('''This is a followup
2165 ''')
2167     def testEmailQuotingRemove(self):
2168         self.instance.config.EMAIL_KEEP_QUOTED_TEXT = 'yes'
2169         self.innerTestQuoting('''Blah blah wrote:
2170 > Blah bklaskdfj sdf asdf jlaskdf skj sdkfjl asdf
2171 >  skdjlkjsdfalsdkfjasdlfkj dlfksdfalksd fj
2174 This is a followup
2175 ''')
2177     def innerTestQuoting(self, expect):
2178         nodeid = self.doNewIssue()
2180         messages = self.db.issue.get(nodeid, 'messages')
2182         self._handle_mail('''Content-Type: text/plain;
2183   charset="iso-8859-1"
2184 From: richard <richard@test.test>
2185 To: issue_tracker@your.tracker.email.domain.example
2186 Message-Id: <followup_dummy_id>
2187 In-Reply-To: <dummy_test_message_id>
2188 Subject: Re: [issue1] Testing...
2190 Blah blah wrote:
2191 > Blah bklaskdfj sdf asdf jlaskdf skj sdkfjl asdf
2192 >  skdjlkjsdfalsdkfjasdlfkj dlfksdfalksd fj
2195 This is a followup
2196 ''')
2197         # figure the new message id
2198         newmessages = self.db.issue.get(nodeid, 'messages')
2199         for msg in messages:
2200             newmessages.remove(msg)
2201         messageid = newmessages[0]
2203         self.compareMessages(self.db.msg.get(messageid, 'content'), expect)
2205     def testUserLookup(self):
2206         i = self.db.user.create(username='user1', address='user1@foo.com')
2207         self.assertEqual(uidFromAddress(self.db, ('', 'user1@foo.com'), 0), i)
2208         self.assertEqual(uidFromAddress(self.db, ('', 'USER1@foo.com'), 0), i)
2209         i = self.db.user.create(username='user2', address='USER2@foo.com')
2210         self.assertEqual(uidFromAddress(self.db, ('', 'USER2@foo.com'), 0), i)
2211         self.assertEqual(uidFromAddress(self.db, ('', 'user2@foo.com'), 0), i)
2213     def testUserAlternateLookup(self):
2214         i = self.db.user.create(username='user1', address='user1@foo.com',
2215                                 alternate_addresses='user1@bar.com')
2216         self.assertEqual(uidFromAddress(self.db, ('', 'user1@bar.com'), 0), i)
2217         self.assertEqual(uidFromAddress(self.db, ('', 'USER1@bar.com'), 0), i)
2219     def testUserAlternateSubstringNomatch(self):
2220         i = self.db.user.create(username='user1', address='user1@foo.com',
2221                                 alternate_addresses='x-user1@bar.com')
2222         self.assertEqual(uidFromAddress(self.db, ('', 'user1@bar.com'), 0), 0)
2223         self.assertEqual(uidFromAddress(self.db, ('', 'USER1@bar.com'), 0), 0)
2225     def testUserCreate(self):
2226         i = uidFromAddress(self.db, ('', 'user@foo.com'), 1)
2227         self.assertNotEqual(uidFromAddress(self.db, ('', 'user@bar.com'), 1), i)
2229     def testRFC2822(self):
2230         ascii_header = "[issue243] This is a \"test\" - with 'quotation' marks"
2231         unicode_header = '[issue244] \xd0\xb0\xd0\xbd\xd0\xb4\xd1\x80\xd0\xb5\xd0\xb9'
2232         unicode_encoded = '=?utf-8?q?[issue244]_=D0=B0=D0=BD=D0=B4=D1=80=D0=B5=D0=B9?='
2233         self.assertEqual(rfc2822.encode_header(ascii_header), ascii_header)
2234         self.assertEqual(rfc2822.encode_header(unicode_header), unicode_encoded)
2236     def testRegistrationConfirmation(self):
2237         otk = "Aj4euk4LZSAdwePohj90SME5SpopLETL"
2238         self.db.getOTKManager().set(otk, username='johannes')
2239         self._handle_mail('''Content-Type: text/plain;
2240   charset="iso-8859-1"
2241 From: Chef <chef@bork.bork.bork>
2242 To: issue_tracker@your.tracker.email.domain.example
2243 Cc: richard@test.test
2244 Message-Id: <dummy_test_message_id>
2245 Subject: Re: Complete your registration to Roundup issue tracker
2246  -- key %s
2248 This is a test confirmation of registration.
2249 ''' % otk)
2250         self.db.user.lookup('johannes')
2252     def testFollowupOnNonIssue(self):
2253         self.db.keyword.create(name='Foo')
2254         self._handle_mail('''Content-Type: text/plain;
2255   charset="iso-8859-1"
2256 From: richard <richard@test.test>
2257 To: issue_tracker@your.tracker.email.domain.example
2258 Message-Id: <followup_dummy_id>
2259 In-Reply-To: <dummy_test_message_id>
2260 Subject: [keyword1] Testing... [name=Bar]
2262 ''')
2263         self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
2265     def testResentFrom(self):
2266         nodeid = self._handle_mail('''Content-Type: text/plain;
2267   charset="iso-8859-1"
2268 From: Chef <chef@bork.bork.bork>
2269 Resent-From: mary <mary@test.test>
2270 To: issue_tracker@your.tracker.email.domain.example
2271 Cc: richard@test.test
2272 Message-Id: <dummy_test_message_id>
2273 Subject: [issue] Testing...
2275 This is a test submission of a new issue.
2276 ''')
2277         assert not os.path.exists(SENDMAILDEBUG)
2278         l = self.db.issue.get(nodeid, 'nosy')
2279         l.sort()
2280         self.assertEqual(l, [self.richard_id, self.mary_id])
2281         return nodeid
2283     def testDejaVu(self):
2284         self.assertRaises(IgnoreLoop, self._handle_mail,
2285             '''Content-Type: text/plain;
2286   charset="iso-8859-1"
2287 From: Chef <chef@bork.bork.bork>
2288 X-Roundup-Loop: hello
2289 To: issue_tracker@your.tracker.email.domain.example
2290 Cc: richard@test.test
2291 Message-Id: <dummy_test_message_id>
2292 Subject: Re: [issue] Testing...
2294 Hi, I've been mis-configured to loop messages back to myself.
2295 ''')
2297     def testItsBulkStupid(self):
2298         self.assertRaises(IgnoreBulk, self._handle_mail,
2299             '''Content-Type: text/plain;
2300   charset="iso-8859-1"
2301 From: Chef <chef@bork.bork.bork>
2302 Precedence: bulk
2303 To: issue_tracker@your.tracker.email.domain.example
2304 Cc: richard@test.test
2305 Message-Id: <dummy_test_message_id>
2306 Subject: Re: [issue] Testing...
2308 Hi, I'm on holidays, and this is a dumb auto-responder.
2309 ''')
2311     def testAutoReplyEmailsAreIgnored(self):
2312         self.assertRaises(IgnoreBulk, self._handle_mail,
2313             '''Content-Type: text/plain;
2314   charset="iso-8859-1"
2315 From: Chef <chef@bork.bork.bork>
2316 To: issue_tracker@your.tracker.email.domain.example
2317 Cc: richard@test.test
2318 Message-Id: <dummy_test_message_id>
2319 Subject: Re: [issue] Out of office AutoReply: Back next week
2321 Hi, I am back in the office next week
2322 ''')
2324     def testNoSubject(self):
2325         self.assertRaises(MailUsageError, self._handle_mail,
2326             '''Content-Type: text/plain;
2327   charset="iso-8859-1"
2328 From: Chef <chef@bork.bork.bork>
2329 To: issue_tracker@your.tracker.email.domain.example
2330 Cc: richard@test.test
2331 Reply-To: chef@bork.bork.bork
2332 Message-Id: <dummy_test_message_id>
2334 ''')
2336     #
2337     # TEST FOR INVALID DESIGNATOR HANDLING
2338     #
2339     def testInvalidDesignator(self):
2340         self.assertRaises(MailUsageError, self._handle_mail,
2341             '''Content-Type: text/plain;
2342   charset="iso-8859-1"
2343 From: Chef <chef@bork.bork.bork>
2344 To: issue_tracker@your.tracker.email.domain.example
2345 Subject: [frobulated] testing
2346 Cc: richard@test.test
2347 Reply-To: chef@bork.bork.bork
2348 Message-Id: <dummy_test_message_id>
2350 ''')
2351         self.assertRaises(MailUsageError, self._handle_mail,
2352             '''Content-Type: text/plain;
2353   charset="iso-8859-1"
2354 From: Chef <chef@bork.bork.bork>
2355 To: issue_tracker@your.tracker.email.domain.example
2356 Subject: [issue12345] testing
2357 Cc: richard@test.test
2358 Reply-To: chef@bork.bork.bork
2359 Message-Id: <dummy_test_message_id>
2361 ''')
2363     def testInvalidClassLoose(self):
2364         self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
2365         nodeid = self._handle_mail('''Content-Type: text/plain;
2366   charset="iso-8859-1"
2367 From: Chef <chef@bork.bork.bork>
2368 To: issue_tracker@your.tracker.email.domain.example
2369 Subject: [frobulated] testing
2370 Cc: richard@test.test
2371 Reply-To: chef@bork.bork.bork
2372 Message-Id: <dummy_test_message_id>
2374 ''')
2375         assert not os.path.exists(SENDMAILDEBUG)
2376         self.assertEqual(self.db.issue.get(nodeid, 'title'),
2377             '[frobulated] testing')
2379     def testInvalidClassLooseReply(self):
2380         self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
2381         nodeid = self._handle_mail('''Content-Type: text/plain;
2382   charset="iso-8859-1"
2383 From: Chef <chef@bork.bork.bork>
2384 To: issue_tracker@your.tracker.email.domain.example
2385 Subject: Re: [frobulated] testing
2386 Cc: richard@test.test
2387 Reply-To: chef@bork.bork.bork
2388 Message-Id: <dummy_test_message_id>
2390 ''')
2391         assert not os.path.exists(SENDMAILDEBUG)
2392         self.assertEqual(self.db.issue.get(nodeid, 'title'),
2393             '[frobulated] testing')
2395     def testInvalidClassLoose(self):
2396         self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
2397         nodeid = self._handle_mail('''Content-Type: text/plain;
2398   charset="iso-8859-1"
2399 From: Chef <chef@bork.bork.bork>
2400 To: issue_tracker@your.tracker.email.domain.example
2401 Subject: [issue1234] testing
2402 Cc: richard@test.test
2403 Reply-To: chef@bork.bork.bork
2404 Message-Id: <dummy_test_message_id>
2406 ''')
2407         assert not os.path.exists(SENDMAILDEBUG)
2408         self.assertEqual(self.db.issue.get(nodeid, 'title'),
2409             '[issue1234] testing')
2411     def testClassLooseOK(self):
2412         self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
2413         self.db.keyword.create(name='Foo')
2414         nodeid = self._handle_mail('''Content-Type: text/plain;
2415   charset="iso-8859-1"
2416 From: Chef <chef@bork.bork.bork>
2417 To: issue_tracker@your.tracker.email.domain.example
2418 Subject: [keyword1] Testing... [name=Bar]
2419 Cc: richard@test.test
2420 Reply-To: chef@bork.bork.bork
2421 Message-Id: <dummy_test_message_id>
2423 ''')
2424         assert not os.path.exists(SENDMAILDEBUG)
2425         self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
2427     def testClassStrictInvalid(self):
2428         self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'strict'
2429         self.instance.config.MAILGW_DEFAULT_CLASS = ''
2431         message = '''Content-Type: text/plain;
2432   charset="iso-8859-1"
2433 From: Chef <chef@bork.bork.bork>
2434 To: issue_tracker@your.tracker.email.domain.example
2435 Subject: Testing...
2436 Cc: richard@test.test
2437 Reply-To: chef@bork.bork.bork
2438 Message-Id: <dummy_test_message_id>
2440 '''
2441         self.assertRaises(MailUsageError, self._handle_mail, message)
2443     def testClassStrictValid(self):
2444         self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'strict'
2445         self.instance.config.MAILGW_DEFAULT_CLASS = ''
2447         nodeid = self._handle_mail('''Content-Type: text/plain;
2448   charset="iso-8859-1"
2449 From: Chef <chef@bork.bork.bork>
2450 To: issue_tracker@your.tracker.email.domain.example
2451 Subject: [issue] Testing...
2452 Cc: richard@test.test
2453 Reply-To: chef@bork.bork.bork
2454 Message-Id: <dummy_test_message_id>
2456 ''')
2458         assert not os.path.exists(SENDMAILDEBUG)
2459         self.assertEqual(self.db.issue.get(nodeid, 'title'), 'Testing...')
2461     #
2462     # TEST FOR INVALID COMMANDS HANDLING
2463     #
2464     def testInvalidCommands(self):
2465         self.assertRaises(MailUsageError, self._handle_mail,
2466             '''Content-Type: text/plain;
2467   charset="iso-8859-1"
2468 From: Chef <chef@bork.bork.bork>
2469 To: issue_tracker@your.tracker.email.domain.example
2470 Subject: testing [frobulated]
2471 Cc: richard@test.test
2472 Reply-To: chef@bork.bork.bork
2473 Message-Id: <dummy_test_message_id>
2475 ''')
2477     def testInvalidCommandPassthrough(self):
2478         self.instance.config.MAILGW_SUBJECT_SUFFIX_PARSING = 'none'
2479         nodeid = self._handle_mail('''Content-Type: text/plain;
2480   charset="iso-8859-1"
2481 From: Chef <chef@bork.bork.bork>
2482 To: issue_tracker@your.tracker.email.domain.example
2483 Subject: testing [frobulated]
2484 Cc: richard@test.test
2485 Reply-To: chef@bork.bork.bork
2486 Message-Id: <dummy_test_message_id>
2488 ''')
2489         assert not os.path.exists(SENDMAILDEBUG)
2490         self.assertEqual(self.db.issue.get(nodeid, 'title'),
2491             'testing [frobulated]')
2493     def testInvalidCommandPassthroughLoose(self):
2494         self.instance.config.MAILGW_SUBJECT_SUFFIX_PARSING = 'loose'
2495         nodeid = self._handle_mail('''Content-Type: text/plain;
2496   charset="iso-8859-1"
2497 From: Chef <chef@bork.bork.bork>
2498 To: issue_tracker@your.tracker.email.domain.example
2499 Subject: testing [frobulated]
2500 Cc: richard@test.test
2501 Reply-To: chef@bork.bork.bork
2502 Message-Id: <dummy_test_message_id>
2504 ''')
2505         assert not os.path.exists(SENDMAILDEBUG)
2506         self.assertEqual(self.db.issue.get(nodeid, 'title'),
2507             'testing [frobulated]')
2509     def testInvalidCommandPassthroughLooseOK(self):
2510         self.instance.config.MAILGW_SUBJECT_SUFFIX_PARSING = 'loose'
2511         nodeid = self._handle_mail('''Content-Type: text/plain;
2512   charset="iso-8859-1"
2513 From: Chef <chef@bork.bork.bork>
2514 To: issue_tracker@your.tracker.email.domain.example
2515 Subject: testing [assignedto=mary]
2516 Cc: richard@test.test
2517 Reply-To: chef@bork.bork.bork
2518 Message-Id: <dummy_test_message_id>
2520 ''')
2521         assert not os.path.exists(SENDMAILDEBUG)
2522         self.assertEqual(self.db.issue.get(nodeid, 'title'), 'testing')
2523         self.assertEqual(self.db.issue.get(nodeid, 'assignedto'), self.mary_id)
2525     def testCommandDelimiters(self):
2526         self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '{}'
2527         nodeid = self._handle_mail('''Content-Type: text/plain;
2528   charset="iso-8859-1"
2529 From: Chef <chef@bork.bork.bork>
2530 To: issue_tracker@your.tracker.email.domain.example
2531 Subject: testing {assignedto=mary}
2532 Cc: richard@test.test
2533 Reply-To: chef@bork.bork.bork
2534 Message-Id: <dummy_test_message_id>
2536 ''')
2537         assert not os.path.exists(SENDMAILDEBUG)
2538         self.assertEqual(self.db.issue.get(nodeid, 'title'), 'testing')
2539         self.assertEqual(self.db.issue.get(nodeid, 'assignedto'), self.mary_id)
2541     def testPrefixDelimiters(self):
2542         self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '{}'
2543         self.db.keyword.create(name='Foo')
2544         self._handle_mail('''Content-Type: text/plain;
2545   charset="iso-8859-1"
2546 From: richard <richard@test.test>
2547 To: issue_tracker@your.tracker.email.domain.example
2548 Message-Id: <followup_dummy_id>
2549 In-Reply-To: <dummy_test_message_id>
2550 Subject: {keyword1} Testing... {name=Bar}
2552 ''')
2553         assert not os.path.exists(SENDMAILDEBUG)
2554         self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
2556     def testCommandDelimitersIgnore(self):
2557         self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '{}'
2558         nodeid = self._handle_mail('''Content-Type: text/plain;
2559   charset="iso-8859-1"
2560 From: Chef <chef@bork.bork.bork>
2561 To: issue_tracker@your.tracker.email.domain.example
2562 Subject: testing [assignedto=mary]
2563 Cc: richard@test.test
2564 Reply-To: chef@bork.bork.bork
2565 Message-Id: <dummy_test_message_id>
2567 ''')
2568         assert not os.path.exists(SENDMAILDEBUG)
2569         self.assertEqual(self.db.issue.get(nodeid, 'title'),
2570             'testing [assignedto=mary]')
2571         self.assertEqual(self.db.issue.get(nodeid, 'assignedto'), None)
2573     def testReplytoMatch(self):
2574         self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose'
2575         nodeid = self.doNewIssue()
2576         nodeid2 = self._handle_mail('''Content-Type: text/plain;
2577   charset="iso-8859-1"
2578 From: Chef <chef@bork.bork.bork>
2579 To: issue_tracker@your.tracker.email.domain.example
2580 Message-Id: <dummy_test_message_id2>
2581 In-Reply-To: <dummy_test_message_id>
2582 Subject: Testing...
2584 Followup message.
2585 ''')
2587         nodeid3 = self._handle_mail('''Content-Type: text/plain;
2588   charset="iso-8859-1"
2589 From: Chef <chef@bork.bork.bork>
2590 To: issue_tracker@your.tracker.email.domain.example
2591 Message-Id: <dummy_test_message_id3>
2592 In-Reply-To: <dummy_test_message_id2>
2593 Subject: Testing...
2595 Yet another message in the same thread/issue.
2596 ''')
2598         self.assertEqual(nodeid, nodeid2)
2599         self.assertEqual(nodeid, nodeid3)
2601     def testHelpSubject(self):
2602         message = '''Content-Type: text/plain;
2603   charset="iso-8859-1"
2604 From: Chef <chef@bork.bork.bork>
2605 To: issue_tracker@your.tracker.email.domain.example
2606 Message-Id: <dummy_test_message_id2>
2607 In-Reply-To: <dummy_test_message_id>
2608 Subject: hElp
2611 '''
2612         self.assertRaises(MailUsageHelp, self._handle_mail, message)
2614     def testMaillistSubject(self):
2615         self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '[]'
2616         self.db.keyword.create(name='Foo')
2617         self._handle_mail('''Content-Type: text/plain;
2618   charset="iso-8859-1"
2619 From: Chef <chef@bork.bork.bork>
2620 To: issue_tracker@your.tracker.email.domain.example
2621 Subject: [mailinglist-name] [keyword1] Testing.. [name=Bar]
2622 Cc: richard@test.test
2623 Reply-To: chef@bork.bork.bork
2624 Message-Id: <dummy_test_message_id>
2626 ''')
2628         assert not os.path.exists(SENDMAILDEBUG)
2629         self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
2631     def testUnknownPrefixSubject(self):
2632         self.db.keyword.create(name='Foo')
2633         self._handle_mail('''Content-Type: text/plain;
2634   charset="iso-8859-1"
2635 From: Chef <chef@bork.bork.bork>
2636 To: issue_tracker@your.tracker.email.domain.example
2637 Subject: VeryStrangeRe: [keyword1] Testing.. [name=Bar]
2638 Cc: richard@test.test
2639 Reply-To: chef@bork.bork.bork
2640 Message-Id: <dummy_test_message_id>
2642 ''')
2644         assert not os.path.exists(SENDMAILDEBUG)
2645         self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar')
2647     def testOneCharSubject(self):
2648         message = '''Content-Type: text/plain;
2649   charset="iso-8859-1"
2650 From: Chef <chef@bork.bork.bork>
2651 To: issue_tracker@your.tracker.email.domain.example
2652 Subject: b
2653 Cc: richard@test.test
2654 Reply-To: chef@bork.bork.bork
2655 Message-Id: <dummy_test_message_id>
2657 '''
2658         try:
2659             self._handle_mail(message)
2660         except MailUsageError:
2661             self.fail('MailUsageError raised')
2663     def testIssueidLast(self):
2664         nodeid1 = self.doNewIssue()
2665         nodeid2 = self._handle_mail('''Content-Type: text/plain;
2666   charset="iso-8859-1"
2667 From: mary <mary@test.test>
2668 To: issue_tracker@your.tracker.email.domain.example
2669 Message-Id: <followup_dummy_id>
2670 In-Reply-To: <dummy_test_message_id>
2671 Subject: New title [issue1]
2673 This is a second followup
2674 ''')
2676         assert nodeid1 == nodeid2
2677         self.assertEqual(self.db.issue.get(nodeid2, 'title'), "Testing...")
2679     def testSecurityMessagePermissionContent(self):
2680         id = self.doNewIssue()
2681         issue = self.db.issue.getnode (id)
2682         self.db.security.addRole(name='Nomsg')
2683         self.db.security.addPermissionToRole('Nomsg', 'Email Access')
2684         for cl in 'issue', 'file', 'keyword':
2685             for p in 'View', 'Edit', 'Create':
2686                 self.db.security.addPermissionToRole('Nomsg', p, cl)
2687         self.db.user.set(self.mary_id, roles='Nomsg')
2688         nodeid = self._handle_mail('''Content-Type: text/plain;
2689   charset="iso-8859-1"
2690 From: Chef <chef@bork.bork.bork>
2691 To: issue_tracker@your.tracker.email.domain.example
2692 Message-Id: <dummy_test_message_id_2>
2693 Subject: [issue%(id)s] Testing... [nosy=+mary]
2695 Just a test reply
2696 '''%locals())
2697         assert os.path.exists(SENDMAILDEBUG)
2698         self.compareMessages(self._get_mail(),
2699 '''FROM: roundup-admin@your.tracker.email.domain.example
2700 TO: chef@bork.bork.bork, richard@test.test
2701 Content-Type: text/plain; charset="utf-8"
2702 Subject: [issue1] Testing...
2703 To: richard@test.test
2704 From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
2705 Reply-To: Roundup issue tracker
2706  <issue_tracker@your.tracker.email.domain.example>
2707 MIME-Version: 1.0
2708 Message-Id: <dummy_test_message_id_2>
2709 In-Reply-To: <dummy_test_message_id>
2710 X-Roundup-Name: Roundup issue tracker
2711 X-Roundup-Loop: hello
2712 X-Roundup-Issue-Status: chatting
2713 Content-Transfer-Encoding: quoted-printable
2716 Bork, Chef <chef@bork.bork.bork> added the comment:
2718 Just a test reply
2720 ----------
2721 nosy: +mary
2722 status: unread -> chatting
2724 _______________________________________________________________________
2725 Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
2726 <http://tracker.example/cgi-bin/roundup.cgi/bugs/issue1>
2727 _______________________________________________________________________
2728 ''')
2730     def testOutlookAttachment(self):
2731         message = '''X-MimeOLE: Produced By Microsoft Exchange V6.5
2732 Content-class: urn:content-classes:message
2733 MIME-Version: 1.0
2734 Content-Type: multipart/mixed;
2735         boundary="----_=_NextPart_001_01CACA65.40A51CBC"
2736 Subject: Example of a failed outlook attachment e-mail
2737 Date: Tue, 23 Mar 2010 01:43:44 -0700
2738 Message-ID: <CA37F17219784343816CA6613D2E339205E7D0F9@nrcwstexb1.nrc.ca>
2739 X-MS-Has-Attach: yes
2740 X-MS-TNEF-Correlator: 
2741 Thread-Topic: Example of a failed outlook attachment e-mail
2742 Thread-Index: AcrKJo/t3pUBBwTpSwWNE3LE67UBDQ==
2743 From: "Hugh" <richard@test.test>
2744 To: <richard@test.test>
2745 X-OriginalArrivalTime: 23 Mar 2010 08:45:57.0350 (UTC) FILETIME=[41893860:01CACA65]
2747 This is a multi-part message in MIME format.
2749 ------_=_NextPart_001_01CACA65.40A51CBC
2750 Content-Type: multipart/alternative;
2751         boundary="----_=_NextPart_002_01CACA65.40A51CBC"
2754 ------_=_NextPart_002_01CACA65.40A51CBC
2755 Content-Type: text/plain;
2756         charset="us-ascii"
2757 Content-Transfer-Encoding: quoted-printable
2760 Hi Richard,
2762 I suppose this isn't the exact message that was sent but is a resend of
2763 one of my trial messages that failed.  For your benefit I changed the
2764 subject line and am adding these words to the message body.  Should
2765 still be as problematic, but if you like I can resend an exact copy of a
2766 failed message changing nothing except putting your address instead of
2767 our tracker.
2769 Thanks very much for taking time to look into this.  Much appreciated.
2771  <<battery backup>>=20
2773 ------_=_NextPart_002_01CACA65.40A51CBC
2774 Content-Type: text/html;
2775         charset="us-ascii"
2776 Content-Transfer-Encoding: quoted-printable
2778 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
2779 <HTML>
2780 <HEAD>
2781 <META HTTP-EQUIV=3D"Content-Type" CONTENT=3D"text/html; =
2782 charset=3Dus-ascii">
2783 <META NAME=3D"Generator" CONTENT=3D"MS Exchange Server version =
2784 6.5.7654.12">
2785 <TITLE>Example of a failed outlook attachment e-mail</TITLE>
2786 </HEAD>
2787 <BODY>
2788 <!-- Converted from text/rtf format -->
2789 <BR>
2791 <P><FONT SIZE=3D2 FACE=3D"Arial">Hi Richard,</FONT>
2792 </P>
2794 <P><FONT SIZE=3D2 FACE=3D"Arial">I suppose this isn't the exact message =
2795 that was sent but is a resend of one of my trial messages that =
2796 failed.&nbsp; For your benefit I changed the subject line and am adding =
2797 these words to the message body.&nbsp; Should still be as problematic, =
2798 but if you like I can resend an exact copy of a failed message changing =
2799 nothing except putting your address instead of our tracker.</FONT></P>
2801 <P><FONT SIZE=3D2 FACE=3D"Arial">Thanks very much for taking time to =
2802 look into this.&nbsp; Much appreciated.</FONT>
2803 </P>
2804 <BR>
2806 <P><FONT FACE=3D"Arial" SIZE=3D2 COLOR=3D"#000000"> &lt;&lt;battery =
2807 backup&gt;&gt; </FONT>
2808 </P>
2810 </BODY>
2811 </HTML>
2812 ------_=_NextPart_002_01CACA65.40A51CBC--
2814 ------_=_NextPart_001_01CACA65.40A51CBC
2815 Content-Type: message/rfc822
2816 Content-Transfer-Encoding: 7bit
2818 X-MimeOLE: Produced By Microsoft Exchange V6.5
2819 MIME-Version: 1.0
2820 Content-Type: multipart/alternative;
2821         boundary="----_=_NextPart_003_01CAC15A.29717800"
2822 X-OriginalArrivalTime: 11 Mar 2010 20:33:51.0249 (UTC) FILETIME=[28FEE010:01CAC15A]
2823 Content-class: urn:content-classes:message
2824 Subject: battery backup
2825 Date: Thu, 11 Mar 2010 13:33:43 -0700
2826 Message-ID: <p06240809c7bf02f9624c@[128.114.22.203]>
2827 X-MS-Has-Attach: 
2828 X-MS-TNEF-Correlator: 
2829 Thread-Topic: battery backup
2830 Thread-Index: AcrBWimtulTrSvBdQ2CcfZ8lyQdxmQ==
2831 From: "Jerry" <jerry@test.test>
2832 To: "Hugh" <hugh@test.test>
2834 This is a multi-part message in MIME format.
2836 ------_=_NextPart_003_01CAC15A.29717800
2837 Content-Type: text/plain;
2838         charset="iso-8859-1"
2839 Content-Transfer-Encoding: quoted-printable
2841 Dear Hugh,
2842         A car batter has an energy capacity of ~ 500Wh.  A UPS=20
2843 battery is worse than this.
2845 if we need to provied 100kW for 30 minutes that will take 100 car=20
2846 batteries.  This seems like an awful lot of batteries.
2848 Of course I like your idea of making the time 1 minute, so we get to=20
2849 a more modest number of batteries
2851 Jerry
2854 ------_=_NextPart_003_01CAC15A.29717800
2855 Content-Type: text/html;
2856         charset="iso-8859-1"
2857 Content-Transfer-Encoding: quoted-printable
2859 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
2860 <HTML>
2861 <HEAD>
2862 <META HTTP-EQUIV=3D"Content-Type" CONTENT=3D"text/html; =
2863 charset=3Diso-8859-1">
2864 <META NAME=3D"Generator" CONTENT=3D"MS Exchange Server version =
2865 6.5.7654.12">
2866 <TITLE>battery backup</TITLE>
2867 </HEAD>
2868 <BODY>
2869 <!-- Converted from text/plain format -->
2871 <P><FONT SIZE=3D2>Dear Hugh,</FONT>
2873 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <FONT SIZE=3D2>A car =
2874 batter has an energy capacity of ~ 500Wh.&nbsp; A UPS </FONT>
2876 <BR><FONT SIZE=3D2>battery is worse than this.</FONT>
2877 </P>
2879 <P><FONT SIZE=3D2>if we need to provied 100kW for 30 minutes that will =
2880 take 100 car </FONT>
2882 <BR><FONT SIZE=3D2>batteries.&nbsp; This seems like an awful lot of =
2883 batteries.</FONT>
2884 </P>
2886 <P><FONT SIZE=3D2>Of course I like your idea of making the time 1 =
2887 minute, so we get to </FONT>
2889 <BR><FONT SIZE=3D2>a more modest number of batteries</FONT>
2890 </P>
2892 <P><FONT SIZE=3D2>Jerry</FONT>
2893 </P>
2895 </BODY>
2896 </HTML>
2897 ------_=_NextPart_003_01CAC15A.29717800--
2899 ------_=_NextPart_001_01CACA65.40A51CBC--
2900 '''
2901         nodeid = self._handle_mail(message)
2902         assert not os.path.exists(SENDMAILDEBUG)
2903         msgid = self.db.issue.get(nodeid, 'messages')[0]
2904         self.assert_(self.db.msg.get(msgid, 'content').startswith('Hi Richard'))
2905         self.assertEqual(self.db.msg.get(msgid, 'files'), ['1', '2'])
2906         fileid = self.db.msg.get(msgid, 'files')[0]
2907         self.assertEqual(self.db.file.get(fileid, 'type'), 'text/html')
2908         fileid = self.db.msg.get(msgid, 'files')[1]
2909         self.assertEqual(self.db.file.get(fileid, 'type'), 'message/rfc822')
2911     def testForwardedMessageAttachment(self):
2912         message = '''Return-Path: <rgg@test.test>
2913 Received: from localhost(127.0.0.1), claiming to be "[115.130.26.69]"
2914 via SMTP by localhost, id smtpdAAApLaWrq; Tue Apr 13 23:10:05 2010
2915 Message-ID: <4BC4F9C7.50409@test.test>
2916 Date: Wed, 14 Apr 2010 09:09:59 +1000
2917 From: Rupert Goldie <rgg@test.test>
2918 User-Agent: Thunderbird 2.0.0.24 (Windows/20100228)
2919 MIME-Version: 1.0
2920 To: ekit issues <issues@test.test>
2921 Subject: [Fwd: PHP ERROR (fb)] post limit reached
2922 Content-Type: multipart/mixed; boundary="------------000807090608060304010403"
2924 This is a multi-part message in MIME format.
2925 --------------000807090608060304010403
2926 Content-Type: text/plain; charset=ISO-8859-1; format=flowed
2927 Content-Transfer-Encoding: 7bit
2929 Catch this exception and log it without emailing.
2931 --------------000807090608060304010403
2932 Content-Type: message/rfc822; name="PHP ERROR (fb).eml"
2933 Content-Transfer-Encoding: 7bit
2934 Content-Disposition: inline; filename="PHP ERROR (fb).eml"
2936 Return-Path: <ektravj@test.test>
2937 X-Sieve: CMU Sieve 2.2
2938 via SMTP by crown.off.ekorp.com, id smtpdAAA1JaW1o; Tue Apr 13 23:01:04 2010
2939 X-Virus-Scanned: by amavisd-new at ekit.com
2940 To: facebook-errors@test.test
2941 From: ektravj@test.test
2942 Subject: PHP ERROR (fb)
2943 Message-Id: <20100413230100.D601D27E84@mail2.elax3.ekorp.com>
2944 Date: Tue, 13 Apr 2010 23:01:00 +0000 (UTC)
2946 [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
2947 Stack trace:
2948 #0 /app/01/www/virtual/fb.ekit.com/htdocs/gateway/ekit/feed/index.php(178): fb_exceptions(Object(FacebookRestClientException))
2949 #1 {main}
2950  thrown in /app/01/www/virtual/fb.ekit.com/htdocs/includes/functions.php on line 280
2953 --------------000807090608060304010403--
2954 '''
2955         nodeid = self._handle_mail(message)
2956         assert not os.path.exists(SENDMAILDEBUG)
2957         msgid = self.db.issue.get(nodeid, 'messages')[0]
2958         self.assertEqual(self.db.msg.get(msgid, 'content'),
2959             'Catch this exception and log it without emailing.')
2960         self.assertEqual(self.db.msg.get(msgid, 'files'), ['1'])
2961         fileid = self.db.msg.get(msgid, 'files')[0]
2962         self.assertEqual(self.db.file.get(fileid, 'type'), 'message/rfc822')
2964 pgp_test_key = """
2965 -----BEGIN PGP PRIVATE KEY BLOCK-----
2966 Version: GnuPG v1.4.10 (GNU/Linux)
2968 lQOYBE6NqtsBCADG3UUMYxjwUOpDDVvr0Y8qkvKsgdF79en1zfHtRYlmZc+EJxg8
2969 53CCFGReQWJwOjyP3/SLJwJqfiPR7MAYAqJsm/4U2lxF7sIlEnlrRpFuvB625KOQ
2970 oedCkI4nLa+4QAXHxVX2qLx7es3r2JAoitZLX7ZtUB7qGSRh98DmdAgCY3CFN7iZ
2971 w6xpvIU+LNbsHSo1sf8VP6z7NHQFacgrVvLyRJ4C5lTPU42iM5E6HKxYFExNV3Rn
2972 +2G0bsuiifHV6nJQD73onjwcC6tU97W779dllHlhG3SSP0KlnwmCCvPMlQvROk0A
2973 rLyzKWcUpZwK1aLRYByjFMH9WYXRkhf08bkDABEBAAEAB/9dcmSb6YUyiBNM5t4m
2974 9hZcXykBvw79PRVvmBLy+BYUtArLgsN0+xx3Q7XWRMtJCVSkFw0GxpHwEM4sOyAZ
2975 KEPC3ZqLmgB6LDO2z/OWYVa9vlCAiPgDYtEVCnCCIInN/ue4dBZtDeVj8NUK2n0D
2976 UBpa2OMUgu3D+4SJNK7EnAmXdOaP6yfe6SXwcQfti8UoSFMJRkQkbY1rm/6iPfON
2977 t2RBAc7jW4eRzdciWCfvJfMSj9cqxTBQWz5vVadeY9Bm/IKw1HiKNBrJratq2v+D
2978 VGr0EkE9oOa5zbgZt2CFvknE4YhGmv81xFdK5GXr8L7nluZrePMblWbkI2ICTbV0
2979 RKLhBADYLvyDFX3cCoFzWmCl5L32G6LLfTt0yU0eUHcAzXd7QjOZN289HWYEmdVi
2980 kpxQPDxhWz+m8qt0HJGFl2+BKpZJBaT/L5AcqTBODxarxCSBTIVhCjD/46XvLY0h
2981 b2ZnG8HSLyFdRj07vk+qTvcF58qUuYFSLIF2t2imTCR/PwR/LwQA632vn2/7KIHj
2982 DR0O+G9eccTtAfX4TN4Q4Ua3WByClLZu/LSAenCLZ1CHVABEH6dwwjEARLeNUdLi
2983 Xy5KKlpr2vkoh96fnw0r2yg7dlBXq4yQKjJBXwNaKpuvqgzd8en0zJGLXxzt0NT3
2984 H+QNIP2WZMJSDQcDh3HhQrH0IeNdDm0D/iyJgSMXvqjm+KhYIa3xiloQsCRlDNm+
2985 XC7Eo5hsjvBaIKba6o9oL9oEiSVUFryPWKWIpi0P7/F5voJL6KFSZTor3x3o9CcC
2986 qHyqMHfNL23EAVJulySfPYLC7S3QB+tCBLXmKxb/YXCSLVi/UDzVgvWN6KIknZg2
2987 6uDLUzPbzDGjOZ20K1JvdW5kdXAgVGVzdGtleSA8cm91bmR1cC1hZG1pbkBleGFt
2988 cGxlLmNvbT6JATgEEwECACIFAk6NqtsCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4B
2989 AheAAAoJEFrc/VYxw4dBG7oIAMCU9sRjK0dS7z/IGJ8KcCOQNN674AooJLn+J9Ew
2990 BT6/WxMY13nm/iK0uX2sOGnnXdg1PJ15IvD8zB5wXLbe25t6oRl5G58vmeKEyjc8
2991 QTB43/c8EsqY1ob7EVcuhrJCSS/JM8ApzQyXrh2QNmS+mBCJcx74MeipE6mNVT9j
2992 VscizixdFjqvJLkbW1kGac3Wj+c3ICNUIp0lbwb+Ve2rXlU+iXHEDqaVJDMEppme
2993 gDiZl+bKYrqljhZkH9Slv55uqqXUSg1SmTm/2orHUdAmDc6Y6azKNqQEqD2B0JyT
2994 jTJQJVMl5Oln63aZDCTxHkoqn8q06OjLJRD4on7jlanZEladA5gETo2q2wEIALEF
2995 poVkZrnqme2M8FObrQyVB+ZYT2mox56WLyInbxVFDg20qqIvQfVE0P69Yuf1OXkj
2996 q7bNI03Jvo+uzxpztOKPDo7tnbQ7bXbOmq3n4wUoN29NMrYNg6tF1ubEv1WwYUMw
2997 7LfF4BLMETXpT0JElV1+awfP9rrGiyWkH4enG612HT+1OoA0R0nNH0kslD6OhdoR
2998 VDqkyiCmdY9x176EhzhL3vCoN6ywRVTfFbAJiMv9UDzxs0SStmVOK/l5XLfWQO6f
2999 9boAHihpnxEfPIJhsD+FpVKVf3g85qWAjh2BfuzdW79vjLBdTHJQxg4HdhliWbXg
3000 PjjrVEgWEFVc+NDlNb0AEQEAAQAH/A1a6sbniI8q3DVoIP19zN7FI5UaQSuB2Jrl
3001 +Q+vlUQv3dvk2cwQmqj2vyRo2gcRS3u7LYpGDGLNqfshv22JyzId2YWo9vE7sTTP
3002 E4EJRz8CsLlMmVsoxoVBE0cnvXOpMef6z0ZyFEdMGVmi4iA9bQi3r+V6qBehQQA0
3003 U034VTCPN4yvWyq6TWsABesOx48nkQ5TlduIq2ZGNCR8Vd1fe6vGM7YXyQWxy5ke
3004 guqmph73H2bOB6hSuUnyBFKtinrF9MbCGA0PqheUVqy0p7og6x/pEoAVkKBJ9Ki+
3005 ePuQtBl5h9e3SbiN+r7aa6T0Ygx/7igl4eWPfvJYIXYXc4aKiwEEANEa5rBoN7Ta
3006 ED+R47Rg9w/EW3VDQ6R3Szy1rvIKjC6JlDyKlGgTeWEFjDeTwCB4xU7YtxVpt6bk
3007 b7RBtDkRck2+DwnscutA7Uxn267UxzNUd1IxhUccRFRfRS7OEnmlVmaLUnOeHHwe
3008 OrZyRSiNVnh0QABEJnwNjX4m139v6YD9BADYuM5XCawI63pYa3/l7UX9H5EH95OZ
3009 G9Hw7pXQ/YJYerSxRx+2q0+koRcdoby1TVaRrdDC+kOm3PI7e66S5rnaZ1FZeYQP
3010 nVYzyGqNnsvncs24kYBL8DYaDDfdm7vfzSEqia0VNqZ4TMbwJLk5f5Ys4WOF791G
3011 LPJgrAPG1jgDwQQAovKbw0u6blIUKsUYOLsviaLCyFC9DwaHqIZwjy8omnh7MaKE
3012 7+MXxJpfcVqFifj3CmqMdSmTfkgbKQPAI46Q1OKWvkvUxEvi7WATo4taEXupRFL5
3013 jnL8c4h46z8UpMX2CMwWU0k1Et/zlBoYy7gNON7tF2/uuN18zWFBlD72HuM9HIkB
3014 HwQYAQIACQUCTo2q2wIbDAAKCRBa3P1WMcOHQYI+CACDXJf1e695LpcsrVxKgiQr
3015 9fTbNJYB+tjbnd9vas92Gz1wZcQV9RjLkYQeEbOpWQud/1UeLRsFECMj7kbgAEqz
3016 7fIO4SeN8hFEvvZ+lI0AoBi4XvuUcCm5kvAodvmF8M9kQiUzF1gm+R9QQeJFDLpW
3017 8Gg7J3V3qM+N0FuXrypYcsEv7n/RJ1n+lhTW5hFzKBlNL4WrAhY/QsXEbmdsa478
3018 tzuHlETtjMm4g4DgppUdlCMegcpjjC9zKsN5xFOQmNMTO/6rPFUqk3k3T6I0LV4O
3019 zm4xNC+wwAA69ibnbrY1NR019et7RYW+qBudGbpJB1ABzkf/NsaCj6aTaubt7PZP
3020 =3uFZ
3021 -----END PGP PRIVATE KEY BLOCK-----
3022 """
3024 john_doe_key = """
3025 -----BEGIN PGP PRIVATE KEY BLOCK-----
3026 Version: GnuPG v1.4.10 (GNU/Linux)
3028 lQHYBE6NwvABBACxg7QqV2qHywwM3wae6HAHJVEo7EeYA6Lv0pZlW3Aw4CCCnpgJ
3029 jA7CekGFcmGmoCaN9ezuVAPTgUlK4yt8a7P6cT0vw1q341Om9IEKAu59RpNZN/H9
3030 6GfZ95bU51W/hdTFysH1DRwbCR3MowvLeA6Pk4cZlPsYHD0SD3De2i1BewARAQAB
3031 AAP+IRi4L6jKwPS3k3LFrj0SHhL0Fdgv5QTQjTxLNCyfN02iYhglqqoFWncm3jWc
3032 RU/YwGEYwrrBV97kBmVihzkhfgFRsxynE9PMGKKEAuRcAl21RPJDFA6Dlnp6M2No
3033 rR6eoAhrlZ8+KsK9JaXSMalzO/Yh4u3mOinq3f3XL96wAEkCAMAxeZMF5pnXARNR
3034 Y7u2clhNNnLuf+BzpENCFMaWzWPyTcvbf4xNK7ZHPxFVZpX5/qAPJ8rnTaOTHxnN
3035 5PgqbO8CAOxyrTw/muakTJLg+FXdn8BgxZGJXMT7KmkU9SReefjo7c1WlnZxKIAy
3036 6vLIG8WMGpdfCFDve0YLr/GGyDtOjDUB/RN3gn6qnAJThBnVk2wESZVx41fihbIF
3037 ACCKc9heFskzwurtvvp+bunM3quwrSH1hWvxiWJlDmGSn8zQFypGChifgLQZSm9o
3038 biBEb2UgPGpvaG5AdGVzdC50ZXN0Poi4BBMBAgAiBQJOjcLwAhsDBgsJCAcDAgYV
3039 CAIJCgsEFgIDAQIeAQIXgAAKCRC/z7qg+FujnPWiA/9T5SOGraRNIVVIyvJvYwkG
3040 OTAfQ0K3QMlLoQMPmaEbx9Q+isF15M9sOMcl1XGO4UNWuCPIIN8z/y/OLgAB0ZuL
3041 GlnAPPOOZ+MlaUXiMYo8oi416QZrMDf2H/Nkc10csiXm+zMl8RqeIQBEeljNyJ+t
3042 MG1EWn/PHTwFTd/VePuQdJ0B2AROjcLwAQQApw+72jKy0/wqg5SAtnVSkA1F3Jna
3043 /OG+ufz5dX57jkMFRvFoksWIWqHmiCjdE5QV8j+XTnjElhLsmrgjl7aAFveb30R6
3044 ImmcpKMN31vAp4RZlnyYbYUCY4IXFuz3n1CaUL+mRx5yNJykrZNfpWNf2pwozkZq
3045 lcDI69ymIW5acXUAEQEAAQAD/R7Jdf98l1scngMYo228ikYUxBqm2eX/fiQNXDWM
3046 ZR2u+TJ9O53MvFejfXX7Pd6lTDQUBwDFncjgXO0YYSrMzabhqpqoKLqOIpZmBuWC
3047 Hh1lvcFoIYoDR2LkiJ9EPBUEVUBDsUO8ajkILEE3G+DDpCaf9Vo82lCVyhDESqyt
3048 v4lxAgDOLpoq1Whv5Ejr6FifTWytCiQjH2P1SmePlQmy6oEJRUYA1t4zYrzCJUX8
3049 VAvPjh9JXilP6mhDbyQArWllewV9AgDPbVOf75ktRwfhje26tZsukqWYJCc1XvoH
3050 3PTzA7vH1HZZq7dvxa87PiSnkOLEsIAsI+4jpeMxpPlQRxUvHf1ZAf9rK3v3HMJ/
3051 2xVzwK24Oaj+g2O7D/fdqtLFGe5S5JobnTyp9xArDAhaZ/AKfDMYjUIKMP+bdNAf
3052 y8fQUtuawFltm1GInwQYAQIACQUCTo3C8AIbDAAKCRC/z7qg+FujnDzYA/9EU6Pv
3053 Ci1+DCtxjnq7IOvOjqExhFNGvN9Dw17Tl8HcyW3if9v5RxeSWYKl0DhzVdzMQgH/
3054 78q4F4W1q2IkB7SCpXizHLIc3eh8iZkbWZE+CGPvTpqyF03Yi16qhxpAbkGs2Yhq
3055 jTx5oJ4CL5fybBOZLg+BTlK4HIee6xEcbNoq+A==
3056 =ZKBW
3057 -----END PGP PRIVATE KEY BLOCK-----
3058 """
3060 ownertrust = """
3061 723762CD5A5FECB76DC72DF85ADCFD5631C38741:6:
3062 2940C247A1FBAD508A1AF24BBFCFBAA0F85BA39C:6:
3063 """
3065 class MailgwPGPTestCase(MailgwTestAbstractBase):
3066     pgphome = 'pgp-test-home'
3067     def setUp(self):
3068         MailgwTestAbstractBase.setUp(self)
3069         self.db.security.addRole(name = 'pgp', description = 'PGP Role')
3070         self.instance.config['PGP_HOMEDIR'] = self.pgphome
3071         self.instance.config['PGP_ROLES'] = 'pgp'
3072         self.instance.config['PGP_ENABLE'] = True
3073         self.instance.config['MAIL_DOMAIN'] = 'example.com'
3074         self.instance.config['ADMIN_EMAIL'] = 'roundup-admin@example.com'
3075         self.db.user.set(self.john_id, roles='User,pgp')
3076         os.mkdir(self.pgphome)
3077         os.environ['GNUPGHOME'] = self.pgphome
3078         ctx = pyme.core.Context()
3079         key = pyme.core.Data(pgp_test_key)
3080         ctx.op_import(key)
3081         key = pyme.core.Data(john_doe_key)
3082         ctx.op_import(key)
3083         # trust-modelling with pyme isn't working in 0.8.1
3084         # based on libgpgme11 1.2.0, also tried in C -- same thing.
3085         otrust = os.popen ('gpg --import-ownertrust 2> /dev/null', 'w')
3086         otrust.write(ownertrust)
3087         otrust.close()
3089     def tearDown(self):
3090         MailgwTestAbstractBase.tearDown(self)
3091         if os.path.exists(self.pgphome):
3092             shutil.rmtree(self.pgphome)
3094     def testPGPUnsignedMessage(self):
3095         self.assertRaises(MailUsageError, self._handle_mail,
3096             '''Content-Type: text/plain;
3097   charset="iso-8859-1"
3098 From: John Doe <john@test.test>
3099 To: issue_tracker@your.tracker.email.domain.example
3100 Message-Id: <dummy_test_message_id>
3101 Subject: [issue] Testing non-signed message...
3103 This is no pgp signed message.
3104 ''')
3106     signed_msg = '''Content-Disposition: inline
3107 From: John Doe <john@test.test>
3108 To: issue_tracker@your.tracker.email.domain.example
3109 Subject: [issue] Testing signed message...
3110 Content-Type: multipart/signed; micalg=pgp-sha1;
3111         protocol="application/pgp-signature"; boundary="cWoXeonUoKmBZSoM"
3114 --cWoXeonUoKmBZSoM
3115 Content-Type: text/plain; charset=us-ascii
3116 Content-Disposition: inline
3118 This is a pgp signed message.
3120 --cWoXeonUoKmBZSoM
3121 Content-Type: application/pgp-signature; name="signature.asc"
3122 Content-Description: Digital signature
3123 Content-Disposition: inline
3125 -----BEGIN PGP SIGNATURE-----
3126 Version: GnuPG v1.4.10 (GNU/Linux)
3128 iJwEAQECAAYFAk6N4A4ACgkQv8+6oPhbo5x5nAP/d7R7SxTvLoVESI+1r7eDXp1J
3129 LvBVU2EF3YFYKBHMLcWmjG92fNjnHX6NENTEhTeBynba5IPEwUfITC+7PmgPmQkA
3130 VXnFZnwraHxsYgyFsVFN1kkTSbwRUlWl9+nTEsr0yBLTpZN0QSIDcwu+i/xVcg+t
3131 ZQ4K6R3m3AOw7BLdvZs=
3132 =wpYk
3133 -----END PGP SIGNATURE-----
3135 --cWoXeonUoKmBZSoM--
3136 '''
3138     def testPGPSignedMessage(self):
3139         nodeid = self._handle_mail(self.signed_msg)
3140         m = self.db.issue.get(nodeid, 'messages')[0]
3141         self.assertEqual(self.db.msg.get(m, 'content'), 
3142             'This is a pgp signed message.')
3144     def testPGPSignedMessageFail(self):
3145         # require both, signing and encryption
3146         self.instance.config['PGP_REQUIRE_INCOMING'] = 'both'
3147         self.assertRaises(MailUsageError, self._handle_mail, self.signed_msg)
3149     encrypted_msg = '''Content-Disposition: inline
3150 From: John Doe <john@test.test>
3151 To: roundup-admin@example.com
3152 Subject: [issue] Testing encrypted message...
3153 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";
3154         boundary="d6Gm4EdcadzBjdND"
3156 --d6Gm4EdcadzBjdND
3157 Content-Type: application/pgp-encrypted
3158 Content-Disposition: attachment
3160 Version: 1
3162 --d6Gm4EdcadzBjdND
3163 Content-Type: application/octet-stream
3164 Content-Disposition: inline; filename="msg.asc"
3166 -----BEGIN PGP MESSAGE-----
3167 Version: GnuPG v1.4.10 (GNU/Linux)
3169 hQEMAzfeQttq+Q2YAQf9FxCtZVgC7jAy6UkeAJ1imCpnh9DgKA5w40OFtrY4mVAp
3170 cL7kCkvGvJCW7uQZrmSgIiYaZGLI3GS42XutORC6E6PzBEW0fJUMIXYmoSd0OFeY
3171 3H2+854qu37W/uCOWM9OnPFIH8g8q8DgYy88i0goM+Ot9Q96yFfJ7QymanOZJgVa
3172 MNC+oKDiIZKiE3PCwtGr+8CHZN/9J6O4FeJijBlr09C5LXc+Nif5T0R0nt17MAns
3173 9g2UvGxW8U24NAS1mOg868U05hquLPIcFz9jGZGknJu7HBpOkQ9GjKqkzN8pgZVN
3174 VbN8IdDqi0QtRKE44jtWQlyNlESMjv6GtC2V9F6qKNK8AfHtBexDhyv4G9cPFFNO
3175 afQ6e4dPi89RYIQyydtwiqao8fj6jlAy2Z1cbr7YxwBG7BeUZv9yis7ShaAIo78S
3176 82MrCYpSjfHNwKiSfC5yITw22Uv4wWgixVdAsaSdtBqEKXJPG9LNey18ArsBjSM1
3177 P81iDOWUp/uyIe5ZfvNI38BBxEYslPTUlDk2GB8J2Vun7IWHoj9a4tY3IotC9jBr
3178 5Qnigzqrt7cJZX6OrN0c+wnOjXbMGYXmgSs4jeM=
3179 =XX5Q
3180 -----END PGP MESSAGE-----
3182 --d6Gm4EdcadzBjdND--
3183 '''
3184     def testPGPEncryptedUnsignedMessageError(self):
3185         self.assertRaises(MailUsageError, self._handle_mail, self.encrypted_msg)
3187     def testPGPEncryptedUnsignedMessage(self):
3188         # no error if we don't require a signature:
3189         self.instance.config['PGP_REQUIRE_INCOMING'] = 'encrypted'
3190         nodeid = self._handle_mail (self.encrypted_msg)
3191         m = self.db.issue.get(nodeid, 'messages')[0]
3192         self.assertEqual(self.db.msg.get(m, 'content'), 
3193             'This is the text to be encrypted')
3195     def testPGPEncryptedUnsignedMessageFromNonPGPUser(self):
3196         msg = self.encrypted_msg.replace('John Doe <john@test.test>',
3197             '"Contrary, Mary" <mary@test.test>')
3198         nodeid = self._handle_mail (msg)
3199         m = self.db.issue.get(nodeid, 'messages')[0]
3200         self.assertEqual(self.db.msg.get(m, 'content'), 
3201             'This is the text to be encrypted')
3202         self.assertEqual(self.db.msg.get(m, 'author'), self.mary_id)
3204     # check that a bounce-message that is triggered *after*
3205     # decrypting is properly encrypted:
3206     def testPGPEncryptedUnsignedMessageCheckBounce(self):
3207         # allow non-signed msg
3208         self.instance.config['PGP_REQUIRE_INCOMING'] = 'encrypted'
3209         # don't allow creation of message, trigger error *after* decrypt
3210         self.db.user.set(self.john_id, roles='pgp')
3211         self.db.security.addPermissionToRole('pgp', 'Email Access')
3212         self.db.security.addPermissionToRole('pgp', 'Create', 'issue')
3213         # trap_exc=1: we want a bounce message:
3214         self._handle_mail(self.encrypted_msg, trap_exc=1)
3215         m = self._get_mail()
3216         fp = FeedParser()
3217         fp.feed(m)
3218         parts = fp.close().get_payload()
3219         self.assertEqual(len(parts),2)
3220         self.assertEqual(parts[0].get_payload().strip(), 'Version: 1')
3221         crypt = pyme.core.Data(parts[1].get_payload())
3222         plain = pyme.core.Data()
3223         ctx = pyme.core.Context()
3224         res = ctx.op_decrypt(crypt, plain)
3225         self.assertEqual(res, None)
3226         plain.seek(0,0)
3227         fp = FeedParser()
3228         fp.feed(plain.read())
3229         parts = fp.close().get_payload()
3230         self.assertEqual(len(parts),2)
3231         self.assertEqual(parts[0].get_payload().strip(),
3232             'You are not permitted to create messages.')
3233         self.assertEqual(parts[1].get_payload().strip(),
3234             '''Content-Type: text/plain; charset=us-ascii
3235 Content-Disposition: inline
3237 This is the text to be encrypted''')
3240     def testPGPEncryptedSignedMessage(self):
3241         # require both, signing and encryption
3242         self.instance.config['PGP_REQUIRE_INCOMING'] = 'both'
3243         nodeid = self._handle_mail('''Content-Disposition: inline
3244 From: John Doe <john@test.test>
3245 To: roundup-admin@example.com
3246 Subject: Testing encrypted and signed message
3247 MIME-Version: 1.0
3248 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";
3249         boundary="ReaqsoxgOBHFXBhH"
3251 --ReaqsoxgOBHFXBhH
3252 Content-Type: application/pgp-encrypted
3253 Content-Disposition: attachment
3255 Version: 1
3257 --ReaqsoxgOBHFXBhH
3258 Content-Type: application/octet-stream
3259 Content-Disposition: inline; filename="msg.asc"
3261 -----BEGIN PGP MESSAGE-----
3262 Version: GnuPG v1.4.10 (GNU/Linux)
3264 hQEMAzfeQttq+Q2YAQf+NaC3r8qBURQqxHH9IAP4vg0QAP2yj3n0v6guo1lRf5BA
3265 EUfTQ3jc3chxLvzTgoUIuMOvhlNroqR1lgLwhfSTCyuKWDZa+aVNiSgsB2MD44Xd
3266 mAkKKmnmOGLmfbICbPQZxl4xNhCMTHiAy1xQE6mTj/+pEAq5XxjJUwn/gJ3O1Wmd
3267 NyWtJY2N+TRbxUVB2WhG1j9J1D2sjhG26TciE8JeuLDZzaiVNOW9YlX2Lw5KtlkR
3268 Hkgw6Xme06G0XXZUcm9JuBU/7oFP/tSrC1tBsnVlq1pZYf6AygIBdXWb9gD/WmXh
3269 7Eu/xCKrw4RFnXnTgmBz/NHRfVDkfdSscZqexnG1D9LAwQHSuVf8sxDPNesv0W+8
3270 e49loVjvU+Y0BCFQAbWSW4iOEUYZpW/ITRE4+wIqMXZbAraeBV0KPZ4hAa3qSmf+
3271 oZBRcbzssL163Odx/OHRuK2J2CHC654+crrlTBnxd/RUKgRbSUKwrZzB2G6OPcGv
3272 wfiqXsY+XvSZtTbWuvUJxePh8vhhhjpuo1JtlrYc3hZ9OYgoCoV1JiLl5c60U5Es
3273 oUT9GDl1Qsgb4dF4TJ1IBj+riYiocYpJxPhxzsy6liSLNy2OA6VEjG0FGk53+Ok9
3274 7UzOA+WaHJHSXafZzrdP1TWJUFlOMA+dOgTKpH69eL1+IRfywOjEwp1UNSbLnJpc
3275 D0QQLwIFttplKvYkn0DZByJCVnIlGkl4s5LM5rnc8iecX8Jad0iRIlPV6CVM+Nso
3276 WdARUfyJfXAmz8uk4f2sVfeMu1gdMySdjvxwlgHDJdBPIG51r2b8L/NCTiC57YjF
3277 zGhS06FLl3V1xx6gBlpqQHjut3efrAGpXGBVpnTJMOcgYAk=
3278 =jt/n
3279 -----END PGP MESSAGE-----
3281 --ReaqsoxgOBHFXBhH--
3282 ''')
3283         m = self.db.issue.get(nodeid, 'messages')[0]
3284         self.assertEqual(self.db.msg.get(m, 'content'), 
3285             'This is the text of a signed and encrypted email.')
3288 def test_suite():
3289     suite = unittest.TestSuite()
3290     suite.addTest(unittest.makeSuite(MailgwTestCase))
3291     if pyme is not None:
3292         suite.addTest(unittest.makeSuite(MailgwPGPTestCase))
3293     else:
3294         print "Skipping PGP tests"
3295     return suite
3297 if __name__ == '__main__':
3298     runner = unittest.TextTestRunner()
3299     unittest.main(testRunner=runner)
3301 # vim: set filetype=python sts=4 sw=4 et si :