/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
6614.1.1 by Vincent Ladeuil
Fix assert_ being deprecated by using assertTrue.
1
# Copyright (C) 2007, 2009, 2011, 2014, 2016 Canonical Ltd
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
16
6592.1.1 by Vincent Ladeuil
Fix minor incompatible change in email python 2.7.6 module.
17
import sys
6791.2.3 by Jelmer Vernooij
Fix more imports.
18
try:
19
    from email.header import decode_header
20
except ImportError:  # python < 3
21
    from email.Header import decode_header
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
22
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
23
from .. import __version__ as _breezy_version
24
from ..email_message import EmailMessage
25
from ..errors import BzrBadParameterNotUnicode
26
from ..smtp_connection import SMTPConnection
27
from .. import tests
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
28
29
EMPTY_MESSAGE = '''\
30
From: from@from.com
31
Subject: subject
32
To: to@to.com
33
User-Agent: Bazaar (%s)
34
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
35
''' % _breezy_version
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
36
37
_SIMPLE_MESSAGE = '''\
38
MIME-Version: 1.0
39
Content-Type: text/plain; charset="%%s"
40
Content-Transfer-Encoding: %%s
41
From: from@from.com
42
Subject: subject
43
To: to@to.com
44
User-Agent: Bazaar (%s)
45
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
46
%%s''' % _breezy_version
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
47
48
SIMPLE_MESSAGE_ASCII = _SIMPLE_MESSAGE % ('us-ascii', '7bit', 'body')
49
SIMPLE_MESSAGE_UTF8 = _SIMPLE_MESSAGE % ('utf-8', 'base64', 'YsOzZHk=\n')
2639.1.2 by John Arbash Meinel
Some cleanups for the EmailMessage class.
50
SIMPLE_MESSAGE_8BIT = _SIMPLE_MESSAGE % ('8-bit', 'base64', 'YvRkeQ==\n')
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
51
52
53
BOUNDARY = '=====123456=='
54
55
_MULTIPART_HEAD = '''\
56
Content-Type: multipart/mixed; boundary="%(boundary)s"
57
MIME-Version: 1.0
58
From: from@from.com
59
Subject: subject
60
To: to@to.com
61
User-Agent: Bazaar (%(version)s)
62
63
--%(boundary)s
64
MIME-Version: 1.0
65
Content-Type: text/plain; charset="us-ascii"
66
Content-Transfer-Encoding: 7bit
67
Content-Disposition: inline
68
69
body
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
70
''' %  { 'version': _breezy_version, 'boundary': BOUNDARY }
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
71
6592.1.1 by Vincent Ladeuil
Fix minor incompatible change in email python 2.7.6 module.
72
73
def final_newline_or_not(msg):
74
    if sys.version_info >= (2, 7, 6):
75
        # Some internals of python's email module changed in an (minor)
76
        # incompatible way: a final newline is appended in 2.7.6...
77
       msg += '\n'
78
    return msg
79
80
81
def simple_multipart_message():
82
    msg = _MULTIPART_HEAD + '--%s--' % BOUNDARY
83
    return final_newline_or_not(msg)
84
85
86
def complex_multipart_message(typ):
87
    msg = _MULTIPART_HEAD + '''\
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
88
--%(boundary)s
89
MIME-Version: 1.0
90
Content-Type: text/%%s; charset="us-ascii"; name="lines.txt"
91
Content-Transfer-Encoding: 7bit
92
Content-Disposition: inline
93
94
a
95
b
96
c
97
d
98
e
99
100
--%(boundary)s--''' %  { 'boundary': BOUNDARY }
6592.1.1 by Vincent Ladeuil
Fix minor incompatible change in email python 2.7.6 module.
101
    msg = final_newline_or_not(msg)
102
    return msg % (typ,)
103
104
105
class TestEmailMessage(tests.TestCase):
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
106
107
    def test_empty_message(self):
108
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject')
6809.1.1 by Martin
Apply 2to3 ws_comma fixer
109
        self.assertEqualDiff(EMPTY_MESSAGE, msg.as_string())
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
110
111
    def test_simple_message(self):
