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 import tools
24 from openerp.addons.mail.tests.test_mail_base import TestMailBase
25 from openerp.tools.mail import html_sanitize, append_content_to_html
27 MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
29 Received: by mail1.openerp.com (Postfix, from userid 10002)
30 id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
31 From: Sylvie Lelitre <sylvie.lelitre@agrolait.com>
34 Content-Type: multipart/alternative;
35 boundary="----=_Part_4200734_24778174.1344608186754"
36 Date: Fri, 10 Aug 2012 14:16:26 +0000
37 Message-ID: <1198923581.41972151344608186760.JavaMail@agrolait.com>
39 ------=_Part_4200734_24778174.1344608186754
40 Content-Type: text/plain; charset=utf-8
41 Content-Transfer-Encoding: quoted-printable
43 Please call me as soon as possible this afternoon!
47 ------=_Part_4200734_24778174.1344608186754
48 Content-Type: text/html; charset=utf-8
49 Content-Transfer-Encoding: quoted-printable
51 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
54 <meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8" />
56 <body style=3D"margin: 0; padding: 0; background: #ffffff;-webkit-text-size-adjust: 100%;">=20
58 <p>Please call me as soon as possible this afternoon!</p>
65 ------=_Part_4200734_24778174.1344608186754--
68 MAIL_TEMPLATE_PLAINTEXT = """Return-Path: <whatever-2a840@postmaster.twitter.com>
70 Received: by mail1.openerp.com (Postfix, from userid 10002)
71 id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
72 From: Sylvie Lelitre <sylvie.lelitre@agrolait.com>
75 Content-Type: text/plain
76 Date: Fri, 10 Aug 2012 14:16:26 +0000
80 Please call me as soon as possible this afternoon!
87 class test_mail(TestMailBase):
89 def _mock_send_get_mail_body(self, *args, **kwargs):
90 # def _send_get_mail_body(self, cr, uid, mail, partner=None, context=None)
91 body = append_content_to_html(args[2].body_html, kwargs.get('partner').name if kwargs.get('partner') else 'No specific partner', plaintext=False)
95 super(test_mail, self).setUp()
97 # Mock send_get_mail_body to test its functionality without other addons override
98 self._send_get_mail_body = self.registry('mail.mail').send_get_mail_body
99 self.registry('mail.mail').send_get_mail_body = self._mock_send_get_mail_body
103 self.registry('mail.mail').send_get_mail_body = self._send_get_mail_body
104 super(test_mail, self).tearDown()
106 def test_00_message_process(self):
107 """ Testing incoming emails processing. """
108 cr, uid, user_raoul = self.cr, self.uid, self.user_raoul
110 # groups@.. will cause the creation of new mail groups
111 self.mail_group_model_id = self.ir_model.search(cr, uid, [('model', '=', 'mail.group')])[0]
112 self.mail_alias.create(cr, uid, {'alias_name': 'groups', 'alias_model_id': self.mail_group_model_id})
114 # Incoming mail creates a new mail_group "frogs"
115 self.assertEqual(self.mail_group.search(cr, uid, [('name', '=', 'frogs')]), [])
116 mail_frogs = MAIL_TEMPLATE.format(to='groups@example.com, other@gmail.com', subject='frogs', extra='')
117 self.mail_thread.message_process(cr, uid, None, mail_frogs)
118 frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'frogs')])
119 self.assertTrue(len(frog_groups) == 1)
121 # Previously-created group can be emailed now - it should have an implicit alias group+frogs@...
122 frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
123 group_messages = frog_group.message_ids
124 self.assertTrue(len(group_messages) == 1, 'New group should only have the original message')
125 mail_frog_news = MAIL_TEMPLATE.format(to='Friendly Frogs <group+frogs@example.com>', subject='news', extra='')
126 self.mail_thread.message_process(cr, uid, None, mail_frog_news)
128 self.assertTrue(len(frog_group.message_ids) == 2, 'Group should contain 2 messages now')
130 # Even with a wrong destination, a reply should end up in the correct thread
131 mail_reply = MAIL_TEMPLATE.format(to='erroneous@example.com>', subject='Re: news',
132 extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
133 self.mail_thread.message_process(cr, uid, None, mail_reply)
135 self.assertTrue(len(frog_group.message_ids) == 3, 'Group should contain 3 messages now')
137 # No model passed and no matching alias must raise
138 mail_spam = MAIL_TEMPLATE.format(to='noone@example.com', subject='spam', extra='')
139 self.assertRaises(Exception,
140 self.mail_thread.message_process,
141 cr, uid, None, mail_spam)
143 # plain text content should be wrapped and stored as html
144 test_msg_id = '<deadcafe.1337@smtp.agrolait.com>'
145 mail_text = MAIL_TEMPLATE_PLAINTEXT.format(to='groups@example.com', subject='frogs', extra='', msg_id=test_msg_id)
146 self.mail_thread.message_process(cr, uid, None, mail_text)
147 new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id', '=', test_msg_id)])[0])
148 self.assertEqual(new_mail.body, '\n<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>\n',
149 'plaintext mail incorrectly parsed')
151 # Do: post a new message, with a known partner
152 test_msg_id = '<deadcafe.1337-2@smtp.agrolait.com>'
153 TEMPLATE_MOD = MAIL_TEMPLATE_PLAINTEXT.replace('Sylvie Lelitre <sylvie.lelitre@agrolait.com>', user_raoul.email)
154 mail_new = TEMPLATE_MOD.format(to='Friendly Frogs <group+frogs@example.com>', subject='extra news', extra='', msg_id=test_msg_id)
155 self.mail_thread.message_process(cr, uid, None, mail_new)
156 new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id', '=', test_msg_id)])[0])
157 # Test: author_id set, not email_from
158 self.assertEqual(new_mail.author_id, user_raoul.partner_id, 'message process wrong author found')
159 self.assertFalse(new_mail.email_from, 'message process should not set the email_from when an author is found')
161 # Do: post a new message, with a unknown partner
162 test_msg_id = '<deadcafe.1337-3@smtp.agrolait.com>'
163 TEMPLATE_MOD = MAIL_TEMPLATE_PLAINTEXT.replace('Sylvie Lelitre <sylvie.lelitre@agrolait.com>', '_abcd_')
164 mail_new = TEMPLATE_MOD.format(to='Friendly Frogs <group+frogs@example.com>', subject='super news', extra='', msg_id=test_msg_id)
165 self.mail_thread.message_process(cr, uid, None, mail_new)
166 new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id', '=', test_msg_id)])[0])
167 # Test: author_id set, not email_from
168 self.assertFalse(new_mail.author_id, 'message process shnould not have found a partner for _abcd_ email address')
169 self.assertIn('_abcd_', new_mail.email_from, 'message process should set en email_from when not finding a partner_id')
171 def test_05_thread_parent_resolution(self):
172 """Verify parent/child relationships are correctly established when processing incoming mails"""
173 cr, uid = self.cr, self.uid
174 group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
175 msg1 = group_pigs.message_post(body='My Body', subject='1')
176 msg2 = group_pigs.message_post(body='My Body', subject='2')
177 msg1, msg2 = self.mail_message.browse(cr, uid, [msg1, msg2])
178 self.assertTrue(msg1.message_id, "New message should have a proper message_id")
180 # Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms
181 # 1. In-Reply-To header
182 reply_msg = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1',
183 extra='In-Reply-To: %s' % msg1.message_id)
184 self.mail_group.message_process(cr, uid, None, reply_msg)
186 # 2. References header
187 reply_msg2 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: Re: 1',
188 extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id)
189 self.mail_group.message_process(cr, uid, None, reply_msg2)
191 # 3. Subject contains [<ID>] + model passed to message+process -> only attached to group, not to mail
192 reply_msg3 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com',
193 extra='', subject='Re: [%s] 1' % self.group_pigs_id)
194 self.mail_group.message_process(cr, uid, 'mail.group', reply_msg3)
198 self.assertEqual(5, len(group_pigs.message_ids), 'group should contain 5 messages')
199 self.assertEqual(2, len(msg1.child_ids), 'msg1 should have 2 children now')
201 def test_10_followers_function_field(self):
202 """ Tests designed for the many2many function field 'follower_ids'.
203 We will test to perform writes using the many2many commands 0, 3, 4,
205 cr, uid, user_admin, partner_bert_id, group_pigs = self.cr, self.uid, self.user_admin, self.partner_bert_id, self.group_pigs
207 # Data: create 'disturbing' values in mail.followers: same res_id, other res_model; same res_model, other res_id
208 group_dummy_id = self.mail_group.create(cr, uid,
209 {'name': 'Dummy group'})
210 self.mail_followers.create(cr, uid,
211 {'res_model': 'mail.thread', 'res_id': self.group_pigs_id, 'partner_id': partner_bert_id})
212 self.mail_followers.create(cr, uid,
213 {'res_model': 'mail.group', 'res_id': group_dummy_id, 'partner_id': partner_bert_id})
215 # Pigs just created: should be only Admin as follower
216 follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
217 self.assertEqual(follower_ids, set([user_admin.partner_id.id]), 'Admin should be the only Pigs fan')
219 # Subscribe Bert through a '4' command
220 group_pigs.write({'message_follower_ids': [(4, partner_bert_id)]})
222 follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
223 self.assertEqual(follower_ids, set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the only Pigs fans')
225 # Unsubscribe Bert through a '3' command
226 group_pigs.write({'message_follower_ids': [(3, partner_bert_id)]})
228 follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
229 self.assertEqual(follower_ids, set([user_admin.partner_id.id]), 'Admin should be the only Pigs fan')
231 # Set followers through a '6' command
232 group_pigs.write({'message_follower_ids': [(6, 0, [partner_bert_id])]})
234 follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
235 self.assertEqual(follower_ids, set([partner_bert_id]), 'Bert should be the only Pigs fan')
237 # Add a follower created on the fly through a '0' command
238 group_pigs.write({'message_follower_ids': [(0, 0, {'name': 'Patrick Fiori'})]})
239 partner_patrick_id = self.res_partner.search(cr, uid, [('name', '=', 'Patrick Fiori')])[0]
241 follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
242 self.assertEqual(follower_ids, set([partner_bert_id, partner_patrick_id]), 'Bert and Patrick should be the only Pigs fans')
244 # Finally, unlink through a '5' command
245 group_pigs.write({'message_follower_ids': [(5, 0)]})
247 follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
248 self.assertFalse(follower_ids, 'Pigs group should not have fans anymore')
250 # Test dummy data has not been altered
251 fol_obj_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.thread'), ('res_id', '=', self.group_pigs_id)])
252 follower_ids = set([follower.partner_id.id for follower in self.mail_followers.browse(cr, uid, fol_obj_ids)])
253 self.assertEqual(follower_ids, set([partner_bert_id]), 'Bert should be the follower of dummy mail.thread data')
254 fol_obj_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', group_dummy_id)])
255 follower_ids = set([follower.partner_id.id for follower in self.mail_followers.browse(cr, uid, fol_obj_ids)])
256 self.assertEqual(follower_ids, set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the followers of dummy mail.group data')
258 def test_11_message_followers_and_subtypes(self):
259 """ Tests designed for the subscriber API as well as message subtypes """
260 cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
261 # Data: message subtypes
262 self.mail_message_subtype.create(cr, uid, {'name': 'mt_mg_def', 'default': True, 'res_model': 'mail.group'})
263 self.mail_message_subtype.create(cr, uid, {'name': 'mt_other_def', 'default': True, 'res_model': 'crm.lead'})
264 self.mail_message_subtype.create(cr, uid, {'name': 'mt_all_def', 'default': True, 'res_model': False})
265 mt_mg_nodef = self.mail_message_subtype.create(cr, uid, {'name': 'mt_mg_nodef', 'default': False, 'res_model': 'mail.group'})
266 mt_all_nodef = self.mail_message_subtype.create(cr, uid, {'name': 'mt_all_nodef', 'default': False, 'res_model': False})
267 default_group_subtypes = self.mail_message_subtype.search(cr, uid, [('default', '=', True), '|', ('res_model', '=', 'mail.group'), ('res_model', '=', False)])
269 # ----------------------------------------
270 # CASE1: test subscriptions with subtypes
271 # ----------------------------------------
273 # Do: Subscribe Raoul three times (niak niak) through message_subscribe_users
274 group_pigs.message_subscribe_users([user_raoul.id, user_raoul.id])
275 group_pigs.message_subscribe_users([user_raoul.id])
277 # Test: 2 followers (Admin and Raoul)
278 follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
279 self.assertEqual(set(follower_ids), set([user_raoul.partner_id.id, user_admin.partner_id.id]), 'Admin and Raoul should be the only 2 Pigs fans')
280 # Test: Raoul follows default subtypes
281 fol_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id), ('partner_id', '=', user_raoul.partner_id.id)])
282 fol_obj = self.mail_followers.browse(cr, uid, fol_ids)[0]
283 fol_subtype_ids = set([subtype.id for subtype in fol_obj.subtype_ids])
284 self.assertEqual(set(fol_subtype_ids), set(default_group_subtypes), 'subscription subtypes are incorrect')
286 # Do: Unsubscribe Raoul twice through message_unsubscribe_users
287 group_pigs.message_unsubscribe_users([user_raoul.id, user_raoul.id])
289 # Test: 1 follower (Admin)
290 follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
291 self.assertEqual(follower_ids, [user_admin.partner_id.id], 'Admin must be the only Pigs fan')
293 # Do: subscribe Admin with subtype_ids
294 group_pigs.message_subscribe_users([uid], [mt_mg_nodef, mt_all_nodef])
295 fol_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id), ('partner_id', '=', user_admin.partner_id.id)])
296 fol_obj = self.mail_followers.browse(cr, uid, fol_ids)[0]
297 fol_subtype_ids = set([subtype.id for subtype in fol_obj.subtype_ids])
298 self.assertEqual(set(fol_subtype_ids), set([mt_mg_nodef, mt_all_nodef]), 'subscription subtypes are incorrect')
300 # ----------------------------------------
301 # CASE2: test mail_thread fields
302 # ----------------------------------------
304 subtype_data = group_pigs._get_subscription_data(None, None)[group_pigs.id]['message_subtype_data']
305 self.assertEqual(set(subtype_data.keys()), set(['Discussions', 'mt_mg_def', 'mt_all_def', 'mt_mg_nodef', 'mt_all_nodef']), 'mail.group available subtypes incorrect')
306 self.assertFalse(subtype_data['Discussions']['followed'], 'Admin should not follow Discussions in pigs')
307 self.assertTrue(subtype_data['mt_mg_nodef']['followed'], 'Admin should follow mt_mg_nodef in pigs')
308 self.assertTrue(subtype_data['mt_all_nodef']['followed'], 'Admin should follow mt_all_nodef in pigs')
310 def test_20_message_quote_context(self):
311 """ Tests designed for message_post. """
312 cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
314 msg1_id = self.mail_message.create(cr, uid, {'body': 'Thread header about Zap Brannigan', 'subject': 'My subject'})
315 msg2_id = self.mail_message.create(cr, uid, {'body': 'First answer, should not be displayed', 'subject': 'Re: My subject', 'parent_id': msg1_id})
316 msg3_id = self.mail_message.create(cr, uid, {'body': 'Second answer', 'subject': 'Re: My subject', 'parent_id': msg1_id})
317 msg4_id = self.mail_message.create(cr, uid, {'body': 'Third answer', 'subject': 'Re: My subject', 'parent_id': msg1_id})
318 msg_new_id = self.mail_message.create(cr, uid, {'body': 'My answer I am propagating', 'subject': 'Re: My subject', 'parent_id': msg1_id})
320 result = self.mail_message.message_quote_context(cr, uid, msg_new_id, limit=3)
321 self.assertIn('Thread header about Zap Brannigan', result, 'Thread header content should be in quote.')
322 self.assertIn('Second answer', result, 'Answer should be in quote.')
323 self.assertIn('Third answer', result, 'Answer should be in quote.')
324 self.assertIn('expandable', result, 'Expandable should be present.')
325 self.assertNotIn('First answer, should not be displayed', result, 'Old answer should not be in quote.')
326 self.assertNotIn('My answer I am propagating', result, 'Thread header content should be in quote.')
328 def test_21_message_post(self):
329 """ Tests designed for message_post. """
330 cr, uid, user_raoul, group_pigs = self.cr, self.uid, self.user_raoul, self.group_pigs
331 self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
332 # 1 - Bert Tartopoils, with email, should receive emails for comments and emails
333 p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
334 # 2 - Carine Poilvache, with email, should never receive emails
335 p_c_id = self.res_partner.create(cr, uid, {'name': 'Carine Poilvache', 'email': 'c@c', 'notification_email_send': 'email'})
336 # 3 - Dédé Grosbedon, without email, to test email verification; should receive emails for every message
337 p_d_id = self.res_partner.create(cr, uid, {'name': 'Dédé Grosbedon', 'notification_email_send': 'all'})
339 # Subscribe Raoul, #1, #2
340 group_pigs.message_subscribe([self.partner_raoul_id, p_b_id, p_c_id])
344 _mail_subject = '%s posted on %s' % (user_raoul.name, group_pigs.name)
345 _body1 = 'Pigs rules'
346 _mail_body1 = 'Pigs rules\n<div><p>Raoul</p></div>\n'
347 _mail_bodyalt1 = 'Pigs rules\nRaoul\n'
348 _body2 = '<html>Pigs rules</html>'
349 _mail_body2 = html_sanitize('<html>Pigs rules\n<div><p>Raoul</p></div>\n</html>')
350 _mail_bodyalt2 = 'Pigs rules\nRaoul'
351 _attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
353 # ----------------------------------------
354 # CASE1: post comment, body and subject specified
355 # ----------------------------------------
357 # 1. Post a new comment on Pigs
358 self._init_mock_build_email()
359 msg1_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id, body=_body1, subject=_subject, type='comment', subtype='mt_comment')
360 message1 = self.mail_message.browse(cr, uid, msg1_id)
361 sent_emails = self._build_email_kwargs_list
362 # Test: mail.mail notifications have been deleted
363 self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg1_id)]), 'mail.mail notifications should have been auto-deleted!')
364 # Test: mail_message: subject is _subject, body is _body1 (no formatting done)
365 self.assertEqual(message1.subject, _subject, 'mail.message subject incorrect')
366 self.assertEqual(message1.body, _body1, 'mail.message body incorrect')
367 # Test: sent_email: email send by server: correct subject, body, body_alternative
368 self.assertEqual(len(sent_emails), 2, 'sent_email number of sent emails incorrect')
369 for sent_email in sent_emails:
370 self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
371 self.assertTrue(sent_email['body'] in [_mail_body1 + '\nBert Tartopoils\n', _mail_body1 + '\nAdministrator\n'],
372 'sent_email body incorrect')
373 # the html2plaintext uses etree or beautiful soup, so the result may be slighly different
374 # depending if you have installed beautiful soup.
375 self.assertTrue(sent_email['body_alternative'] in [_mail_bodyalt1 + '\nBert Tartopoils\n', _mail_bodyalt1 + '\nAdministrator\n'],
376 'sent_email body_alternative is incorrect')
377 # Test: mail_message: notified_partner_ids = group followers
378 message_pids = set([partner.id for partner in message1.notified_partner_ids])
379 test_pids = set([self.partner_admin_id, p_b_id, p_c_id])
380 self.assertEqual(test_pids, message_pids, 'mail.message notified partners incorrect')
381 # Test: notification linked to this message = group followers = notified_partner_ids
382 notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', msg1_id)])
383 notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
384 self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
385 # Test: sent_email: email_to should contain b@b, not c@c (pref email), not a@a (writer)
386 for sent_email in sent_emails:
387 self.assertTrue(set(sent_email['email_to']).issubset(set(['a@a', 'b@b'])), 'sent_email email_to is incorrect')
389 # ----------------------------------------
390 # CASE2: post an email with attachments, parent_id, partner_ids, parent notification
391 # ----------------------------------------
393 # 1. Post a new email comment on Pigs
394 self._init_mock_build_email()
395 msg2_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id, body=_body2, type='email', subtype='mt_comment',
396 partner_ids=[(6, 0, [p_d_id])], parent_id=msg1_id, attachments=_attachments)
397 message2 = self.mail_message.browse(cr, uid, msg2_id)
398 sent_emails = self._build_email_kwargs_list
399 self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg2_id)]), 'mail.mail notifications should have been auto-deleted!')
400 # Test: mail_message: subject is False, body is _body2 (no formatting done), parent_id is msg_id
401 self.assertEqual(message2.subject, False, 'mail.message subject incorrect')
402 self.assertEqual(message2.body, html_sanitize(_body2), 'mail.message body incorrect')
403 self.assertEqual(message2.parent_id.id, msg1_id, 'mail.message parent_id incorrect')
404 # Test: sent_email: email send by server: correct automatic subject, body, body_alternative
405 self.assertEqual(len(sent_emails), 3, 'sent_email number of sent emails incorrect')
406 for sent_email in sent_emails:
407 self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
408 self.assertIn(_mail_body2, sent_email['body'], 'sent_email body incorrect')
409 self.assertIn(_mail_bodyalt2, sent_email['body_alternative'], 'sent_email body_alternative incorrect')
410 # Test: mail_message: notified_partner_ids = group followers
411 message_pids = set([partner.id for partner in message2.notified_partner_ids])
412 test_pids = set([self.partner_admin_id, p_b_id, p_c_id, p_d_id])
413 self.assertEqual(message_pids, test_pids, 'mail.message partners incorrect')
414 # Test: notifications linked to this message = group followers = notified_partner_ids
415 notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', msg2_id)])
416 notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
417 self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
418 # Test: sent_email: email_to should contain b@b, c@c, not a@a (writer)
419 for sent_email in sent_emails:
420 self.assertTrue(set(sent_email['email_to']).issubset(set(['a@a', 'b@b', 'c@c'])), 'sent_email email_to incorrect')
422 for attach in message2.attachment_ids:
423 self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
424 self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
425 self.assertIn((attach.name, attach.datas.decode('base64')), _attachments,
426 'mail.message attachment name / data incorrect')
427 # Test: download attachments
428 for attach in message2.attachment_ids:
429 dl_attach = self.mail_message.download_attachment(cr, user_raoul.id, id_message=message2.id, attachment_id=attach.id)
430 self.assertIn((dl_attach['filename'], dl_attach['base64'].decode('base64')), _attachments, 'mail.message download_attachment is incorrect')
432 # 2. Dédé has been notified -> should also have been notified of the parent message
434 message_pids = set([partner.id for partner in message1.notified_partner_ids])
435 test_pids = set([self.partner_admin_id, p_b_id, p_c_id, p_d_id])
436 self.assertEqual(test_pids, message_pids, 'mail.message parent notification not created')
438 # 3. Reply to the last message, check that its parent will be the first message
439 msg3_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id, body='Test', parent_id=msg2_id)
440 message = self.mail_message.browse(cr, uid, msg3_id)
441 self.assertEqual(message.parent_id.id, msg1_id, 'message_post did not flatten the thread structure')
443 def test_25_message_compose_wizard(self):
444 """ Tests designed for the mail.compose.message wizard. """
445 cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
446 mail_compose = self.registry('mail.compose.message')
447 self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
448 group_bird_id = self.mail_group.create(cr, uid, {'name': 'Bird', 'description': 'Bird resistance'})
449 group_bird = self.mail_group.browse(cr, uid, group_bird_id)
453 _body = 'Pigs <b>rule</b>'
454 _reply_subject = 'Re: Pigs'
456 {'name': 'First', 'datas_fname': 'first.txt', 'datas': 'My first attachment'.encode('base64')},
457 {'name': 'Second', 'datas_fname': 'second.txt', 'datas': 'My second attachment'.encode('base64')}
459 _attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')]
461 # 1 - Bert Tartopoils, with email, should receive emails for comments and emails
462 p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
463 # 2 - Carine Poilvache, with email, should never receive emails
464 p_c_id = self.res_partner.create(cr, uid, {'name': 'Carine Poilvache', 'email': 'c@c', 'notification_email_send': 'email'})
465 # 3 - Dédé Grosbedon, without email, to test email verification; should receive emails for every message
466 p_d_id = self.res_partner.create(cr, uid, {'name': 'Dédé Grosbedon', 'notification_email_send': 'all'})
469 group_pigs.message_subscribe([p_b_id])
471 # ----------------------------------------
472 # CASE1: comment on group_pigs
473 # ----------------------------------------
475 # 1. Comment group_pigs with body_text and subject
476 compose_id = mail_compose.create(cr, uid,
477 {'subject': _subject, 'body': _body, 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
478 {'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id,
479 'default_content_subtype': 'plaintext'})
480 compose = mail_compose.browse(cr, uid, compose_id)
481 # Test: mail.compose.message: composition_mode, model, res_id
482 self.assertEqual(compose.composition_mode, 'comment', 'mail.compose.message incorrect composition_mode')
483 self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model')
484 self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
486 # 2. Post the comment, get created message
487 mail_compose.send_mail(cr, uid, [compose_id])
489 message = group_pigs.message_ids[0]
490 # Test: mail.message: subject, body inside pre
491 self.assertEqual(message.subject, _subject, 'mail.message incorrect subject')
492 self.assertEqual(message.body, _body, 'mail.message incorrect body')
493 # Test: mail.message: notified_partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
494 msg_pids = [partner.id for partner in message.notified_partner_ids]
495 test_pids = [p_b_id, p_c_id, p_d_id]
496 notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
497 self.assertEqual(len(notif_ids), 3, 'mail.message: too much notifications created')
498 self.assertEqual(set(msg_pids), set(test_pids), 'mail.message notified_partner_ids incorrect')
500 # ----------------------------------------
501 # CASE2: reply to last comment with attachments
502 # ----------------------------------------
504 # 1. Update last comment subject, reply with attachments
505 message.write({'subject': _subject})
506 compose_id = mail_compose.create(cr, uid,
507 {'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]},
508 {'default_composition_mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id, 'default_parent_id': message.id})
509 compose = mail_compose.browse(cr, uid, compose_id)
510 # Test: model, res_id, parent_id
511 self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model')
512 self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
513 self.assertEqual(compose.parent_id.id, message.id, 'mail.compose.message incorrect parent_id')
514 # Test: mail.message: subject as Re:.., body in html, parent_id
515 self.assertEqual(compose.subject, _reply_subject, 'mail.message incorrect subject')
516 # self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote>', compose.body, 'mail.message body is incorrect')
517 self.assertEqual(compose.parent_id and compose.parent_id.id, message.id, 'mail.message parent_id incorrect')
518 # Test: mail.message: attachments
519 for attach in compose.attachment_ids:
520 self.assertIn((attach.datas_fname, attach.datas.decode('base64')), _attachments_test, 'mail.message attachment name / data incorrect')
522 # ----------------------------------------
523 # CASE3: mass_mail on Pigs and Bird
524 # ----------------------------------------
526 # 1. mass_mail on pigs and bird
527 compose_id = mail_compose.create(cr, uid,
528 {'subject': _subject, 'body': '${object.description}'},
529 {'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': False,
530 'active_ids': [self.group_pigs_id, group_bird_id]})
531 compose = mail_compose.browse(cr, uid, compose_id)
533 # 2. Post the comment, get created message for each group
534 mail_compose.send_mail(cr, uid, [compose_id],
535 context={'default_res_id': -1, 'active_ids': [self.group_pigs_id, group_bird_id]})
538 message1 = group_pigs.message_ids[0]
539 message2 = group_bird.message_ids[0]
540 # Test: Pigs and Bird did receive their message
541 test_msg_ids = self.mail_message.search(cr, uid, [], limit=2)
542 self.assertIn(message1.id, test_msg_ids, 'Pigs did not receive its mass mailing message')
543 self.assertIn(message2.id, test_msg_ids, 'Bird did not receive its mass mailing message')
544 # Test: mail.message: subject, body
545 self.assertEqual(message1.subject, _subject, 'mail.message subject incorrect')
546 self.assertEqual(message1.body, group_pigs.description, 'mail.message body incorrect')
547 self.assertEqual(message2.subject, _subject, 'mail.message subject incorrect')
548 self.assertEqual(message2.body, group_bird.description, 'mail.message body incorrect')
550 def test_30_needaction(self):
551 """ Tests for mail.message needaction. """
552 cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
553 group_pigs_demo = self.mail_group.browse(cr, self.user_raoul_id, self.group_pigs_id)
554 na_admin_base = self.mail_message._needaction_count(cr, uid, domain=[])
555 na_demo_base = self.mail_message._needaction_count(cr, user_raoul.id, domain=[])
557 # Test: number of unread notification = needaction on mail.message
558 notif_ids = self.mail_notification.search(cr, uid, [
559 ('partner_id', '=', user_admin.partner_id.id),
562 na_count = self.mail_message._needaction_count(cr, uid, domain=[])
563 self.assertEqual(len(notif_ids), na_count, 'unread notifications count does not match needaction count')
565 # Do: post 2 message on group_pigs as admin, 3 messages as demo user
566 for dummy in range(2):
567 group_pigs.message_post(body='My Body', subtype='mt_comment')
568 for dummy in range(3):
569 group_pigs_demo.message_post(body='My Demo Body', subtype='mt_comment')
571 # Test: admin has 3 new notifications (from demo), and 3 new needaction
572 notif_ids = self.mail_notification.search(cr, uid, [
573 ('partner_id', '=', user_admin.partner_id.id),
576 self.assertEqual(len(notif_ids), na_admin_base + 3, 'Admin should have 3 new unread notifications')
577 na_admin = self.mail_message._needaction_count(cr, uid, domain=[])
578 na_admin_group = self.mail_message._needaction_count(cr, uid, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
579 self.assertEqual(na_admin, na_admin_base + 3, 'Admin should have 3 new needaction')
580 self.assertEqual(na_admin_group, 3, 'Admin should have 3 needaction related to Pigs')
581 # Test: demo has 0 new notifications (not a follower, not receiving its own messages), and 0 new needaction
582 notif_ids = self.mail_notification.search(cr, uid, [
583 ('partner_id', '=', user_raoul.partner_id.id),
586 self.assertEqual(len(notif_ids), na_demo_base + 0, 'Demo should have 0 new unread notifications')
587 na_demo = self.mail_message._needaction_count(cr, user_raoul.id, domain=[])
588 na_demo_group = self.mail_message._needaction_count(cr, user_raoul.id, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
589 self.assertEqual(na_demo, na_demo_base + 0, 'Demo should have 0 new needaction')
590 self.assertEqual(na_demo_group, 0, 'Demo should have 0 needaction related to Pigs')
592 def test_40_track_field(self):
593 """ Testing auto tracking of fields. """
594 def _strip_string_spaces(body):
595 return body.replace(' ', '').replace('\n', '')
597 cr, uid = self.cr, self.uid
599 # Data: subscribe Raoul to Pigs, because he will change the public attribute and may loose access to the record
600 self.mail_group.message_subscribe_users(cr, uid, [self.group_pigs_id], [self.user_raoul_id])
602 # Data: res.users.group, to test group_public_id automatic logging
603 group_system_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_system')
604 group_system_id = group_system_ref and group_system_ref[1] or False
606 # Data: custom subtypes
607 mt_private_id = self.mail_message_subtype.create(cr, uid, {'name': 'private', 'description': 'Private public'})
608 self.ir_model_data.create(cr, uid, {'name': 'mt_private', 'model': 'mail.group', 'module': 'mail', 'res_id': mt_private_id})
609 mt_name_supername_id = self.mail_message_subtype.create(cr, uid, {'name': 'name_supername', 'description': 'Supername name'})
610 self.ir_model_data.create(cr, uid, {'name': 'mt_name_supername', 'model': 'mail.group', 'module': 'mail', 'res_id': mt_name_supername_id})
611 mt_group_public_id = self.mail_message_subtype.create(cr, uid, {'name': 'group_public', 'description': 'Group changed'})
612 self.ir_model_data.create(cr, uid, {'name': 'mt_group_public', 'model': 'mail.group', 'module': 'mail', 'res_id': mt_group_public_id})
614 # Data: alter mail_group model for testing purposes (test on classic, selection and many2one fields)
615 self.mail_group._track = {
617 'mail.mt_private': lambda self, cr, uid, obj, ctx=None: obj.public == 'private',
620 'mail.mt_name_supername': lambda self, cr, uid, obj, ctx=None: obj.name == 'supername',
623 'mail.mt_group_public': lambda self, cr, uid, obj, ctx=None: True,
626 public_col = self.mail_group._columns.get('public')
627 name_col = self.mail_group._columns.get('name')
628 group_public_col = self.mail_group._columns.get('group_public_id')
629 public_col._track_visibility = 1
630 name_col._track_visibility = 2
631 group_public_col._track_visibility = 1
633 # Test: change name -> always tracked, not related to a subtype
634 self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'public': 'public'})
635 self.group_pigs.refresh()
636 self.assertEqual(len(self.group_pigs.message_ids), 1, 'tracked: a message should have been produced')
637 # Test: first produced message: no subtype, name change tracked
638 last_msg = self.group_pigs.message_ids[-1]
639 self.assertFalse(last_msg.subtype_id, 'tracked: message should not have been linked to a subtype')
640 self.assertIn('SelectedGroupOnly->Public', _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
641 self.assertIn('Pigs', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
643 # Test: change name as supername, public as private -> 2 subtypes
644 self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'name': 'supername', 'public': 'private'})
645 self.group_pigs.refresh()
646 self.assertEqual(len(self.group_pigs.message_ids), 3, 'tracked: two messages should have been produced')
647 # Test: first produced message: mt_name_supername
648 last_msg = self.group_pigs.message_ids[-2]
649 self.assertEqual(last_msg.subtype_id.id, mt_private_id, 'tracked: message should be linked to mt_private subtype')
650 self.assertIn('Private public', last_msg.body, 'tracked: message body does not hold the subtype description')
651 self.assertIn('Pigs->supername', _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
652 # Test: second produced message: mt_name_supername
653 last_msg = self.group_pigs.message_ids[-3]
654 self.assertEqual(last_msg.subtype_id.id, mt_name_supername_id, 'tracked: message should be linked to mt_name_supername subtype')
655 self.assertIn('Supername name', last_msg.body, 'tracked: message body does not hold the subtype description')
656 self.assertIn('Public->Private', _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
657 self.assertIn('Pigs->supername', _strip_string_spaces(last_msg.body), 'tracked feature: message body does not hold always tracked field')
659 # Test: change public as public, group_public_id -> 1 subtype, name always tracked
660 self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'public': 'public', 'group_public_id': group_system_id})
661 self.group_pigs.refresh()
662 self.assertEqual(len(self.group_pigs.message_ids), 4, 'tracked: one message should have been produced')
663 # Test: first produced message: mt_group_public_id, with name always tracked, public tracked on change
664 last_msg = self.group_pigs.message_ids[-4]
665 self.assertEqual(last_msg.subtype_id.id, mt_group_public_id, 'tracked: message should not be linked to any subtype')
666 self.assertIn('Group changed', last_msg.body, 'tracked: message body does not hold the subtype description')
667 self.assertIn('Private->Public', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold changed tracked field')
668 self.assertIn('HumanResources/Employee->Administration/Settings', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
670 # Test: change not tracked field, no tracking message
671 self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'description': 'Dummy'})
672 self.group_pigs.refresh()
673 self.assertEqual(len(self.group_pigs.message_ids), 4, 'tracked: No message should have been produced')
675 # Data: removed changes
676 public_col._track_visibility = False
677 name_col._track_visibility = False
678 group_public_col._track_visibility = False
679 self.mail_group._track = {}