X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=test%2Ftest_multipart.py;h=0cbc384c668a7cf5b9e4bc65f505dd56066a1fd4;hb=f2228172dcbed77780ef7b60b10ac3e85cded918;hp=e58362929ed527166a95c837ee98522a262dd737;hpb=e8e0c9b06279ec296229fcbe2f1c308f4f67afb8;p=roundup.git diff --git a/test/test_multipart.py b/test/test_multipart.py index e583629..0cbc384 100644 --- a/test/test_multipart.py +++ b/test/test_multipart.py @@ -15,19 +15,72 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: test_multipart.py,v 1.5 2002-09-10 00:19:55 richard Exp $ +# $Id: test_multipart.py,v 1.8 2007-09-22 07:25:35 jpend Exp $ -import unittest, cStringIO +import unittest +from cStringIO import StringIO from roundup.mailgw import Message +class TestMessage(Message): + # A note on message/rfc822: The content of such an attachment is an + # email with at least one header line. RFC2046 tells us: """ A + # media type of "message/rfc822" indicates that the body contains an + # encapsulated message, with the syntax of an RFC 822 message. + # However, unlike top-level RFC 822 messages, the restriction that + # each "message/rfc822" body must include a "From", "Date", and at + # least one destination header is removed and replaced with the + # requirement that at least one of "From", "Subject", or "Date" must + # be present.""" + # This means we have to add a newline after the mime-header before + # the subject, otherwise the subject is part of the mime header not + # part of the email header. + table = {'multipart/signed': ' boundary="boundary-%(indent)s";\n', + 'multipart/mixed': ' boundary="boundary-%(indent)s";\n', + 'multipart/alternative': ' boundary="boundary-%(indent)s";\n', + 'text/plain': ' name="foo.txt"\nfoo\n', + 'application/pgp-signature': ' name="foo.gpg"\nfoo\n', + 'application/pdf': ' name="foo.pdf"\nfoo\n', + 'message/rfc822': '\nSubject: foo\n\nfoo\n'} + + def __init__(self, spec): + """Create a basic MIME message according to 'spec'. + + Each line of a spec has one content-type, which is optionally indented. + The indentation signifies how deep in the MIME hierarchy the + content-type is. + + """ + parts = [] + for line in spec.splitlines(): + content_type = line.strip() + if not content_type: + continue + + indent = self.getIndent(line) + if indent: + parts.append('\n--boundary-%s\n' % indent) + parts.append('Content-type: %s;\n' % content_type) + parts.append(self.table[content_type] % {'indent': indent + 1}) + + Message.__init__(self, StringIO(''.join(parts))) + + def getIndent(self, line): + """Get the current line's indentation, using four-space indents.""" + count = 0 + for char in line: + if char != ' ': + break + count += 1 + return count / 4 + class MultipartTestCase(unittest.TestCase): def setUp(self): - self.fp = cStringIO.StringIO() + self.fp = StringIO() w = self.fp.write w('Content-Type: multipart/mixed; boundary="foo"\r\n\r\n') w('This is a multipart message. Ignore this bit.\r\n') - w('--foo\r\n') + w('\r\n--foo\r\n') w('Content-Type: text/plain\r\n\r\n') w('Hello, world!\r\n') @@ -35,26 +88,26 @@ class MultipartTestCase(unittest.TestCase): w('Blah blah\r\n') w('foo\r\n') w('-foo\r\n') - w('--foo\r\n') + w('\r\n--foo\r\n') w('Content-Type: multipart/alternative; boundary="bar"\r\n\r\n') w('This is a multipart message. Ignore this bit.\r\n') - w('--bar\r\n') + w('\r\n--bar\r\n') w('Content-Type: text/plain\r\n\r\n') w('Hello, world!\r\n') w('\r\n') w('Blah blah\r\n') - w('--bar\r\n') + w('\r\n--bar\r\n') w('Content-Type: text/html\r\n\r\n') w('Hello, world!\r\n') - w('--bar--\r\n') - w('--foo\r\n') + w('\r\n--bar--\r\n') + w('\r\n--foo\r\n') w('Content-Type: text/plain\r\n\r\n') w('Last bit\n') - w('--foo--\r\n') + w('\r\n--foo--\r\n') self.fp.seek(0) def testMultipart(self): @@ -62,54 +115,128 @@ class MultipartTestCase(unittest.TestCase): self.assert_(m is not None) # skip the first bit - p = m.getPart() + p = m.getpart() self.assert_(p is not None) self.assertEqual(p.fp.read(), 'This is a multipart message. Ignore this bit.\r\n') # first text/plain - p = m.getPart() + p = m.getpart() self.assert_(p is not None) self.assertEqual(p.gettype(), 'text/plain') self.assertEqual(p.fp.read(), 'Hello, world!\r\n\r\nBlah blah\r\nfoo\r\n-foo\r\n') # sub-multipart - p = m.getPart() + p = m.getpart() self.assert_(p is not None) self.assertEqual(p.gettype(), 'multipart/alternative') # sub-multipart text/plain - q = p.getPart() + q = p.getpart() self.assert_(q is not None) - q = p.getPart() + q = p.getpart() self.assert_(q is not None) self.assertEqual(q.gettype(), 'text/plain') self.assertEqual(q.fp.read(), 'Hello, world!\r\n\r\nBlah blah\r\n') # sub-multipart text/html - q = p.getPart() + q = p.getpart() self.assert_(q is not None) self.assertEqual(q.gettype(), 'text/html') self.assertEqual(q.fp.read(), 'Hello, world!\r\n') # sub-multipart end - q = p.getPart() + q = p.getpart() self.assert_(q is None) # final text/plain - p = m.getPart() + p = m.getpart() self.assert_(p is not None) self.assertEqual(p.gettype(), 'text/plain') self.assertEqual(p.fp.read(), 'Last bit\n') # end - p = m.getPart() + p = m.getpart() self.assert_(p is None) -def suite(): - return unittest.makeSuite(MultipartTestCase, 'test') + def TestExtraction(self, spec, expected): + self.assertEqual(TestMessage(spec).extract_content(), expected) + + def testTextPlain(self): + self.TestExtraction('text/plain', ('foo\n', [])) + + def testAttachedTextPlain(self): + self.TestExtraction(""" +multipart/mixed + text/plain + text/plain""", + ('foo\n', + [('foo.txt', 'text/plain', 'foo\n')])) + + def testMultipartMixed(self): + self.TestExtraction(""" +multipart/mixed + text/plain + application/pdf""", + ('foo\n', + [('foo.pdf', 'application/pdf', 'foo\n')])) + + def testMultipartAlternative(self): + self.TestExtraction(""" +multipart/alternative + text/plain + application/pdf +""", ('foo\n', [('foo.pdf', 'application/pdf', 'foo\n')])) + + def testDeepMultipartAlternative(self): + self.TestExtraction(""" +multipart/mixed + multipart/alternative + text/plain + application/pdf +""", ('foo\n', [('foo.pdf', 'application/pdf', 'foo\n')])) + + def testSignedText(self): + self.TestExtraction(""" +multipart/signed + text/plain + application/pgp-signature""", ('foo\n', [])) + + def testSignedAttachments(self): + self.TestExtraction(""" +multipart/signed + multipart/mixed + text/plain + application/pdf + application/pgp-signature""", + ('foo\n', + [('foo.pdf', 'application/pdf', 'foo\n')])) + + def testAttachedSignature(self): + self.TestExtraction(""" +multipart/mixed + text/plain + application/pgp-signature""", + ('foo\n', + [('foo.gpg', 'application/pgp-signature', 'foo\n')])) + + def testMessageRfc822(self): + self.TestExtraction(""" +multipart/mixed + message/rfc822""", + (None, + [('foo.eml', 'message/rfc822', 'Subject: foo\n\nfoo\n')])) + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(MultipartTestCase)) + return suite + +if __name__ == '__main__': + runner = unittest.TextTestRunner() + unittest.main(testRunner=runner) # vim: set filetype=python ts=4 sw=4 et si