112
        pairs = {
113
            'body': SIMPLE_MESSAGE_ASCII,
114
            u'b\xf3dy': SIMPLE_MESSAGE_UTF8,
115
            'b\xc3\xb3dy': SIMPLE_MESSAGE_UTF8,
2639.1.2 by John Arbash Meinel
Some cleanups for the EmailMessage class.
116
            'b\xf4dy': SIMPLE_MESSAGE_8BIT,
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
117
        }
118
        for body, expected in pairs.items():
119
            msg = EmailMessage('from@from.com', 'to@to.com', 'subject', body)
120
            self.assertEqualDiff(expected, msg.as_string())
121
6592.1.1 by Vincent Ladeuil
Fix minor incompatible change in email python 2.7.6 module.
122
    def test_multipart_message_simple(self):
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
123
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject')
124
        msg.add_inline_attachment('body')
6592.1.1 by Vincent Ladeuil
Fix minor incompatible change in email python 2.7.6 module.
125
        self.assertEqualDiff(simple_multipart_message(),
126
                             msg.as_string(BOUNDARY))
127
128
129
    def test_multipart_message_complex(self):
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
130
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject', 'body')
131
        msg.add_inline_attachment(u'a\nb\nc\nd\ne\n', 'lines.txt', 'x-subtype')
6592.1.1 by Vincent Ladeuil
Fix minor incompatible change in email python 2.7.6 module.
132
        self.assertEqualDiff(complex_multipart_message('x-subtype'),
133
                             msg.as_string(BOUNDARY))
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
134
135
    def test_headers_accept_unicode_and_utf8(self):
2625.6.3 by Adeodato Simó
Changes after review by John.
136
        for user in [ u'Pepe P\xe9rez <pperez@ejemplo.com>',
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
137
                'Pepe P\xc3\xa9red <pperez@ejemplo.com>' ]:
2625.6.3 by Adeodato Simó
Changes after review by John.
138
            msg = EmailMessage(user, user, user) # no exception raised
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
139
140
            for header in ['From', 'To', 'Subject']:
141
                value = msg[header]
142
                str(value).decode('ascii') # no UnicodeDecodeError
143
144
    def test_headers_reject_8bit(self):
145
        for i in range(3): # from_address, to_address, subject
146
            x = [ '"J. Random Developer" <jrandom@example.com>' ] * 3
147
            x[i] = 'Pepe P\xe9rez <pperez@ejemplo.com>'
148
            self.assertRaises(BzrBadParameterNotUnicode, EmailMessage, *x)
149
150
    def test_multiple_destinations(self):
151
        to_addresses = [ 'to1@to.com', 'to2@to.com', 'to3@to.com' ]
152
        msg = EmailMessage('from@from.com', to_addresses, 'subject')
153
        self.assertContainsRe(msg.as_string(), 'To: ' +
154
                ', '.join(to_addresses)) # re.M can't be passed, so no ^$
155
156
    def test_retrieving_headers(self):
157
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject')
158
        for header, value in [('From', 'from@from.com'), ('To', 'to@to.com'),
159
                ('Subject', 'subject')]:
160
            self.assertEqual(value, msg.get(header))
161
            self.assertEqual(value, msg[header])
162
        self.assertEqual(None, msg.get('Does-Not-Exist'))
163
        self.assertEqual(None, msg['Does-Not-Exist'])
164
        self.assertEqual('None', msg.get('Does-Not-Exist', 'None'))
165
166
    def test_setting_headers(self):
167
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject')
168
        msg['To'] = 'to2@to.com'
169
        msg['Cc'] = 'cc@cc.com'
170
        self.assertEqual('to2@to.com', msg['To'])
171
        self.assertEqual('cc@cc.com', msg['Cc'])
172
173
    def test_address_to_encoded_header(self):
174
        def decode(s):
175
            """Convert a RFC2047-encoded string to a unicode string."""
176
            return ' '.join([chunk.decode(encoding or 'ascii')
177
                             for chunk, encoding in decode_header(s)])
178
179
        address = 'jrandom@example.com'
180
        encoded = EmailMessage.address_to_encoded_header(address)
181
        self.assertEqual(address, encoded)
