[FIX] mail: fixed bounce email recognition + invite email headers + mass mailing...
[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
147 class TestMailgateway(TestMail):
148
149     def test_00_message_parse(self):
150         """ Testing incoming emails parsing """
151         cr, uid = self.cr, self.uid
152
153         res = self.mail_thread.message_parse(cr, uid, MAIL_TEMPLATE_PLAINTEXT)
154         self.assertIn('Please call me as soon as possible this afternoon!', res.get('body', ''),
155                       'message_parse: missing text in text/plain body after parsing')
156
157         res = self.mail_thread.message_parse(cr, uid, MAIL_TEMPLATE)
158         self.assertIn('<p>Please call me as soon as possible this afternoon!</p>', res.get('body', ''),
159                       'message_parse: missing html in multipart/alternative body after parsing')
160
161         res = self.mail_thread.message_parse(cr, uid, MAIL_MULTIPART_MIXED)
162         self.assertNotIn('Should create a multipart/mixed: from gmail, *bold*, with attachment', res.get('body', ''),
163                          'message_parse: text version should not be in body after parsing multipart/mixed')
164         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', ''),
165                       'message_parse: html version should be in body after parsing multipart/mixed')
166
167     @mute_logger('openerp.addons.mail.mail_thread', 'openerp.models')
168     def test_10_message_process(self):
169         """ Testing incoming emails processing. """
170         cr, uid, user_raoul = self.cr, self.uid, self.user_raoul
171
172         def format_and_process(template, to='groups@example.com, other@gmail.com', subject='Frogs',
173                                extra='', email_from='Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>',
174                                msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>',
175                                model=None):
176             self.assertEqual(self.mail_group.search(cr, uid, [('name', '=', subject)]), [])
177             mail = template.format(to=to, subject=subject, extra=extra, email_from=email_from, msg_id=msg_id)
178             self.mail_thread.message_process(cr, uid, model, mail)
179             return self.mail_group.search(cr, uid, [('name', '=', subject)])
180
181         # --------------------------------------------------
182         # Data creation
183         # --------------------------------------------------
184
185         # groups@.. will cause the creation of new mail groups
186         self.mail_group_model_id = self.ir_model.search(cr, uid, [('model', '=', 'mail.group')])[0]
187         alias_id = self.mail_alias.create(cr, uid, {
188             'alias_name': 'groups',
189             'alias_user_id': False,
190             'alias_model_id': self.mail_group_model_id,
191             'alias_parent_model_id': self.mail_group_model_id,
192             'alias_parent_thread_id': self.group_pigs_id,
193             'alias_contact': 'everyone'})
194
195         # --------------------------------------------------
196         # Test1: new record creation
197         # --------------------------------------------------
198
199         # Do: incoming mail from an unknown partner on an alias creates a new mail_group "frogs"
200         self._init_mock_build_email()
201         frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com')
202         sent_emails = self._build_email_kwargs_list
203         # Test: one group created by mailgateway administrator
204         self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
205         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
206         res = self.mail_group.get_metadata(cr, uid, [frog_group.id])[0].get('create_uid') or [None]
207         self.assertEqual(res[0], uid,
208                          'message_process: group should have been created by uid as alias_user__id is False on the alias')
209         # Test: one message that is the incoming email
210         self.assertEqual(len(frog_group.message_ids), 1,
211                          'message_process: newly created group should have the incoming email in message_ids')
212         msg = frog_group.message_ids[0]
213         self.assertEqual('Frogs', msg.subject,
214                          'message_process: newly created group should have the incoming email as first message')
215         self.assertIn('Please call me as soon as possible this afternoon!', msg.body,
216                       'message_process: newly created group should have the incoming email as first message')
217         self.assertEqual('email', msg.type,
218                          'message_process: newly created group should have an email as first message')
219         self.assertEqual('Discussions', msg.subtype_id.name,
220                          'message_process: newly created group should not have a log first message but an email')
221         # Test: message: unknown email address -> message has email_from, not author_id
222         self.assertFalse(msg.author_id,
223                          'message_process: message on created group should not have an author_id')
224         self.assertIn('test.sylvie.lelitre@agrolait.com', msg.email_from,
225                       'message_process: message on created group should have an email_from')
226         # Test: followers: nobody
227         self.assertEqual(len(frog_group.message_follower_ids), 0, 'message_process: newly create group should not have any follower')
228         # Test: sent emails: no-one
229         self.assertEqual(len(sent_emails), 0,
230                          'message_process: should create emails without any follower added')
231         # Data: unlink group
232         frog_group.unlink()
233
234         # Do: incoming email from an unknown partner on a Partners only alias -> bounce
235         self._init_mock_build_email()
236         self.mail_alias.write(cr, uid, [alias_id], {'alias_contact': 'partners'})
237         frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other2@gmail.com')
238         # Test: no group created
239         self.assertTrue(len(frog_groups) == 0)
240         # Test: email bounced
241         sent_emails = self._build_email_kwargs_list
242         self.assertEqual(len(sent_emails), 1,
243                          'message_process: incoming email on Partners alias should send a bounce email')
244         self.assertIn('Frogs', sent_emails[0].get('subject'),
245                       'message_process: bounce email on Partners alias should contain the original subject')
246         self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to'),
247                       'message_process: bounce email on Partners alias should have original email sender as recipient')
248
249         # Do: incoming email from an unknown partner on a Followers only alias -> bounce
250         self._init_mock_build_email()
251         self.mail_alias.write(cr, uid, [alias_id], {'alias_contact': 'followers'})
252         frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other3@gmail.com')
253         # Test: no group created
254         self.assertTrue(len(frog_groups) == 0)
255         # Test: email bounced
256         sent_emails = self._build_email_kwargs_list
257         self.assertEqual(len(sent_emails), 1,
258                          'message_process: incoming email on Followers alias should send a bounce email')
259         self.assertIn('Frogs', sent_emails[0].get('subject'),
260                       'message_process: bounce email on Followers alias should contain the original subject')
261         self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to'),
262                       'message_process: bounce email on Followers alias should have original email sender as recipient')
263
264         # Do: incoming email from a known partner on a Partners alias -> ok (+ test on alias.user_id)
265         self.mail_alias.write(cr, uid, [alias_id], {'alias_user_id': self.user_raoul_id, 'alias_contact': 'partners'})
266         p1id = self.res_partner.create(cr, uid, {'name': 'Sylvie Lelitre', 'email': 'test.sylvie.lelitre@agrolait.com'})
267         p2id = self.res_partner.create(cr, uid, {'name': 'Other Poilvache', 'email': 'other4@gmail.com'})
268         self._init_mock_build_email()
269         frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other4@gmail.com')
270         sent_emails = self._build_email_kwargs_list
271         # Test: one group created by Raoul
272         self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
273         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
274         res = self.mail_group.get_metadata(cr, uid, [frog_group.id])[0].get('create_uid') or [None]
275         self.assertEqual(res[0], self.user_raoul_id,
276                          'message_process: group should have been created by alias_user_id')
277         # Test: one message that is the incoming email
278         self.assertEqual(len(frog_group.message_ids), 1,
279                          'message_process: newly created group should have the incoming email in message_ids')
280         msg = frog_group.message_ids[0]
281         # Test: message: author found
282         self.assertEqual(p1id, msg.author_id.id,
283                          'message_process: message on created group should have Sylvie as author_id')
284         self.assertIn('Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>', msg.email_from,
285                       'message_process: message on created group should have have an email_from')
286         # Test: author (not recipient and not Raoul (as alias owner)) added as follower
287         frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
288         self.assertEqual(frog_follower_ids, set([p1id]),
289                          'message_process: newly created group should have 1 follower (author, not creator, not recipients)')
290         # Test: sent emails: no-one, no bounce effet
291         sent_emails = self._build_email_kwargs_list
292         self.assertEqual(len(sent_emails), 0,
293                          'message_process: should not bounce incoming emails')
294         # Data: unlink group
295         frog_group.unlink()
296
297         # Do: incoming email from a not follower Partner on a Followers only alias -> bounce
298         self._init_mock_build_email()
299         self.mail_alias.write(cr, uid, [alias_id], {'alias_user_id': False, 'alias_contact': 'followers'})
300         frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other5@gmail.com')
301         # Test: no group created
302         self.assertTrue(len(frog_groups) == 0)
303         # Test: email bounced
304         sent_emails = self._build_email_kwargs_list
305         self.assertEqual(len(sent_emails), 1,
306                          'message_process: incoming email on Partners alias should send a bounce email')
307
308         # Do: incoming email from a parent document follower on a Followers only alias -> ok
309         self._init_mock_build_email()
310         self.mail_group.message_subscribe(cr, uid, [self.group_pigs_id], [p1id])
311         frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other6@gmail.com')
312         # Test: one group created by Raoul (or Sylvie maybe, if we implement it)
313         self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
314         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
315         # Test: one message that is the incoming email
316         self.assertEqual(len(frog_group.message_ids), 1,
317                          'message_process: newly created group should have the incoming email in message_ids')
318         # Test: author (and not recipient) added as follower
319         frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
320         self.assertEqual(frog_follower_ids, set([p1id]),
321                          'message_process: newly created group should have 1 follower (author, not creator, not recipients)')
322         # Test: sent emails: no-one, no bounce effet
323         sent_emails = self._build_email_kwargs_list
324         self.assertEqual(len(sent_emails), 0,
325                          'message_process: should not bounce incoming emails')
326
327         # --------------------------------------------------
328         # Test2: update-like alias
329         # --------------------------------------------------
330
331         # Do: Pigs alias is restricted, should bounce
332         self._init_mock_build_email()
333         self.mail_group.write(cr, uid, [frog_group.id], {'alias_name': 'frogs', 'alias_contact': 'followers', 'alias_force_thread_id': frog_group.id})
334         frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
335                                          msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
336                                          to='frogs@example.com>', subject='Re: news')
337         # Test: no group 'Re: news' created, still only 1 Frogs group
338         self.assertEqual(len(frog_groups), 0,
339                          'message_process: reply on Frogs should not have created a new group with new subject')
340         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
341         self.assertEqual(len(frog_groups), 1,
342                          'message_process: reply on Frogs should not have created a duplicate group with old subject')
343         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
344         # Test: email bounced
345         sent_emails = self._build_email_kwargs_list
346         self.assertEqual(len(sent_emails), 1,
347                          'message_process: incoming email on Followers alias should send a bounce email')
348         self.assertIn('Re: news', sent_emails[0].get('subject'),
349                       'message_process: bounce email on Followers alias should contain the original subject')
350
351         # Do: Pigs alias is restricted, should accept Followers
352         self._init_mock_build_email()
353         self.mail_group.message_subscribe(cr, uid, [frog_group.id], [p2id])
354         frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
355                                          msg_id='<1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>',
356                                          to='frogs@example.com>', subject='Re: cats')
357         # Test: no group 'Re: news' created, still only 1 Frogs group
358         self.assertEqual(len(frog_groups), 0,
359                          'message_process: reply on Frogs should not have created a new group with new subject')
360         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
361         self.assertEqual(len(frog_groups), 1,
362                          'message_process: reply on Frogs should not have created a duplicate group with old subject')
363         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
364         # Test: one new message
365         self.assertEqual(len(frog_group.message_ids), 2, 'message_process: group should contain 2 messages after reply')
366         # Test: sent emails: 1 (Sylvie copy of the incoming email, but no bounce)
367         sent_emails = self._build_email_kwargs_list
368         self.assertEqual(len(sent_emails), 1,
369                          'message_process: one email should have been generated')
370         self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to')[0],
371                       'message_process: email should be sent to Sylvie')
372         self.mail_group.message_unsubscribe(cr, uid, [frog_group.id], [p2id])
373
374         # --------------------------------------------------
375         # Test3: discussion and replies
376         # --------------------------------------------------
377
378         # Do: even with a wrong destination, a reply should end up in the correct thread
379         frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
380                                          msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
381                                          to='erroneous@example.com>', subject='Re: news',
382                                          extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>\n')
383         # Test: no group 'Re: news' created, still only 1 Frogs group
384         self.assertEqual(len(frog_groups), 0,
385                          'message_process: reply on Frogs should not have created a new group with new subject')
386         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
387         self.assertEqual(len(frog_groups), 1,
388                          'message_process: reply on Frogs should not have created a duplicate group with old subject')
389         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
390         # Test: one new message
391         self.assertEqual(len(frog_group.message_ids), 3, 'message_process: group should contain 3 messages after reply')
392         # Test: author (and not recipient) added as follower
393         frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
394         self.assertEqual(frog_follower_ids, set([p1id, p2id]),
395                          'message_process: after reply, group should have 2 followers')
396
397         # 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
398         self.assertRaises(ValueError,
399                           format_and_process,
400                           MAIL_TEMPLATE, email_from='other5@gmail.com',
401                           to='noone@example.com', subject='spam',
402                           extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>' % frog_group.id,
403                           msg_id='<1.1.JavaMail.new@agrolait.com>')
404
405         # When 6.1 messages are present, compat mode is available
406         # Create a fake 6.1 message
407         tmp_msg_id = self.mail_message.create(cr, uid, {'model': 'mail.group', 'res_id': frog_group.id})
408         self.mail_message.write(cr, uid, [tmp_msg_id], {'message_id': False})
409         # Do: compat mode accepts partial-matching emails
410         frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other5@gmail.com',
411                                          msg_id='<1.2.JavaMail.new@agrolait.com>',
412                                          to='noone@example.com>', subject='spam',
413                                          extra='In-Reply-To: <12321321-openerp-%d-mail.group@%s>' % (frog_group.id, socket.gethostname()))
414         self.mail_message.unlink(cr, uid, [tmp_msg_id])
415         # Test: no group 'Re: news' created, still only 1 Frogs group
416         self.assertEqual(len(frog_groups), 0,
417                          'message_process: reply on Frogs should not have created a new group with new subject')
418         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
419         self.assertEqual(len(frog_groups), 1,
420                          'message_process: reply on Frogs should not have created a duplicate group with old subject')
421         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
422         # Test: one new message
423         self.assertEqual(len(frog_group.message_ids), 4, 'message_process: group should contain 4 messages after reply')
424
425         # 6.1 compat mode should not work if hostname does not match!
426         tmp_msg_id = self.mail_message.create(cr, uid, {'model': 'mail.group', 'res_id': frog_group.id})
427         self.mail_message.write(cr, uid, [tmp_msg_id], {'message_id': False})
428         self.assertRaises(ValueError,
429                           format_and_process,
430                           MAIL_TEMPLATE, email_from='other5@gmail.com',
431                           msg_id='<1.3.JavaMail.new@agrolait.com>',
432                           to='noone@example.com>', subject='spam',
433                           extra='In-Reply-To: <12321321-openerp-%d-mail.group@neighbor.com>' % frog_group.id)
434         self.mail_message.unlink(cr, uid, [tmp_msg_id])
435
436
437         # Do: due to some issue, same email goes back into the mailgateway
438         frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
439                                          msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
440                                          subject='Re: news', extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>\n')
441         # Test: no group 'Re: news' created, still only 1 Frogs group
442         self.assertEqual(len(frog_groups), 0,
443                          'message_process: reply on Frogs should not have created a new group with new subject')
444         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
445         self.assertEqual(len(frog_groups), 1,
446                          'message_process: reply on Frogs should not have created a duplicate group with old subject')
447         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
448         # Test: no new message
449         self.assertEqual(len(frog_group.message_ids), 4, 'message_process: message with already existing message_id should not have been duplicated')
450         # Test: message_id is still unique
451         msg_ids = self.mail_message.search(cr, uid, [('message_id', 'ilike', '<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>')])
452         self.assertEqual(len(msg_ids), 1,
453                          'message_process: message with already existing message_id should not have been duplicated')
454
455         # --------------------------------------------------
456         # Test4: email_from and partner finding
457         # --------------------------------------------------
458
459         # Data: extra partner with Raoul's email -> test the 'better author finding'
460         extra_partner_id = self.res_partner.create(cr, uid, {'name': 'A-Raoul', 'email': 'test_raoul@email.com'})
461
462         # Do: post a new message, with a known partner -> duplicate emails -> partner
463         format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
464                            subject='Re: news (2)',
465                            msg_id='<1198923581.41972151344608186760.JavaMail.new1@agrolait.com>',
466                            extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
467         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
468         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
469         # Test: author is A-Raoul (only existing)
470         self.assertEqual(frog_group.message_ids[0].author_id.id, extra_partner_id,
471                          'message_process: email_from -> author_id wrong')
472
473         # Do: post a new message, with a known partner -> duplicate emails -> user
474         frog_group.message_unsubscribe([extra_partner_id])
475         self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'})
476         format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
477                            to='groups@example.com', subject='Re: news (3)',
478                            msg_id='<1198923581.41972151344608186760.JavaMail.new2@agrolait.com>',
479                            extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
480         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
481         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
482         # Test: author is Raoul (user), not A-Raoul
483         self.assertEqual(frog_group.message_ids[0].author_id.id, self.partner_raoul_id,
484                          'message_process: email_from -> author_id wrong')
485
486         # Do: post a new message, with a known partner -> duplicate emails -> partner because is follower
487         frog_group.message_unsubscribe([self.partner_raoul_id])
488         frog_group.message_subscribe([extra_partner_id])
489         raoul_email = self.user_raoul.email
490         self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'})
491         format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
492                            to='groups@example.com', subject='Re: news (3)',
493                            msg_id='<1198923581.41972151344608186760.JavaMail.new3@agrolait.com>',
494                            extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
495         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
496         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
497         # Test: author is Raoul (user), not A-Raoul
498         self.assertEqual(frog_group.message_ids[0].author_id.id, extra_partner_id,
499                          'message_process: email_from -> author_id wrong')
500
501         self.res_users.write(cr, uid, self.user_raoul_id, {'email': raoul_email})
502
503         # --------------------------------------------------
504         # Test5: misc gateway features
505         # --------------------------------------------------
506
507         # Do: incoming email with model that does not accepts incoming emails must raise
508         self.assertRaises(ValueError,
509                           format_and_process,
510                           MAIL_TEMPLATE,
511                           to='noone@example.com', subject='spam', extra='', model='res.country',
512                           msg_id='<1198923581.41972151344608186760.JavaMail.new4@agrolait.com>')
513
514         # Do: incoming email without model and without alias must raise
515         self.assertRaises(ValueError,
516                           format_and_process,
517                           MAIL_TEMPLATE,
518                           to='noone@example.com', subject='spam', extra='',
519                           msg_id='<1198923581.41972151344608186760.JavaMail.new5@agrolait.com>')
520
521         # Do: incoming email with model that accepting incoming emails as fallback
522         frog_groups = format_and_process(MAIL_TEMPLATE,
523                                          to='noone@example.com',
524                                          subject='Spammy', extra='', model='mail.group',
525                                          msg_id='<1198923581.41972151344608186760.JavaMail.new6@agrolait.com>')
526         self.assertEqual(len(frog_groups), 1,
527                          'message_process: erroneous email but with a fallback model should have created a new mail.group')
528
529         # Do: incoming email in plaintext should be stored as  html
530         frog_groups = format_and_process(MAIL_TEMPLATE_PLAINTEXT,
531                                          to='groups@example.com', subject='Frogs Return', extra='',
532                                          msg_id='<deadcafe.1337@smtp.agrolait.com>')
533         # Test: one group created with one message
534         self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
535         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
536         msg = frog_group.message_ids[0]
537         # Test: plain text content should be wrapped and stored as html
538         self.assertIn('<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>', msg.body,
539                       'message_process: plaintext incoming email incorrectly parsed')
540
541     @mute_logger('openerp.addons.mail.mail_thread', 'openerp.models')
542     def test_20_thread_parent_resolution(self):
543         """ Testing parent/child relationships are correctly established when processing incoming mails """
544         cr, uid = self.cr, self.uid
545
546         def format(template, to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1',
547                                 extra='', email_from='Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>',
548                                 msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>'):
549             return template.format(to=to, subject=subject, extra=extra, email_from=email_from, msg_id=msg_id)
550
551         group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
552         msg1 = group_pigs.message_post(body='My Body', subject='1')
553         msg2 = group_pigs.message_post(body='My Body', subject='2')
554         msg1, msg2 = self.mail_message.browse(cr, uid, [msg1, msg2])
555         self.assertTrue(msg1.message_id, "message_process: new message should have a proper message_id")
556
557         # Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms
558         # 0. Direct alias match
559         reply_msg1 = format(MAIL_TEMPLATE, to='Pretty Pigs <group+pigs@example.com>',
560                             extra='In-Reply-To: %s' % msg1.message_id,
561                             msg_id='<1198923581.41972151344608186760.JavaMail.2@agrolait.com>')
562         self.mail_group.message_process(cr, uid, None, reply_msg1)
563
564         # 1. In-Reply-To header
565         reply_msg2 = format(MAIL_TEMPLATE, to='erroneous@example.com',
566                             extra='In-Reply-To: %s' % msg1.message_id,
567                             msg_id='<1198923581.41972151344608186760.JavaMail.3@agrolait.com>')
568         self.mail_group.message_process(cr, uid, None, reply_msg2)
569
570         # 2. References header
571         reply_msg3 = format(MAIL_TEMPLATE, to='erroneous@example.com',
572                             extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id,
573                             msg_id='<1198923581.41972151344608186760.JavaMail.4@agrolait.com>')
574         self.mail_group.message_process(cr, uid, None, reply_msg3)
575
576         # 3. Subject contains [<ID>] + model passed to message+process -> only attached to group, but not to mail (not in msg1.child_ids)
577         reply_msg4 = format(MAIL_TEMPLATE, to='erroneous@example.com',
578                             extra='', subject='Re: [%s] 1' % self.group_pigs_id,
579                             msg_id='<1198923581.41972151344608186760.JavaMail.5@agrolait.com>')
580         self.mail_group.message_process(cr, uid, 'mail.group', reply_msg4)
581
582         group_pigs.refresh()
583         msg1.refresh()
584         self.assertEqual(6, len(group_pigs.message_ids), 'message_process: group should contain 6 messages')
585         self.assertEqual(3, len(msg1.child_ids), 'message_process: msg1 should have 3 children now')
586
587     def test_30_private_discussion(self):
588         """ Testing private discussion between partners. """
589         cr, uid = self.cr, self.uid
590
591         def format(template, to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1',
592                                 extra='', email_from='Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>',
593                                 msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>'):
594             return template.format(to=to, subject=subject, extra=extra, email_from=email_from, msg_id=msg_id)
595
596         # Do: Raoul writes to Bert and Administrator, with a thread_model in context that should not be taken into account
597         msg1_pids = [self.partner_admin_id, self.partner_bert_id]
598         msg1_id = self.mail_thread.message_post(
599             cr, self.user_raoul_id, False,
600             partner_ids=msg1_pids,
601             subtype='mail.mt_comment',
602             context={'thread_model': 'mail.group'}
603         )
604
605         # Test: message recipients
606         msg = self.mail_message.browse(cr, uid, msg1_id)
607         msg_pids = [p.id for p in msg.partner_ids]
608         msg_nids = [p.id for p in msg.notified_partner_ids]
609         test_pids = msg1_pids
610         test_nids = msg1_pids
611         self.assertEqual(set(msg_pids), set(test_pids),
612                          'message_post: private discussion: incorrect recipients')
613         self.assertEqual(set(msg_nids), set(test_nids),
614                          'message_post: private discussion: incorrect notified recipients')
615         self.assertEqual(msg.model, False,
616                          'message_post: private discussion: context key "thread_model" not correctly ignored when having no res_id')
617         # Test: message-id
618         self.assertIn('openerp-private', msg.message_id,
619                       'message_post: private discussion: message-id should contain the private keyword')
620
621         # Do: Bert replies through mailgateway (is a customer)
622         reply_message = format(MAIL_TEMPLATE, to='not_important@mydomain.com',
623                                email_from='bert@bert.fr',
624                                extra='In-Reply-To: %s' % msg.message_id,
625                                msg_id='<test30.JavaMail.0@agrolait.com>')
626         self.mail_thread.message_process(cr, uid, None, reply_message)
627
628         # Test: last mail_message created
629         msg2_id = self.mail_message.search(cr, uid, [], limit=1)[0]
630
631         # Test: message recipients
632         msg = self.mail_message.browse(cr, uid, msg2_id)
633         msg_pids = [p.id for p in msg.partner_ids]
634         msg_nids = [p.id for p in msg.notified_partner_ids]
635         test_pids = [self.partner_admin_id, self.partner_raoul_id]
636         test_nids = test_pids
637         self.assertEqual(msg.author_id.id, self.partner_bert_id,
638                          'message_post: private discussion: wrong author through mailgatewya based on email')
639         self.assertEqual(set(msg_pids), set(test_pids),
640                          'message_post: private discussion: incorrect recipients when replying')
641         self.assertEqual(set(msg_nids), set(test_nids),
642                          'message_post: private discussion: incorrect notified recipients when replying')
643
644         # Do: Bert replies through chatter (is a customer)
645         msg3_id = self.mail_thread.message_post(
646             cr, uid, False,
647             author_id=self.partner_bert_id,
648             parent_id=msg1_id, subtype='mail.mt_comment')
649
650         # Test: message recipients
651         msg = self.mail_message.browse(cr, uid, msg3_id)
652         msg_pids = [p.id for p in msg.partner_ids]
653         msg_nids = [p.id for p in msg.notified_partner_ids]
654         test_pids = [self.partner_admin_id, self.partner_raoul_id]
655         test_nids = test_pids
656         self.assertEqual(set(msg_pids), set(test_pids),
657                          'message_post: private discussion: incorrect recipients when replying')
658         self.assertEqual(set(msg_nids), set(test_nids),
659                          'message_post: private discussion: incorrect notified recipients when replying')
660
661         # Do: Administrator replies
662         msg3_id = self.mail_thread.message_post(cr, uid, False, parent_id=msg3_id, subtype='mail.mt_comment')
663
664         # Test: message recipients
665         msg = self.mail_message.browse(cr, uid, msg3_id)
666         msg_pids = [p.id for p in msg.partner_ids]
667         msg_nids = [p.id for p in msg.notified_partner_ids]
668         test_pids = [self.partner_bert_id, self.partner_raoul_id]
669         test_nids = test_pids
670         self.assertEqual(set(msg_pids), set(test_pids),
671                          'message_post: private discussion: incorrect recipients when replying')
672         self.assertEqual(set(msg_nids), set(test_nids),
673                          'message_post: private discussion: incorrect notified recipients when replying')