~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_email_message.py

Add bzrlib.pyutils, which has get_named_object, a wrapper around __import__.

This is used to replace various ad hoc implementations of the same logic,
notably the version used in registry's _LazyObjectGetter which had a bug when
getting a module without also getting a member.  And of course, this new
function has unit tests, unlike the replaced code.

This also adds a KnownHooksRegistry subclass to provide a more natural home for
some other logic.

I'm not thrilled about the name of the new module or the new functions, but it's
hard to think of good names for such generic functionality.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
from email.Header import decode_header
 
18
 
 
19
from bzrlib import __version__ as _bzrlib_version
 
20
from bzrlib.email_message import EmailMessage
 
21
from bzrlib.errors import BzrBadParameterNotUnicode
 
22
from bzrlib.smtp_connection import SMTPConnection
 
23
from bzrlib.tests import TestCase
 
24
 
 
25
EMPTY_MESSAGE = '''\
 
26
From: from@from.com
 
27
Subject: subject
 
28
To: to@to.com
 
29
User-Agent: Bazaar (%s)
 
30
 
 
31
''' % _bzrlib_version
 
32
 
 
33
_SIMPLE_MESSAGE = '''\
 
34
MIME-Version: 1.0
 
35
Content-Type: text/plain; charset="%%s"
 
36
Content-Transfer-Encoding: %%s
 
37
From: from@from.com
 
38
Subject: subject
 
39
To: to@to.com
 
40
User-Agent: Bazaar (%s)
 
41
 
 
42
%%s''' % _bzrlib_version
 
43
 
 
44
SIMPLE_MESSAGE_ASCII = _SIMPLE_MESSAGE % ('us-ascii', '7bit', 'body')
 
45
SIMPLE_MESSAGE_UTF8 = _SIMPLE_MESSAGE % ('utf-8', 'base64', 'YsOzZHk=\n')
 
46
SIMPLE_MESSAGE_8BIT = _SIMPLE_MESSAGE % ('8-bit', 'base64', 'YvRkeQ==\n')
 
47
 
 
48
 
 
49
BOUNDARY = '=====123456=='
 
50
 
 
51
_MULTIPART_HEAD = '''\
 
52
Content-Type: multipart/mixed; boundary="%(boundary)s"
 
53
MIME-Version: 1.0
 
54
From: from@from.com
 
55
Subject: subject
 
56
To: to@to.com
 
57
User-Agent: Bazaar (%(version)s)
 
58
 
 
59
--%(boundary)s
 
60
MIME-Version: 1.0
 
61
Content-Type: text/plain; charset="us-ascii"
 
62
Content-Transfer-Encoding: 7bit
 
63
Content-Disposition: inline
 
64
 
 
65
body
 
66
''' %  { 'version': _bzrlib_version, 'boundary': BOUNDARY }
 
67
 
 
68
SIMPLE_MULTIPART_MESSAGE = _MULTIPART_HEAD + '--%s--' % BOUNDARY
 
69
 
 
70
COMPLEX_MULTIPART_MESSAGE = _MULTIPART_HEAD + '''\
 
71
--%(boundary)s
 
72
MIME-Version: 1.0
 
73
Content-Type: text/%%s; charset="us-ascii"; name="lines.txt"
 
74
Content-Transfer-Encoding: 7bit
 
75
Content-Disposition: inline
 
76
 
 
77
a
 
78
b
 
79
c
 
80
d
 
81
e
 
82
 
 
83
--%(boundary)s--''' %  { 'boundary': BOUNDARY }
 
84
 
 
85
 
 
86
class TestEmailMessage(TestCase):
 
87
 
 
88
    def test_empty_message(self):
 
89
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject')
 
90
        self.assertEqualDiff(EMPTY_MESSAGE , msg.as_string())
 
91
 
 
92
    def test_simple_message(self):
 
93
        pairs = {
 
94
            'body': SIMPLE_MESSAGE_ASCII,
 
95
            u'b\xf3dy': SIMPLE_MESSAGE_UTF8,
 
96
            'b\xc3\xb3dy': SIMPLE_MESSAGE_UTF8,
 
97
            'b\xf4dy': SIMPLE_MESSAGE_8BIT,
 
98
        }
 
99
        for body, expected in pairs.items():
 
100
            msg = EmailMessage('from@from.com', 'to@to.com', 'subject', body)
 
101
            self.assertEqualDiff(expected, msg.as_string())
 
102
 
 
103
    def test_multipart_message(self):
 
104
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject')
 
105
        msg.add_inline_attachment('body')
 
106
        self.assertEqualDiff(SIMPLE_MULTIPART_MESSAGE, msg.as_string(BOUNDARY))
 
107
 
 
108
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject', 'body')
 
109
        msg.add_inline_attachment(u'a\nb\nc\nd\ne\n', 'lines.txt', 'x-subtype')
 
110
        self.assertEqualDiff(COMPLEX_MULTIPART_MESSAGE % 'x-subtype',
 
111
                msg.as_string(BOUNDARY))
 
112
 
 
113
    def test_headers_accept_unicode_and_utf8(self):
 
114
        for user in [ u'Pepe P\xe9rez <pperez@ejemplo.com>',
 
115
                'Pepe P\xc3\xa9red <pperez@ejemplo.com>' ]:
 
116
            msg = EmailMessage(user, user, user) # no exception raised
 
117
 
 
118
            for header in ['From', 'To', 'Subject']:
 
119
                value = msg[header]
 
120
                str(value).decode('ascii') # no UnicodeDecodeError
 
121
 
 
122
    def test_headers_reject_8bit(self):
 
123
        for i in range(3): # from_address, to_address, subject
 
124
            x = [ '"J. Random Developer" <jrandom@example.com>' ] * 3
 
