1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Business Applications
5 # Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
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.
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.
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/>.
20 ##############################################################################
22 from openerp.addons.mail.tests.common import TestMail
23 from openerp.tools import mute_logger
26 MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
28 Received: by mail1.openerp.com (Postfix, from userid 10002)
29 id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
33 Content-Type: multipart/alternative;
34 boundary="----=_Part_4200734_24778174.1344608186754"
35 Date: Fri, 10 Aug 2012 14:16:26 +0000
38 ------=_Part_4200734_24778174.1344608186754
39 Content-Type: text/plain; charset=utf-8
40 Content-Transfer-Encoding: quoted-printable
42 Please call me as soon as possible this afternoon!
46 ------=_Part_4200734_24778174.1344608186754
47 Content-Type: text/html; charset=utf-8
48 Content-Transfer-Encoding: quoted-printable
50 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
53 <meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8" />
55 <body style=3D"margin: 0; padding: 0; background: #ffffff;-webkit-text-size-adjust: 100%;">=20
57 <p>Please call me as soon as possible this afternoon!</p>
64 ------=_Part_4200734_24778174.1344608186754--
67 MAIL_TEMPLATE_PLAINTEXT = """Return-Path: <whatever-2a840@postmaster.twitter.com>
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>
74 Content-Type: text/plain
75 Date: Fri, 10 Aug 2012 14:16:26 +0000
79 Please call me as soon as possible this afternoon!
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
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
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
120 --089e01536c4ed4d17204e49b8e96
121 Content-Type: multipart/alternative; boundary=089e01536c4ed4d16d04e49b8e94
123 --089e01536c4ed4d16d04e49b8e94
124 Content-Type: text/plain; charset=ISO-8859-1
126 Should create a multipart/mixed: from gmail, *bold*, with attachment.
131 --089e01536c4ed4d16d04e49b8e94
132 Content-Type: text/html; charset=ISO-8859-1
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>
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
144 --089e01536c4ed4d17204e49b8e96--"""
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\))
156 --Apple-Mail=_9331E12B-8BD2-4EC7-B53E-01F3FBEC9227
157 Content-Transfer-Encoding: 7bit
158 Content-Type: text/plain;
161 First and second part
163 --Apple-Mail=_9331E12B-8BD2-4EC7-B53E-01F3FBEC9227
164 Content-Type: multipart/mixed;
165 boundary="Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F"
167 --Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F
168 Content-Transfer-Encoding: 7bit
169 Content-Type: text/html;
172 <html><head></head><body>First part</body></html>
174 --Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F
175 Content-Disposition: inline;
176 filename=thetruth.pdf
177 Content-Type: application/pdf;
179 Content-Transfer-Encoding: base64
181 SSBhbSB0aGUgQmF0TWFuCg==
183 --Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F
184 Content-Transfer-Encoding: 7bit
185 Content-Type: text/html;
188 <html><head></head><body>Second part</body></html>
189 --Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F--
191 --Apple-Mail=_9331E12B-8BD2-4EC7-B53E-01F3FBEC9227--
194 class TestMailgateway(TestMail):
196 def test_00_message_parse(self):
197 """ Testing incoming emails parsing """
198 cr, uid = self.cr, self.uid
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')
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')
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')
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')
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
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>',
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)])
236 # --------------------------------------------------
238 # --------------------------------------------------
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'})
250 # --------------------------------------------------
251 # Test1: new record creation
252 # --------------------------------------------------
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')
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')
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')
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')
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')
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')
382 # --------------------------------------------------
383 # Test2: update-like alias
384 # --------------------------------------------------
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')
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])
429 # --------------------------------------------------
430 # Test3: discussion and replies
431 # --------------------------------------------------
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')
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,
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>')
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')
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,
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])
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')
508 # --------------------------------------------------
509 # Test4: email_from and partner finding
510 # --------------------------------------------------
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'})
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')
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')
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')
554 self.res_users.write(cr, uid, self.user_raoul_id, {'email': raoul_email})
556 # --------------------------------------------------
557 # Test5: misc gateway features
558 # --------------------------------------------------
560 # Do: incoming email with model that does not accepts incoming emails must raise
561 self.assertRaises(ValueError,
564 to='noone@example.com', subject='spam', extra='', model='res.country',
565 msg_id='<1198923581.41972151344608186760.JavaMail.new4@agrolait.com>')
567 # Do: incoming email without model and without alias must raise
568 self.assertRaises(ValueError,
571 to='noone@example.com', subject='spam', extra='',
572 msg_id='<1198923581.41972151344608186760.JavaMail.new5@agrolait.com>')
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')
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')
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
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)
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")
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)
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)
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)
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)
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')
640 def test_30_private_discussion(self):
641 """ Testing private discussion between partners. """
642 cr, uid = self.cr, self.uid
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)
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'}
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')
671 self.assertIn('openerp-private', msg.message_id,
672 'message_post: private discussion: message-id should contain the private keyword')
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)
681 # Test: last mail_message created
682 msg2_id = self.mail_message.search(cr, uid, [], limit=1)[0]
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')
697 # Do: Bert replies through chatter (is a customer)
698 msg3_id = self.mail_thread.message_post(
700 author_id=self.partner_bert_id,
701 parent_id=msg1_id, subtype='mail.mt_comment')
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')
714 # Do: Administrator replies
715 msg3_id = self.mail_thread.message_post(cr, uid, False, parent_id=msg3_id, subtype='mail.mt_comment')
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')