182
183
        address = 'J Random Developer <jrandom@example.com>'
184
        encoded = EmailMessage.address_to_encoded_header(address)
185
        self.assertEqual(address, encoded)
186
187
        address = '"J. Random Developer" <jrandom@example.com>'
188
        encoded = EmailMessage.address_to_encoded_header(address)
189
        self.assertEqual(address, encoded)
190
191
        address = u'Pepe P\xe9rez <pperez@ejemplo.com>' # unicode ok
192
        encoded = EmailMessage.address_to_encoded_header(address)
6614.1.1 by Vincent Ladeuil
Fix assert_ being deprecated by using assertTrue.
193
        self.assertTrue('pperez@ejemplo.com' in encoded) # addr must be unencoded
6614.1.3 by Vincent Ladeuil
Fix assertEquals being deprecated by using assertEqual.
194
        self.assertEqual(address, decode(encoded))
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
195
196
        address = 'Pepe P\xc3\xa9red <pperez@ejemplo.com>' # UTF-8 ok
197
        encoded = EmailMessage.address_to_encoded_header(address)
6614.1.1 by Vincent Ladeuil
Fix assert_ being deprecated by using assertTrue.
198
        self.assertTrue('pperez@ejemplo.com' in encoded)
6614.1.3 by Vincent Ladeuil
Fix assertEquals being deprecated by using assertEqual.
199
        self.assertEqual(address, decode(encoded).encode('utf-8'))
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
200
201
        address = 'Pepe P\xe9rez <pperez@ejemplo.com>' # ISO-8859-1 not ok
202
        self.assertRaises(BzrBadParameterNotUnicode,
203
                EmailMessage.address_to_encoded_header, address)
204
205
    def test_string_with_encoding(self):
206
        pairs = {
207
                u'Pepe':        ('Pepe', 'ascii'),
208
                u'P\xe9rez':    ('P\xc3\xa9rez', 'utf-8'),
209
                'Perez':         ('Perez', 'ascii'), # u'Pepe' == 'Pepe'
210
                'P\xc3\xa9rez': ('P\xc3\xa9rez', 'utf-8'),
2639.1.2 by John Arbash Meinel
Some cleanups for the EmailMessage class.
211
                'P\xe8rez':     ('P\xe8rez', '8-bit'),
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
212
        }
2625.6.3 by Adeodato Simó
Changes after review by John.
213
        for string_, pair in pairs.items():
214
            self.assertEqual(pair, EmailMessage.string_with_encoding(string_))
6592.1.1 by Vincent Ladeuil
Fix minor incompatible change in email python 2.7.6 module.
215
216
217
class TestSend(tests.TestCase):
218
219
    def setUp(self):
220
        super(TestSend, self).setUp()
221
        self.messages = []
222
223
        def send_as_append(_self, msg):
224
            self.messages.append(msg.as_string(BOUNDARY))
225
226
        self.overrideAttr(SMTPConnection, 'send_email', send_as_append)
227
228
229
230
    def send_email(self, attachment=None, attachment_filename=None,
231
                   attachment_mime_subtype='plain'):
232
        class FakeConfig:
233
            def get(self, option):
234
                return None
235
236
        EmailMessage.send(FakeConfig(), 'from@from.com', 'to@to.com',
237
                          'subject', 'body',
238
                          attachment=attachment,
239
                          attachment_filename=attachment_filename,
240
                          attachment_mime_subtype=attachment_mime_subtype)
241
242
    def assertMessage(self, expected):
243
        self.assertLength(1, self.messages)
244
        self.assertEqualDiff(expected, self.messages[0])
245
246
    def test_send_plain(self):
247
        self.send_email(u'a\nb\nc\nd\ne\n', 'lines.txt')
248
        self.assertMessage(complex_multipart_message('plain'))
249
250
    def test_send_patch(self):
251
        self.send_email(u'a\nb\nc\nd\ne\n', 'lines.txt', 'x-patch')
252
        self.assertMessage(complex_multipart_message('x-patch'))
253
254
    def test_send_simple(self):
255
          self.send_email()
256
          self.assertMessage(SIMPLE_MESSAGE_ASCII)
257