X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=test%2Ftest_mailgw.py;h=fde8c460c3846d3bf2ce9403a619b4c6e69c26d2;hb=d5597ec3a5458ae238e653aff0b333b44ad165a2;hp=650133fd4031613fb2dfa430eed8493fc4fdca91;hpb=2bea38fd3b346105cd0a6f3389ccdd0e8c663974;p=roundup.git diff --git a/test/test_mailgw.py b/test/test_mailgw.py index 650133f..fde8c46 100644 --- a/test/test_mailgw.py +++ b/test/test_mailgw.py @@ -1,3 +1,4 @@ +# -*- encoding: utf-8 -*- # # Copyright (c) 2001 Richard Jones, richard@bofh.asn.au. # This module is free software, and you may redistribute it and/or modify @@ -8,44 +9,105 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # -# $Id: test_mailgw.py,v 1.30 2002-09-12 04:21:20 richard Exp $ +# $Id: test_mailgw.py,v 1.96 2008-08-19 01:40:59 richard Exp $ -import unittest, cStringIO, tempfile, os, shutil, errno, imp, sys, difflib +# TODO: test bcc -# Note: Should parse emails according to RFC2822 instead of performing a -# literal string comparision. Parsing the messages allows the tests to work for -# any legal serialization of an email. -#try : -# import email -#except ImportError : -# import rfc822 as email +import unittest, tempfile, os, shutil, errno, imp, sys, difflib, rfc822, time +import gpgmelib +from email.parser import FeedParser -from roundup.mailgw import MailGW, Unauthorized -from roundup import init, instance -# TODO: make this output only enough equal lines for context, not all of -# them +try: + import pyme, pyme.core +except ImportError: + pyme = None + + +from cStringIO import StringIO + +if not os.environ.has_key('SENDMAILDEBUG'): + os.environ['SENDMAILDEBUG'] = 'mail-test.log' +SENDMAILDEBUG = os.environ['SENDMAILDEBUG'] + +from roundup import mailgw, i18n, roundupdb +from roundup.mailgw import MailGW, Unauthorized, uidFromAddress, \ + parseContent, IgnoreLoop, IgnoreBulk, MailUsageError, MailUsageHelp +from roundup import init, instance, password, rfc2822, __version__ +from roundup.anypy.sets_ import set + +#import db_test_base +import memorydb + +class Message(rfc822.Message): + """String-based Message class with equivalence test.""" + def __init__(self, s): + rfc822.Message.__init__(self, StringIO(s.strip())) + + def __eq__(self, other): + return (self.dict == other.dict and + self.fp.read() == other.fp.read()) + +class Tracker(object): + def open(self, journaltag): + return self.db + class DiffHelper: - def compareStrings(self, s2, s1): + def compareMessages(self, new, old): + """Compare messages for semantic equivalence.""" + new, old = Message(new), Message(old) + + # all Roundup-generated messages have "Precedence: bulk" + old['Precedence'] = 'bulk' + + # don't try to compare the date + del new['date'], old['date'] + + if not new == old: + res = [] + + replace = {} + for key in new.keys(): + if key.startswith('from '): + # skip the unix from line + continue + if key.lower() == 'x-roundup-version': + # version changes constantly, so handle it specially + if new[key] != __version__: + res.append(' %s: %r != %r' % (key, __version__, + new[key])) + elif key.lower() == 'content-type' and 'boundary=' in new[key]: + # handle mime messages + newmime = new[key].split('=',1)[-1].strip('"') + oldmime = old.get(key, '').split('=',1)[-1].strip('"') + replace ['--' + newmime] = '--' + oldmime + replace ['--' + newmime + '--'] = '--' + oldmime + '--' + elif new.get(key, '') != old.get(key, ''): + res.append(' %s: %r != %r' % (key, old.get(key, ''), + new.get(key, ''))) + + body_diff = self.compareStrings(new.fp.read(), old.fp.read(), + replace=replace) + if body_diff: + res.append('') + res.extend(body_diff) + + if res: + res.insert(0, 'Generated message not correct (diff follows, expected vs. actual):') + raise AssertionError, '\n'.join(res) + + def compareStrings(self, s2, s1, replace={}): '''Note the reversal of s2 and s1 - difflib.SequenceMatcher wants the first to be the "original" but in the calls in this file, the second arg is the original. Ho hum. + Do replacements over the replace dict -- used for mime boundary ''' - if s1 == s2: + l1 = s1.strip().split('\n') + l2 = [replace.get(i,i) for i in s2.strip().split('\n')] + if l1 == l2: return - - # under python2.[12] we allow a difference of one trailing empty line. - if sys.version_info[0:2] == (2,1): - if s1+'\n' == s2: - return - if sys.version_info[0:2] == (2,2): - if s1 == s2+'\n': - return - - l1=s1.split('\n') - l2=s2.split('\n') s = difflib.SequenceMatcher(None, l1, l2) - res = ['Generated message not correct (diff follows):'] + res = [] for value, s1s, s1e, s2s, s2e in s.get_opcodes(): if value == 'equal': for i in range(s1s, s1e): @@ -61,93 +123,214 @@ class DiffHelper: res.append('- %s'%l1[i]) res.append('+ %s'%l2[j]) - raise AssertionError, '\n'.join(res) + return res -class MailgwTestCase(unittest.TestCase, DiffHelper): +class MailgwTestAbstractBase(unittest.TestCase, DiffHelper): count = 0 schema = 'classic' def setUp(self): - MailgwTestCase.count = MailgwTestCase.count + 1 - self.dirname = '_test_mailgw_%s'%self.count - try: - shutil.rmtree(self.dirname) - except OSError, error: - if error.errno not in (errno.ENOENT, errno.ESRCH): raise - # create the instance - init.install(self.dirname, 'classic', 'anydbm') - init.initialise(self.dirname, 'sekrit') - # check we can load the package - self.instance = instance.open(self.dirname) - # and open the database - self.db = self.instance.open('sekrit') - self.db.user.create(username='Chef', address='chef@bork.bork.bork', - roles='User') - self.db.user.create(username='richard', address='richard@test', - roles='User') - self.db.user.create(username='mary', address='mary@test', - roles='User') - self.db.user.create(username='john', address='john@test', - alternate_addresses='jondoe@test\njohn.doe@test', roles='User') + self.old_translate_ = mailgw._ + roundupdb._ = mailgw._ = i18n.get_translation(language='C').gettext + self.__class__.count = self.__class__.count + 1 + + # and open the database / "instance" + self.db = memorydb.create('admin') + self.instance = Tracker() + self.instance.db = self.db + self.instance.config = self.db.config + self.instance.MailGW = MailGW + + self.chef_id = self.db.user.create(username='Chef', + address='chef@bork.bork.bork', realname='Bork, Chef', roles='User') + self.richard_id = self.db.user.create(username='richard', + address='richard@test.test', roles='User') + self.mary_id = self.db.user.create(username='mary', + address='mary@test.test', roles='User', realname='Contrary, Mary') + self.john_id = self.db.user.create(username='john', + address='john@test.test', roles='User', realname='John Doe', + alternate_addresses='jondoe@test.test\njohn.doe@test.test') + self.rgg_id = self.db.user.create(username='rgg', + address='rgg@test.test', roles='User') def tearDown(self): - if os.path.exists(os.environ['SENDMAILDEBUG']): - os.remove(os.environ['SENDMAILDEBUG']) + roundupdb._ = mailgw._ = self.old_translate_ + if os.path.exists(SENDMAILDEBUG): + os.remove(SENDMAILDEBUG) self.db.close() + + def _create_mailgw(self, message, args=()): + class MailGW(self.instance.MailGW): + def handle_message(self, message): + return self._handle_message(message) + handler = MailGW(self.instance, args) + handler.db = self.db + return handler + + def _handle_mail(self, message, args=(), trap_exc=0): + handler = self._create_mailgw(message, args) + handler.trapExceptions = trap_exc + return handler.main(StringIO(message)) + + def _get_mail(self): + f = open(SENDMAILDEBUG) try: - shutil.rmtree(self.dirname) - except OSError, error: - if error.errno not in (errno.ENOENT, errno.ESRCH): raise + return f.read() + finally: + f.close() + + # Normal test-case used for both non-pgp test and a test while pgp + # is enabled, so this test is run in both test suites. + def testEmptyMessage(self): + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: +Subject: [issue] Testing... + +''') + assert not os.path.exists(SENDMAILDEBUG) + self.assertEqual(self.db.issue.get(nodeid, 'title'), 'Testing...') + + +class MailgwTestCase(MailgwTestAbstractBase): + + def testMessageWithFromInIt(self): + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: +Subject: [issue] Testing... + +From here to there! +''') + assert not os.path.exists(SENDMAILDEBUG) + msgid = self.db.issue.get(nodeid, 'messages')[0] + self.assertEqual(self.db.msg.get(msgid, 'content'), 'From here to there!') + + def testNoMessageId(self): + self.instance.config['MAIL_DOMAIN'] = 'example.com' + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Subject: [issue] Testing... + +Hi there! +''') + assert not os.path.exists(SENDMAILDEBUG) + msgid = self.db.issue.get(nodeid, 'messages')[0] + messageid = self.db.msg.get(msgid, 'messageid') + x1, x2 = messageid.split('@') + self.assertEqual(x2, 'example.com>') + x = x1.split('.')[-1] + self.assertEqual(x, 'issueNone') + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: [issue%(nodeid)s] Testing... + +Just a test reply +'''%locals()) + msgid = self.db.issue.get(nodeid, 'messages')[-1] + messageid = self.db.msg.get(msgid, 'messageid') + x1, x2 = messageid.split('@') + self.assertEqual(x2, 'example.com>') + x = x1.split('.')[-1] + self.assertEqual(x, "issue%s"%nodeid) + + def testOptions(self): + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Reply-To: chef@bork.bork.bork +Subject: [issue] Testing... + +Hi there! +''', (('-C', 'issue'), ('-S', 'status=chatting;priority=critical'))) + self.assertEqual(self.db.issue.get(nodeid, 'status'), '3') + self.assertEqual(self.db.issue.get(nodeid, 'priority'), '1') + + def testOptionsMulti(self): + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Reply-To: chef@bork.bork.bork +Subject: [issue] Testing... + +Hi there! +''', (('-C', 'issue'), ('-S', 'status=chatting'), ('-S', 'priority=critical'))) + self.assertEqual(self.db.issue.get(nodeid, 'status'), '3') + self.assertEqual(self.db.issue.get(nodeid, 'priority'), '1') + + def testOptionClass(self): + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Reply-To: chef@bork.bork.bork +Subject: [issue] Testing... [status=chatting;priority=critical] + +Hi there! +''', (('-c', 'issue'),)) + self.assertEqual(self.db.issue.get(nodeid, 'title'), 'Testing...') + self.assertEqual(self.db.issue.get(nodeid, 'status'), '3') + self.assertEqual(self.db.issue.get(nodeid, 'priority'), '1') def doNewIssue(self): - message = cStringIO.StringIO('''Content-Type: text/plain; + nodeid = self._handle_mail('''Content-Type: text/plain; charset="iso-8859-1" From: Chef To: issue_tracker@your.tracker.email.domain.example -Cc: richard@test +Cc: richard@test.test Message-Id: Subject: [issue] Testing... This is a test submission of a new issue. ''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - nodeid = handler.main(message) - if os.path.exists(os.environ['SENDMAILDEBUG']): - error = open(os.environ['SENDMAILDEBUG']).read() - self.assertEqual('no error', error) + assert not os.path.exists(SENDMAILDEBUG) l = self.db.issue.get(nodeid, 'nosy') l.sort() - self.assertEqual(l, ['3', '4']) + self.assertEqual(l, [self.chef_id, self.richard_id]) + return nodeid def testNewIssue(self): self.doNewIssue() def testNewIssueNosy(self): self.instance.config.ADD_AUTHOR_TO_NOSY = 'yes' - message = cStringIO.StringIO('''Content-Type: text/plain; + nodeid = self._handle_mail('''Content-Type: text/plain; charset="iso-8859-1" From: Chef To: issue_tracker@your.tracker.email.domain.example -Cc: richard@test +Cc: richard@test.test Message-Id: Subject: [issue] Testing... This is a test submission of a new issue. ''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - nodeid = handler.main(message) - if os.path.exists(os.environ['SENDMAILDEBUG']): - error = open(os.environ['SENDMAILDEBUG']).read() - self.assertEqual('no error', error) + assert not os.path.exists(SENDMAILDEBUG) l = self.db.issue.get(nodeid, 'nosy') l.sort() - self.assertEqual(l, ['3', '4']) + self.assertEqual(l, [self.chef_id, self.richard_id]) def testAlternateAddress(self): - message = cStringIO.StringIO('''Content-Type: text/plain; + self._handle_mail('''Content-Type: text/plain; charset="iso-8859-1" -From: John Doe +From: John Doe To: issue_tracker@your.tracker.email.domain.example Message-Id: Subject: [issue] Testing... @@ -155,35 +338,27 @@ Subject: [issue] Testing... This is a test submission of a new issue. ''') userlist = self.db.user.list() - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - handler.main(message) - if os.path.exists(os.environ['SENDMAILDEBUG']): - error = open(os.environ['SENDMAILDEBUG']).read() - self.assertEqual('no error', error) + assert not os.path.exists(SENDMAILDEBUG) self.assertEqual(userlist, self.db.user.list(), "user created when it shouldn't have been") def testNewIssueNoClass(self): - message = cStringIO.StringIO('''Content-Type: text/plain; + self._handle_mail('''Content-Type: text/plain; charset="iso-8859-1" From: Chef To: issue_tracker@your.tracker.email.domain.example -Cc: richard@test +Cc: richard@test.test Message-Id: Subject: Testing... This is a test submission of a new issue. ''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - handler.main(message) - if os.path.exists(os.environ['SENDMAILDEBUG']): - error = open(os.environ['SENDMAILDEBUG']).read() - self.assertEqual('no error', error) + assert not os.path.exists(SENDMAILDEBUG) def testNewIssueAuthMsg(self): - message = cStringIO.StringIO('''Content-Type: text/plain; + # TODO: fix the damn config - this is apalling + self.db.config.MESSAGES_TO_AUTHOR = 'yes' + self._handle_mail('''Content-Type: text/plain; charset="iso-8859-1" From: Chef To: issue_tracker@your.tracker.email.domain.example @@ -192,591 +367,2924 @@ Subject: [issue] Testing... [nosy=mary; assignedto=richard] This is a test submission of a new issue. ''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - # TODO: fix the damn config - this is apalling - self.db.config.MESSAGES_TO_AUTHOR = 'yes' - handler.main(message) - - self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), + self.compareMessages(self._get_mail(), '''FROM: roundup-admin@your.tracker.email.domain.example -TO: chef@bork.bork.bork, mary@test, richard@test -Content-Type: text/plain +TO: chef@bork.bork.bork, mary@test.test, richard@test.test +Content-Type: text/plain; charset="utf-8" Subject: [issue1] Testing... -To: chef@bork.bork.bork, mary@test, richard@test -From: "Chef" -Reply-To: "Roundup issue tracker" +To: chef@bork.bork.bork, mary@test.test, richard@test.test +From: "Bork, Chef" +Reply-To: Roundup issue tracker + MIME-Version: 1.0 Message-Id: X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: unread Content-Transfer-Encoding: quoted-printable -New submission from Chef : +New submission from Bork, Chef : This is a test submission of a new issue. - ---------- assignedto: richard messages: 1 nosy: Chef, mary, richard status: unread title: Testing... -_________________________________________________________________________ -"Roundup issue tracker" -http://your.tracker.url.example/issue1 -_________________________________________________________________________ -''') - - # BUG - # def testMultipart(self): - # '''With more than one part''' - # see MultipartEnc tests: but if there is more than one part - # we return a multipart/mixed and the boundary contains - # the ip address of the test machine. - # BUG should test some binary attamchent too. +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +''') - def testSimpleFollowup(self): - self.doNewIssue() - message = cStringIO.StringIO('''Content-Type: text/plain; + def testNewIssueNoAuthorInfo(self): + self.db.config.MAIL_ADD_AUTHORINFO = 'no' + self._handle_mail('''Content-Type: text/plain; charset="iso-8859-1" -From: mary +From: Chef To: issue_tracker@your.tracker.email.domain.example -Message-Id: -In-Reply-To: -Subject: [issue1] Testing... +Message-Id: +Subject: [issue] Testing... [nosy=mary; assignedto=richard] -This is a second followup +This is a test submission of a new issue. ''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - handler.main(message) - self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), + self.compareMessages(self._get_mail(), '''FROM: roundup-admin@your.tracker.email.domain.example -TO: chef@bork.bork.bork, richard@test -Content-Type: text/plain +TO: chef@bork.bork.bork, mary@test.test, richard@test.test +Content-Type: text/plain; charset="utf-8" Subject: [issue1] Testing... -To: chef@bork.bork.bork, richard@test -From: "mary" -Reply-To: "Roundup issue tracker" +To: mary@test.test, richard@test.test +From: "Bork, Chef" +Reply-To: Roundup issue tracker + MIME-Version: 1.0 -Message-Id: -In-Reply-To: +Message-Id: X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: unread Content-Transfer-Encoding: quoted-printable - -mary added the comment: - -This is a second followup - +This is a test submission of a new issue. ---------- -status: unread -> chatting -_________________________________________________________________________ -"Roundup issue tracker" -http://your.tracker.url.example/issue1 -_________________________________________________________________________ -''') +assignedto: richard +messages: 1 +nosy: Chef, mary, richard +status: unread +title: Testing... - def testFollowup(self): - self.doNewIssue() +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +''') - message = cStringIO.StringIO('''Content-Type: text/plain; + def testNewIssueNoAuthorEmail(self): + self.db.config.MAIL_ADD_AUTHOREMAIL = 'no' + self._handle_mail('''Content-Type: text/plain; charset="iso-8859-1" -From: richard +From: Chef To: issue_tracker@your.tracker.email.domain.example -Message-Id: -In-Reply-To: -Subject: [issue1] Testing... [assignedto=mary; nosy=+john] +Message-Id: +Subject: [issue] Testing... [nosy=mary; assignedto=richard] -This is a followup +This is a test submission of a new issue. ''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - handler.main(message) - l = self.db.issue.get('1', 'nosy') - l.sort() - self.assertEqual(l, ['3', '4', '5', '6']) - - self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), + self.compareMessages(self._get_mail(), '''FROM: roundup-admin@your.tracker.email.domain.example -TO: chef@bork.bork.bork, john@test, mary@test -Content-Type: text/plain +TO: chef@bork.bork.bork, mary@test.test, richard@test.test +Content-Type: text/plain; charset="utf-8" Subject: [issue1] Testing... -To: chef@bork.bork.bork, john@test, mary@test -From: "richard" -Reply-To: "Roundup issue tracker" +To: mary@test.test, richard@test.test +From: "Bork, Chef" +Reply-To: Roundup issue tracker + MIME-Version: 1.0 -Message-Id: -In-Reply-To: +Message-Id: X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: unread Content-Transfer-Encoding: quoted-printable +New submission from Bork, Chef: -richard added the comment: - -This is a followup - +This is a test submission of a new issue. ---------- -assignedto: -> mary -nosy: +john, mary -status: unread -> chatting -_________________________________________________________________________ -"Roundup issue tracker" -http://your.tracker.url.example/issue1 -_________________________________________________________________________ +assignedto: richard +messages: 1 +nosy: Chef, mary, richard +status: unread +title: Testing... + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ ''') - def testFollowupTitleMatch(self): - self.doNewIssue() - message = cStringIO.StringIO('''Content-Type: text/plain; - charset="iso-8859-1" -From: richard + multipart_msg = '''From: mary To: issue_tracker@your.tracker.email.domain.example Message-Id: In-Reply-To: -Subject: Re: Testing... [assignedto=mary; nosy=+john] +Subject: [issue1] Testing... +Content-Type: multipart/mixed; boundary="bxyzzy" +Content-Disposition: inline -This is a followup -''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - handler.main(message) - self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), -'''FROM: roundup-admin@your.tracker.email.domain.example -TO: chef@bork.bork.bork, john@test, mary@test -Content-Type: text/plain -Subject: [issue1] Testing... -To: chef@bork.bork.bork, john@test, mary@test -From: "richard" -Reply-To: "Roundup issue tracker" -MIME-Version: 1.0 +--bxyzzy +Content-Type: multipart/alternative; boundary="bCsyhTFzCvuiizWE" +Content-Disposition: inline + +--bCsyhTFzCvuiizWE +Content-Type: text/plain; charset=us-ascii +Content-Disposition: inline + +test attachment first text/plain + +--bCsyhTFzCvuiizWE +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="first.dvi" +Content-Transfer-Encoding: base64 + +SnVzdCBhIHRlc3QgAQo= + +--bCsyhTFzCvuiizWE +Content-Type: text/plain; charset=us-ascii +Content-Disposition: inline + +test attachment second text/plain + +--bCsyhTFzCvuiizWE +Content-Type: text/html +Content-Disposition: inline + + +to be ignored. + + +--bCsyhTFzCvuiizWE-- + +--bxyzzy +Content-Type: multipart/alternative; boundary="bCsyhTFzCvuiizWF" +Content-Disposition: inline + +--bCsyhTFzCvuiizWF +Content-Type: text/plain; charset=us-ascii +Content-Disposition: inline + +test attachment third text/plain + +--bCsyhTFzCvuiizWF +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="second.dvi" +Content-Transfer-Encoding: base64 + +SnVzdCBhIHRlc3QK + +--bCsyhTFzCvuiizWF-- + +--bxyzzy-- +''' + + multipart_msg_latin1 = '''From: mary +To: issue_tracker@your.tracker.email.domain.example Message-Id: In-Reply-To: -X-Roundup-Name: Roundup issue tracker -Content-Transfer-Encoding: quoted-printable +Subject: [issue1] Testing... +Content-Type: multipart/alternative; boundary=001485f339f8f361fb049188dbba -richard added the comment: +--001485f339f8f361fb049188dbba +Content-Type: text/plain; charset=ISO-8859-1 +Content-Transfer-Encoding: quoted-printable -This is a followup +umlaut =E4=F6=FC=C4=D6=DC=DF +--001485f339f8f361fb049188dbba +Content-Type: text/html; charset=ISO-8859-1 +Content-Transfer-Encoding: quoted-printable ----------- -assignedto: -> mary -nosy: +john, mary -status: unread -> chatting -_________________________________________________________________________ -"Roundup issue tracker" -http://your.tracker.url.example/issue1 -_________________________________________________________________________ -''') +umlaut =E4=F6=FC=C4=D6=DC=DF - def testFollowupNosyAuthor(self): - self.doNewIssue() - self.db.config.ADD_AUTHOR_TO_NOSY = 'yes' - message = cStringIO.StringIO('''Content-Type: text/plain; - charset="iso-8859-1" -From: john@test +--001485f339f8f361fb049188dbba-- +''' + + multipart_msg_rfc822 = '''From: mary To: issue_tracker@your.tracker.email.domain.example Message-Id: In-Reply-To: Subject: [issue1] Testing... +Content-Type: multipart/mixed; boundary=001485f339f8f361fb049188dbba -This is a followup -''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - handler.main(message) +This is a multi-part message in MIME format. +--001485f339f8f361fb049188dbba +Content-Type: text/plain; charset=ISO-8859-15 +Content-Transfer-Encoding: 7bit - self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), -'''FROM: roundup-admin@your.tracker.email.domain.example -TO: chef@bork.bork.bork, richard@test -Content-Type: text/plain -Subject: [issue1] Testing... -To: chef@bork.bork.bork, richard@test -From: "john" -Reply-To: "Roundup issue tracker" +First part: Text + +--001485f339f8f361fb049188dbba +Content-Type: message/rfc822; name="Fwd: Original email subject.eml" +Content-Transfer-Encoding: 7bit +Content-Disposition: attachment; filename="Fwd: Original email subject.eml" + +Message-Id: +In-Reply-To: MIME-Version: 1.0 -Message-Id: -In-Reply-To: -X-Roundup-Name: Roundup issue tracker -Content-Transfer-Encoding: quoted-printable +Subject: Fwd: Original email subject +Date: Mon, 23 Aug 2010 08:23:33 +0200 +Content-Type: multipart/alternative; boundary="090500050101020406060002" +This is a multi-part message in MIME format. +--090500050101020406060002 +Content-Type: text/plain; charset=ISO-8859-15; format=flowed +Content-Transfer-Encoding: 7bit -john added the comment: +some text in inner email +======================== -This is a followup +--090500050101020406060002 +Content-Type: text/html; charset=ISO-8859-15 +Content-Transfer-Encoding: 7bit + +some text in inner email +======================== + ----------- -nosy: +john -status: unread -> chatting -_________________________________________________________________________ -"Roundup issue tracker" -http://your.tracker.url.example/issue1 -_________________________________________________________________________ +--090500050101020406060002-- -''') +--001485f339f8f361fb049188dbba-- +''' - def testFollowupNosyRecipients(self): + def testMultipartKeepAlternatives(self): self.doNewIssue() - self.db.config.ADD_RECIPIENTS_TO_NOSY = 'yes' - message = cStringIO.StringIO('''Content-Type: text/plain; - charset="iso-8859-1" -From: richard@test + self._handle_mail(self.multipart_msg) + messages = self.db.issue.get('1', 'messages') + messages.sort() + msg = self.db.msg.getnode (messages[-1]) + assert(len(msg.files) == 5) + names = {0 : 'first.dvi', 4 : 'second.dvi'} + content = {3 : 'test attachment third text/plain\n', + 4 : 'Just a test\n'} + for n, id in enumerate (msg.files): + f = self.db.file.getnode (id) + self.assertEqual(f.name, names.get (n, 'unnamed')) + if n in content : + self.assertEqual(f.content, content [n]) + self.assertEqual(msg.content, 'test attachment second text/plain') + + def testMultipartSeveralAttachmentMessages(self): + self.doNewIssue() + self._handle_mail(self.multipart_msg) + messages = self.db.issue.get('1', 'messages') + messages.sort() + self.assertEqual(messages[-1], '2') + msg = self.db.msg.getnode (messages[-1]) + self.assertEqual(len(msg.files), 5) + issue = self.db.issue.getnode ('1') + self.assertEqual(len(issue.files), 5) + names = {0 : 'first.dvi', 4 : 'second.dvi'} + content = {3 : 'test attachment third text/plain\n', + 4 : 'Just a test\n'} + for n, id in enumerate (msg.files): + f = self.db.file.getnode (id) + self.assertEqual(f.name, names.get (n, 'unnamed')) + if n in content : + self.assertEqual(f.content, content [n]) + self.assertEqual(msg.content, 'test attachment second text/plain') + self.assertEqual(msg.files, ['1', '2', '3', '4', '5']) + self.assertEqual(issue.files, ['1', '2', '3', '4', '5']) + + self._handle_mail(self.multipart_msg) + issue = self.db.issue.getnode ('1') + self.assertEqual(len(issue.files), 10) + messages = self.db.issue.get('1', 'messages') + messages.sort() + self.assertEqual(messages[-1], '3') + msg = self.db.msg.getnode (messages[-1]) + self.assertEqual(issue.files, [str(i+1) for i in range(10)]) + self.assertEqual(msg.files, ['6', '7', '8', '9', '10']) + + def testMultipartKeepFiles(self): + self.doNewIssue() + self._handle_mail(self.multipart_msg) + messages = self.db.issue.get('1', 'messages') + messages.sort() + msg = self.db.msg.getnode (messages[-1]) + self.assertEqual(len(msg.files), 5) + issue = self.db.issue.getnode ('1') + self.assertEqual(len(issue.files), 5) + names = {0 : 'first.dvi', 4 : 'second.dvi'} + content = {3 : 'test attachment third text/plain\n', + 4 : 'Just a test\n'} + for n, id in enumerate (msg.files): + f = self.db.file.getnode (id) + self.assertEqual(f.name, names.get (n, 'unnamed')) + if n in content : + self.assertEqual(f.content, content [n]) + self.assertEqual(msg.content, 'test attachment second text/plain') + self._handle_mail('''From: mary To: issue_tracker@your.tracker.email.domain.example -Cc: john@test -Message-Id: +Message-Id: In-Reply-To: Subject: [issue1] Testing... -This is a followup +This ist a message without attachment ''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - handler.main(message) + issue = self.db.issue.getnode ('1') + self.assertEqual(len(issue.files), 5) + self.assertEqual(issue.files, ['1', '2', '3', '4', '5']) - self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), + def testMultipartDropAlternatives(self): + self.doNewIssue() + self.db.config.MAILGW_IGNORE_ALTERNATIVES = True + self._handle_mail(self.multipart_msg) + messages = self.db.issue.get('1', 'messages') + messages.sort() + msg = self.db.msg.getnode (messages[-1]) + self.assertEqual(len(msg.files), 2) + names = {1 : 'second.dvi'} + content = {0 : 'test attachment third text/plain\n', + 1 : 'Just a test\n'} + for n, id in enumerate (msg.files): + f = self.db.file.getnode (id) + self.assertEqual(f.name, names.get (n, 'unnamed')) + if n in content : + self.assertEqual(f.content, content [n]) + self.assertEqual(msg.content, 'test attachment second text/plain') + + def testMultipartCharsetUTF8NoAttach(self): + c = 'umlaut \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f' + self.doNewIssue() + self.db.config.NOSY_MAX_ATTACHMENT_SIZE = 0 + self._handle_mail(self.multipart_msg_latin1) + messages = self.db.issue.get('1', 'messages') + messages.sort() + msg = self.db.msg.getnode (messages[-1]) + self.assertEqual(len(msg.files), 1) + name = 'unnamed' + content = '' + c + '\n' + for n, id in enumerate (msg.files): + f = self.db.file.getnode (id) + self.assertEqual(f.name, name) + self.assertEqual(f.content, content) + self.assertEqual(msg.content, c) + self.compareMessages(self._get_mail(), '''FROM: roundup-admin@your.tracker.email.domain.example -TO: chef@bork.bork.bork -Content-Type: text/plain +TO: chef@bork.bork.bork, richard@test.test +Content-Type: text/plain; charset="utf-8" Subject: [issue1] Testing... -To: chef@bork.bork.bork -From: "richard" -Reply-To: "Roundup issue tracker" +To: chef@bork.bork.bork, richard@test.test +From: "Contrary, Mary" +Reply-To: Roundup issue tracker + MIME-Version: 1.0 Message-Id: In-Reply-To: X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +X-Roundup-Issue-Files: unnamed Content-Transfer-Encoding: quoted-printable -richard added the comment: - -This is a followup +Contrary, Mary added the comment: +umlaut =C3=A4=C3=B6=C3=BC=C3=84=C3=96=C3=9C=C3=9F +File 'unnamed' not attached - you can download it from http://tracker.examp= +le/cgi-bin/roundup.cgi/bugs/file1. ---------- -nosy: +john status: unread -> chatting -_________________________________________________________________________ -"Roundup issue tracker" -http://your.tracker.url.example/issue1 -_________________________________________________________________________ +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ ''') - def testFollowupNosyAuthorAndCopy(self): + def testMultipartCharsetLatin1NoAttach(self): + c = 'umlaut \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f' self.doNewIssue() - self.db.config.ADD_AUTHOR_TO_NOSY = 'yes' - self.db.config.MESSAGES_TO_AUTHOR = 'yes' - message = cStringIO.StringIO('''Content-Type: text/plain; - charset="iso-8859-1" -From: john@test -To: issue_tracker@your.tracker.email.domain.example -Message-Id: -In-Reply-To: -Subject: [issue1] Testing... - -This is a followup -''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - handler.main(message) - - self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), + self.db.config.NOSY_MAX_ATTACHMENT_SIZE = 0 + self.db.config.MAIL_CHARSET = 'iso-8859-1' + self._handle_mail(self.multipart_msg_latin1) + messages = self.db.issue.get('1', 'messages') + messages.sort() + msg = self.db.msg.getnode (messages[-1]) + self.assertEqual(len(msg.files), 1) + name = 'unnamed' + content = '' + c + '\n' + for n, id in enumerate (msg.files): + f = self.db.file.getnode (id) + self.assertEqual(f.name, name) + self.assertEqual(f.content, content) + self.assertEqual(msg.content, c) + self.compareMessages(self._get_mail(), '''FROM: roundup-admin@your.tracker.email.domain.example -TO: chef@bork.bork.bork, john@test, richard@test -Content-Type: text/plain +TO: chef@bork.bork.bork, richard@test.test +Content-Type: text/plain; charset="iso-8859-1" Subject: [issue1] Testing... -To: chef@bork.bork.bork, john@test, richard@test -From: "john" -Reply-To: "Roundup issue tracker" +To: chef@bork.bork.bork, richard@test.test +From: "Contrary, Mary" +Reply-To: Roundup issue tracker + MIME-Version: 1.0 Message-Id: In-Reply-To: X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +X-Roundup-Issue-Files: unnamed Content-Transfer-Encoding: quoted-printable -john added the comment: - -This is a followup +Contrary, Mary added the comment: +umlaut =E4=F6=FC=C4=D6=DC=DF +File 'unnamed' not attached - you can download it from http://tracker.examp= +le/cgi-bin/roundup.cgi/bugs/file1. ---------- -nosy: +john status: unread -> chatting -_________________________________________________________________________ -"Roundup issue tracker" -http://your.tracker.url.example/issue1 -_________________________________________________________________________ +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ ''') - def testFollowupNoNosyAuthor(self): + def testMultipartCharsetUTF8AttachFile(self): + c = 'umlaut \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f' self.doNewIssue() - self.instance.config.ADD_AUTHOR_TO_NOSY = 'no' - message = cStringIO.StringIO('''Content-Type: text/plain; - charset="iso-8859-1" -From: john@test -To: issue_tracker@your.tracker.email.domain.example -Message-Id: -In-Reply-To: -Subject: [issue1] Testing... - -This is a followup -''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - handler.main(message) - - self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), + self._handle_mail(self.multipart_msg_latin1) + messages = self.db.issue.get('1', 'messages') + messages.sort() + msg = self.db.msg.getnode (messages[-1]) + self.assertEqual(len(msg.files), 1) + name = 'unnamed' + content = '' + c + '\n' + for n, id in enumerate (msg.files): + f = self.db.file.getnode (id) + self.assertEqual(f.name, name) + self.assertEqual(f.content, content) + self.assertEqual(msg.content, c) + self.compareMessages(self._get_mail(), '''FROM: roundup-admin@your.tracker.email.domain.example -TO: chef@bork.bork.bork, richard@test -Content-Type: text/plain +TO: chef@bork.bork.bork, richard@test.test +Content-Type: multipart/mixed; boundary="utf-8" Subject: [issue1] Testing... -To: chef@bork.bork.bork, richard@test -From: "john" -Reply-To: "Roundup issue tracker" +To: chef@bork.bork.bork, richard@test.test +From: "Contrary, Mary" +Reply-To: Roundup issue tracker + MIME-Version: 1.0 Message-Id: In-Reply-To: X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +X-Roundup-Issue-Files: unnamed Content-Transfer-Encoding: quoted-printable -john added the comment: +--utf-8 +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: quoted-printable -This is a followup +Contrary, Mary added the comment: + +umlaut =C3=A4=C3=B6=C3=BC=C3=84=C3=96=C3=9C=C3=9F ---------- status: unread -> chatting -_________________________________________________________________________ -"Roundup issue tracker" -http://your.tracker.url.example/issue1 -_________________________________________________________________________ -''') +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +--utf-8 +Content-Type: text/html +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; + filename="unnamed" - def testFollowupNoNosyRecipients(self): - self.doNewIssue() - self.instance.config.ADD_RECIPIENTS_TO_NOSY = 'no' - message = cStringIO.StringIO('''Content-Type: text/plain; - charset="iso-8859-1" -From: richard@test -To: issue_tracker@your.tracker.email.domain.example -Cc: john@test -Message-Id: -In-Reply-To: -Subject: [issue1] Testing... +PGh0bWw+dW1sYXV0IMOkw7bDvMOEw5bDnMOfPC9odG1sPgo= -This is a followup +--utf-8-- ''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - handler.main(message) - self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), + def testMultipartCharsetLatin1AttachFile(self): + c = 'umlaut \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f' + self.doNewIssue() + self.db.config.MAIL_CHARSET = 'iso-8859-1' + self._handle_mail(self.multipart_msg_latin1) + messages = self.db.issue.get('1', 'messages') + messages.sort() + msg = self.db.msg.getnode (messages[-1]) + self.assertEqual(len(msg.files), 1) + name = 'unnamed' + content = '' + c + '\n' + for n, id in enumerate (msg.files): + f = self.db.file.getnode (id) + self.assertEqual(f.name, name) + self.assertEqual(f.content, content) + self.assertEqual(msg.content, c) + self.compareMessages(self._get_mail(), '''FROM: roundup-admin@your.tracker.email.domain.example -TO: chef@bork.bork.bork -Content-Type: text/plain +TO: chef@bork.bork.bork, richard@test.test +Content-Type: multipart/mixed; boundary="utf-8" Subject: [issue1] Testing... -To: chef@bork.bork.bork -From: "richard" -Reply-To: "Roundup issue tracker" +To: chef@bork.bork.bork, richard@test.test +From: "Contrary, Mary" +Reply-To: Roundup issue tracker + MIME-Version: 1.0 Message-Id: In-Reply-To: X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +X-Roundup-Issue-Files: unnamed Content-Transfer-Encoding: quoted-printable -richard added the comment: +--utf-8 +MIME-Version: 1.0 +Content-Type: text/plain; charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + -This is a followup +Contrary, Mary added the comment: +umlaut =E4=F6=FC=C4=D6=DC=DF ---------- status: unread -> chatting -_________________________________________________________________________ -"Roundup issue tracker" -http://your.tracker.url.example/issue1 -_________________________________________________________________________ +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +--utf-8 +Content-Type: text/html +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; + filename="unnamed" + +PGh0bWw+dW1sYXV0IMOkw7bDvMOEw5bDnMOfPC9odG1sPgo= + +--utf-8-- ''') - def testNosyRemove(self): + def testMultipartRFC822(self): self.doNewIssue() - - message = cStringIO.StringIO('''Content-Type: text/plain; - charset="iso-8859-1" -From: richard -To: issue_tracker@your.tracker.email.domain.example + self._handle_mail(self.multipart_msg_rfc822) + messages = self.db.issue.get('1', 'messages') + messages.sort() + msg = self.db.msg.getnode (messages[-1]) + self.assertEqual(len(msg.files), 1) + name = "Fwd: Original email subject.eml" + for n, id in enumerate (msg.files): + f = self.db.file.getnode (id) + self.assertEqual(f.name, name) + self.assertEqual(msg.content, 'First part: Text') + self.compareMessages(self._get_mail(), +'''TO: chef@bork.bork.bork, richard@test.test +Content-Type: text/plain; charset="utf-8" +Subject: [issue1] Testing... +To: chef@bork.bork.bork, richard@test.test +From: "Contrary, Mary" +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 Message-Id: In-Reply-To: -Subject: [issue1] Testing... [nosy=-richard] +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +X-Roundup-Issue-Files: Fwd: Original email subject.eml +Content-Transfer-Encoding: quoted-printable -''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - handler.main(message) - l = self.db.issue.get('1', 'nosy') - l.sort() - self.assertEqual(l, ['3']) - # NO NOSY MESSAGE SHOULD BE SENT! - self.assert_(not os.path.exists(os.environ['SENDMAILDEBUG'])) +--utf-8 +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: quoted-printable - def testNewUserAuthor(self): - # first without the permission - # heh... just ignore the API for a second ;) - self.db.security.role['Anonymous'].permissions=[] - anonid = self.db.user.lookup('anonymous') - self.db.user.set(anonid, roles='Anonymous') - self.db.security.hasPermission('Email Registration', anonid) - l = self.db.user.list() - l.sort() - s = '''Content-Type: text/plain; +Contrary, Mary added the comment: + +First part: Text + +---------- +status: unread -> chatting + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +--utf-8 +Content-Type: message/rfc822 +MIME-Version: 1.0 +Content-Disposition: attachment; + filename="Fwd: Original email subject.eml" + +Message-Id: +In-Reply-To: +MIME-Version: 1.0 +Subject: Fwd: Original email subject +Date: Mon, 23 Aug 2010 08:23:33 +0200 +Content-Type: multipart/alternative; boundary="090500050101020406060002" + +This is a multi-part message in MIME format. +--090500050101020406060002 +Content-Type: text/plain; charset=ISO-8859-15; format=flowed +Content-Transfer-Encoding: 7bit + +some text in inner email +======================== + +--090500050101020406060002 +Content-Type: text/html; charset=ISO-8859-15 +Content-Transfer-Encoding: 7bit + + +some text in inner email +======================== + + +--090500050101020406060002-- + +--utf-8-- +''') + + def testMultipartRFC822Unpack(self): + self.doNewIssue() + self.db.config.MAILGW_UNPACK_RFC822 = True + self._handle_mail(self.multipart_msg_rfc822) + messages = self.db.issue.get('1', 'messages') + messages.sort() + msg = self.db.msg.getnode (messages[-1]) + self.assertEqual(len(msg.files), 2) + t = 'some text in inner email\n========================\n' + content = {0 : t, 1 : '\n' + t + '\n'} + for n, id in enumerate (msg.files): + f = self.db.file.getnode (id) + self.assertEqual(f.name, 'unnamed') + if n in content : + self.assertEqual(f.content, content [n]) + self.assertEqual(msg.content, 'First part: Text') + + def testSimpleFollowup(self): + self.doNewIssue() + self._handle_mail('''Content-Type: text/plain; charset="iso-8859-1" -From: fubar +From: mary To: issue_tracker@your.tracker.email.domain.example -Message-Id: -Subject: [issue] Testing... +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... -This is a test submission of a new issue. -''' - message = cStringIO.StringIO(s) - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - self.assertRaises(Unauthorized, handler.main, message) - m = self.db.user.list() - m.sort() - self.assertEqual(l, m) +This is a second followup +''') + self.compareMessages(self._get_mail(), +'''FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork, richard@test.test +Content-Type: text/plain; charset="utf-8" +Subject: [issue1] Testing... +To: chef@bork.bork.bork, richard@test.test +From: "Contrary, Mary" +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +Content-Transfer-Encoding: quoted-printable - # now with the permission - p = self.db.security.getPermission('Email Registration') - self.db.security.role['Anonymous'].permissions=[p] - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - message = cStringIO.StringIO(s) - handler.main(message) - m = self.db.user.list() - m.sort() - self.assertNotEqual(l, m) - def testEnc01(self): +Contrary, Mary added the comment: + +This is a second followup + +---------- +status: unread -> chatting + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +''') + + def testFollowup(self): self.doNewIssue() - message = cStringIO.StringIO('''Content-Type: text/plain; + + self._handle_mail('''Content-Type: text/plain; charset="iso-8859-1" -From: mary +From: richard To: issue_tracker@your.tracker.email.domain.example Message-Id: In-Reply-To: +Subject: [issue1] Testing... [assignedto=mary; nosy=+john] + +This is a followup +''') + l = self.db.issue.get('1', 'nosy') + l.sort() + self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id, + self.john_id]) + + self.compareMessages(self._get_mail(), +'''FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork, john@test.test, mary@test.test +Content-Type: text/plain; charset="utf-8" Subject: [issue1] Testing... -Content-Type: text/plain; - charset="iso-8859-1" +To: chef@bork.bork.bork, john@test.test, mary@test.test +From: richard +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting Content-Transfer-Encoding: quoted-printable -A message with encoding (encoded oe =F6) +richard added the comment: + +This is a followup + +---------- +assignedto: -> mary +nosy: +john, mary +status: unread -> chatting + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +''') + + def testFollowupNoSubjectChange(self): + self.db.config.MAILGW_SUBJECT_UPDATES_TITLE = 'no' + self.doNewIssue() + + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: [issue1] Wrzlbrmft... [assignedto=mary; nosy=+john] + +This is a followup ''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - handler.main(message) - self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), + l = self.db.issue.get('1', 'nosy') + l.sort() + self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id, + self.john_id]) + + self.compareMessages(self._get_mail(), '''FROM: roundup-admin@your.tracker.email.domain.example -TO: chef@bork.bork.bork, richard@test -Content-Type: text/plain +TO: chef@bork.bork.bork, john@test.test, mary@test.test +Content-Type: text/plain; charset="utf-8" Subject: [issue1] Testing... -To: chef@bork.bork.bork, richard@test -From: "mary" -Reply-To: "Roundup issue tracker" +To: chef@bork.bork.bork, john@test.test, mary@test.test +From: richard +Reply-To: Roundup issue tracker + MIME-Version: 1.0 Message-Id: In-Reply-To: X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting Content-Transfer-Encoding: quoted-printable -mary added the comment: +richard added the comment: -A message with encoding (encoded oe =F6) +This is a followup ---------- +assignedto: -> mary +nosy: +john, mary status: unread -> chatting -_________________________________________________________________________ -"Roundup issue tracker" -http://your.tracker.url.example/issue1 -_________________________________________________________________________ -''') +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +''') + self.assertEqual(self.db.issue.get('1','title'), 'Testing...') - def testMultipartEnc01(self): + def testFollowupExplicitSubjectChange(self): self.doNewIssue() - message = cStringIO.StringIO('''Content-Type: text/plain; + + self._handle_mail('''Content-Type: text/plain; charset="iso-8859-1" -From: mary +From: richard To: issue_tracker@your.tracker.email.domain.example Message-Id: In-Reply-To: +Subject: [issue1] Wrzlbrmft... [assignedto=mary; nosy=+john; title=new title] + +This is a followup +''') + l = self.db.issue.get('1', 'nosy') + l.sort() + self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id, + self.john_id]) + + self.compareMessages(self._get_mail(), +'''FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork, john@test.test, mary@test.test +Content-Type: text/plain; charset="utf-8" +Subject: [issue1] new title +To: chef@bork.bork.bork, john@test.test, mary@test.test +From: richard +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +Content-Transfer-Encoding: quoted-printable + + +richard added the comment: + +This is a followup + +---------- +assignedto: -> mary +nosy: +john, mary +status: unread -> chatting +title: Testing... -> new title + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +''') + + def testNosyGeneration(self): + self.db.issue.create(title='test') + + # create a nosy message + msg = self.db.msg.create(content='This is a test', + author=self.richard_id, messageid='') + self.db.journaltag = 'richard' + l = self.db.issue.create(title='test', messages=[msg], + nosy=[self.chef_id, self.mary_id, self.john_id]) + + self.compareMessages(self._get_mail(), +'''FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork, john@test.test, mary@test.test +Content-Type: text/plain; charset="utf-8" +Subject: [issue2] test +To: chef@bork.bork.bork, john@test.test, mary@test.test +From: richard +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 +Message-Id: +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: unread +Content-Transfer-Encoding: quoted-printable + + +New submission from richard : + +This is a test + +---------- +messages: 1 +nosy: Chef, john, mary, richard +status: unread +title: test + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +''') + + def testPropertyChangeOnly(self): + self.doNewIssue() + oldvalues = self.db.getnode('issue', '1').copy() + oldvalues['assignedto'] = None + # reconstruct old behaviour: This would reuse the + # database-handle from the doNewIssue above which has committed + # as user "Chef". So we close and reopen the db as that user. + #self.db.close() actually don't close 'cos this empties memorydb + self.db = self.instance.open('Chef') + self.db.issue.set('1', assignedto=self.chef_id) + self.db.commit() + self.db.issue.nosymessage('1', None, oldvalues) + + new_mail = "" + for line in self._get_mail().split("\n"): + if "Message-Id: " in line: + continue + if "Date: " in line: + continue + new_mail += line+"\n" + + self.compareMessages(new_mail, """ +FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork, richard@test.test +Content-Type: text/plain; charset="utf-8" Subject: [issue1] Testing... -Content-Type: multipart/mixed; - boundary="----_=_NextPart_000_01" +To: chef@bork.bork.bork, richard@test.test +From: "Bork, Chef" +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: unread +X-Roundup-Version: 1.3.3 +In-Reply-To: +MIME-Version: 1.0 +Reply-To: Roundup issue tracker + +Content-Transfer-Encoding: quoted-printable -This message is in MIME format. Since your mail reader does not understand -this format, some or all of this message may not be legible. -------_=_NextPart_000_01 -Content-Type: text/plain; - charset="iso-8859-1" +Change by Bork, Chef : + + +---------- +assignedto: -> Chef + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +""") + + + # + # FOLLOWUP TITLE MATCH + # + def testFollowupTitleMatch(self): + self.doNewIssue() + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Subject: Re: Testing... [assignedto=mary; nosy=+john] + +This is a followup +''') + self.compareMessages(self._get_mail(), +'''FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork, john@test.test, mary@test.test +Content-Type: text/plain; charset="utf-8" +Subject: [issue1] Testing... +To: chef@bork.bork.bork, john@test.test, mary@test.test +From: richard +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting Content-Transfer-Encoding: quoted-printable -A message with first part encoded (encoded oe =F6) +richard added the comment: + +This is a followup + +---------- +assignedto: -> mary +nosy: +john, mary +status: unread -> chatting + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +''') + + def testFollowupTitleMatchMultiRe(self): + nodeid1 = self.doNewIssue() + nodeid2 = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Subject: Re: Testing... [assignedto=mary; nosy=+john] + +This is a followup +''') + + nodeid3 = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Subject: Ang: Re: Testing... + +This is a followup +''') + self.assertEqual(nodeid1, nodeid2) + self.assertEqual(nodeid1, nodeid3) + + def testFollowupTitleMatchNever(self): + nodeid = self.doNewIssue() + self.db.config.MAILGW_SUBJECT_CONTENT_MATCH = 'never' + self.assertNotEqual(self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Subject: Re: Testing... + +This is a followup +'''), nodeid) + + def testFollowupTitleMatchNeverInterval(self): + nodeid = self.doNewIssue() + # force failure of the interval + time.sleep(2) + self.db.config.MAILGW_SUBJECT_CONTENT_MATCH = 'creation 00:00:01' + self.assertNotEqual(self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Subject: Re: Testing... + +This is a followup +'''), nodeid) + + + def testFollowupTitleMatchInterval(self): + nodeid = self.doNewIssue() + self.db.config.MAILGW_SUBJECT_CONTENT_MATCH = 'creation +1d' + self.assertEqual(self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Subject: Re: Testing... + +This is a followup +'''), nodeid) + + + def testFollowupNosyAuthor(self): + self.doNewIssue() + self.db.config.ADD_AUTHOR_TO_NOSY = 'yes' + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: john@test.test +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... + +This is a followup ''') - handler = self.instance.MailGW(self.instance, self.db) - handler.trapExceptions = 0 - handler.main(message) - self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), + + self.compareMessages(self._get_mail(), '''FROM: roundup-admin@your.tracker.email.domain.example -TO: chef@bork.bork.bork, richard@test -Content-Type: text/plain +TO: chef@bork.bork.bork, richard@test.test +Content-Type: text/plain; charset="utf-8" Subject: [issue1] Testing... -To: chef@bork.bork.bork, richard@test -From: "mary" -Reply-To: "Roundup issue tracker" +To: chef@bork.bork.bork, richard@test.test +From: John Doe +Reply-To: Roundup issue tracker + MIME-Version: 1.0 Message-Id: In-Reply-To: X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting Content-Transfer-Encoding: quoted-printable -mary added the comment: +John Doe added the comment: -A message with first part encoded (encoded oe =F6) +This is a followup ---------- +nosy: +john status: unread -> chatting -_________________________________________________________________________ -"Roundup issue tracker" -http://your.tracker.url.example/issue1 -_________________________________________________________________________ + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ + ''') -def suite(): - l = [unittest.makeSuite(MailgwTestCase), - ] - return unittest.TestSuite(l) + def testFollowupNosyRecipients(self): + self.doNewIssue() + self.db.config.ADD_RECIPIENTS_TO_NOSY = 'yes' + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard@test.test +To: issue_tracker@your.tracker.email.domain.example +Cc: john@test.test +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... + +This is a followup +''') + self.compareMessages(self._get_mail(), +'''FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork +Content-Type: text/plain; charset="utf-8" +Subject: [issue1] Testing... +To: chef@bork.bork.bork +From: richard +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +Content-Transfer-Encoding: quoted-printable + + +richard added the comment: + +This is a followup + +---------- +nosy: +john +status: unread -> chatting + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ + +''') + + def testFollowupNosyAuthorAndCopy(self): + self.doNewIssue() + self.db.config.ADD_AUTHOR_TO_NOSY = 'yes' + self.db.config.MESSAGES_TO_AUTHOR = 'yes' + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: john@test.test +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... + +This is a followup +''') + self.compareMessages(self._get_mail(), +'''FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork, john@test.test, richard@test.test +Content-Type: text/plain; charset="utf-8" +Subject: [issue1] Testing... +To: chef@bork.bork.bork, john@test.test, richard@test.test +From: John Doe +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +Content-Transfer-Encoding: quoted-printable + + +John Doe added the comment: + +This is a followup + +---------- +nosy: +john +status: unread -> chatting + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ + +''') + + def testFollowupNoNosyAuthor(self): + self.doNewIssue() + self.instance.config.ADD_AUTHOR_TO_NOSY = 'no' + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: john@test.test +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... + +This is a followup +''') + self.compareMessages(self._get_mail(), +'''FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork, richard@test.test +Content-Type: text/plain; charset="utf-8" +Subject: [issue1] Testing... +To: chef@bork.bork.bork, richard@test.test +From: John Doe +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +Content-Transfer-Encoding: quoted-printable + + +John Doe added the comment: + +This is a followup + +---------- +status: unread -> chatting + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ + +''') + + def testFollowupNoNosyRecipients(self): + self.doNewIssue() + self.instance.config.ADD_RECIPIENTS_TO_NOSY = 'no' + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard@test.test +To: issue_tracker@your.tracker.email.domain.example +Cc: john@test.test +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... + +This is a followup +''') + self.compareMessages(self._get_mail(), +'''FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork +Content-Type: text/plain; charset="utf-8" +Subject: [issue1] Testing... +To: chef@bork.bork.bork +From: richard +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +Content-Transfer-Encoding: quoted-printable + + +richard added the comment: + +This is a followup + +---------- +status: unread -> chatting + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ + +''') + + def testFollowupEmptyMessage(self): + self.doNewIssue() + + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... [assignedto=mary; nosy=+john] + +''') + l = self.db.issue.get('1', 'nosy') + l.sort() + self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id, + self.john_id]) + + # should be no file created (ie. no message) + assert not os.path.exists(SENDMAILDEBUG) + + def testFollowupEmptyMessageNoSubject(self): + self.doNewIssue() + + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: [issue1] [assignedto=mary; nosy=+john] + +''') + l = self.db.issue.get('1', 'nosy') + l.sort() + self.assertEqual(l, [self.chef_id, self.richard_id, self.mary_id, + self.john_id]) + + # should be no file created (ie. no message) + assert not os.path.exists(SENDMAILDEBUG) + + def testNosyRemove(self): + self.doNewIssue() + + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... [nosy=-richard] + +''') + l = self.db.issue.get('1', 'nosy') + l.sort() + self.assertEqual(l, [self.chef_id]) + + # NO NOSY MESSAGE SHOULD BE SENT! + assert not os.path.exists(SENDMAILDEBUG) + + def testNewUserAuthor(self): + self.db.commit() + l = self.db.user.list() + l.sort() + message = '''Content-Type: text/plain; + charset="iso-8859-1" +From: fubar +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Subject: [issue] Testing... + +This is a test submission of a new issue. +''' + self.db.security.role['anonymous'].permissions=[] + anonid = self.db.user.lookup('anonymous') + self.db.user.set(anonid, roles='Anonymous') + try: + self._handle_mail(message) + except Unauthorized, value: + body_diff = self.compareMessages(str(value), """ +You are not a registered user. + +Unknown address: fubar@bork.bork.bork +""") + assert not body_diff, body_diff + else: + raise AssertionError, "Unathorized not raised when handling mail" + + # Add Web Access role to anonymous, and try again to make sure + # we get a "please register at:" message this time. + p = [ + self.db.security.getPermission('Register', 'user'), + self.db.security.getPermission('Web Access', None), + ] + self.db.security.role['anonymous'].permissions=p + try: + self._handle_mail(message) + except Unauthorized, value: + body_diff = self.compareMessages(str(value), """ +You are not a registered user. Please register at: + +http://tracker.example/cgi-bin/roundup.cgi/bugs/user?template=register + +...before sending mail to the tracker. + +Unknown address: fubar@bork.bork.bork +""") + assert not body_diff, body_diff + else: + raise AssertionError, "Unauthorized not raised when handling mail" + + # Make sure list of users is the same as before. + m = self.db.user.list() + m.sort() + self.assertEqual(l, m) + + # now with the permission + p = [ + self.db.security.getPermission('Register', 'user'), + self.db.security.getPermission('Email Access', None), + ] + self.db.security.role['anonymous'].permissions=p + self._handle_mail(message) + m = self.db.user.list() + m.sort() + self.assertNotEqual(l, m) + + def testNewUserAuthorEncodedName(self): + l = set(self.db.user.list()) + # From: name has Euro symbol in it + message = '''Content-Type: text/plain; + charset="iso-8859-1" +From: =?utf8?b?SOKCrGxsbw==?= +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Subject: [issue] Testing... + +This is a test submission of a new issue. +''' + p = [ + self.db.security.getPermission('Register', 'user'), + self.db.security.getPermission('Email Access', None), + self.db.security.getPermission('Create', 'issue'), + self.db.security.getPermission('Create', 'msg'), + ] + self.db.security.role['anonymous'].permissions = p + self._handle_mail(message) + m = set(self.db.user.list()) + new = list(m - l)[0] + name = self.db.user.get(new, 'realname') + self.assertEquals(name, 'H€llo') + + def testNewUserAuthorMixedEncodedName(self): + l = set(self.db.user.list()) + # From: name has Euro symbol in it + message = '''Content-Type: text/plain; + charset="iso-8859-1" +From: Firstname =?utf-8?b?w6TDtsOf?= Last +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Subject: [issue] Test =?utf-8?b?w4TDlsOc?= umlauts + X1 + X2 + +This is a test submission of a new issue. +''' + p = [ + self.db.security.getPermission('Register', 'user'), + self.db.security.getPermission('Email Access', None), + self.db.security.getPermission('Create', 'issue'), + self.db.security.getPermission('Create', 'msg'), + ] + self.db.security.role['anonymous'].permissions = p + self._handle_mail(message) + title = self.db.issue.get('1', 'title') + self.assertEquals(title, 'Test \xc3\x84\xc3\x96\xc3\x9c umlauts X1 X2') + m = set(self.db.user.list()) + new = list(m - l)[0] + name = self.db.user.get(new, 'realname') + self.assertEquals(name, 'Firstname \xc3\xa4\xc3\xb6\xc3\x9f Last') + + def testUnknownUser(self): + l = set(self.db.user.list()) + message = '''Content-Type: text/plain; + charset="iso-8859-1" +From: Nonexisting User +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Subject: [issue] Testing nonexisting user... + +This is a test submission of a new issue. +''' + # trap_exc=1: we want a bounce message: + ret = self._handle_mail(message, trap_exc=1) + self.compareMessages(self._get_mail(), +'''FROM: Roundup issue tracker +TO: nonexisting@bork.bork.bork +From nobody Tue Jul 14 12:04:11 2009 +Content-Type: multipart/mixed; boundary="===============0639262320==" +MIME-Version: 1.0 +Subject: Failed issue tracker submission +To: nonexisting@bork.bork.bork +From: Roundup issue tracker +Date: Tue, 14 Jul 2009 12:04:11 +0000 +Precedence: bulk +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Version: 1.4.8 +MIME-Version: 1.0 + +--===============0639262320== +Content-Type: text/plain; charset="us-ascii" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit + + + +You are not a registered user. Please register at: + +http://tracker.example/cgi-bin/roundup.cgi/bugs/user?template=register + +...before sending mail to the tracker. + +Unknown address: nonexisting@bork.bork.bork + +--===============0639262320== +Content-Type: text/plain; charset="us-ascii" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit + +Content-Type: text/plain; + charset="iso-8859-1" +From: Nonexisting User +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Subject: [issue] Testing nonexisting user... + +This is a test submission of a new issue. + +--===============0639262320==-- +''') + + def testEnc01(self): + self.db.user.set(self.mary_id, + realname='\xe4\xf6\xfc\xc4\xd6\xdc\xdf, Mary'.decode + ('latin-1').encode('utf-8')) + self.doNewIssue() + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: mary +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... +Content-Type: text/plain; + charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + +A message with encoding (encoded oe =F6) + +''') + self.compareMessages(self._get_mail(), +'''FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork, richard@test.test +Content-Type: text/plain; charset="utf-8" +Subject: [issue1] Testing... +To: chef@bork.bork.bork, richard@test.test +From: =?utf-8?b?w6TDtsO8w4TDlsOcw58sIE1hcnk=?= + +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +Content-Transfer-Encoding: quoted-printable + + +=C3=A4=C3=B6=C3=BC=C3=84=C3=96=C3=9C=C3=9F, Mary added the= + comment: + +A message with encoding (encoded oe =C3=B6) + +---------- +status: unread -> chatting + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +''') + + def testEncNonUTF8(self): + self.doNewIssue() + self.instance.config.EMAIL_CHARSET = 'iso-8859-1' + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: mary +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... +Content-Type: text/plain; + charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + +A message with encoding (encoded oe =F6) + +''') + self.compareMessages(self._get_mail(), +'''FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork, richard@test.test +Content-Type: text/plain; charset="iso-8859-1" +Subject: [issue1] Testing... +To: chef@bork.bork.bork, richard@test.test +From: "Contrary, Mary" +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +Content-Transfer-Encoding: quoted-printable + + +Contrary, Mary added the comment: + +A message with encoding (encoded oe =F6) + +---------- +status: unread -> chatting + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +''') + + + def testMultipartEnc01(self): + self.doNewIssue() + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: mary +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... +Content-Type: multipart/mixed; + boundary="----_=_NextPart_000_01" + +This message is in MIME format. Since your mail reader does not understand +this format, some or all of this message may not be legible. + +------_=_NextPart_000_01 +Content-Type: text/plain; + charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + +A message with first part encoded (encoded oe =F6) + +''') + self.compareMessages(self._get_mail(), +'''FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork, richard@test.test +Content-Type: text/plain; charset="utf-8" +Subject: [issue1] Testing... +To: chef@bork.bork.bork, richard@test.test +From: "Contrary, Mary" +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +Content-Transfer-Encoding: quoted-printable + + +Contrary, Mary added the comment: + +A message with first part encoded (encoded oe =C3=B6) + +---------- +status: unread -> chatting + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +''') + + def testContentDisposition(self): + self.doNewIssue() + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: mary +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... +Content-Type: multipart/mixed; boundary="bCsyhTFzCvuiizWE" +Content-Disposition: inline + + +--bCsyhTFzCvuiizWE +Content-Type: text/plain; charset=us-ascii +Content-Disposition: inline + +test attachment binary + +--bCsyhTFzCvuiizWE +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="main.dvi" +Content-Transfer-Encoding: base64 + +SnVzdCBhIHRlc3QgAQo= + +--bCsyhTFzCvuiizWE-- +''') + messages = self.db.issue.get('1', 'messages') + messages.sort() + file = self.db.file.getnode (self.db.msg.get(messages[-1], 'files')[0]) + self.assertEqual(file.name, 'main.dvi') + self.assertEqual(file.content, 'Just a test \001\n') + + def testFollowupStupidQuoting(self): + self.doNewIssue() + + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: Re: "[issue1] Testing... " + +This is a followup +''') + self.compareMessages(self._get_mail(), +'''FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork +Content-Type: text/plain; charset="utf-8" +Subject: [issue1] Testing... +To: chef@bork.bork.bork +From: richard +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +Content-Transfer-Encoding: quoted-printable + + +richard added the comment: + +This is a followup + +---------- +status: unread -> chatting + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +''') + + def testEmailQuoting(self): + self.instance.config.EMAIL_KEEP_QUOTED_TEXT = 'no' + self.innerTestQuoting('''This is a followup +''') + + def testEmailQuotingRemove(self): + self.instance.config.EMAIL_KEEP_QUOTED_TEXT = 'yes' + self.innerTestQuoting('''Blah blah wrote: +> Blah bklaskdfj sdf asdf jlaskdf skj sdkfjl asdf +> skdjlkjsdfalsdkfjasdlfkj dlfksdfalksd fj +> + +This is a followup +''') + + def innerTestQuoting(self, expect): + nodeid = self.doNewIssue() + + messages = self.db.issue.get(nodeid, 'messages') + + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: Re: [issue1] Testing... + +Blah blah wrote: +> Blah bklaskdfj sdf asdf jlaskdf skj sdkfjl asdf +> skdjlkjsdfalsdkfjasdlfkj dlfksdfalksd fj +> + +This is a followup +''') + # figure the new message id + newmessages = self.db.issue.get(nodeid, 'messages') + for msg in messages: + newmessages.remove(msg) + messageid = newmessages[0] + + self.compareMessages(self.db.msg.get(messageid, 'content'), expect) + + def testUserLookup(self): + i = self.db.user.create(username='user1', address='user1@foo.com') + self.assertEqual(uidFromAddress(self.db, ('', 'user1@foo.com'), 0), i) + self.assertEqual(uidFromAddress(self.db, ('', 'USER1@foo.com'), 0), i) + i = self.db.user.create(username='user2', address='USER2@foo.com') + self.assertEqual(uidFromAddress(self.db, ('', 'USER2@foo.com'), 0), i) + self.assertEqual(uidFromAddress(self.db, ('', 'user2@foo.com'), 0), i) + + def testUserAlternateLookup(self): + i = self.db.user.create(username='user1', address='user1@foo.com', + alternate_addresses='user1@bar.com') + self.assertEqual(uidFromAddress(self.db, ('', 'user1@bar.com'), 0), i) + self.assertEqual(uidFromAddress(self.db, ('', 'USER1@bar.com'), 0), i) + + def testUserAlternateSubstringNomatch(self): + i = self.db.user.create(username='user1', address='user1@foo.com', + alternate_addresses='x-user1@bar.com') + self.assertEqual(uidFromAddress(self.db, ('', 'user1@bar.com'), 0), 0) + self.assertEqual(uidFromAddress(self.db, ('', 'USER1@bar.com'), 0), 0) + + def testUserCreate(self): + i = uidFromAddress(self.db, ('', 'user@foo.com'), 1) + self.assertNotEqual(uidFromAddress(self.db, ('', 'user@bar.com'), 1), i) + + def testRFC2822(self): + ascii_header = "[issue243] This is a \"test\" - with 'quotation' marks" + unicode_header = '[issue244] \xd0\xb0\xd0\xbd\xd0\xb4\xd1\x80\xd0\xb5\xd0\xb9' + unicode_encoded = '=?utf-8?q?[issue244]_=D0=B0=D0=BD=D0=B4=D1=80=D0=B5=D0=B9?=' + self.assertEqual(rfc2822.encode_header(ascii_header), ascii_header) + self.assertEqual(rfc2822.encode_header(unicode_header), unicode_encoded) + + def testRegistrationConfirmation(self): + otk = "Aj4euk4LZSAdwePohj90SME5SpopLETL" + self.db.getOTKManager().set(otk, username='johannes') + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Cc: richard@test.test +Message-Id: +Subject: Re: Complete your registration to Roundup issue tracker + -- key %s + +This is a test confirmation of registration. +''' % otk) + self.db.user.lookup('johannes') + + def testFollowupOnNonIssue(self): + self.db.keyword.create(name='Foo') + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: [keyword1] Testing... [name=Bar] + +''') + self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar') + + def testResentFrom(self): + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +Resent-From: mary +To: issue_tracker@your.tracker.email.domain.example +Cc: richard@test.test +Message-Id: +Subject: [issue] Testing... + +This is a test submission of a new issue. +''') + assert not os.path.exists(SENDMAILDEBUG) + l = self.db.issue.get(nodeid, 'nosy') + l.sort() + self.assertEqual(l, [self.richard_id, self.mary_id]) + return nodeid + + def testDejaVu(self): + self.assertRaises(IgnoreLoop, self._handle_mail, + '''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +X-Roundup-Loop: hello +To: issue_tracker@your.tracker.email.domain.example +Cc: richard@test.test +Message-Id: +Subject: Re: [issue] Testing... + +Hi, I've been mis-configured to loop messages back to myself. +''') + + def testItsBulkStupid(self): + self.assertRaises(IgnoreBulk, self._handle_mail, + '''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +Precedence: bulk +To: issue_tracker@your.tracker.email.domain.example +Cc: richard@test.test +Message-Id: +Subject: Re: [issue] Testing... + +Hi, I'm on holidays, and this is a dumb auto-responder. +''') + + def testAutoReplyEmailsAreIgnored(self): + self.assertRaises(IgnoreBulk, self._handle_mail, + '''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Cc: richard@test.test +Message-Id: +Subject: Re: [issue] Out of office AutoReply: Back next week + +Hi, I am back in the office next week +''') + + def testNoSubject(self): + self.assertRaises(MailUsageError, self._handle_mail, + '''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + + # + # TEST FOR INVALID DESIGNATOR HANDLING + # + def testInvalidDesignator(self): + self.assertRaises(MailUsageError, self._handle_mail, + '''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: [frobulated] testing +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + self.assertRaises(MailUsageError, self._handle_mail, + '''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: [issue12345] testing +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + + def testInvalidClassLoose(self): + self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose' + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: [frobulated] testing +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + assert not os.path.exists(SENDMAILDEBUG) + self.assertEqual(self.db.issue.get(nodeid, 'title'), + '[frobulated] testing') + + def testInvalidClassLooseReply(self): + self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose' + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: Re: [frobulated] testing +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + assert not os.path.exists(SENDMAILDEBUG) + self.assertEqual(self.db.issue.get(nodeid, 'title'), + '[frobulated] testing') + + def testInvalidClassLoose(self): + self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose' + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: [issue1234] testing +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + assert not os.path.exists(SENDMAILDEBUG) + self.assertEqual(self.db.issue.get(nodeid, 'title'), + '[issue1234] testing') + + def testClassLooseOK(self): + self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose' + self.db.keyword.create(name='Foo') + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: [keyword1] Testing... [name=Bar] +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + assert not os.path.exists(SENDMAILDEBUG) + self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar') + + def testClassStrictInvalid(self): + self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'strict' + self.instance.config.MAILGW_DEFAULT_CLASS = '' + + message = '''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: Testing... +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''' + self.assertRaises(MailUsageError, self._handle_mail, message) + + def testClassStrictValid(self): + self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'strict' + self.instance.config.MAILGW_DEFAULT_CLASS = '' + + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: [issue] Testing... +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + + assert not os.path.exists(SENDMAILDEBUG) + self.assertEqual(self.db.issue.get(nodeid, 'title'), 'Testing...') + + # + # TEST FOR INVALID COMMANDS HANDLING + # + def testInvalidCommands(self): + self.assertRaises(MailUsageError, self._handle_mail, + '''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: testing [frobulated] +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + + def testInvalidCommandPassthrough(self): + self.instance.config.MAILGW_SUBJECT_SUFFIX_PARSING = 'none' + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: testing [frobulated] +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + assert not os.path.exists(SENDMAILDEBUG) + self.assertEqual(self.db.issue.get(nodeid, 'title'), + 'testing [frobulated]') + + def testInvalidCommandPassthroughLoose(self): + self.instance.config.MAILGW_SUBJECT_SUFFIX_PARSING = 'loose' + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: testing [frobulated] +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + assert not os.path.exists(SENDMAILDEBUG) + self.assertEqual(self.db.issue.get(nodeid, 'title'), + 'testing [frobulated]') + + def testInvalidCommandPassthroughLooseOK(self): + self.instance.config.MAILGW_SUBJECT_SUFFIX_PARSING = 'loose' + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: testing [assignedto=mary] +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + assert not os.path.exists(SENDMAILDEBUG) + self.assertEqual(self.db.issue.get(nodeid, 'title'), 'testing') + self.assertEqual(self.db.issue.get(nodeid, 'assignedto'), self.mary_id) + + def testCommandDelimiters(self): + self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '{}' + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: testing {assignedto=mary} +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + assert not os.path.exists(SENDMAILDEBUG) + self.assertEqual(self.db.issue.get(nodeid, 'title'), 'testing') + self.assertEqual(self.db.issue.get(nodeid, 'assignedto'), self.mary_id) + + def testPrefixDelimiters(self): + self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '{}' + self.db.keyword.create(name='Foo') + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: {keyword1} Testing... {name=Bar} + +''') + assert not os.path.exists(SENDMAILDEBUG) + self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar') + + def testCommandDelimitersIgnore(self): + self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '{}' + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: testing [assignedto=mary] +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + assert not os.path.exists(SENDMAILDEBUG) + self.assertEqual(self.db.issue.get(nodeid, 'title'), + 'testing [assignedto=mary]') + self.assertEqual(self.db.issue.get(nodeid, 'assignedto'), None) + + def testReplytoMatch(self): + self.instance.config.MAILGW_SUBJECT_PREFIX_PARSING = 'loose' + nodeid = self.doNewIssue() + nodeid2 = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: Testing... + +Followup message. +''') + + nodeid3 = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: Testing... + +Yet another message in the same thread/issue. +''') + + self.assertEqual(nodeid, nodeid2) + self.assertEqual(nodeid, nodeid3) + + def testHelpSubject(self): + message = '''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: hElp + + +''' + self.assertRaises(MailUsageHelp, self._handle_mail, message) + + def testMaillistSubject(self): + self.instance.config.MAILGW_SUBJECT_SUFFIX_DELIMITERS = '[]' + self.db.keyword.create(name='Foo') + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: [mailinglist-name] [keyword1] Testing.. [name=Bar] +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + + assert not os.path.exists(SENDMAILDEBUG) + self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar') + + def testUnknownPrefixSubject(self): + self.db.keyword.create(name='Foo') + self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: VeryStrangeRe: [keyword1] Testing.. [name=Bar] +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''') + + assert not os.path.exists(SENDMAILDEBUG) + self.assertEqual(self.db.keyword.get('1', 'name'), 'Bar') + + def testOneCharSubject(self): + message = '''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Subject: b +Cc: richard@test.test +Reply-To: chef@bork.bork.bork +Message-Id: + +''' + try: + self._handle_mail(message) + except MailUsageError: + self.fail('MailUsageError raised') + + def testIssueidLast(self): + nodeid1 = self.doNewIssue() + nodeid2 = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: mary +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +In-Reply-To: +Subject: New title [issue1] + +This is a second followup +''') + + assert nodeid1 == nodeid2 + self.assertEqual(self.db.issue.get(nodeid2, 'title'), "Testing...") + + def testSecurityMessagePermissionContent(self): + id = self.doNewIssue() + issue = self.db.issue.getnode (id) + self.db.security.addRole(name='Nomsg') + self.db.security.addPermissionToRole('Nomsg', 'Email Access') + for cl in 'issue', 'file', 'keyword': + for p in 'View', 'Edit', 'Create': + self.db.security.addPermissionToRole('Nomsg', p, cl) + self.db.user.set(self.mary_id, roles='Nomsg') + nodeid = self._handle_mail('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Subject: [issue%(id)s] Testing... [nosy=+mary] + +Just a test reply +'''%locals()) + assert os.path.exists(SENDMAILDEBUG) + self.compareMessages(self._get_mail(), +'''FROM: roundup-admin@your.tracker.email.domain.example +TO: chef@bork.bork.bork, richard@test.test +Content-Type: text/plain; charset="utf-8" +Subject: [issue1] Testing... +To: richard@test.test +From: "Bork, Chef" +Reply-To: Roundup issue tracker + +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +X-Roundup-Loop: hello +X-Roundup-Issue-Status: chatting +Content-Transfer-Encoding: quoted-printable + + +Bork, Chef added the comment: + +Just a test reply + +---------- +nosy: +mary +status: unread -> chatting + +_______________________________________________________________________ +Roundup issue tracker + +_______________________________________________________________________ +''') + + def testOutlookAttachment(self): + message = '''X-MimeOLE: Produced By Microsoft Exchange V6.5 +Content-class: urn:content-classes:message +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="----_=_NextPart_001_01CACA65.40A51CBC" +Subject: Example of a failed outlook attachment e-mail +Date: Tue, 23 Mar 2010 01:43:44 -0700 +Message-ID: +X-MS-Has-Attach: yes +X-MS-TNEF-Correlator: +Thread-Topic: Example of a failed outlook attachment e-mail +Thread-Index: AcrKJo/t3pUBBwTpSwWNE3LE67UBDQ== +From: "Hugh" +To: +X-OriginalArrivalTime: 23 Mar 2010 08:45:57.0350 (UTC) FILETIME=[41893860:01CACA65] + +This is a multi-part message in MIME format. + +------_=_NextPart_001_01CACA65.40A51CBC +Content-Type: multipart/alternative; + boundary="----_=_NextPart_002_01CACA65.40A51CBC" + + +------_=_NextPart_002_01CACA65.40A51CBC +Content-Type: text/plain; + charset="us-ascii" +Content-Transfer-Encoding: quoted-printable + + +Hi Richard, + +I suppose this isn't the exact message that was sent but is a resend of +one of my trial messages that failed. For your benefit I changed the +subject line and am adding these words to the message body. Should +still be as problematic, but if you like I can resend an exact copy of a +failed message changing nothing except putting your address instead of +our tracker. + +Thanks very much for taking time to look into this. Much appreciated. + + <>=20 + +------_=_NextPart_002_01CACA65.40A51CBC +Content-Type: text/html; + charset="us-ascii" +Content-Transfer-Encoding: quoted-printable + + + + + + +Example of a failed outlook attachment e-mail + + + +
+ +

