[MERGE] forward port of branch 7.0 up to 75d3ea6
[odoo/odoo.git] / addons / mail / tests / test_mail_gateway.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Business Applications
5 #    Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 from openerp.addons.mail.tests.common import TestMail
23 from openerp.tools import mute_logger
24 import socket
25
26 MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
27 To: {to}
28 Received: by mail1.openerp.com (Postfix, from userid 10002)
29     id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
30 From: {email_from}
31 Subject: {subject}
32 MIME-Version: 1.0
33 Content-Type: multipart/alternative;
34     boundary="----=_Part_4200734_24778174.1344608186754"
35 Date: Fri, 10 Aug 2012 14:16:26 +0000
36 Message-ID: {msg_id}
37 {extra}
38 ------=_Part_4200734_24778174.1344608186754
39 Content-Type: text/plain; charset=utf-8
40 Content-Transfer-Encoding: quoted-printable
41
42 Please call me as soon as possible this afternoon!
43
44 --
45 Sylvie
46 ------=_Part_4200734_24778174.1344608186754
47 Content-Type: text/html; charset=utf-8
48 Content-Transfer-Encoding: quoted-printable
49
50 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
51 <html>
52  <head>=20
53   <meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8" />
54  </head>=20
55  <body style=3D"margin: 0; padding: 0; background: #ffffff;-webkit-text-size-adjust: 100%;">=20
56
57   <p>Please call me as soon as possible this afternoon!</p>
58
59   <p>--<br/>
60      Sylvie
61   <p>
62  </body>
63 </html>
64 ------=_Part_4200734_24778174.1344608186754--
65 """
66
67 MAIL_TEMPLATE_PLAINTEXT = """Return-Path: <whatever-2a840@postmaster.twitter.com>
68 To: {to}
69 Received: by mail1.openerp.com (Postfix, from userid 10002)
70     id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
71 From: Sylvie Lelitre <sylvie.lelitre@agrolait.com>
72 Subject: {subject}
73 MIME-Version: 1.0
74 Content-Type: text/plain
75 Date: Fri, 10 Aug 2012 14:16:26 +0000
76 Message-ID: {msg_id}
77 {extra}
78
79 Please call me as soon as possible this afternoon!
80
81 --
82 Sylvie
83 """
84
85 MAIL_MULTIPART_MIXED = """Return-Path: <ignasse.carambar@gmail.com>
86 X-Original-To: raoul@grosbedon.fr
87 Delivered-To: raoul@grosbedon.fr
88 Received: by mail1.grosbedon.com (Postfix, from userid 10002)
89     id E8166BFACA; Fri, 23 Aug 2013 13:18:01 +0200 (CEST)
90 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail1.grosbedon.com
91 X-Spam-Level: 
92 X-Spam-Status: No, score=-2.6 required=5.0 tests=BAYES_00,FREEMAIL_FROM,
93     HTML_MESSAGE,RCVD_IN_DNSWL_LOW autolearn=unavailable version=3.3.1
94 Received: from mail-ie0-f173.google.com (mail-ie0-f173.google.com [209.85.223.173])
95     by mail1.grosbedon.com (Postfix) with ESMTPS id 9BBD7BFAAA
96     for <raoul@openerp.fr>; Fri, 23 Aug 2013 13:17:55 +0200 (CEST)
97 Received: by mail-ie0-f173.google.com with SMTP id qd12so575130ieb.4
98         for <raoul@grosbedon.fr>; Fri, 23 Aug 2013 04:17:54 -0700 (PDT)
99 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
100         d=gmail.com; s=20120113;
101         h=mime-version:date:message-id:subject:from:to:content-type;
102         bh=dMNHV52EC7GAa7+9a9tqwT9joy9z+1950J/3A6/M/hU=;
103         b=DGuv0VjegdSrEe36ADC8XZ9Inrb3Iu+3/52Bm+caltddXFH9yewTr0JkCRQaJgMwG9
104          qXTQgP8qu/VFEbCh6scu5ZgU1hknzlNCYr3LT+Ih7dAZVUEHUJdwjzUU1LFV95G2RaCd
105          /Lwff6CibuUvrA+0CBO7IRKW0Sn5j0mukYu8dbaKsm6ou6HqS8Nuj85fcXJfHSHp6Y9u
106          dmE8jBh3fHCHF/nAvU+8aBNSIzl1FGfiBYb2jCoapIuVFitKR4q5cuoodpkH9XqqtOdH
107          DG+YjEyi8L7uvdOfN16eMr7hfUkQei1yQgvGu9/5kXoHg9+Gx6VsZIycn4zoaXTV3Nhn
108          nu4g==
109 MIME-Version: 1.0
110 X-Received: by 10.50.124.65 with SMTP id mg1mr1144467igb.43.1377256674216;
111  Fri, 23 Aug 2013 04:17:54 -0700 (PDT)
112 Received: by 10.43.99.71 with HTTP; Fri, 23 Aug 2013 04:17:54 -0700 (PDT)
113 Date: Fri, 23 Aug 2013 13:17:54 +0200
114 Message-ID: <CAP76m_V4BY2F7DWHzwfjteyhW8L2LJswVshtmtVym+LUJ=rASQ@mail.gmail.com>
115 Subject: Test mail multipart/mixed
116 From: =?ISO-8859-1?Q?Raoul Grosbedon=E9e?= <ignasse.carambar@gmail.com>
117 To: Followers of ASUSTeK-Joseph-Walters <raoul@grosbedon.fr>
118 Content-Type: multipart/mixed; boundary=089e01536c4ed4d17204e49b8e96
119
120 --089e01536c4ed4d17204e49b8e96
121 Content-Type: multipart/alternative; boundary=089e01536c4ed4d16d04e49b8e94
122
123 --089e01536c4ed4d16d04e49b8e94
124 Content-Type: text/plain; charset=ISO-8859-1
125
126 Should create a multipart/mixed: from gmail, *bold*, with attachment.
127
128 -- 
129 Marcel Boitempoils.
130
131 --089e01536c4ed4d16d04e49b8e94
132 Content-Type: text/html; charset=ISO-8859-1
133
134 <div dir="ltr">Should create a multipart/mixed: from gmail, <b>bold</b>, with attachment.<br clear="all"><div><br></div>-- <br>Marcel Boitempoils.</div>
135
136 --089e01536c4ed4d16d04e49b8e94--
137 --089e01536c4ed4d17204e49b8e96
138 Content-Type: text/plain; charset=US-ASCII; name="test.txt"
139 Content-Disposition: attachment; filename="test.txt"
140 Content-Transfer-Encoding: base64
141 X-Attachment-Id: f_hkpb27k00
142
143 dGVzdAo=
144 --089e01536c4ed4d17204e49b8e96--"""
145
146 MAIL_MULTIPART_MIXED_TWO = """X-Original-To: raoul@grosbedon.fr
147 Delivered-To: raoul@grosbedon.fr
148 Received: by mail1.grosbedon.com (Postfix, from userid 10002)
149     id E8166BFACA; Fri, 23 Aug 2013 13:18:01 +0200 (CEST)
150 From: "Bruce Wayne" <bruce@wayneenterprises.com>
151 Content-Type: multipart/alternative;
152  boundary="Apple-Mail=_9331E12B-8BD2-4EC7-B53E-01F3FBEC9227"
153 Message-Id: <6BB1FAB2-2104-438E-9447-07AE2C8C4A92@sexample.com>
154 Mime-Version: 1.0 (Mac OS X Mail 7.3 \(1878.6\))
155
156 --Apple-Mail=_9331E12B-8BD2-4EC7-B53E-01F3FBEC9227
157 Content-Transfer-Encoding: 7bit
158 Content-Type: text/plain;
159     charset=us-ascii
160
161 First and second part
162
163 --Apple-Mail=_9331E12B-8BD2-4EC7-B53E-01F3FBEC9227
164 Content-Type: multipart/mixed;
165  boundary="Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F"
166
167 --Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F
168 Content-Transfer-Encoding: 7bit
169 Content-Type: text/html;
170     charset=us-ascii
171
172 <html><head></head><body>First part</body></html>
173
174 --Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F
175 Content-Disposition: inline;
176     filename=thetruth.pdf
177 Content-Type: application/pdf;
178     name="thetruth.pdf"
179 Content-Transfer-Encoding: base64
180
181 SSBhbSB0aGUgQmF0TWFuCg==
182
183 --Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F
184 Content-Transfer-Encoding: 7bit
185 Content-Type: text/html;
186     charset=us-ascii
187
188 <html><head></head><body>Second part</body></html>
189 --Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F--
190
191 --Apple-Mail=_9331E12B-8BD2-4EC7-B53E-01F3FBEC9227--
192 """
193
194 class TestMailgateway(TestMail):
195
196     def test_00_message_parse(self):
197         """ Testing incoming emails parsing """
198         cr, uid = self.cr, self.uid
199
200         res = self.mail_thread.message_parse(cr, uid, MAIL_TEMPLATE_PLAINTEXT)
201         self.assertIn('Please call me as soon as possible this afternoon!', res.get('body', ''),
202                       'message_parse: missing text in text/plain body after parsing')
203
204         res = self.mail_thread.message_parse(cr, uid, MAIL_TEMPLATE)
205         self.assertIn('<p>Please call me as soon as possible this afternoon!</p>', res.get('body', ''),
206                       'message_parse: missing html in multipart/alternative body after parsing')
207
208         res = self.mail_thread.message_parse(cr, uid, MAIL_MULTIPART_MIXED)
209         self.assertNotIn('Should create a multipart/mixed: from gmail, *bold*, with attachment', res.get('body', ''),
210                          'message_parse: text version should not be in body after parsing multipart/mixed')
211         self.assertIn('<div dir="ltr">Should create a multipart/mixed: from gmail, <b>bold</b>, with attachment.<br clear="all"><div><br></div>', res.get('body', ''),
212                       'message_parse: html version should be in body after parsing multipart/mixed')
213
214         res = self.mail_thread.message_parse(cr, uid, MAIL_MULTIPART_MIXED_TWO)
215         self.assertNotIn('First and second part', res.get('body', ''),
216                          'message_parse: text version should not be in body after parsing multipart/mixed')
217         self.assertIn('First part', res.get('body', ''),
218                       'message_parse: first part of the html version should be in body after parsing multipart/mixed')
219         self.assertIn('Second part', res.get('body', ''),
220                       'message_parse: second part of the html version should be in body after parsing multipart/mixed')
221
222     @mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm')
223     def test_10_message_process(self):
224         """ Testing incoming emails processing. """
225         cr, uid, user_raoul = self.cr, self.uid, self.user_raoul
226
227         def format_and_process(template, to='groups@example.com, other@gmail.com', subject='Frogs',
228                                extra='', email_from='Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>',
229                                msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>',
230                                model=None):
231             self.assertEqual(self.mail_group.search(cr, uid, [('name', '=', subject)]), [])
232             mail = template.format(to=to, subject=subject, extra=extra, email_from=email_from, msg_id=msg_id)
233             self.mail_thread.message_process(cr, uid, model, mail)
234             return self.mail_group.search(cr, uid, [('name', '=', subject)])
235
236         # --------------------------------------------------
237         # Data creation
238         # --------------------------------------------------
239
240         # groups@.. will cause the creation of new mail groups
241         self.mail_group_model_id = self.ir_model.search(cr, uid, [('model', '=', 'mail.group')])[0]
242         alias_id = self.mail_alias.create(cr, uid, {
243             'alias_name': 'groups',
244             'alias_user_id': False,
245             'alias_model_id': self.mail_group_model_id,
246             'alias_parent_model_id': self.mail_group_model_id,
247             'alias_parent_thread_id': self.group_pigs_id,
248             'alias_contact': 'everyone'})
249
250         # --------------------------------------------------
251         # Test1: new record creation
252         # --------------------------------------------------
253
254         # Do: incoming mail from an unknown partner on an alias creates a new mail_group "frogs"
255         self._init_mock_build_email()
256         frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com')
257         sent_emails = self._build_email_kwargs_list
258         # Test: one group created by mailgateway administrator
259         self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
260         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
261         res = self.mail_group.perm_read(cr, uid, [frog_group.id], details=False)
262         self.assertEqual(res[0].get('create_uid'), uid,
263                          'message_process: group should have been created by uid as alias_user__id is False on the alias')
264         # Test: one message that is the incoming email
265         self.assertEqual(len(frog_group.message_ids), 1,
266                          'message_process: newly created group should have the incoming email in message_ids')
267         msg = frog_group.message_ids[0]
268         self.assertEqual('Frogs', msg.subject,
269                          'message_process: newly created group should have the incoming email as first message')
270         self.assertIn('Please call me as soon as possible this afternoon!', msg.body,
271                       'message_process: newly created group should have the incoming email as first message')
272         self.assertEqual('email', msg.type,
273                          'message_process: newly created group should have an email as first message')
274         self.assertEqual('Discussions', msg.subtype_id.name,
275                          'message_process: newly created group should not have a log first message but an email')
276         # Test: message: unknown email address -> message has email_from, not author_id
277         self.assertFalse(msg.author_id,
278                          'message_process: message on created group should not have an author_id')
279         self.assertIn('test.sylvie.lelitre@agrolait.com', msg.email_from,
280                       'message_process: message on created group should have an email_from')
281         # Test: followers: nobody
282         self.assertEqual(len(frog_group.message_follower_ids), 0, 'message_process: newly create group should not have any follower')
283         # Test: sent emails: no-one
284         self.assertEqual(len(sent_emails), 0,
285                          'message_process: should create emails without any follower added')
286         # Data: unlink group
287         frog_group.unlink()
288
289         # Do: incoming email from an unknown partner on a Partners only alias -> bounce
290         self._init_mock_build_email()
291         self.mail_alias.write(cr, uid, [alias_id], {'alias_contact': 'partners'})
292         frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other2@gmail.com')
293         # Test: no group created
294         self.assertTrue(len(frog_groups) == 0)
295         # Test: email bounced
296         sent_emails = self._build_email_kwargs_list
297         self.assertEqual(len(sent_emails), 1,
298                          'message_process: incoming email on Partners alias should send a bounce email')
299         self.assertIn('Frogs', sent_emails[0].get('subject'),
300                       'message_process: bounce email on Partners alias should contain the original subject')
301         self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to'),
302                       'message_process: bounce email on Partners alias should have original email sender as recipient')
303
304         # Do: incoming email from an unknown partner on a Followers only alias -> bounce
305         self._init_mock_build_email()
306         self.mail_alias.write(cr, uid, [alias_id], {'alias_contact': 'followers'})
307         frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other3@gmail.com')
308         # Test: no group created
309         self.assertTrue(len(frog_groups) == 0)
310         # Test: email bounced
311         sent_emails = self._build_email_kwargs_list
312         self.assertEqual(len(sent_emails), 1,
313                          'message_process: incoming email on Followers alias should send a bounce email')
314         self.assertIn('Frogs', sent_emails[0].get('subject'),
315                       'message_process: bounce email on Followers alias should contain the original subject')
316         self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to'),
317                       'message_process: bounce email on Followers alias should have original email sender as recipient')
318
319         # Do: incoming email from a known partner on a Partners alias -> ok (+ test on alias.user_id)
320         self.mail_alias.write(cr, uid, [alias_id], {'alias_user_id': self.user_raoul_id, 'alias_contact': 'partners'})
321         p1id = self.res_partner.create(cr, uid, {'name': 'Sylvie Lelitre', 'email': 'test.sylvie.lelitre@agrolait.com'})
322         p2id = self.res_partner.create(cr, uid, {'name': 'Other Poilvache', 'email': 'other4@gmail.com'})
323         self._init_mock_build_email()
324         frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other4@gmail.com')
325         sent_emails = self._build_email_kwargs_list
326         # Test: one group created by Raoul
327         self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
328         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
329         res = self.mail_group.perm_read(cr, uid, [frog_group.id], details=False)
330         self.assertEqual(res[0].get('create_uid'), self.user_raoul_id,
331                          'message_process: group should have been created by alias_user_id')
332         # Test: one message that is the incoming email
333         self.assertEqual(len(frog_group.message_ids), 1,
334                          'message_process: newly created group should have the incoming email in message_ids')
335         msg = frog_group.message_ids[0]
336         # Test: message: author found
337         self.assertEqual(p1id, msg.author_id.id,
338                          'message_process: message on created group should have Sylvie as author_id')
339         self.assertIn('Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>', msg.email_from,
340                       'message_process: message on created group should have have an email_from')
341         # Test: author (not recipient and not Raoul (as alias owner)) added as follower
342         frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
343         self.assertEqual(frog_follower_ids, set([p1id]),
344                          'message_process: newly created group should have 1 follower (author, not creator, not recipients)')
345         # Test: sent emails: no-one, no bounce effet
346         sent_emails = self._build_email_kwargs_list
347         self.assertEqual(len(sent_emails), 0,
348                          'message_process: should not bounce incoming emails')
349         # Data: unlink group
350         frog_group.unlink()
351
352         # Do: incoming email from a not follower Partner on a Followers only alias -> bounce
353         self._init_mock_build_email()
354         self.mail_alias.write(cr, uid, [alias_id], {'alias_user_id': False, 'alias_contact': 'followers'})
355         frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other5@gmail.com')
356         # Test: no group created
357         self.assertTrue(len(frog_groups) == 0)
358         # Test: email bounced
359         sent_emails = self._build_email_kwargs_list
360         self.assertEqual(len(sent_emails), 1,
361                          'message_process: incoming email on Partners alias should send a bounce email')
362
363         # Do: incoming email from a parent document follower on a Followers only alias -> ok
364         self._init_mock_build_email()
365         self.mail_group.message_subscribe(cr, uid, [self.group_pigs_id], [p1id])
366         frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other6@gmail.com')
367         # Test: one group created by Raoul (or Sylvie maybe, if we implement it)
368         self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
369         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
370         # Test: one message that is the incoming email
371         self.assertEqual(len(frog_group.message_ids), 1,
372                          'message_process: newly created group should have the incoming email in message_ids')
373         # Test: author (and not recipient) added as follower
374         frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
375         self.assertEqual(frog_follower_ids, set([p1id]),
376                          'message_process: newly created group should have 1 follower (author, not creator, not recipients)')
377         # Test: sent emails: no-one, no bounce effet
378         sent_emails = self._build_email_kwargs_list
379         self.assertEqual(len(sent_emails), 0,
380                          'message_process: should not bounce incoming emails')
381
382         # --------------------------------------------------
383         # Test2: update-like alias
384         # --------------------------------------------------
385
386         # Do: Pigs alias is restricted, should bounce
387         self._init_mock_build_email()
388         self.mail_group.write(cr, uid, [frog_group.id], {'alias_name': 'frogs', 'alias_contact': 'followers', 'alias_force_thread_id': frog_group.id})
389         frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
390                                          msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
391                                          to='frogs@example.com>', subject='Re: news')
392         # Test: no group 'Re: news' created, still only 1 Frogs group
393         self.assertEqual(len(frog_groups), 0,
394                          'message_process: reply on Frogs should not have created a new group with new subject')
395         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
396         self.assertEqual(len(frog_groups), 1,
397                          'message_process: reply on Frogs should not have created a duplicate group with old subject')
398         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
399         # Test: email bounced
400         sent_emails = self._build_email_kwargs_list
401         self.assertEqual(len(sent_emails), 1,
402                          'message_process: incoming email on Followers alias should send a bounce email')
403         self.assertIn('Re: news', sent_emails[0].get('subject'),
404                       'message_process: bounce email on Followers alias should contain the original subject')
405
406         # Do: Pigs alias is restricted, should accept Followers
407         self._init_mock_build_email()
408         self.mail_group.message_subscribe(cr, uid, [frog_group.id], [p2id])
409         frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
410                                          msg_id='<1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>',
411                                          to='frogs@example.com>', subject='Re: cats')
412         # Test: no group 'Re: news' created, still only 1 Frogs group
413         self.assertEqual(len(frog_groups), 0,
414                          'message_process: reply on Frogs should not have created a new group with new subject')
415         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
416         self.assertEqual(len(frog_groups), 1,
417                          'message_process: reply on Frogs should not have created a duplicate group with old subject')
418         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
419         # Test: one new message
420         self.assertEqual(len(frog_group.message_ids), 2, 'message_process: group should contain 2 messages after reply')
421         # Test: sent emails: 1 (Sylvie copy of the incoming email, but no bounce)
422         sent_emails = self._build_email_kwargs_list
423         self.assertEqual(len(sent_emails), 1,
424                          'message_process: one email should have been generated')
425         self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to')[0],
426                       'message_process: email should be sent to Sylvie')
427         self.mail_group.message_unsubscribe(cr, uid, [frog_group.id], [p2id])
428
429         # --------------------------------------------------
430         # Test3: discussion and replies
431         # --------------------------------------------------
432
433         # Do: even with a wrong destination, a reply should end up in the correct thread
434         frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
435                                          msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
436                                          to='erroneous@example.com>', subject='Re: news',
437                                          extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>\n')
438         # Test: no group 'Re: news' created, still only 1 Frogs group
439         self.assertEqual(len(frog_groups), 0,
440                          'message_process: reply on Frogs should not have created a new group with new subject')
441         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
442         self.assertEqual(len(frog_groups), 1,
443                          'message_process: reply on Frogs should not have created a duplicate group with old subject')
444         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
445         # Test: one new message
446         self.assertEqual(len(frog_group.message_ids), 3, 'message_process: group should contain 3 messages after reply')
447         # Test: author (and not recipient) added as follower
448         frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
449         self.assertEqual(frog_follower_ids, set([p1id, p2id]),
450                          'message_process: after reply, group should have 2 followers')
451
452         # Do: incoming email with ref holding model / res_id but that does not match any message in the thread: must raise since OpenERP saas-3
453         self.assertRaises(ValueError,
454                           format_and_process,
455                           MAIL_TEMPLATE, email_from='other5@gmail.com',
456                           to='noone@example.com', subject='spam',
457                           extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>' % frog_group.id,
458                           msg_id='<1.1.JavaMail.new@agrolait.com>')
459
460         # When 6.1 messages are present, compat mode is available
461         # Create a fake 6.1 message
462         tmp_msg_id = self.mail_message.create(cr, uid, {'message_id': False, 'model': 'mail.group', 'res_id': frog_group.id})
463         # Do: compat mode accepts partial-matching emails
464         frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other5@gmail.com',
465                                          msg_id='<1.2.JavaMail.new@agrolait.com>',
466                                          to='noone@example.com>', subject='spam',
467                                          extra='In-Reply-To: <12321321-openerp-%d-mail.group@%s>' % (frog_group.id, socket.gethostname()))
468         self.mail_message.unlink(cr, uid, [tmp_msg_id])
469         # Test: no group 'Re: news' created, still only 1 Frogs group
470         self.assertEqual(len(frog_groups), 0,
471                          'message_process: reply on Frogs should not have created a new group with new subject')
472         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
473         self.assertEqual(len(frog_groups), 1,
474                          'message_process: reply on Frogs should not have created a duplicate group with old subject')
475         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
476         # Test: one new message
477         self.assertEqual(len(frog_group.message_ids), 4, 'message_process: group should contain 4 messages after reply')
478
479         # 6.1 compat mode should not work if hostname does not match!
480         tmp_msg_id = self.mail_message.create(cr, uid, {'message_id': False, 'model': 'mail.group', 'res_id': frog_group.id})
481         self.assertRaises(ValueError,
482                           format_and_process,
483                           MAIL_TEMPLATE, email_from='other5@gmail.com',
484                           msg_id='<1.3.JavaMail.new@agrolait.com>',
485                           to='noone@example.com>', subject='spam',
486                           extra='In-Reply-To: <12321321-openerp-%d-mail.group@neighbor.com>' % frog_group.id)
487         self.mail_message.unlink(cr, uid, [tmp_msg_id])
488
489
490         # Do: due to some issue, same email goes back into the mailgateway
491         frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
492                                          msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
493                                          subject='Re: news', extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>\n')
494         # Test: no group 'Re: news' created, still only 1 Frogs group
495         self.assertEqual(len(frog_groups), 0,
496                          'message_process: reply on Frogs should not have created a new group with new subject')
497         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
498         self.assertEqual(len(frog_groups), 1,
499                          'message_process: reply on Frogs should not have created a duplicate group with old subject')
500         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
501         # Test: no new message
502         self.assertEqual(len(frog_group.message_ids), 4, 'message_process: message with already existing message_id should not have been duplicated')
503         # Test: message_id is still unique
504         msg_ids = self.mail_message.search(cr, uid, [('message_id', 'ilike', '<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>')])
505         self.assertEqual(len(msg_ids), 1,
506                          'message_process: message with already existing message_id should not have been duplicated')
507
508         # --------------------------------------------------
509         # Test4: email_from and partner finding
510         # --------------------------------------------------
511
512         # Data: extra partner with Raoul's email -> test the 'better author finding'
513         extra_partner_id = self.res_partner.create(cr, uid, {'name': 'A-Raoul', 'email': 'test_raoul@email.com'})
514
515         # Do: post a new message, with a known partner -> duplicate emails -> partner
516         format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
517                            subject='Re: news (2)',
518                            msg_id='<1198923581.41972151344608186760.JavaMail.new1@agrolait.com>',
519                            extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
520         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
521         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
522         # Test: author is A-Raoul (only existing)
523         self.assertEqual(frog_group.message_ids[0].author_id.id, extra_partner_id,
524                          'message_process: email_from -> author_id wrong')
525
526         # Do: post a new message, with a known partner -> duplicate emails -> user
527         frog_group.message_unsubscribe([extra_partner_id])
528         self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'})
529         format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
530                            to='groups@example.com', subject='Re: news (3)',
531                            msg_id='<1198923581.41972151344608186760.JavaMail.new2@agrolait.com>',
532                            extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
533         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
534         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
535         # Test: author is Raoul (user), not A-Raoul
536         self.assertEqual(frog_group.message_ids[0].author_id.id, self.partner_raoul_id,
537                          'message_process: email_from -> author_id wrong')
538
539         # Do: post a new message, with a known partner -> duplicate emails -> partner because is follower
540         frog_group.message_unsubscribe([self.partner_raoul_id])
541         frog_group.message_subscribe([extra_partner_id])
542         raoul_email = self.user_raoul.email
543         self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'})
544         format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
545                            to='groups@example.com', subject='Re: news (3)',
546                            msg_id='<1198923581.41972151344608186760.JavaMail.new3@agrolait.com>',
547                            extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
548         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
549         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
550         # Test: author is Raoul (user), not A-Raoul
551         self.assertEqual(frog_group.message_ids[0].author_id.id, extra_partner_id,
552                          'message_process: email_from -> author_id wrong')
553
554         self.res_users.write(cr, uid, self.user_raoul_id, {'email': raoul_email})
555
556         # --------------------------------------------------
557         # Test5: misc gateway features
558         # --------------------------------------------------
559
560         # Do: incoming email with model that does not accepts incoming emails must raise
561         self.assertRaises(ValueError,
562                           format_and_process,
563                           MAIL_TEMPLATE,
564                           to='noone@example.com', subject='spam', extra='', model='res.country',
565                           msg_id='<1198923581.41972151344608186760.JavaMail.new4@agrolait.com>')
566
567         # Do: incoming email without model and without alias must raise
568         self.assertRaises(ValueError,
569                           format_and_process,
570                           MAIL_TEMPLATE,
571                           to='noone@example.com', subject='spam', extra='',
572                           msg_id='<1198923581.41972151344608186760.JavaMail.new5@agrolait.com>')
573
574         # Do: incoming email with model that accepting incoming emails as fallback
575         frog_groups = format_and_process(MAIL_TEMPLATE,
576                                          to='noone@example.com',
577                                          subject='Spammy', extra='', model='mail.group',
578                                          msg_id='<1198923581.41972151344608186760.JavaMail.new6@agrolait.com>')
579         self.assertEqual(len(frog_groups), 1,
580                          'message_process: erroneous email but with a fallback model should have created a new mail.group')
581
582         # Do: incoming email in plaintext should be stored as  html
583         frog_groups = format_and_process(MAIL_TEMPLATE_PLAINTEXT,
584                                          to='groups@example.com', subject='Frogs Return', extra='',
585                                          msg_id='<deadcafe.1337@smtp.agrolait.com>')
586         # Test: one group created with one message
587         self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
588         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
589         msg = frog_group.message_ids[0]
590         # Test: plain text content should be wrapped and stored as html
591         self.assertIn('<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>', msg.body,
592                       'message_process: plaintext incoming email incorrectly parsed')
593
594     @mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm')
595     def test_20_thread_parent_resolution(self):
596         """ Testing parent/child relationships are correctly established when processing incoming mails """
597         cr, uid = self.cr, self.uid
598
599         def format(template, to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1',
600                                 extra='', email_from='Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>',
601                                 msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>'):
602             return template.format(to=to, subject=subject, extra=extra, email_from=email_from, msg_id=msg_id)
603
604         group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
605         msg1 = group_pigs.message_post(body='My Body', subject='1')
606         msg2 = group_pigs.message_post(body='My Body', subject='2')
607         msg1, msg2 = self.mail_message.browse(cr, uid, [msg1, msg2])
608         self.assertTrue(msg1.message_id, "message_process: new message should have a proper message_id")
609
610         # Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms
611         # 0. Direct alias match
612         reply_msg1 = format(MAIL_TEMPLATE, to='Pretty Pigs <group+pigs@example.com>',
613                             extra='In-Reply-To: %s' % msg1.message_id,
614                             msg_id='<1198923581.41972151344608186760.JavaMail.2@agrolait.com>')
615         self.mail_group.message_process(cr, uid, None, reply_msg1)
616
617         # 1. In-Reply-To header
618         reply_msg2 = format(MAIL_TEMPLATE, to='erroneous@example.com',
619                             extra='In-Reply-To: %s' % msg1.message_id,
620                             msg_id='<1198923581.41972151344608186760.JavaMail.3@agrolait.com>')
621         self.mail_group.message_process(cr, uid, None, reply_msg2)
622
623         # 2. References header
624         reply_msg3 = format(MAIL_TEMPLATE, to='erroneous@example.com',
625                             extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id,
626                             msg_id='<1198923581.41972151344608186760.JavaMail.4@agrolait.com>')
627         self.mail_group.message_process(cr, uid, None, reply_msg3)
628
629         # 3. Subject contains [<ID>] + model passed to message+process -> only attached to group, but not to mail (not in msg1.child_ids)
630         reply_msg4 = format(MAIL_TEMPLATE, to='erroneous@example.com',
631                             extra='', subject='Re: [%s] 1' % self.group_pigs_id,
632                             msg_id='<1198923581.41972151344608186760.JavaMail.5@agrolait.com>')
633         self.mail_group.message_process(cr, uid, 'mail.group', reply_msg4)
634
635         group_pigs.refresh()
636         msg1.refresh()
637         self.assertEqual(6, len(group_pigs.message_ids), 'message_process: group should contain 6 messages')
638         self.assertEqual(3, len(msg1.child_ids), 'message_process: msg1 should have 3 children now')
639
640     def test_30_private_discussion(self):
641         """ Testing private discussion between partners. """
642         cr, uid = self.cr, self.uid
643
644         def format(template, to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1',
645                                 extra='', email_from='Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>',
646                                 msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>'):
647             return template.format(to=to, subject=subject, extra=extra, email_from=email_from, msg_id=msg_id)
648
649         # Do: Raoul writes to Bert and Administrator, with a thread_model in context that should not be taken into account
650         msg1_pids = [self.partner_admin_id, self.partner_bert_id]
651         msg1_id = self.mail_thread.message_post(
652             cr, self.user_raoul_id, False,
653             partner_ids=msg1_pids,
654             subtype='mail.mt_comment',
655             context={'thread_model': 'mail.group'}
656         )
657
658         # Test: message recipients
659         msg = self.mail_message.browse(cr, uid, msg1_id)
660         msg_pids = [p.id for p in msg.partner_ids]
661         msg_nids = [p.id for p in msg.notified_partner_ids]
662         test_pids = msg1_pids
663         test_nids = msg1_pids
664         self.assertEqual(set(msg_pids), set(test_pids),
665                          'message_post: private discussion: incorrect recipients')
666         self.assertEqual(set(msg_nids), set(test_nids),
667                          'message_post: private discussion: incorrect notified recipients')
668         self.assertEqual(msg.model, False,
669                          'message_post: private discussion: context key "thread_model" not correctly ignored when having no res_id')
670         # Test: message-id
671         self.assertIn('openerp-private', msg.message_id,
672                       'message_post: private discussion: message-id should contain the private keyword')
673
674         # Do: Bert replies through mailgateway (is a customer)
675         reply_message = format(MAIL_TEMPLATE, to='not_important@mydomain.com',
676                                email_from='bert@bert.fr',
677                                extra='In-Reply-To: %s' % msg.message_id,
678                                msg_id='<test30.JavaMail.0@agrolait.com>')
679         self.mail_thread.message_process(cr, uid, None, reply_message)
680
681         # Test: last mail_message created
682         msg2_id = self.mail_message.search(cr, uid, [], limit=1)[0]
683
684         # Test: message recipients
685         msg = self.mail_message.browse(cr, uid, msg2_id)
686         msg_pids = [p.id for p in msg.partner_ids]
687         msg_nids = [p.id for p in msg.notified_partner_ids]
688         test_pids = [self.partner_admin_id, self.partner_raoul_id]
689         test_nids = test_pids
690         self.assertEqual(msg.author_id.id, self.partner_bert_id,
691                          'message_post: private discussion: wrong author through mailgatewya based on email')
692         self.assertEqual(set(msg_pids), set(test_pids),
693                          'message_post: private discussion: incorrect recipients when replying')
694         self.assertEqual(set(msg_nids), set(test_nids),
695                          'message_post: private discussion: incorrect notified recipients when replying')
696
697         # Do: Bert replies through chatter (is a customer)
698         msg3_id = self.mail_thread.message_post(
699             cr, uid, False,
700             author_id=self.partner_bert_id,
701             parent_id=msg1_id, subtype='mail.mt_comment')
702
703         # Test: message recipients
704         msg = self.mail_message.browse(cr, uid, msg3_id)
705         msg_pids = [p.id for p in msg.partner_ids]
706         msg_nids = [p.id for p in msg.notified_partner_ids]
707         test_pids = [self.partner_admin_id, self.partner_raoul_id]
708         test_nids = test_pids
709         self.assertEqual(set(msg_pids), set(test_pids),
710                          'message_post: private discussion: incorrect recipients when replying')
711         self.assertEqual(set(msg_nids), set(test_nids),
712                          'message_post: private discussion: incorrect notified recipients when replying')
713
714         # Do: Administrator replies
715         msg3_id = self.mail_thread.message_post(cr, uid, False, parent_id=msg3_id, subtype='mail.mt_comment')
716
717         # Test: message recipients
718         msg = self.mail_message.browse(cr, uid, msg3_id)
719         msg_pids = [p.id for p in msg.partner_ids]
720         msg_nids = [p.id for p in msg.notified_partner_ids]
721         test_pids = [self.partner_bert_id, self.partner_raoul_id]
722         test_nids = test_pids
723         self.assertEqual(set(msg_pids), set(test_pids),
724                          'message_post: private discussion: incorrect recipients when replying')
725         self.assertEqual(set(msg_nids), set(test_nids),
726                          'message_post: private discussion: incorrect notified recipients when replying')