/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
7058.4.2 by Jelmer Vernooij
Fix boundaries handling.
26
from ..sixish import text_type
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
27
from ..smtp_connection import SMTPConnection
28
from .. import tests
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
29
30
EMPTY_MESSAGE = '''\
31
From: from@from.com
32
Subject: subject
33
To: to@to.com
34
User-Agent: Bazaar (%s)
35
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
36
''' % _breezy_version
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
37
38
_SIMPLE_MESSAGE = '''\
39
MIME-Version: 1.0
40
Content-Type: text/plain; charset="%%s"
41
Content-Transfer-Encoding: %%s
42
From: from@from.com
43
Subject: subject
44
To: to@to.com
45
User-Agent: Bazaar (%s)
46
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
47
%%s''' % _breezy_version
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
48
49
SIMPLE_MESSAGE_ASCII = _SIMPLE_MESSAGE % ('us-ascii', '7bit', 'body')
50
SIMPLE_MESSAGE_UTF8 = _SIMPLE_MESSAGE % ('utf-8', 'base64', 'YsOzZHk=\n')
2639.1.2 by John Arbash Meinel
Some cleanups for the EmailMessage class.
51
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.
52
53
54
BOUNDARY = '=====123456=='
55
56
_MULTIPART_HEAD = '''\
57
Content-Type: multipart/mixed; boundary="%(boundary)s"
58
MIME-Version: 1.0
59
From: from@from.com
60
Subject: subject
61
To: to@to.com
62
User-Agent: Bazaar (%(version)s)
63
64
--%(boundary)s
65
MIME-Version: 1.0
66
Content-Type: text/plain; charset="us-ascii"
67
Content-Transfer-Encoding: 7bit
68
Content-Disposition: inline
69
70
body
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
71
''' %  { 'version': _breezy_version, 'boundary': BOUNDARY }
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
72
6592.1.1 by Vincent Ladeuil
Fix minor incompatible change in email python 2.7.6 module.
73
74
def final_newline_or_not(msg):
75
    if sys.version_info >= (2, 7, 6):
76
        # Some internals of python's email module changed in an (minor)
77
        # incompatible way: a final newline is appended in 2.7.6...
78
       msg += '\n'
79
    return msg
80
81
82
def simple_multipart_message():
83
    msg = _MULTIPART_HEAD + '--%s--' % BOUNDARY
84
    return final_newline_or_not(msg)
85
86
87
def complex_multipart_message(typ):
88
    msg = _MULTIPART_HEAD + '''\
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
89
--%(boundary)s
90
MIME-Version: 1.0
91
Content-Type: text/%%s; charset="us-ascii"; name="lines.txt"
92
Content-Transfer-Encoding: 7bit
93
Content-Disposition: inline
94
95
a
96
b
97
c
98
d
99
e
100
101
--%(boundary)s--''' %  { 'boundary': BOUNDARY }
6592.1.1 by Vincent Ladeuil
Fix minor incompatible change in email python 2.7.6 module.
102
    msg = final_newline_or_not(msg)
103
    return msg % (typ,)
104
105
106
class TestEmailMessage(tests.TestCase):
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
107
108
    def test_empty_message(self):
109
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject')
6809.1.1 by Martin
Apply 2to3 ws_comma fixer
110
        self.assertEqualDiff(EMPTY_MESSAGE, msg.as_string())
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
111
112
    def test_simple_message(self):
113
        pairs = {
7058.4.2 by Jelmer Vernooij
Fix boundaries handling.
114
            b'body': SIMPLE_MESSAGE_ASCII,
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
115
            u'b\xf3dy': SIMPLE_MESSAGE_UTF8,
7058.4.2 by Jelmer Vernooij
Fix boundaries handling.
116
            b'b\xc3\xb3dy': SIMPLE_MESSAGE_UTF8,
117
            b'b\xf4dy': SIMPLE_MESSAGE_8BIT,
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
118
        }
119
        for body, expected in pairs.items():
120
            msg = EmailMessage('from@from.com', 'to@to.com', 'subject', body)
121
            self.assertEqualDiff(expected, msg.as_string())
122
6592.1.1 by Vincent Ladeuil
Fix minor incompatible change in email python 2.7.6 module.
123
    def test_multipart_message_simple(self):
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
124
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject')
125
        msg.add_inline_attachment('body')
6592.1.1 by Vincent Ladeuil
Fix minor incompatible change in email python 2.7.6 module.
126
        self.assertEqualDiff(simple_multipart_message(),
127
                             msg.as_string(BOUNDARY))
128
129
130
    def test_multipart_message_complex(self):
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
131
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject', 'body')
132
        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.
133
        self.assertEqualDiff(complex_multipart_message('x-subtype'),
134
                             msg.as_string(BOUNDARY))
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
135
136
    def test_headers_accept_unicode_and_utf8(self):