Hi Richard, +

+ +

I suppose this isn't the exact message = +that was sent but is a resend of one of my trial messages that = +failed.  For your benefit I changed the subject line and am adding = +these words to the message body.  Should still be as problematic, = +but if you like I can resend an exact copy of a failed message changing = +nothing except putting your address instead of our tracker.

+ +

Thanks very much for taking time to = +look into this.  Much appreciated. +

+
+ +

<<battery = +backup>> +

+ + + +------_=_NextPart_002_01CACA65.40A51CBC-- + +------_=_NextPart_001_01CACA65.40A51CBC +Content-Type: message/rfc822 +Content-Transfer-Encoding: 7bit + +X-MimeOLE: Produced By Microsoft Exchange V6.5 +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="----_=_NextPart_003_01CAC15A.29717800" +X-OriginalArrivalTime: 11 Mar 2010 20:33:51.0249 (UTC) FILETIME=[28FEE010:01CAC15A] +Content-class: urn:content-classes:message +Subject: battery backup +Date: Thu, 11 Mar 2010 13:33:43 -0700 +Message-ID: +X-MS-Has-Attach: +X-MS-TNEF-Correlator: +Thread-Topic: battery backup +Thread-Index: AcrBWimtulTrSvBdQ2CcfZ8lyQdxmQ== +From: "Jerry" +To: "Hugh" + +This is a multi-part message in MIME format. + +------_=_NextPart_003_01CAC15A.29717800 +Content-Type: text/plain; + charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + +Dear Hugh, + A car batter has an energy capacity of ~ 500Wh. A UPS=20 +battery is worse than this. + +if we need to provied 100kW for 30 minutes that will take 100 car=20 +batteries. This seems like an awful lot of batteries. + +Of course I like your idea of making the time 1 minute, so we get to=20 +a more modest number of batteries + +Jerry + + +------_=_NextPart_003_01CAC15A.29717800 +Content-Type: text/html; + charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + + + + + + +battery backup + + + + +

