[MERGE] forward port of branch 8.0 up to 491372e
[odoo/odoo.git] / addons / mail / tests / test_mail_features.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.mail_mail import mail_mail
23 from openerp.addons.mail.mail_thread import mail_thread
24 from openerp.addons.mail.tests.common import TestMail
25 from openerp.tools import mute_logger, email_split, html2plaintext
26 from openerp.tools.mail import html_sanitize
27
28 class test_mail(TestMail):
29
30     def test_000_alias_setup(self):
31         """ Test basic mail.alias setup works, before trying to use them for routing """
32         cr, uid = self.cr, self.uid
33         self.user_valentin_id = self.res_users.create(cr, uid,
34             {'name': 'Valentin Cognito', 'email': 'valentin.cognito@gmail.com', 'login': 'valentin.cognito', 'alias_name': 'valentin.cognito'})
35         self.user_valentin = self.res_users.browse(cr, uid, self.user_valentin_id)
36         self.assertEquals(self.user_valentin.alias_name, self.user_valentin.login, "Login should be used as alias")
37
38         self.user_pagan_id = self.res_users.create(cr, uid,
39             {'name': 'Pagan Le Marchant', 'email': 'plmarchant@gmail.com', 'login': 'plmarchant@gmail.com', 'alias_name': 'plmarchant@gmail.com'})
40         self.user_pagan = self.res_users.browse(cr, uid, self.user_pagan_id)
41         self.assertEquals(self.user_pagan.alias_name, 'plmarchant', "If login is an email, the alias should keep only the local part")
42
43         self.user_barty_id = self.res_users.create(cr, uid,
44             {'name': 'Bartholomew Ironside', 'email': 'barty@gmail.com', 'login': 'b4r+_#_R3wl$$', 'alias_name': 'b4r+_#_R3wl$$'})
45         self.user_barty = self.res_users.browse(cr, uid, self.user_barty_id)
46         self.assertEquals(self.user_barty.alias_name, 'b4r+_-_r3wl-', 'Disallowed chars should be replaced by hyphens')
47
48     def test_00_followers_function_field(self):
49         """ Tests designed for the many2many function field 'follower_ids'.
50             We will test to perform writes using the many2many commands 0, 3, 4,
51             5 and 6. """
52         cr, uid, user_admin, partner_bert_id, group_pigs = self.cr, self.uid, self.user_admin, self.partner_bert_id, self.group_pigs
53
54         # Data: create 'disturbing' values in mail.followers: same res_id, other res_model; same res_model, other res_id
55         group_dummy_id = self.mail_group.create(cr, uid,
56             {'name': 'Dummy group'}, {'mail_create_nolog': True})
57         self.mail_followers.create(cr, uid,
58             {'res_model': 'mail.thread', 'res_id': self.group_pigs_id, 'partner_id': partner_bert_id})
59         self.mail_followers.create(cr, uid,
60             {'res_model': 'mail.group', 'res_id': group_dummy_id, 'partner_id': partner_bert_id})
61
62         # Pigs just created: should be only Admin as follower
63         follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
64         self.assertEqual(follower_ids, set([user_admin.partner_id.id]), 'Admin should be the only Pigs fan')
65
66         # Subscribe Bert through a '4' command
67         group_pigs.write({'message_follower_ids': [(4, partner_bert_id)]})
68         group_pigs.refresh()
69         follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
70         self.assertEqual(follower_ids, set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the only Pigs fans')
71
72         # Unsubscribe Bert through a '3' command
73         group_pigs.write({'message_follower_ids': [(3, partner_bert_id)]})
74         group_pigs.refresh()
75         follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
76         self.assertEqual(follower_ids, set([user_admin.partner_id.id]), 'Admin should be the only Pigs fan')
77
78         # Set followers through a '6' command
79         group_pigs.write({'message_follower_ids': [(6, 0, [partner_bert_id])]})
80         group_pigs.refresh()
81         follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
82         self.assertEqual(follower_ids, set([partner_bert_id]), 'Bert should be the only Pigs fan')
83
84         # Add a follower created on the fly through a '0' command
85         group_pigs.write({'message_follower_ids': [(0, 0, {'name': 'Patrick Fiori'})]})
86         partner_patrick_id = self.res_partner.search(cr, uid, [('name', '=', 'Patrick Fiori')])[0]
87         group_pigs.refresh()
88         follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
89         self.assertEqual(follower_ids, set([partner_bert_id, partner_patrick_id]), 'Bert and Patrick should be the only Pigs fans')
90
91         # Finally, unlink through a '5' command
92         group_pigs.write({'message_follower_ids': [(5, 0)]})
93         group_pigs.refresh()
94         follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
95         self.assertFalse(follower_ids, 'Pigs group should not have fans anymore')
96
97         # Test dummy data has not been altered
98         fol_obj_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.thread'), ('res_id', '=', self.group_pigs_id)])
99         follower_ids = set([follower.partner_id.id for follower in self.mail_followers.browse(cr, uid, fol_obj_ids)])
100         self.assertEqual(follower_ids, set([partner_bert_id]), 'Bert should be the follower of dummy mail.thread data')
101         fol_obj_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', group_dummy_id)])
102         follower_ids = set([follower.partner_id.id for follower in self.mail_followers.browse(cr, uid, fol_obj_ids)])
103         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')
104
105     def test_05_message_followers_and_subtypes(self):
106         """ Tests designed for the subscriber API as well as message subtypes """
107         cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
108         # Data: message subtypes
109         self.mail_message_subtype.create(cr, uid, {'name': 'mt_mg_def', 'default': True, 'res_model': 'mail.group'})
110         self.mail_message_subtype.create(cr, uid, {'name': 'mt_other_def', 'default': True, 'res_model': 'crm.lead'})
111         self.mail_message_subtype.create(cr, uid, {'name': 'mt_all_def', 'default': True, 'res_model': False})
112         mt_mg_nodef = self.mail_message_subtype.create(cr, uid, {'name': 'mt_mg_nodef', 'default': False, 'res_model': 'mail.group'})
113         mt_all_nodef = self.mail_message_subtype.create(cr, uid, {'name': 'mt_all_nodef', 'default': False, 'res_model': False})
114         default_group_subtypes = self.mail_message_subtype.search(cr, uid, [('default', '=', True), '|', ('res_model', '=', 'mail.group'), ('res_model', '=', False)])
115
116         # ----------------------------------------
117         # CASE1: test subscriptions with subtypes
118         # ----------------------------------------
119
120         # Do: subscribe Raoul, should have default subtypes
121         group_pigs.message_subscribe_users([user_raoul.id])
122         group_pigs.refresh()
123         # Test: 2 followers (Admin and Raoul)
124         follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
125         self.assertEqual(set(follower_ids), set([user_raoul.partner_id.id, user_admin.partner_id.id]),
126                         'message_subscribe: Admin and Raoul should be the only 2 Pigs fans')
127         # Raoul follows default subtypes
128         fol_ids = self.mail_followers.search(cr, uid, [
129                         ('res_model', '=', 'mail.group'),
130                         ('res_id', '=', self.group_pigs_id),
131                         ('partner_id', '=', user_raoul.partner_id.id)
132                     ])
133         fol_obj = self.mail_followers.browse(cr, uid, fol_ids)[0]
134         fol_subtype_ids = set([subtype.id for subtype in fol_obj.subtype_ids])
135         self.assertEqual(set(fol_subtype_ids), set(default_group_subtypes),
136                         'message_subscribe: Raoul subscription subtypes are incorrect, should be all default ones')
137
138         # Do: subscribe Raoul with specified new subtypes
139         group_pigs.message_subscribe_users([user_raoul.id], subtype_ids=[mt_mg_nodef])
140         # Test: 2 followers (Admin and Raoul)
141         follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
142         self.assertEqual(set(follower_ids), set([user_raoul.partner_id.id, user_admin.partner_id.id]),
143                         'message_subscribe: Admin and Raoul should be the only 2 Pigs fans')
144         # Test: 2 lines in mail.followers (no duplicate for Raoul)
145         fol_ids = self.mail_followers.search(cr, uid, [
146                         ('res_model', '=', 'mail.group'),
147                         ('res_id', '=', self.group_pigs_id),
148                     ])
149         self.assertEqual(len(fol_ids), 2,
150                         'message_subscribe: subscribing an already-existing follower should not create new entries in mail.followers')
151         # Test: Raoul follows only specified subtypes
152         fol_ids = self.mail_followers.search(cr, uid, [
153                         ('res_model', '=', 'mail.group'),
154                         ('res_id', '=', self.group_pigs_id),
155                         ('partner_id', '=', user_raoul.partner_id.id)
156                     ])
157         fol_obj = self.mail_followers.browse(cr, uid, fol_ids)[0]
158         fol_subtype_ids = set([subtype.id for subtype in fol_obj.subtype_ids])
159         self.assertEqual(set(fol_subtype_ids), set([mt_mg_nodef]),
160                         'message_subscribe: Raoul subscription subtypes are incorrect, should be only specified')
161
162         # Do: Subscribe Raoul without specified subtypes: should not erase existing subscription subtypes
163         group_pigs.message_subscribe_users([user_raoul.id, user_raoul.id])
164         group_pigs.message_subscribe_users([user_raoul.id])
165         group_pigs.refresh()
166         # Test: 2 followers (Admin and Raoul)
167         follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
168         self.assertEqual(set(follower_ids), set([user_raoul.partner_id.id, user_admin.partner_id.id]),
169                         'message_subscribe: Admin and Raoul should be the only 2 Pigs fans')
170         # Test: Raoul follows default subtypes
171         fol_ids = self.mail_followers.search(cr, uid, [
172                         ('res_model', '=', 'mail.group'),
173                         ('res_id', '=', self.group_pigs_id),
174                         ('partner_id', '=', user_raoul.partner_id.id)
175                     ])
176         fol_obj = self.mail_followers.browse(cr, uid, fol_ids)[0]
177         fol_subtype_ids = set([subtype.id for subtype in fol_obj.subtype_ids])
178         self.assertEqual(set(fol_subtype_ids), set([mt_mg_nodef]),
179                         'message_subscribe: Raoul subscription subtypes are incorrect, should be only specified')
180
181         # Do: Unsubscribe Raoul twice through message_unsubscribe_users
182         group_pigs.message_unsubscribe_users([user_raoul.id, user_raoul.id])
183         group_pigs.refresh()
184         # Test: 1 follower (Admin)
185         follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
186         self.assertEqual(follower_ids, [user_admin.partner_id.id], 'Admin must be the only Pigs fan')
187         # Test: 1 lines in mail.followers (no duplicate for Raoul)
188         fol_ids = self.mail_followers.search(cr, uid, [
189                         ('res_model', '=', 'mail.group'),
190                         ('res_id', '=', self.group_pigs_id)
191                     ])
192         self.assertEqual(len(fol_ids), 1,
193                         'message_subscribe: group should have only 1 entry in mail.follower for 1 follower')
194
195         # Do: subscribe Admin with subtype_ids
196         group_pigs.message_subscribe_users([uid], [mt_mg_nodef, mt_all_nodef])
197         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)])
198         fol_obj = self.mail_followers.browse(cr, uid, fol_ids)[0]
199         fol_subtype_ids = set([subtype.id for subtype in fol_obj.subtype_ids])
200         self.assertEqual(set(fol_subtype_ids), set([mt_mg_nodef, mt_all_nodef]), 'subscription subtypes are incorrect')
201
202         # ----------------------------------------
203         # CASE2: test mail_thread fields
204         # ----------------------------------------
205
206         subtype_data = group_pigs._get_subscription_data(None, None)[group_pigs.id]['message_subtype_data']
207         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')
208         self.assertFalse(subtype_data['Discussions']['followed'], 'Admin should not follow Discussions in pigs')
209         self.assertTrue(subtype_data['mt_mg_nodef']['followed'], 'Admin should follow mt_mg_nodef in pigs')
210         self.assertTrue(subtype_data['mt_all_nodef']['followed'], 'Admin should follow mt_all_nodef in pigs')
211
212     def test_11_notification_url(self):
213         """ Tests designed to test the URL added in notification emails. """
214         cr, uid, group_pigs = self.cr, self.uid, self.group_pigs
215         # Test URL formatting
216         base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url')
217
218         # Partner data
219         partner_raoul = self.res_partner.browse(cr, uid, self.partner_raoul_id)
220         partner_bert_id = self.res_partner.create(cr, uid, {'name': 'bert'})
221         partner_bert = self.res_partner.browse(cr, uid, partner_bert_id)
222         # Mail data
223         mail_mail_id = self.mail_mail.create(cr, uid, {'state': 'exception'})
224         mail = self.mail_mail.browse(cr, uid, mail_mail_id)
225
226         # Test: link for nobody -> None
227         url = mail_mail._get_partner_access_link(self.mail_mail, cr, uid, mail)
228         self.assertEqual(url, None,
229                          'notification email: mails not send to a specific partner should not have any URL')
230
231         # Test: link for partner -> None
232         url = mail_mail._get_partner_access_link(self.mail_mail, cr, uid, mail, partner=partner_bert)
233         self.assertEqual(url, None,
234                          'notification email: mails send to a not-user partner should not have any URL')
235
236         # Test: link for user -> signin
237         url = mail_mail._get_partner_access_link(self.mail_mail, cr, uid, mail, partner=partner_raoul)
238         self.assertIn(base_url, url,
239                       'notification email: link should contain web.base.url')
240         self.assertIn('db=%s' % cr.dbname, url,
241                       'notification email: link should contain database name')
242         self.assertIn('action=mail.action_mail_redirect', url,
243                       'notification email: link should contain the redirect action')
244         self.assertIn('login=%s' % partner_raoul.user_ids[0].login, url,
245                       'notification email: link should contain the user login')
246
247         # Test: link for user -> with model and res_id
248         mail_mail_id = self.mail_mail.create(cr, uid, {'model': 'mail.group', 'res_id': group_pigs.id})
249         mail = self.mail_mail.browse(cr, uid, mail_mail_id)
250         url = mail_mail._get_partner_access_link(self.mail_mail, cr, uid, mail, partner=partner_raoul)
251         self.assertIn(base_url, url,
252                       'notification email: link should contain web.base.url')
253         self.assertIn('db=%s' % cr.dbname, url,
254                       'notification email: link should contain database name')
255         self.assertIn('action=mail.action_mail_redirect', url,
256                       'notification email: link should contain the redirect action')
257         self.assertIn('login=%s' % partner_raoul.user_ids[0].login, url,
258                       'notification email: link should contain the user login')
259         self.assertIn('model=mail.group', url,
260                       'notification email: link should contain the model when having not notification email on a record')
261         self.assertIn('res_id=%s' % group_pigs.id, url,
262                       'notification email: link should contain the res_id when having not notification email on a record')
263
264         # Test: link for user -> with model and res_id
265         mail_mail_id = self.mail_mail.create(cr, uid, {'notification': True, 'model': 'mail.group', 'res_id': group_pigs.id})
266         mail = self.mail_mail.browse(cr, uid, mail_mail_id)
267         url = mail_mail._get_partner_access_link(self.mail_mail, cr, uid, mail, partner=partner_raoul)
268         self.assertIn(base_url, url,
269                       'notification email: link should contain web.base.url')
270         self.assertIn('db=%s' % cr.dbname, url,
271                       'notification email: link should contain database name')
272         self.assertIn('action=mail.action_mail_redirect', url,
273                       'notification email: link should contain the redirect action')
274         self.assertIn('login=%s' % partner_raoul.user_ids[0].login, url,
275                       'notification email: link should contain the user login')
276         self.assertIn('message_id=%s' % mail.mail_message_id.id, url,
277                       'notification email: link based on message should contain the mail_message id')
278         self.assertNotIn('model=mail.group', url,
279                          'notification email: link based on message should not contain model')
280         self.assertNotIn('res_id=%s' % group_pigs.id, url,
281                          'notification email: link based on message should not contain res_id')
282
283     @mute_logger('openerp.addons.mail.mail_thread', 'openerp.models')
284     def test_12_inbox_redirection(self):
285         """ Tests designed to test the inbox redirection of emails notification URLs. """
286         cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
287         model, act_id = self.ir_model_data.get_object_reference(cr, uid, 'mail', 'action_mail_inbox_feeds')
288         # Data: post a message on pigs
289         msg_id = self.group_pigs.message_post(body='My body', partner_ids=[self.partner_bert_id], type='comment', subtype='mail.mt_comment')
290
291         # No specific parameters -> should redirect to Inbox
292         action = mail_thread.message_redirect_action(self.mail_thread, cr, self.user_raoul_id, {'params': {}})
293         self.assertEqual(
294             action.get('type'), 'ir.actions.client',
295             'URL redirection: action without parameters should redirect to client action Inbox'
296         )
297         self.assertEqual(
298             action.get('id'), act_id,
299             'URL redirection: action without parameters should redirect to client action Inbox'
300         )
301
302         # Raoul has read access to Pigs -> should redirect to form view of Pigs
303         action = mail_thread.message_redirect_action(self.mail_thread, cr, self.user_raoul_id, {'params': {'message_id': msg_id}})
304         self.assertEqual(
305             action.get('type'), 'ir.actions.act_window',
306             'URL redirection: action with message_id for read-accredited user should redirect to Pigs'
307         )
308         self.assertEqual(
309             action.get('res_id'), group_pigs.id,
310             'URL redirection: action with message_id for read-accredited user should redirect to Pigs'
311         )
312         action = mail_thread.message_redirect_action(self.mail_thread, cr, self.user_raoul_id, {'params': {'model': 'mail.group', 'res_id': group_pigs.id}})
313         self.assertEqual(
314             action.get('type'), 'ir.actions.act_window',
315             'URL redirection: action with message_id for read-accredited user should redirect to Pigs'
316         )
317         self.assertEqual(
318             action.get('res_id'), group_pigs.id,
319             'URL redirection: action with message_id for read-accredited user should redirect to Pigs'
320         )
321
322         # Bert has no read access to Pigs -> should redirect to Inbox
323         action = mail_thread.message_redirect_action(self.mail_thread, cr, self.user_bert_id, {'params': {'message_id': msg_id}})
324         self.assertEqual(
325             action.get('type'), 'ir.actions.client',
326             'URL redirection: action without parameters should redirect to client action Inbox'
327         )
328         self.assertEqual(
329             action.get('id'), act_id,
330             'URL redirection: action without parameters should redirect to client action Inbox'
331         )
332         action = mail_thread.message_redirect_action(self.mail_thread, cr, self.user_bert_id, {'params': {'model': 'mail.group', 'res_id': group_pigs.id}})
333         self.assertEqual(
334             action.get('type'), 'ir.actions.client',
335             'URL redirection: action without parameters should redirect to client action Inbox'
336         )
337         self.assertEqual(
338             action.get('id'), act_id,
339             'URL redirection: action without parameters should redirect to client action Inbox'
340         )
341
342     def test_20_message_post(self):
343         """ Tests designed for message_post. """
344         cr, uid, user_raoul, group_pigs = self.cr, self.uid, self.user_raoul, self.group_pigs
345
346         # --------------------------------------------------
347         # Data creation
348         # --------------------------------------------------
349         # 0 - Update existing users-partners
350         self.res_users.write(cr, uid, [uid], {'email': 'a@a', 'notify_email': 'always'})
351         self.res_users.write(cr, uid, [self.user_raoul_id], {'email': 'r@r'})
352         # 1 - Bert Tartopoils, with email, should receive emails for comments and emails
353         p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
354         # 2 - Carine Poilvache, with email, should receive emails for emails
355         p_c_id = self.res_partner.create(cr, uid, {'name': 'Carine Poilvache', 'email': 'c@c', 'notify_email': 'none'})
356         # 3 - Dédé Grosbedon, without email, to test email verification; should receive emails for every message
357         p_d_id = self.res_partner.create(cr, uid, {'name': 'Dédé Grosbedon', 'email': 'd@d', 'notify_email': 'always'})
358         # 4 - Attachments
359         attach1_id = self.ir_attachment.create(cr, user_raoul.id, {
360             'name': 'Attach1', 'datas_fname': 'Attach1',
361             'datas': 'bWlncmF0aW9uIHRlc3Q=',
362             'res_model': 'mail.compose.message', 'res_id': 0})
363         attach2_id = self.ir_attachment.create(cr, user_raoul.id, {
364             'name': 'Attach2', 'datas_fname': 'Attach2',
365             'datas': 'bWlncmF0aW9uIHRlc3Q=',
366             'res_model': 'mail.compose.message', 'res_id': 0})
367         attach3_id = self.ir_attachment.create(cr, user_raoul.id, {
368             'name': 'Attach3', 'datas_fname': 'Attach3',
369             'datas': 'bWlncmF0aW9uIHRlc3Q=',
370             'res_model': 'mail.compose.message', 'res_id': 0})
371         # 5 - Mail data
372         _subject = 'Pigs'
373         _mail_subject = 'Re: %s' % (group_pigs.name)
374         _body1 = '<p>Pigs rules</p>'
375         _body2 = '<html>Pigs rocks</html>'
376         _attachments = [
377             ('List1', 'My first attachment'),
378             ('List2', 'My second attachment')
379         ]
380
381         # --------------------------------------------------
382         # CASE1: post comment + partners + attachments
383         # --------------------------------------------------
384
385         # Data: set alias_domain to see emails with alias
386         self.registry('ir.config_parameter').set_param(self.cr, self.uid, 'mail.catchall.domain', 'schlouby.fr')
387         # Data: change Pigs name to test reply_to
388         self.mail_group.write(cr, uid, [self.group_pigs_id], {'name': '"Pigs" !ù $%-'})
389
390         # Do: subscribe Raoul
391         new_follower_ids = [self.partner_raoul_id]
392         group_pigs.message_subscribe(new_follower_ids)
393         # Test: group followers = Raoul + uid
394         group_fids = [follower.id for follower in group_pigs.message_follower_ids]
395         test_fids = new_follower_ids + [self.partner_admin_id]
396         self.assertEqual(set(test_fids), set(group_fids),
397                         'message_subscribe: incorrect followers after subscribe')
398
399         # Do: Raoul message_post on Pigs
400         self._init_mock_build_email()
401         msg1_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id,
402                             body=_body1, subject=_subject, partner_ids=[p_b_id, p_c_id],
403                             attachment_ids=[attach1_id, attach2_id], attachments=_attachments,
404                             type='comment', subtype='mt_comment')
405         msg = self.mail_message.browse(cr, uid, msg1_id)
406         msg_message_id = msg.message_id
407         msg_pids = [partner.id for partner in msg.notified_partner_ids]
408         msg_aids = [attach.id for attach in msg.attachment_ids]
409         sent_emails = self._build_email_kwargs_list
410
411         # Test: mail_message: subject and body not modified
412         self.assertEqual(_subject, msg.subject, 'message_post: mail.message subject incorrect')
413         self.assertEqual(_body1, msg.body, 'message_post: mail.message body incorrect')
414         # Test: mail_message: notified_partner_ids = group followers + partner_ids - author
415         test_pids = set([self.partner_admin_id, p_b_id, p_c_id])
416         self.assertEqual(test_pids, set(msg_pids), 'message_post: mail.message notified partners incorrect')
417         # Test: mail_message: attachments (4, attachment_ids + attachments)
418         test_aids = set([attach1_id, attach2_id])
419         msg_attach_names = set([attach.name for attach in msg.attachment_ids])
420         test_attach_names = set(['Attach1', 'Attach2', 'List1', 'List2'])
421         self.assertEqual(len(msg_aids), 4,
422                         'message_post: mail.message wrong number of attachments')
423         self.assertEqual(msg_attach_names, test_attach_names,
424                         'message_post: mail.message attachments incorrectly added')
425         self.assertTrue(test_aids.issubset(set(msg_aids)),
426                         'message_post: mail.message attachments duplicated')
427         for attach in msg.attachment_ids:
428             self.assertEqual(attach.res_model, 'mail.group',
429                             'message_post: mail.message attachments were not linked to the document')
430             self.assertEqual(attach.res_id, group_pigs.id,
431                             'message_post: mail.message attachments were not linked to the document')
432             if 'List' in attach.name:
433                 self.assertIn((attach.name, attach.datas.decode('base64')), _attachments,
434                                 'message_post: mail.message attachment name / data incorrect')
435                 dl_attach = self.mail_message.download_attachment(cr, user_raoul.id, id_message=msg.id, attachment_id=attach.id)
436                 self.assertIn((dl_attach['filename'], dl_attach['base64'].decode('base64')), _attachments,
437                                 'message_post: mail.message download_attachment is incorrect')
438
439         # Test: followers: same as before (author was already subscribed)
440         group_pigs.refresh()
441         group_fids = [follower.id for follower in group_pigs.message_follower_ids]
442         test_fids = new_follower_ids + [self.partner_admin_id]
443         self.assertEqual(set(test_fids), set(group_fids),
444                         'message_post: wrong followers after posting')
445
446         # Test: mail_mail: notifications have been deleted
447         self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg1_id)]),
448                         'message_post: mail.mail notifications should have been auto-deleted!')
449
450         # Test: notifications emails: to a and b, c is email only, r is author
451         test_emailto = ['Administrator <a@a>', 'Bert Tartopoils <b@b>']
452         # test_emailto = ['"Followers of -Pigs-" <a@a>', '"Followers of -Pigs-" <b@b>']
453         self.assertEqual(len(sent_emails), 2,
454                         'message_post: notification emails wrong number of send emails')
455         self.assertEqual(set([m['email_to'][0] for m in sent_emails]), set(test_emailto),
456                         'message_post: notification emails wrong recipients (email_to)')
457         for sent_email in sent_emails:
458             self.assertEqual(sent_email['email_from'], 'Raoul Grosbedon <raoul@schlouby.fr>',
459                             'message_post: notification email wrong email_from: should use alias of sender')
460             self.assertEqual(len(sent_email['email_to']), 1,
461                             'message_post: notification email sent to more than one email address instead of a precise partner')
462             self.assertIn(sent_email['email_to'][0], test_emailto,
463                             'message_post: notification email email_to incorrect')
464             self.assertEqual(sent_email['reply_to'], u'"YourCompany \\"Pigs\\" !ù $%-" <group+pigs@schlouby.fr>',
465                             'message_post: notification email reply_to incorrect')
466             self.assertEqual(_subject, sent_email['subject'],
467                             'message_post: notification email subject incorrect')
468             self.assertIn(_body1, sent_email['body'],
469                             'message_post: notification email body incorrect')
470             self.assertIn('Pigs rules', sent_email['body_alternative'],
471                             'message_post: notification email body alternative should contain the body')
472             self.assertNotIn('<p>', sent_email['body_alternative'],
473                             'message_post: notification email body alternative still contains html')
474             self.assertFalse(sent_email['references'],
475                             'message_post: references should be False when sending a message that is not a reply')
476
477         # Test: notification linked to this message = group followers = notified_partner_ids
478         notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', msg1_id)])
479         notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
480         self.assertEqual(notif_pids, test_pids,
481                         'message_post: mail.message created mail.notification incorrect')
482
483         # Data: Pigs name back to normal
484         self.mail_group.write(cr, uid, [self.group_pigs_id], {'name': 'Pigs'})
485
486         # --------------------------------------------------
487         # CASE2: reply + parent_id + parent notification
488         # --------------------------------------------------
489
490         # Data: remove alias_domain to see emails with alias
491         param_ids = self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.domain')])
492         self.registry('ir.config_parameter').unlink(cr, uid, param_ids)
493
494         # Do: Raoul message_post on Pigs
495         self._init_mock_build_email()
496         msg2_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id,
497                         body=_body2, type='email', subtype='mt_comment',
498                         partner_ids=[p_d_id], parent_id=msg1_id, attachment_ids=[attach3_id],
499                         context={'mail_post_autofollow': True})
500         msg = self.mail_message.browse(cr, uid, msg2_id)
501         msg_pids = [partner.id for partner in msg.notified_partner_ids]
502         msg_aids = [attach.id for attach in msg.attachment_ids]
503         sent_emails = self._build_email_kwargs_list
504
505         # Test: mail_message: subject is False, body, parent_id is msg_id
506         self.assertEqual(msg.subject, False, 'message_post: mail.message subject incorrect')
507         self.assertEqual(msg.body, html_sanitize(_body2), 'message_post: mail.message body incorrect')
508         self.assertEqual(msg.parent_id.id, msg1_id, 'message_post: mail.message parent_id incorrect')
509         # Test: mail_message: notified_partner_ids = group followers
510         test_pids = [self.partner_admin_id, p_d_id]
511         self.assertEqual(set(test_pids), set(msg_pids), 'message_post: mail.message partners incorrect')
512         # Test: mail_message: notifications linked to this message = group followers = notified_partner_ids
513         notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', msg2_id)])
514         notif_pids = [notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)]
515         self.assertEqual(set(test_pids), set(notif_pids), 'message_post: mail.message notification partners incorrect')
516
517         # Test: mail_mail: notifications deleted
518         self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg2_id)]), 'mail.mail notifications should have been auto-deleted!')
519
520         # Test: emails send by server (to a, b, c, d)
521         test_emailto = [u'Administrator <a@a>', u'Bert Tartopoils <b@b>', u'Carine Poilvache <c@c>', u'D\xe9d\xe9 Grosbedon <d@d>']
522         # test_emailto = [u'"Followers of Pigs" <a@a>', u'"Followers of Pigs" <b@b>', u'"Followers of Pigs" <c@c>', u'"Followers of Pigs" <d@d>']
523         # self.assertEqual(len(sent_emails), 3, 'sent_email number of sent emails incorrect')
524         for sent_email in sent_emails:
525             self.assertEqual(sent_email['email_from'], 'Raoul Grosbedon <r@r>',
526                             'message_post: notification email wrong email_from: should use email of sender when no alias domain set')
527             self.assertEqual(len(sent_email['email_to']), 1,
528                             'message_post: notification email sent to more than one email address instead of a precise partner')
529             self.assertIn(sent_email['email_to'][0], test_emailto,
530                             'message_post: notification email email_to incorrect')
531             self.assertEqual(email_split(sent_email['reply_to']), ['r@r'],  # was '"Followers of Pigs" <r@r>', but makes no sense
532                             'message_post: notification email reply_to incorrect: should have raoul email')
533             self.assertEqual(_mail_subject, sent_email['subject'],
534                             'message_post: notification email subject incorrect')
535             self.assertIn(html_sanitize(_body2), sent_email['body'],
536                             'message_post: notification email does not contain the body')
537             self.assertIn('Pigs rocks', sent_email['body_alternative'],
538                             'message_post: notification email body alternative should contain the body')
539             self.assertNotIn('<p>', sent_email['body_alternative'],
540                             'message_post: notification email body alternative still contains html')
541             self.assertIn(msg_message_id, sent_email['references'],
542                             'message_post: notification email references lacks parent message message_id')
543         # Test: attachments + download
544         for attach in msg.attachment_ids:
545             self.assertEqual(attach.res_model, 'mail.group',
546                             'message_post: mail.message attachment res_model incorrect')
547             self.assertEqual(attach.res_id, self.group_pigs_id,
548                             'message_post: mail.message attachment res_id incorrect')
549
550         # Test: Dédé has been notified -> should also have been notified of the parent message
551         msg = self.mail_message.browse(cr, uid, msg1_id)
552         msg_pids = set([partner.id for partner in msg.notified_partner_ids])
553         test_pids = set([self.partner_admin_id, p_b_id, p_c_id, p_d_id])
554         self.assertEqual(test_pids, msg_pids, 'message_post: mail.message parent notification not created')
555
556          # Do: reply to last message
557         msg3_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id, body='Test', parent_id=msg2_id)
558         msg = self.mail_message.browse(cr, uid, msg3_id)
559         # Test: check that its parent will be the first message
560         self.assertEqual(msg.parent_id.id, msg1_id, 'message_post did not flatten the thread structure')
561
562     def test_25_message_compose_wizard(self):
563         """ Tests designed for the mail.compose.message wizard. """
564         cr, uid, user_raoul, group_pigs = self.cr, self.uid, self.user_raoul, self.group_pigs
565         mail_compose = self.registry('mail.compose.message')
566
567         # --------------------------------------------------
568         # Data creation
569         # --------------------------------------------------
570         # 0 - Update existing users-partners
571         self.res_users.write(cr, uid, [uid], {'email': 'a@a'})
572         self.res_users.write(cr, uid, [self.user_raoul_id], {'email': 'r@r'})
573         # 1 - Bert Tartopoils, with email, should receive emails for comments and emails
574         p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
575         # 2 - Carine Poilvache, with email, should receive emails for emails
576         p_c_id = self.res_partner.create(cr, uid, {'name': 'Carine Poilvache', 'email': 'c@c', 'notify_email': 'always'})
577         # 3 - Dédé Grosbedon, without email, to test email verification; should receive emails for every message
578         p_d_id = self.res_partner.create(cr, uid, {'name': 'Dédé Grosbedon', 'email': 'd@d', 'notify_email': 'always'})
579         # 4 - Create a Bird mail.group, that will be used to test mass mailing
580         group_bird_id = self.mail_group.create(cr, uid,
581             {
582                 'name': 'Bird',
583                 'description': 'Bird resistance',
584             }, context={'mail_create_nolog': True})
585         group_bird = self.mail_group.browse(cr, uid, group_bird_id)
586         # 5 - Mail data
587         _subject = 'Pigs'
588         _body = 'Pigs <b>rule</b>'
589         _reply_subject = 'Re: %s' % _subject
590         _attachments = [
591             {'name': 'First', 'datas_fname': 'first.txt', 'datas': 'My first attachment'.encode('base64')},
592             {'name': 'Second', 'datas_fname': 'second.txt', 'datas': 'My second attachment'.encode('base64')}
593             ]
594         _attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')]
595         # 6 - Subscribe Bert to Pigs
596         group_pigs.message_subscribe([p_b_id])
597
598         # --------------------------------------------------
599         # CASE1: wizard + partners + context keys
600         # --------------------------------------------------
601
602         # Do: Raoul wizard-composes on Pigs with auto-follow for partners, not for author
603         compose_id = mail_compose.create(cr, user_raoul.id,
604             {
605                 'subject': _subject,
606                 'body': _body,
607                 'partner_ids': [(4, p_c_id), (4, p_d_id)],
608             }, context={
609                 'default_composition_mode': 'comment',
610                 'default_model': 'mail.group',
611                 'default_res_id': self.group_pigs_id,
612             })
613         compose = mail_compose.browse(cr, uid, compose_id)
614
615         # Test: mail.compose.message: composition_mode, model, res_id
616         self.assertEqual(compose.composition_mode,  'comment', 'compose wizard: mail.compose.message incorrect composition_mode')
617         self.assertEqual(compose.model,  'mail.group', 'compose wizard: mail.compose.message incorrect model')
618         self.assertEqual(compose.res_id, self.group_pigs_id, 'compose wizard: mail.compose.message incorrect res_id')
619
620         # Do: Post the comment
621         mail_compose.send_mail(cr, user_raoul.id, [compose_id], {'mail_post_autofollow': True, 'mail_create_nosubscribe': True})
622         group_pigs.refresh()
623         message = group_pigs.message_ids[0]
624         # Test: mail_mail: notifications have been deleted
625         self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', message.id)]),'message_send: mail.mail message should have been auto-deleted!')
626         # Test: mail.group: followers (c and d added by auto follow key; raoul not added by nosubscribe key)
627         pigs_pids = [p.id for p in group_pigs.message_follower_ids]
628         test_pids = [self.partner_admin_id, p_b_id, p_c_id, p_d_id]
629         self.assertEqual(set(pigs_pids), set(test_pids),
630                         'compose wizard: mail_post_autofollow and mail_create_nosubscribe context keys not correctly taken into account')
631
632         # Test: mail.message: subject, body inside p
633         self.assertEqual(message.subject, _subject, 'compose wizard: mail.message incorrect subject')
634         self.assertEqual(message.body, '<p>%s</p>' % _body, 'compose wizard: mail.message incorrect body')
635         # Test: mail.message: notified_partner_ids = admin + bert (followers) + c + d (recipients)
636         msg_pids = [partner.id for partner in message.notified_partner_ids]
637         test_pids = [self.partner_admin_id, p_b_id, p_c_id, p_d_id]
638         self.assertEqual(set(msg_pids), set(test_pids),
639                         'compose wizard: mail.message notified_partner_ids incorrect')
640
641         # --------------------------------------------------
642         # CASE2: reply + attachments
643         # --------------------------------------------------
644
645         # Do: Reply with attachments
646         compose_id = mail_compose.create(cr, user_raoul.id,
647             {
648                 'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]
649             }, context={
650                 'default_composition_mode': 'comment',
651                 'default_res_id': self.group_pigs_id,
652                 'default_parent_id': message.id
653             })
654         compose = mail_compose.browse(cr, uid, compose_id)
655
656         # Test: mail.compose.message: model, res_id, parent_id
657         self.assertEqual(compose.model, 'mail.group', 'compose wizard: mail.compose.message incorrect model')
658         self.assertEqual(compose.res_id, self.group_pigs_id, 'compose wizard: mail.compose.message incorrect res_id')
659         self.assertEqual(compose.parent_id.id, message.id, 'compose wizard: mail.compose.message incorrect parent_id')
660
661         # Test: mail.compose.message: subject as Re:.., body, parent_id
662         self.assertEqual(compose.subject, _reply_subject, 'compose wizard: mail.compose.message incorrect subject')
663         self.assertFalse(compose.body, 'compose wizard: mail.compose.message body should not contain parent message body')
664         self.assertEqual(compose.parent_id and compose.parent_id.id, message.id, 'compose wizard: mail.compose.message parent_id incorrect')
665         # Test: mail.compose.message: attachments
666         for attach in compose.attachment_ids:
667             self.assertIn((attach.datas_fname, attach.datas.decode('base64')), _attachments_test,
668                             'compose wizard: mail.message attachment name / data incorrect')
669
670         # --------------------------------------------------
671         # CASE3: mass_mail on Pigs and Bird
672         # --------------------------------------------------
673
674         # Do: Compose in mass_mail_mode on pigs and bird
675         compose_id = mail_compose.create(
676             cr, user_raoul.id, {
677                 'subject': _subject,
678                 'body': '${object.description}',
679                 'partner_ids': [(4, p_c_id), (4, p_d_id)],
680             }, context={
681                 'default_composition_mode': 'mass_mail',
682                 'default_model': 'mail.group',
683                 'default_res_id': False,
684                 'active_ids': [self.group_pigs_id, group_bird_id],
685             })
686         compose = mail_compose.browse(cr, uid, compose_id)
687
688         # Do: Post the comment, get created message for each group
689         mail_compose.send_mail(cr, user_raoul.id, [compose_id], context={
690                         'default_res_id': -1,
691                         'active_ids': [self.group_pigs_id, group_bird_id]
692                     })
693         # check mail_mail
694         mail_mail_ids = self.mail_mail.search(cr, uid, [('subject', '=', _subject)])
695         for mail_mail in self.mail_mail.browse(cr, uid, mail_mail_ids):
696             self.assertEqual(set([p.id for p in mail_mail.recipient_ids]), set([p_c_id, p_d_id]),
697                              'compose wizard: mail_mail mass mailing: mail.mail in mass mail incorrect recipients')
698
699         # check logged messages
700         group_pigs.refresh()
701         group_bird.refresh()
702         message1 = group_pigs.message_ids[0]
703         message2 = group_bird.message_ids[0]
704
705         # Test: Pigs and Bird did receive their message
706         test_msg_ids = self.mail_message.search(cr, uid, [], limit=2)
707         mail_ids = self.mail_mail.search(cr, uid, [('mail_message_id', '=', message2.id)])
708         mail_record_id = self.mail_mail.browse(cr, uid, mail_ids)[0]
709         self.assertTrue(mail_record_id, "'message_send: mail.mail message should have in processing mail queue'" )
710         #check mass mail state...
711         test_mail_ids = self.mail_mail.search(cr, uid, [('state', '=', 'exception')])
712         self.assertNotIn(mail_ids, test_mail_ids, 'compose wizard: Mail sending Failed!!')
713         self.assertIn(message1.id, test_msg_ids, 'compose wizard: Pigs did not receive its mass mailing message')
714         self.assertIn(message2.id, test_msg_ids, 'compose wizard: Bird did not receive its mass mailing message')
715
716         # Test: mail.message: subject, body, subtype, notified partners (nobody + specific recipients)
717         self.assertEqual(message1.subject, _subject,
718                         'compose wizard: message_post: mail.message in mass mail subject incorrect')
719         self.assertEqual(message1.body, '<p>%s</p>' % group_pigs.description,
720                         'compose wizard: message_post: mail.message in mass mail body incorrect')
721         # self.assertEqual(set([p.id for p in message1.notified_partner_ids]), set([p_c_id, p_d_id]),
722         #                 'compose wizard: message_post: mail.message in mass mail incorrect notified partners')
723         self.assertEqual(message2.subject, _subject,
724                         'compose wizard: message_post: mail.message in mass mail subject incorrect')
725         self.assertEqual(message2.body, '<p>%s</p>' % group_bird.description,
726                         'compose wizard: message_post: mail.message in mass mail body incorrect')
727         # self.assertEqual(set([p.id for p in message2.notified_partner_ids]), set([p_c_id, p_d_id]),
728         #                 'compose wizard: message_post: mail.message in mass mail incorrect notified partners')
729
730         # Test: mail.group followers: author not added as follower in mass mail mode
731         pigs_pids = [p.id for p in group_pigs.message_follower_ids]
732         test_pids = [self.partner_admin_id, p_b_id, p_c_id, p_d_id]
733         self.assertEqual(set(pigs_pids), set(test_pids),
734                         'compose wizard: mail_post_autofollow and mail_create_nosubscribe context keys not correctly taken into account')
735         bird_pids = [p.id for p in group_bird.message_follower_ids]
736         test_pids = [self.partner_admin_id]
737         self.assertEqual(set(bird_pids), set(test_pids),
738                         'compose wizard: mail_post_autofollow and mail_create_nosubscribe context keys not correctly taken into account')
739
740         # Do: Compose in mass_mail, coming from list_view, we have an active_domain that should be supported
741         compose_id = mail_compose.create(cr, user_raoul.id,
742             {
743                 'subject': _subject,
744                 'body': '${object.description}',
745                 'partner_ids': [(4, p_c_id), (4, p_d_id)],
746             }, context={
747                 'default_composition_mode': 'mass_mail',
748                 'default_model': 'mail.group',
749                 'default_res_id': False,
750                 'active_ids': [self.group_pigs_id],
751                 'active_domain': [('name', 'in', ['Pigs', 'Bird'])],
752             })
753         compose = mail_compose.browse(cr, uid, compose_id)
754
755         # Do: Post the comment, get created message for each group
756         mail_compose.send_mail(
757             cr, user_raoul.id, [compose_id], context={
758                 'default_res_id': -1,
759                 'active_ids': [self.group_pigs_id, group_bird_id]
760             })
761         group_pigs.refresh()
762         group_bird.refresh()
763         message1 = group_pigs.message_ids[0]
764         message2 = group_bird.message_ids[0]
765
766         # Test: Pigs and Bird did receive their message
767         test_msg_ids = self.mail_message.search(cr, uid, [], limit=2)
768         self.assertIn(message1.id, test_msg_ids, 'compose wizard: Pigs did not receive its mass mailing message')
769         self.assertIn(message2.id, test_msg_ids, 'compose wizard: Bird did not receive its mass mailing message')
770
771     def test_30_needaction(self):
772         """ Tests for mail.message needaction. """
773         cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
774         na_admin_base = self.mail_message._needaction_count(cr, uid, domain=[])
775         na_demo_base = self.mail_message._needaction_count(cr, user_raoul.id, domain=[])
776
777         # Test: number of unread notification = needaction on mail.message
778         notif_ids = self.mail_notification.search(cr, uid, [
779             ('partner_id', '=', user_admin.partner_id.id),
780             ('is_read', '=', False)
781             ])
782         na_count = self.mail_message._needaction_count(cr, uid, domain=[])
783         self.assertEqual(len(notif_ids), na_count, 'unread notifications count does not match needaction count')
784
785         # Do: post 2 message on group_pigs as admin, 3 messages as demo user
786         for dummy in range(2):
787             group_pigs.message_post(body='My Body', subtype='mt_comment')
788         raoul_pigs = group_pigs.sudo(user_raoul)
789         for dummy in range(3):
790             raoul_pigs.message_post(body='My Demo Body', subtype='mt_comment')
791
792         # Test: admin has 3 new notifications (from demo), and 3 new needaction
793         notif_ids = self.mail_notification.search(cr, uid, [
794             ('partner_id', '=', user_admin.partner_id.id),
795             ('is_read', '=', False)
796             ])
797         self.assertEqual(len(notif_ids), na_admin_base + 3, 'Admin should have 3 new unread notifications')
798         na_admin = self.mail_message._needaction_count(cr, uid, domain=[])
799         na_admin_group = self.mail_message._needaction_count(cr, uid, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
800         self.assertEqual(na_admin, na_admin_base + 3, 'Admin should have 3 new needaction')
801         self.assertEqual(na_admin_group, 3, 'Admin should have 3 needaction related to Pigs')
802         # Test: demo has 0 new notifications (not a follower, not receiving its own messages), and 0 new needaction
803         notif_ids = self.mail_notification.search(cr, uid, [
804             ('partner_id', '=', user_raoul.partner_id.id),
805             ('is_read', '=', False)
806             ])
807         self.assertEqual(len(notif_ids), na_demo_base + 0, 'Demo should have 0 new unread notifications')
808         na_demo = self.mail_message._needaction_count(cr, user_raoul.id, domain=[])
809         na_demo_group = self.mail_message._needaction_count(cr, user_raoul.id, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
810         self.assertEqual(na_demo, na_demo_base + 0, 'Demo should have 0 new needaction')
811         self.assertEqual(na_demo_group, 0, 'Demo should have 0 needaction related to Pigs')
812
813     def test_40_track_field(self):
814         """ Testing auto tracking of fields. """
815         def _strip_string_spaces(body):
816             return body.replace(' ', '').replace('\n', '')
817
818         # Data: subscribe Raoul to Pigs, because he will change the public attribute and may loose access to the record
819         cr, uid = self.cr, self.uid
820         self.mail_group.message_subscribe_users(cr, uid, [self.group_pigs_id], [self.user_raoul_id])
821
822         # Data: res.users.group, to test group_public_id automatic logging
823         group_system_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_system')
824         group_system_id = group_system_ref and group_system_ref[1] or False
825
826         # Data: custom subtypes
827         mt_private_id = self.mail_message_subtype.create(cr, uid, {'name': 'private', 'description': 'Private public'})
828         self.ir_model_data.create(cr, uid, {'name': 'mt_private', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_private_id})
829         mt_name_supername_id = self.mail_message_subtype.create(cr, uid, {'name': 'name_supername', 'description': 'Supername name'})
830         self.ir_model_data.create(cr, uid, {'name': 'mt_name_supername', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_name_supername_id})
831         mt_group_public_set_id = self.mail_message_subtype.create(cr, uid, {'name': 'group_public_set', 'description': 'Group set'})
832         self.ir_model_data.create(cr, uid, {'name': 'mt_group_public_set', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_group_public_set_id})
833         mt_group_public_id = self.mail_message_subtype.create(cr, uid, {'name': 'group_public', 'description': 'Group changed'})
834         self.ir_model_data.create(cr, uid, {'name': 'mt_group_public', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_group_public_id})
835
836         # Data: alter mail_group model for testing purposes (test on classic, selection and many2one fields)
837         cls = type(self.mail_group)
838         self.assertNotIn('_track', cls.__dict__)
839         cls._track = {
840             'public': {
841                 'mail.mt_private': lambda self, cr, uid, obj, ctx=None: obj.public == 'private',
842             },
843             'name': {
844                 'mail.mt_name_supername': lambda self, cr, uid, obj, ctx=None: obj.name == 'supername',
845             },
846             'group_public_id': {
847                 'mail.mt_group_public_set': lambda self, cr, uid, obj, ctx=None: obj.group_public_id,
848                 'mail.mt_group_public': lambda self, cr, uid, obj, ctx=None: True,
849             },
850         }
851         visibility = {'public': 'onchange', 'name': 'always', 'group_public_id': 'onchange'}
852         for key in visibility:
853             self.assertFalse(hasattr(getattr(cls, key), 'track_visibility'))
854             getattr(cls, key).track_visibility = visibility[key]
855
856         @self.addCleanup
857         def cleanup():
858             delattr(cls, '_track')
859             for key in visibility:
860                 del getattr(cls, key).track_visibility
861
862         # Test: change name -> always tracked, not related to a subtype
863         self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'public': 'public'})
864         self.group_pigs.refresh()
865         self.assertEqual(len(self.group_pigs.message_ids), 1, 'tracked: a message should have been produced')
866         # Test: first produced message: no subtype, name change tracked
867         last_msg = self.group_pigs.message_ids[-1]
868         self.assertFalse(last_msg.subtype_id, 'tracked: message should not have been linked to a subtype')
869         self.assertIn(u"Selectedgroupofusers\u2192Everyone", _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
870         self.assertIn('Pigs', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
871
872         # Test: change name as supername, public as private -> 2 subtypes
873         self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'name': 'supername', 'public': 'private'})
874         self.group_pigs.refresh()
875         self.assertEqual(len(self.group_pigs.message_ids), 3, 'tracked: two messages should have been produced')
876         # Test: first produced message: mt_name_supername
877         last_msg = self.group_pigs.message_ids[-2]
878         self.assertEqual(last_msg.subtype_id.id, mt_private_id, 'tracked: message should be linked to mt_private subtype')
879         self.assertIn('Private public', last_msg.body, 'tracked: message body does not hold the subtype description')
880         self.assertIn(u'Pigs\u2192supername', _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
881         # Test: second produced message: mt_name_supername
882         last_msg = self.group_pigs.message_ids[-3]
883         self.assertEqual(last_msg.subtype_id.id, mt_name_supername_id, 'tracked: message should be linked to mt_name_supername subtype')
884         self.assertIn('Supername name', last_msg.body, 'tracked: message body does not hold the subtype description')
885         self.assertIn(u"Everyone\u2192Invitedpeopleonly", _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
886         self.assertIn(u'Pigs\u2192supername', _strip_string_spaces(last_msg.body), 'tracked feature: message body does not hold always tracked field')
887
888         # Test: change public as public, group_public_id -> 2 subtypes, name always tracked
889         self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'public': 'public', 'group_public_id': group_system_id})
890         self.group_pigs.refresh()
891         self.assertEqual(len(self.group_pigs.message_ids), 5, 'tracked: one message should have been produced')
892         # Test: first produced message: mt_group_public_set_id, with name always tracked, public tracked on change
893         last_msg = self.group_pigs.message_ids[-4]
894         self.assertEqual(last_msg.subtype_id.id, mt_group_public_set_id, 'tracked: message should be linked to mt_group_public_set_id')
895         self.assertIn('Group set', last_msg.body, 'tracked: message body does not hold the subtype description')
896         self.assertIn(u"Invitedpeopleonly\u2192Everyone", _strip_string_spaces(last_msg.body), 'tracked: message body does not hold changed tracked field')
897         self.assertIn(u'HumanResources/Employee\u2192Administration/Settings', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
898         # Test: second produced message: mt_group_public_id, with name always tracked, public tracked on change
899         last_msg = self.group_pigs.message_ids[-5]
900         self.assertEqual(last_msg.subtype_id.id, mt_group_public_id, 'tracked: message should be linked to mt_group_public_id')
901         self.assertIn('Group changed', last_msg.body, 'tracked: message body does not hold the subtype description')
902         self.assertIn(u"Invitedpeopleonly\u2192Everyone", _strip_string_spaces(last_msg.body), 'tracked: message body does not hold changed tracked field')
903         self.assertIn(u'HumanResources/Employee\u2192Administration/Settings', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
904
905         # Test: change group_public_id to False -> 1 subtype, name always tracked
906         self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'group_public_id': False})
907         self.group_pigs.refresh()
908         self.assertEqual(len(self.group_pigs.message_ids), 6, 'tracked: one message should have been produced')
909         # Test: first produced message: mt_group_public_set_id, with name always tracked, public tracked on change
910         last_msg = self.group_pigs.message_ids[-6]
911         self.assertEqual(last_msg.subtype_id.id, mt_group_public_id, 'tracked: message should be linked to mt_group_public_id')
912         self.assertIn('Group changed', last_msg.body, 'tracked: message body does not hold the subtype description')
913         self.assertIn(u'Administration/Settings\u2192', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
914
915         # Test: change not tracked field, no tracking message
916         self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'description': 'Dummy'})
917         self.group_pigs.refresh()
918         self.assertEqual(len(self.group_pigs.message_ids), 6, 'tracked: No message should have been produced')