Code

- some formatting
[roundup.git] / test / test_multipart.py
1 #
2 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
3 # This module is free software, and you may redistribute it and/or modify
4 # under the same terms as Python, so long as this copyright message and
5 # disclaimer are retained in their original form.
6 #
7 # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
8 # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
9 # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
10 # POSSIBILITY OF SUCH DAMAGE.
11 #
12 # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14 # FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
17
18 # $Id: test_multipart.py,v 1.8 2007-09-22 07:25:35 jpend Exp $ 
20 import unittest
21 from cStringIO import StringIO
23 from roundup.mailgw import Message
25 class TestMessage(Message):
26     # A note on message/rfc822: The content of such an attachment is an
27     # email with at least one header line. RFC2046 tells us: """   A
28     # media type of "message/rfc822" indicates that the body contains an
29     # encapsulated message, with the syntax of an RFC 822 message.
30     # However, unlike top-level RFC 822 messages, the restriction that
31     # each "message/rfc822" body must include a "From", "Date", and at
32     # least one destination header is removed and replaced with the
33     # requirement that at least one of "From", "Subject", or "Date" must
34     # be present."""
35     # This means we have to add a newline after the mime-header before
36     # the subject, otherwise the subject is part of the mime header not
37     # part of the email header.
38     table = {'multipart/signed': '    boundary="boundary-%(indent)s";\n',
39              'multipart/mixed': '    boundary="boundary-%(indent)s";\n',
40              'multipart/alternative': '    boundary="boundary-%(indent)s";\n',
41              'text/plain': '    name="foo.txt"\nfoo\n',
42              'application/pgp-signature': '    name="foo.gpg"\nfoo\n',
43              'application/pdf': '    name="foo.pdf"\nfoo\n',
44              'message/rfc822': '\nSubject: foo\n\nfoo\n'}
46     def __init__(self, spec):
47         """Create a basic MIME message according to 'spec'.
49         Each line of a spec has one content-type, which is optionally indented.
50         The indentation signifies how deep in the MIME hierarchy the
51         content-type is.
53         """
54         parts = []
55         for line in spec.splitlines():
56             content_type = line.strip()
57             if not content_type:
58                 continue
60             indent = self.getIndent(line)
61             if indent:
62                 parts.append('\n--boundary-%s\n' % indent)
63             parts.append('Content-type: %s;\n' % content_type)
64             parts.append(self.table[content_type] % {'indent': indent + 1})
66         Message.__init__(self, StringIO(''.join(parts)))
68     def getIndent(self, line):
69         """Get the current line's indentation, using four-space indents."""
70         count = 0
71         for char in line:
72             if char != ' ':
73                 break
74             count += 1
75         return count / 4
77 class MultipartTestCase(unittest.TestCase):
78     def setUp(self):
79         self.fp = StringIO()
80         w = self.fp.write
81         w('Content-Type: multipart/mixed; boundary="foo"\r\n\r\n')
82         w('This is a multipart message. Ignore this bit.\r\n')
83         w('\r\n--foo\r\n')
85         w('Content-Type: text/plain\r\n\r\n')
86         w('Hello, world!\r\n')
87         w('\r\n')
88         w('Blah blah\r\n')
89         w('foo\r\n')
90         w('-foo\r\n')
91         w('\r\n--foo\r\n')
93         w('Content-Type: multipart/alternative; boundary="bar"\r\n\r\n')
94         w('This is a multipart message. Ignore this bit.\r\n')
95         w('\r\n--bar\r\n')
97         w('Content-Type: text/plain\r\n\r\n')
98         w('Hello, world!\r\n')
99         w('\r\n')
100         w('Blah blah\r\n')
101         w('\r\n--bar\r\n')
103         w('Content-Type: text/html\r\n\r\n')
104         w('<b>Hello, world!</b>\r\n')
105         w('\r\n--bar--\r\n')
106         w('\r\n--foo\r\n')
108         w('Content-Type: text/plain\r\n\r\n')
109         w('Last bit\n')
110         w('\r\n--foo--\r\n')
111         self.fp.seek(0)
113     def testMultipart(self):
114         m = Message(self.fp)
115         self.assert_(m is not None)
117         # skip the first bit
118         p = m.getpart()
119         self.assert_(p is not None)
120         self.assertEqual(p.fp.read(),
121             'This is a multipart message. Ignore this bit.\r\n')
123         # first text/plain
124         p = m.getpart()
125         self.assert_(p is not None)
126         self.assertEqual(p.gettype(), 'text/plain')
127         self.assertEqual(p.fp.read(),
128             'Hello, world!\r\n\r\nBlah blah\r\nfoo\r\n-foo\r\n')
130         # sub-multipart
131         p = m.getpart()
132         self.assert_(p is not None)
133         self.assertEqual(p.gettype(), 'multipart/alternative')
135         # sub-multipart text/plain
136         q = p.getpart()
137         self.assert_(q is not None)
138         q = p.getpart()
139         self.assert_(q is not None)
140         self.assertEqual(q.gettype(), 'text/plain')
141         self.assertEqual(q.fp.read(), 'Hello, world!\r\n\r\nBlah blah\r\n')
143         # sub-multipart text/html
144         q = p.getpart()
145         self.assert_(q is not None)
146         self.assertEqual(q.gettype(), 'text/html')
147         self.assertEqual(q.fp.read(), '<b>Hello, world!</b>\r\n')
149         # sub-multipart end
150         q = p.getpart()
151         self.assert_(q is None)
153         # final text/plain
154         p = m.getpart()
155         self.assert_(p is not None)
156         self.assertEqual(p.gettype(), 'text/plain')
157         self.assertEqual(p.fp.read(),
158             'Last bit\n')
160         # end
161         p = m.getpart()
162         self.assert_(p is None)
164     def TestExtraction(self, spec, expected):
165         self.assertEqual(TestMessage(spec).extract_content(), expected)
167     def testTextPlain(self):
168         self.TestExtraction('text/plain', ('foo\n', []))
170     def testAttachedTextPlain(self):
171         self.TestExtraction("""
172 multipart/mixed
173     text/plain
174     text/plain""",
175                   ('foo\n',
176                    [('foo.txt', 'text/plain', 'foo\n')]))
178     def testMultipartMixed(self):
179         self.TestExtraction("""
180 multipart/mixed
181     text/plain
182     application/pdf""",
183                   ('foo\n',
184                    [('foo.pdf', 'application/pdf', 'foo\n')]))
186     def testMultipartAlternative(self):
187         self.TestExtraction("""
188 multipart/alternative
189     text/plain
190     application/pdf
191 """, ('foo\n', [('foo.pdf', 'application/pdf', 'foo\n')]))
193     def testDeepMultipartAlternative(self):
194         self.TestExtraction("""
195 multipart/mixed
196     multipart/alternative
197         text/plain
198         application/pdf
199 """, ('foo\n', [('foo.pdf', 'application/pdf', 'foo\n')]))
201     def testSignedText(self):
202         self.TestExtraction("""
203 multipart/signed
204     text/plain
205     application/pgp-signature""", ('foo\n', []))
207     def testSignedAttachments(self):
208         self.TestExtraction("""
209 multipart/signed
210     multipart/mixed
211         text/plain
212         application/pdf
213     application/pgp-signature""",
214                   ('foo\n',
215                    [('foo.pdf', 'application/pdf', 'foo\n')]))
217     def testAttachedSignature(self):
218         self.TestExtraction("""
219 multipart/mixed
220     text/plain
221     application/pgp-signature""",
222                   ('foo\n',
223                    [('foo.gpg', 'application/pgp-signature', 'foo\n')]))
225     def testMessageRfc822(self):
226         self.TestExtraction("""
227 multipart/mixed
228     message/rfc822""",
229                   (None,
230                    [('foo.eml', 'message/rfc822', 'Subject: foo\n\nfoo\n')]))
232 def test_suite():
233     suite = unittest.TestSuite()
234     suite.addTest(unittest.makeSuite(MultipartTestCase))
235     return suite
237 if __name__ == '__main__':
238     runner = unittest.TextTestRunner()
239     unittest.main(testRunner=runner)
242 # vim: set filetype=python ts=4 sw=4 et si