Dear Hugh, + +
        A car = +batter has an energy capacity of ~ 500Wh.  A UPS + +
battery is worse than this. +

+ +

if we need to provied 100kW for 30 minutes that will = +take 100 car + +
batteries.  This seems like an awful lot of = +batteries. +

+ +

Of course I like your idea of making the time 1 = +minute, so we get to + +
a more modest number of batteries +

+ +

Jerry +

+ + + +------_=_NextPart_003_01CAC15A.29717800-- + +------_=_NextPart_001_01CACA65.40A51CBC-- +''' + nodeid = self._handle_mail(message) + assert not os.path.exists(SENDMAILDEBUG) + msgid = self.db.issue.get(nodeid, 'messages')[0] + self.assert_(self.db.msg.get(msgid, 'content').startswith('Hi Richard')) + self.assertEqual(self.db.msg.get(msgid, 'files'), ['1', '2']) + fileid = self.db.msg.get(msgid, 'files')[0] + self.assertEqual(self.db.file.get(fileid, 'type'), 'text/html') + fileid = self.db.msg.get(msgid, 'files')[1] + self.assertEqual(self.db.file.get(fileid, 'type'), 'message/rfc822') + + def testForwardedMessageAttachment(self): + message = '''Return-Path: +Received: from localhost(127.0.0.1), claiming to be "[115.130.26.69]" +via SMTP by localhost, id smtpdAAApLaWrq; Tue Apr 13 23:10:05 2010 +Message-ID: <4BC4F9C7.50409@test.test> +Date: Wed, 14 Apr 2010 09:09:59 +1000 +From: Rupert Goldie +User-Agent: Thunderbird 2.0.0.24 (Windows/20100228) +MIME-Version: 1.0 +To: ekit issues +Subject: [Fwd: PHP ERROR (fb)] post limit reached +Content-Type: multipart/mixed; boundary="------------000807090608060304010403" + +This is a multi-part message in MIME format. +--------------000807090608060304010403 +Content-Type: text/plain; charset=ISO-8859-1; format=flowed +Content-Transfer-Encoding: 7bit + +Catch this exception and log it without emailing. + +--------------000807090608060304010403 +Content-Type: message/rfc822; name="PHP ERROR (fb).eml" +Content-Transfer-Encoding: 7bit +Content-Disposition: inline; filename="PHP ERROR (fb).eml" + +Return-Path: +X-Sieve: CMU Sieve 2.2 +via SMTP by crown.off.ekorp.com, id smtpdAAA1JaW1o; Tue Apr 13 23:01:04 2010 +X-Virus-Scanned: by amavisd-new at ekit.com +To: facebook-errors@test.test +From: ektravj@test.test +Subject: PHP ERROR (fb) +Message-Id: <20100413230100.D601D27E84@mail2.elax3.ekorp.com> +Date: Tue, 13 Apr 2010 23:01:00 +0000 (UTC) + +[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 +Stack trace: +#0 /app/01/www/virtual/fb.ekit.com/htdocs/gateway/ekit/feed/index.php(178): fb_exceptions(Object(FacebookRestClientException)) +#1 {main} + thrown in /app/01/www/virtual/fb.ekit.com/htdocs/includes/functions.php on line 280 + + +--------------000807090608060304010403-- +''' + nodeid = self._handle_mail(message) + assert not os.path.exists(SENDMAILDEBUG) + msgid = self.db.issue.get(nodeid, 'messages')[0] + self.assertEqual(self.db.msg.get(msgid, 'content'), + 'Catch this exception and log it without emailing.') + self.assertEqual(self.db.msg.get(msgid, 'files'), ['1']) + fileid = self.db.msg.get(msgid, 'files')[0] + self.assertEqual(self.db.file.get(fileid, 'type'), 'message/rfc822') + +pgp_test_key = """ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +lQOYBE6NqtsBCADG3UUMYxjwUOpDDVvr0Y8qkvKsgdF79en1zfHtRYlmZc+EJxg8 +53CCFGReQWJwOjyP3/SLJwJqfiPR7MAYAqJsm/4U2lxF7sIlEnlrRpFuvB625KOQ +oedCkI4nLa+4QAXHxVX2qLx7es3r2JAoitZLX7ZtUB7qGSRh98DmdAgCY3CFN7iZ +w6xpvIU+LNbsHSo1sf8VP6z7NHQFacgrVvLyRJ4C5lTPU42iM5E6HKxYFExNV3Rn ++2G0bsuiifHV6nJQD73onjwcC6tU97W779dllHlhG3SSP0KlnwmCCvPMlQvROk0A +rLyzKWcUpZwK1aLRYByjFMH9WYXRkhf08bkDABEBAAEAB/9dcmSb6YUyiBNM5t4m +9hZcXykBvw79PRVvmBLy+BYUtArLgsN0+xx3Q7XWRMtJCVSkFw0GxpHwEM4sOyAZ +KEPC3ZqLmgB6LDO2z/OWYVa9vlCAiPgDYtEVCnCCIInN/ue4dBZtDeVj8NUK2n0D +UBpa2OMUgu3D+4SJNK7EnAmXdOaP6yfe6SXwcQfti8UoSFMJRkQkbY1rm/6iPfON +t2RBAc7jW4eRzdciWCfvJfMSj9cqxTBQWz5vVadeY9Bm/IKw1HiKNBrJratq2v+D +VGr0EkE9oOa5zbgZt2CFvknE4YhGmv81xFdK5GXr8L7nluZrePMblWbkI2ICTbV0 +RKLhBADYLvyDFX3cCoFzWmCl5L32G6LLfTt0yU0eUHcAzXd7QjOZN289HWYEmdVi +kpxQPDxhWz+m8qt0HJGFl2+BKpZJBaT/L5AcqTBODxarxCSBTIVhCjD/46XvLY0h +b2ZnG8HSLyFdRj07vk+qTvcF58qUuYFSLIF2t2imTCR/PwR/LwQA632vn2/7KIHj +DR0O+G9eccTtAfX4TN4Q4Ua3WByClLZu/LSAenCLZ1CHVABEH6dwwjEARLeNUdLi +Xy5KKlpr2vkoh96fnw0r2yg7dlBXq4yQKjJBXwNaKpuvqgzd8en0zJGLXxzt0NT3 +H+QNIP2WZMJSDQcDh3HhQrH0IeNdDm0D/iyJgSMXvqjm+KhYIa3xiloQsCRlDNm+ +XC7Eo5hsjvBaIKba6o9oL9oEiSVUFryPWKWIpi0P7/F5voJL6KFSZTor3x3o9CcC +qHyqMHfNL23EAVJulySfPYLC7S3QB+tCBLXmKxb/YXCSLVi/UDzVgvWN6KIknZg2 +6uDLUzPbzDGjOZ20K1JvdW5kdXAgVGVzdGtleSA8cm91bmR1cC1hZG1pbkBleGFt +cGxlLmNvbT6JATgEEwECACIFAk6NqtsCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4B +AheAAAoJEFrc/VYxw4dBG7oIAMCU9sRjK0dS7z/IGJ8KcCOQNN674AooJLn+J9Ew +BT6/WxMY13nm/iK0uX2sOGnnXdg1PJ15IvD8zB5wXLbe25t6oRl5G58vmeKEyjc8 +QTB43/c8EsqY1ob7EVcuhrJCSS/JM8ApzQyXrh2QNmS+mBCJcx74MeipE6mNVT9j +VscizixdFjqvJLkbW1kGac3Wj+c3ICNUIp0lbwb+Ve2rXlU+iXHEDqaVJDMEppme +gDiZl+bKYrqljhZkH9Slv55uqqXUSg1SmTm/2orHUdAmDc6Y6azKNqQEqD2B0JyT +jTJQJVMl5Oln63aZDCTxHkoqn8q06OjLJRD4on7jlanZEladA5gETo2q2wEIALEF +poVkZrnqme2M8FObrQyVB+ZYT2mox56WLyInbxVFDg20qqIvQfVE0P69Yuf1OXkj +q7bNI03Jvo+uzxpztOKPDo7tnbQ7bXbOmq3n4wUoN29NMrYNg6tF1ubEv1WwYUMw +7LfF4BLMETXpT0JElV1+awfP9rrGiyWkH4enG612HT+1OoA0R0nNH0kslD6OhdoR +VDqkyiCmdY9x176EhzhL3vCoN6ywRVTfFbAJiMv9UDzxs0SStmVOK/l5XLfWQO6f +9boAHihpnxEfPIJhsD+FpVKVf3g85qWAjh2BfuzdW79vjLBdTHJQxg4HdhliWbXg +PjjrVEgWEFVc+NDlNb0AEQEAAQAH/A1a6sbniI8q3DVoIP19zN7FI5UaQSuB2Jrl ++Q+vlUQv3dvk2cwQmqj2vyRo2gcRS3u7LYpGDGLNqfshv22JyzId2YWo9vE7sTTP +E4EJRz8CsLlMmVsoxoVBE0cnvXOpMef6z0ZyFEdMGVmi4iA9bQi3r+V6qBehQQA0 +U034VTCPN4yvWyq6TWsABesOx48nkQ5TlduIq2ZGNCR8Vd1fe6vGM7YXyQWxy5ke +guqmph73H2bOB6hSuUnyBFKtinrF9MbCGA0PqheUVqy0p7og6x/pEoAVkKBJ9Ki+ +ePuQtBl5h9e3SbiN+r7aa6T0Ygx/7igl4eWPfvJYIXYXc4aKiwEEANEa5rBoN7Ta +ED+R47Rg9w/EW3VDQ6R3Szy1rvIKjC6JlDyKlGgTeWEFjDeTwCB4xU7YtxVpt6bk +b7RBtDkRck2+DwnscutA7Uxn267UxzNUd1IxhUccRFRfRS7OEnmlVmaLUnOeHHwe +OrZyRSiNVnh0QABEJnwNjX4m139v6YD9BADYuM5XCawI63pYa3/l7UX9H5EH95OZ +G9Hw7pXQ/YJYerSxRx+2q0+koRcdoby1TVaRrdDC+kOm3PI7e66S5rnaZ1FZeYQP +nVYzyGqNnsvncs24kYBL8DYaDDfdm7vfzSEqia0VNqZ4TMbwJLk5f5Ys4WOF791G +LPJgrAPG1jgDwQQAovKbw0u6blIUKsUYOLsviaLCyFC9DwaHqIZwjy8omnh7MaKE +7+MXxJpfcVqFifj3CmqMdSmTfkgbKQPAI46Q1OKWvkvUxEvi7WATo4taEXupRFL5 +jnL8c4h46z8UpMX2CMwWU0k1Et/zlBoYy7gNON7tF2/uuN18zWFBlD72HuM9HIkB +HwQYAQIACQUCTo2q2wIbDAAKCRBa3P1WMcOHQYI+CACDXJf1e695LpcsrVxKgiQr +9fTbNJYB+tjbnd9vas92Gz1wZcQV9RjLkYQeEbOpWQud/1UeLRsFECMj7kbgAEqz +7fIO4SeN8hFEvvZ+lI0AoBi4XvuUcCm5kvAodvmF8M9kQiUzF1gm+R9QQeJFDLpW +8Gg7J3V3qM+N0FuXrypYcsEv7n/RJ1n+lhTW5hFzKBlNL4WrAhY/QsXEbmdsa478 +tzuHlETtjMm4g4DgppUdlCMegcpjjC9zKsN5xFOQmNMTO/6rPFUqk3k3T6I0LV4O +zm4xNC+wwAA69ibnbrY1NR019et7RYW+qBudGbpJB1ABzkf/NsaCj6aTaubt7PZP +=3uFZ +-----END PGP PRIVATE KEY BLOCK----- +""" + +john_doe_key = """ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +lQHYBE6NwvABBACxg7QqV2qHywwM3wae6HAHJVEo7EeYA6Lv0pZlW3Aw4CCCnpgJ +jA7CekGFcmGmoCaN9ezuVAPTgUlK4yt8a7P6cT0vw1q341Om9IEKAu59RpNZN/H9 +6GfZ95bU51W/hdTFysH1DRwbCR3MowvLeA6Pk4cZlPsYHD0SD3De2i1BewARAQAB +AAP+IRi4L6jKwPS3k3LFrj0SHhL0Fdgv5QTQjTxLNCyfN02iYhglqqoFWncm3jWc +RU/YwGEYwrrBV97kBmVihzkhfgFRsxynE9PMGKKEAuRcAl21RPJDFA6Dlnp6M2No +rR6eoAhrlZ8+KsK9JaXSMalzO/Yh4u3mOinq3f3XL96wAEkCAMAxeZMF5pnXARNR +Y7u2clhNNnLuf+BzpENCFMaWzWPyTcvbf4xNK7ZHPxFVZpX5/qAPJ8rnTaOTHxnN +5PgqbO8CAOxyrTw/muakTJLg+FXdn8BgxZGJXMT7KmkU9SReefjo7c1WlnZxKIAy +6vLIG8WMGpdfCFDve0YLr/GGyDtOjDUB/RN3gn6qnAJThBnVk2wESZVx41fihbIF +ACCKc9heFskzwurtvvp+bunM3quwrSH1hWvxiWJlDmGSn8zQFypGChifgLQZSm9o +biBEb2UgPGpvaG5AdGVzdC50ZXN0Poi4BBMBAgAiBQJOjcLwAhsDBgsJCAcDAgYV +CAIJCgsEFgIDAQIeAQIXgAAKCRC/z7qg+FujnPWiA/9T5SOGraRNIVVIyvJvYwkG +OTAfQ0K3QMlLoQMPmaEbx9Q+isF15M9sOMcl1XGO4UNWuCPIIN8z/y/OLgAB0ZuL +GlnAPPOOZ+MlaUXiMYo8oi416QZrMDf2H/Nkc10csiXm+zMl8RqeIQBEeljNyJ+t +MG1EWn/PHTwFTd/VePuQdJ0B2AROjcLwAQQApw+72jKy0/wqg5SAtnVSkA1F3Jna +/OG+ufz5dX57jkMFRvFoksWIWqHmiCjdE5QV8j+XTnjElhLsmrgjl7aAFveb30R6 +ImmcpKMN31vAp4RZlnyYbYUCY4IXFuz3n1CaUL+mRx5yNJykrZNfpWNf2pwozkZq +lcDI69ymIW5acXUAEQEAAQAD/R7Jdf98l1scngMYo228ikYUxBqm2eX/fiQNXDWM +ZR2u+TJ9O53MvFejfXX7Pd6lTDQUBwDFncjgXO0YYSrMzabhqpqoKLqOIpZmBuWC +Hh1lvcFoIYoDR2LkiJ9EPBUEVUBDsUO8ajkILEE3G+DDpCaf9Vo82lCVyhDESqyt +v4lxAgDOLpoq1Whv5Ejr6FifTWytCiQjH2P1SmePlQmy6oEJRUYA1t4zYrzCJUX8 +VAvPjh9JXilP6mhDbyQArWllewV9AgDPbVOf75ktRwfhje26tZsukqWYJCc1XvoH +3PTzA7vH1HZZq7dvxa87PiSnkOLEsIAsI+4jpeMxpPlQRxUvHf1ZAf9rK3v3HMJ/ +2xVzwK24Oaj+g2O7D/fdqtLFGe5S5JobnTyp9xArDAhaZ/AKfDMYjUIKMP+bdNAf +y8fQUtuawFltm1GInwQYAQIACQUCTo3C8AIbDAAKCRC/z7qg+FujnDzYA/9EU6Pv +Ci1+DCtxjnq7IOvOjqExhFNGvN9Dw17Tl8HcyW3if9v5RxeSWYKl0DhzVdzMQgH/ +78q4F4W1q2IkB7SCpXizHLIc3eh8iZkbWZE+CGPvTpqyF03Yi16qhxpAbkGs2Yhq +jTx5oJ4CL5fybBOZLg+BTlK4HIee6xEcbNoq+A== +=ZKBW +-----END PGP PRIVATE KEY BLOCK----- +""" + +ownertrust = """ +723762CD5A5FECB76DC72DF85ADCFD5631C38741:6: +2940C247A1FBAD508A1AF24BBFCFBAA0F85BA39C:6: +""" + +class MailgwPGPTestCase(MailgwTestAbstractBase): + pgphome = gpgmelib.pgphome + def setUp(self): + MailgwTestAbstractBase.setUp(self) + self.db.security.addRole(name = 'pgp', description = 'PGP Role') + self.instance.config['PGP_HOMEDIR'] = self.pgphome + self.instance.config['PGP_ROLES'] = 'pgp' + self.instance.config['PGP_ENABLE'] = True + self.instance.config['MAIL_DOMAIN'] = 'example.com' + self.instance.config['ADMIN_EMAIL'] = 'roundup-admin@example.com' + self.db.user.set(self.john_id, roles='User,pgp') + gpgmelib.setUpPGP() + + def tearDown(self): + MailgwTestAbstractBase.tearDown(self) + gpgmelib.tearDownPGP() + + def testPGPUnsignedMessage(self): + self.assertRaises(MailUsageError, self._handle_mail, + '''Content-Type: text/plain; + charset="iso-8859-1" +From: John Doe +To: issue_tracker@your.tracker.email.domain.example +Message-Id: +Subject: [issue] Testing non-signed message... + +This is no pgp signed message. +''') + + signed_msg = '''Content-Disposition: inline +From: John Doe +To: issue_tracker@your.tracker.email.domain.example +Subject: [issue] Testing signed message... +Content-Type: multipart/signed; micalg=pgp-sha1; + protocol="application/pgp-signature"; boundary="cWoXeonUoKmBZSoM" + + +--cWoXeonUoKmBZSoM +Content-Type: text/plain; charset=us-ascii +Content-Disposition: inline + +This is a pgp signed message. + +--cWoXeonUoKmBZSoM +Content-Type: application/pgp-signature; name="signature.asc" +Content-Description: Digital signature +Content-Disposition: inline + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.10 (GNU/Linux) + +iJwEAQECAAYFAk6N4A4ACgkQv8+6oPhbo5x5nAP/d7R7SxTvLoVESI+1r7eDXp1J +LvBVU2EF3YFYKBHMLcWmjG92fNjnHX6NENTEhTeBynba5IPEwUfITC+7PmgPmQkA +VXnFZnwraHxsYgyFsVFN1kkTSbwRUlWl9+nTEsr0yBLTpZN0QSIDcwu+i/xVcg+t +ZQ4K6R3m3AOw7BLdvZs= +=wpYk +-----END PGP SIGNATURE----- + +--cWoXeonUoKmBZSoM-- +''' + + def testPGPSignedMessage(self): + nodeid = self._handle_mail(self.signed_msg) + m = self.db.issue.get(nodeid, 'messages')[0] + self.assertEqual(self.db.msg.get(m, 'content'), + 'This is a pgp signed message.') + + def testPGPSignedMessageFail(self): + # require both, signing and encryption + self.instance.config['PGP_REQUIRE_INCOMING'] = 'both' + self.assertRaises(MailUsageError, self._handle_mail, self.signed_msg) + + encrypted_msg = '''Content-Disposition: inline +From: John Doe +To: roundup-admin@example.com +Subject: [issue] Testing encrypted message... +Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; + boundary="d6Gm4EdcadzBjdND" + +--d6Gm4EdcadzBjdND +Content-Type: application/pgp-encrypted +Content-Disposition: attachment + +Version: 1 + +--d6Gm4EdcadzBjdND +Content-Type: application/octet-stream +Content-Disposition: inline; filename="msg.asc" + +-----BEGIN PGP MESSAGE----- +Version: GnuPG v1.4.10 (GNU/Linux) + +hQEMAzfeQttq+Q2YAQf9FxCtZVgC7jAy6UkeAJ1imCpnh9DgKA5w40OFtrY4mVAp +cL7kCkvGvJCW7uQZrmSgIiYaZGLI3GS42XutORC6E6PzBEW0fJUMIXYmoSd0OFeY +3H2+854qu37W/uCOWM9OnPFIH8g8q8DgYy88i0goM+Ot9Q96yFfJ7QymanOZJgVa +MNC+oKDiIZKiE3PCwtGr+8CHZN/9J6O4FeJijBlr09C5LXc+Nif5T0R0nt17MAns +9g2UvGxW8U24NAS1mOg868U05hquLPIcFz9jGZGknJu7HBpOkQ9GjKqkzN8pgZVN +VbN8IdDqi0QtRKE44jtWQlyNlESMjv6GtC2V9F6qKNK8AfHtBexDhyv4G9cPFFNO +afQ6e4dPi89RYIQyydtwiqao8fj6jlAy2Z1cbr7YxwBG7BeUZv9yis7ShaAIo78S +82MrCYpSjfHNwKiSfC5yITw22Uv4wWgixVdAsaSdtBqEKXJPG9LNey18ArsBjSM1 +P81iDOWUp/uyIe5ZfvNI38BBxEYslPTUlDk2GB8J2Vun7IWHoj9a4tY3IotC9jBr +5Qnigzqrt7cJZX6OrN0c+wnOjXbMGYXmgSs4jeM= +=XX5Q +-----END PGP MESSAGE----- + +--d6Gm4EdcadzBjdND-- +''' + def testPGPEncryptedUnsignedMessageError(self): + self.assertRaises(MailUsageError, self._handle_mail, self.encrypted_msg) + + def testPGPEncryptedUnsignedMessage(self): + # no error if we don't require a signature: + self.instance.config['PGP_REQUIRE_INCOMING'] = 'encrypted' + nodeid = self._handle_mail (self.encrypted_msg) + m = self.db.issue.get(nodeid, 'messages')[0] + self.assertEqual(self.db.msg.get(m, 'content'), + 'This is the text to be encrypted') + + def testPGPEncryptedUnsignedMessageFromNonPGPUser(self): + msg = self.encrypted_msg.replace('John Doe ', + '"Contrary, Mary" ') + nodeid = self._handle_mail (msg) + m = self.db.issue.get(nodeid, 'messages')[0] + self.assertEqual(self.db.msg.get(m, 'content'), + 'This is the text to be encrypted') + self.assertEqual(self.db.msg.get(m, 'author'), self.mary_id) + + # check that a bounce-message that is triggered *after* + # decrypting is properly encrypted: + def testPGPEncryptedUnsignedMessageCheckBounce(self): + # allow non-signed msg + self.instance.config['PGP_REQUIRE_INCOMING'] = 'encrypted' + # don't allow creation of message, trigger error *after* decrypt + self.db.user.set(self.john_id, roles='pgp') + self.db.security.addPermissionToRole('pgp', 'Email Access') + self.db.security.addPermissionToRole('pgp', 'Create', 'issue') + # trap_exc=1: we want a bounce message: + self._handle_mail(self.encrypted_msg, trap_exc=1) + m = self._get_mail() + fp = FeedParser() + fp.feed(m) + parts = fp.close().get_payload() + self.assertEqual(len(parts),2) + self.assertEqual(parts[0].get_payload().strip(), 'Version: 1') + crypt = pyme.core.Data(parts[1].get_payload()) + plain = pyme.core.Data() + ctx = pyme.core.Context() + res = ctx.op_decrypt(crypt, plain) + self.assertEqual(res, None) + plain.seek(0,0) + fp = FeedParser() + fp.feed(plain.read()) + parts = fp.close().get_payload() + self.assertEqual(len(parts),2) + self.assertEqual(parts[0].get_payload().strip(), + 'You are not permitted to create messages.') + self.assertEqual(parts[1].get_payload().strip(), + '''Content-Type: text/plain; charset=us-ascii +Content-Disposition: inline + +This is the text to be encrypted''') + + + def testPGPEncryptedSignedMessage(self): + # require both, signing and encryption + self.instance.config['PGP_REQUIRE_INCOMING'] = 'both' + nodeid = self._handle_mail('''Content-Disposition: inline +From: John Doe +To: roundup-admin@example.com +Subject: Testing encrypted and signed message +MIME-Version: 1.0 +Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; + boundary="ReaqsoxgOBHFXBhH" + +--ReaqsoxgOBHFXBhH +Content-Type: application/pgp-encrypted +Content-Disposition: attachment + +Version: 1 + +--ReaqsoxgOBHFXBhH +Content-Type: application/octet-stream +Content-Disposition: inline; filename="msg.asc" + +-----BEGIN PGP MESSAGE----- +Version: GnuPG v1.4.10 (GNU/Linux) + +hQEMAzfeQttq+Q2YAQf+NaC3r8qBURQqxHH9IAP4vg0QAP2yj3n0v6guo1lRf5BA +EUfTQ3jc3chxLvzTgoUIuMOvhlNroqR1lgLwhfSTCyuKWDZa+aVNiSgsB2MD44Xd +mAkKKmnmOGLmfbICbPQZxl4xNhCMTHiAy1xQE6mTj/+pEAq5XxjJUwn/gJ3O1Wmd +NyWtJY2N+TRbxUVB2WhG1j9J1D2sjhG26TciE8JeuLDZzaiVNOW9YlX2Lw5KtlkR +Hkgw6Xme06G0XXZUcm9JuBU/7oFP/tSrC1tBsnVlq1pZYf6AygIBdXWb9gD/WmXh +7Eu/xCKrw4RFnXnTgmBz/NHRfVDkfdSscZqexnG1D9LAwQHSuVf8sxDPNesv0W+8 +e49loVjvU+Y0BCFQAbWSW4iOEUYZpW/ITRE4+wIqMXZbAraeBV0KPZ4hAa3qSmf+ +oZBRcbzssL163Odx/OHRuK2J2CHC654+crrlTBnxd/RUKgRbSUKwrZzB2G6OPcGv +wfiqXsY+XvSZtTbWuvUJxePh8vhhhjpuo1JtlrYc3hZ9OYgoCoV1JiLl5c60U5Es +oUT9GDl1Qsgb4dF4TJ1IBj+riYiocYpJxPhxzsy6liSLNy2OA6VEjG0FGk53+Ok9 +7UzOA+WaHJHSXafZzrdP1TWJUFlOMA+dOgTKpH69eL1+IRfywOjEwp1UNSbLnJpc +D0QQLwIFttplKvYkn0DZByJCVnIlGkl4s5LM5rnc8iecX8Jad0iRIlPV6CVM+Nso +WdARUfyJfXAmz8uk4f2sVfeMu1gdMySdjvxwlgHDJdBPIG51r2b8L/NCTiC57YjF +zGhS06FLl3V1xx6gBlpqQHjut3efrAGpXGBVpnTJMOcgYAk= +=jt/n +-----END PGP MESSAGE----- + +--ReaqsoxgOBHFXBhH-- +''') + m = self.db.issue.get(nodeid, 'messages')[0] + self.assertEqual(self.db.msg.get(m, 'content'), + 'This is the text of a signed and encrypted email.') + + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(MailgwTestCase)) + if pyme is not None: + suite.addTest(unittest.makeSuite(MailgwPGPTestCase)) + else: + print "Skipping PGP tests" + return suite +if __name__ == '__main__': + runner = unittest.TextTestRunner() + unittest.main(testRunner=runner) -# vim: set filetype=python ts=4 sw=4 et si +# vim: set filetype=python sts=4 sw=4 et si :