Code

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