2625.6.3 by Adeodato Simó
Changes after review by John.
137
        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.
138
                'Pepe P\xc3\xa9red <pperez@ejemplo.com>' ]:
2625.6.3 by Adeodato Simó
Changes after review by John.
139
            msg = EmailMessage(user, user, user) # no exception raised
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
140
141
            for header in ['From', 'To', 'Subject']:
142
                value = msg[header]
143
                str(value).decode('ascii') # no UnicodeDecodeError
144
145
    def test_headers_reject_8bit(self):
146
        for i in range(3): # from_address, to_address, subject
147
            x = [ '"J. Random Developer" <jrandom@example.com>' ] * 3
148
            x[i] = 'Pepe P\xe9rez <pperez@ejemplo.com>'
149
            self.assertRaises(BzrBadParameterNotUnicode, EmailMessage, *x)
150
151
    def test_multiple_destinations(self):
152
        to_addresses = [ 'to1@to.com', 'to2@to.com', 'to3@to.com' ]
153
        msg = EmailMessage('from@from.com', to_addresses, 'subject')
154
        self.assertContainsRe(msg.as_string(), 'To: ' +
155
                ', '.join(to_addresses)) # re.M can't be passed, so no ^$
156
157
    def test_retrieving_headers(self):
158
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject')
159
        for header, value in [('From', 'from@from.com'), ('To', 'to@to.com'),
160
                ('Subject', 'subject')]:
161
            self.assertEqual(value, msg.get(header))
162
            self.assertEqual(value, msg[header])
163
        self.assertEqual(None, msg.get('Does-Not-Exist'))
164
        self.assertEqual(None, msg['Does-Not-Exist'])
165
        self.assertEqual('None', msg.get('Does-Not-Exist', 'None'))
166
167
    def test_setting_headers(self):
168
        msg = EmailMessage('from@from.com', 'to@to.com', 'subject')
169
        msg['To'] = 'to2@to.com'
170
        msg['Cc'] = 'cc@cc.com'
171
        self.assertEqual('to2@to.com', msg['To'])
172
        self.assertEqual('cc@cc.com', msg['Cc'])
173
174
    def test_address_to_encoded_header(self):
175
        def decode(s):
176
            """Convert a RFC2047-encoded string to a unicode string."""
177
            return ' '.join([chunk.decode(encoding or 'ascii')
178
                             for chunk, encoding in decode_header(s)])
179
180
        address = 'jrandom@example.com'
181
        encoded = EmailMessage.address_to_encoded_header(address)
182
        self.assertEqual(address, encoded)
183
184
        address = 'J Random Developer <jrandom@example.com>'
185
        encoded = EmailMessage.address_to_encoded_header(address)
186
        self.assertEqual(address, encoded)
187
188
        address = '"J. Random Developer" <jrandom@example.com>'
189
        encoded = EmailMessage.address_to_encoded_header(address)
190
        self.assertEqual(address, encoded)
191
192
        address = u'Pepe P\xe9rez <pperez@ejemplo.com>' # unicode ok
193
        encoded = EmailMessage.address_to_encoded_header(address)
6614.1.1 by Vincent Ladeuil
Fix assert_ being deprecated by using assertTrue.
194
        self.assertTrue('pperez@ejemplo.com' in encoded) # addr must be unencoded
6614.1.3 by Vincent Ladeuil
Fix assertEquals being deprecated by using assertEqual.
195
        self.assertEqual(address, decode(encoded))
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
196
197
        address = 'Pepe P\xc3\xa9red <pperez@ejemplo.com>' # UTF-8 ok
198
        encoded = EmailMessage.address_to_encoded_header(address)
6614.1.1 by Vincent Ladeuil
Fix assert_ being deprecated by using assertTrue.
199
        self.assertTrue('pperez@ejemplo.com' in encoded)
6614.1.3 by Vincent Ladeuil
Fix assertEquals being deprecated by using assertEqual.
200
        self.assertEqual(address, decode(encoded).encode('utf-8'))
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
201
202
        address = 'Pepe P\xe9rez <pperez@ejemplo.com>' # ISO-8859-1 not ok
203
        self.assertRaises(BzrBadParameterNotUnicode,
204
                EmailMessage.address_to_encoded_header, address)
205
206
    def test_string_with_encoding(self):
207
        pairs = {
7045.5.4 by Jelmer Vernooij
Fix a few more tests.
208
                u'Pepe':        (b'Pepe', 'ascii'),
7058.4.2 by Jelmer Vernooij
Fix boundaries handling.
209
                u'P\xe9rez':    (b'P\xc3\xa9rez', 'utf-8'),
210
                b'P\xc3\xa9rez': (b'P\xc3\xa9rez', 'utf-8'),
211
                b'P\xe8rez':     (b'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