125
            x[i] = 'Pepe P\xe9rez <pperez@ejemplo.com>'
 
126
            self.assertRaises(BzrBadParameterNotUnicode, EmailMessage, *x)
 
127
 
 
128
    def test_multiple_destinations(self):
 
129
        to_addresses = [ 'to1@to.com', 'to2@to.com', 'to3@to.com' ]
 
130
        msg = EmailMessage('from@from.com', to_addresses, 'subject')
 
131
        self.assertContainsRe(msg.as_string(), 'To: ' +
 
132
                ', '.join(to_addresses)) # re.M can't be passed, so no ^$
 
133
 
 
134
    def test_retrieving_headers(self):
 
135
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject')
 
136
        for header, value in [('From', 'from@from.com'), ('To', 'to@to.com'),
 
137
                ('Subject', 'subject')]:
 
138
            self.assertEqual(value, msg.get(header))
 
139
            self.assertEqual(value, msg[header])
 
140
        self.assertEqual(None, msg.get('Does-Not-Exist'))
 
141
        self.assertEqual(None, msg['Does-Not-Exist'])
 
142
        self.assertEqual('None', msg.get('Does-Not-Exist', 'None'))
 
143
 
 
144
    def test_setting_headers(self):
 
145
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject')
 
146
        msg['To'] = 'to2@to.com'
 
147
        msg['Cc'] = 'cc@cc.com'
 
148
        self.assertEqual('to2@to.com', msg['To'])
 
149
        self.assertEqual('cc@cc.com', msg['Cc'])
 
150
 
 
151
    def test_send(self):
 
152
        class FakeConfig:
 
153
            def get_user_option(self, option):
 
154
                return None
 
155
 
 
156
        messages = []
 
157
 
 
158
        def send_as_append(_self, msg):
 
159
            messages.append(msg.as_string(BOUNDARY))
 
160
 
 
161
        old_send_email = SMTPConnection.send_email
 
162
        try:
 
163
            SMTPConnection.send_email = send_as_append
 
164
 
 
165
            EmailMessage.send(FakeConfig(), 'from@from.com', 'to@to.com',
 
166
                    'subject', 'body', u'a\nb\nc\nd\ne\n', 'lines.txt')
 
167
            self.assertEqualDiff(COMPLEX_MULTIPART_MESSAGE % 'plain',
 
168
                    messages[0])
 
169
            messages[:] = []
 
170
 
 
171
            EmailMessage.send(FakeConfig(), 'from@from.com', 'to@to.com',
 
172
                    'subject', 'body', u'a\nb\nc\nd\ne\n', 'lines.txt',
 
173
                    'x-patch')
 
174
            self.assertEqualDiff(COMPLEX_MULTIPART_MESSAGE % 'x-patch',
 
175
                    messages[0])
 
176
            messages[:] = []
 
177
 
 
178
            EmailMessage.send(FakeConfig(), 'from@from.com', 'to@to.com',
 
179
                    'subject', 'body')
 
180
            self.assertEqualDiff(SIMPLE_MESSAGE_ASCII , messages[0])
 
181
            messages[:] = []
 
182
        finally:
 
183
            SMTPConnection.send_email = old_send_email
 
184
 
 
185
    def test_address_to_encoded_header(self):
 
186
        def decode(s):
 
187
            """Convert a RFC2047-encoded string to a unicode string."""
 
188
            return ' '.join([chunk.decode(encoding or 'ascii')
 
189
                             for chunk, encoding in decode_header(s)])
 
190
 
 
191
        address = 'jrandom@example.com'
 
192
        encoded = EmailMessage.address_to_encoded_header(address)
 
193
        self.assertEqual(address, encoded)
 
194
 
 
195
        address = 'J Random Developer <jrandom@example.com>'
 
196
        encoded = EmailMessage.address_to_encoded_header(address)
 
197
        self.assertEqual(address, encoded)
 
198
 
 
199
        address = '"J. Random Developer" <jrandom@example.com>'
 
200
        encoded = EmailMessage.address_to_encoded_header(address)
 
201
        self.assertEqual(address, encoded)
 
202
 
 
203
        address = u'Pepe P\xe9rez <pperez@ejemplo.com>' # unicode ok
 
204
        encoded = EmailMessage.address_to_encoded_header(address)
 
205
        self.assert_('pperez@ejemplo.com' in encoded) # addr must be unencoded
 
206
        self.assertEquals(address, decode(encoded))
 
207
 
 
208
        address = 'Pepe P\xc3\xa9red <pperez@ejemplo.com>' # UTF-8 ok
 
209
        encoded = EmailMessage.address_to_encoded_header(address)
 
210
        self.assert_('pperez@ejemplo.com' in encoded)
 
211
        self.assertEquals(address, decode(encoded).encode('utf-8'))
 
212
 
 
213
        address = 'Pepe P\xe9rez <pperez@ejemplo.com>' # ISO-8859-1 not ok
 
214
        self.assertRaises(BzrBadParameterNotUnicode,
 
215
                EmailMessage.address_to_encoded_header, address)
 
216
 
 
217
    def test_string_with_encoding(self):
 
218
        pairs = {
 
219
                u'Pepe':        ('Pepe', 'ascii'),
 
220
                u'P\xe9rez':    ('P\xc3\xa9rez', 'utf-8'),
 
221
                'Perez':         ('Perez', 'ascii'), # u'Pepe' == 'Pepe'
 
222
                'P\xc3\xa9rez': ('P\xc3\xa9rez', 'utf-8'),
 
223
                'P\xe8rez':     ('P\xe8rez', '8-bit'),
 
224
        }
 
225
        for string_, pair in pairs.items():
 
226
            self.assertEqual(pair, EmailMessage.string_with_encoding(string_))