[MERGE] mail/chatter complete review/refactoring
[odoo/odoo.git] / addons / mail / tests / test_mail.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.tests import common
23 from openerp.tools.html_sanitize import html_sanitize
24
25 MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
26 To: {to}
27 Received: by mail1.openerp.com (Postfix, from userid 10002)
28     id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
29 From: Sylvie Lelitre <sylvie.lelitre@agrolait.com>
30 Subject: {subject}
31 MIME-Version: 1.0
32 Content-Type: multipart/alternative; 
33     boundary="----=_Part_4200734_24778174.1344608186754"
34 Date: Fri, 10 Aug 2012 14:16:26 +0000
35 Message-ID: <1198923581.41972151344608186760.JavaMail@agrolait.com>
36 {extra}
37 ------=_Part_4200734_24778174.1344608186754
38 Content-Type: text/plain; charset=utf-8
39 Content-Transfer-Encoding: quoted-printable
40
41 Please call me as soon as possible this afternoon!
42
43 --
44 Sylvie
45 ------=_Part_4200734_24778174.1344608186754
46 Content-Type: text/html; charset=utf-8
47 Content-Transfer-Encoding: quoted-printable
48
49 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
50 <html>
51  <head>=20
52   <meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8" />
53  </head>=20
54  <body style=3D"margin: 0; padding: 0; background: #ffffff;-webkit-text-size-adjust: 100%;">=20
55   
56   <p>Please call me as soon as possible this afternoon!</p>
57   
58   <p>--<br/>
59      Sylvie
60   <p>
61  </body>
62 </html>
63 ------=_Part_4200734_24778174.1344608186754--
64 """
65
66 MAIL_TEMPLATE_PLAINTEXT = """Return-Path: <whatever-2a840@postmaster.twitter.com>
67 To: {to}
68 Received: by mail1.openerp.com (Postfix, from userid 10002)
69     id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
70 From: Sylvie Lelitre <sylvie.lelitre@agrolait.com>
71 Subject: {subject}
72 MIME-Version: 1.0
73 Content-Type: text/plain
74 Date: Fri, 10 Aug 2012 14:16:26 +0000
75 Message-ID: {msg_id}
76 {extra}
77
78 Please call me as soon as possible this afternoon!
79
80 --
81 Sylvie
82 """
83
84
85 class test_mail(common.TransactionCase):
86
87     def _mock_smtp_gateway(self, *args, **kwargs):
88         return True
89
90     def _mock_build_email(self, *args, **kwargs):
91         self._build_email_args = args
92         self._build_email_kwargs = kwargs
93         return self.build_email_real(*args, **kwargs)
94
95     def setUp(self):
96         super(test_mail, self).setUp()
97         self.ir_model = self.registry('ir.model')
98         self.mail_alias = self.registry('mail.alias')
99         self.mail_thread = self.registry('mail.thread')
100         self.mail_group = self.registry('mail.group')
101         self.mail_mail = self.registry('mail.mail')
102         self.mail_message = self.registry('mail.message')
103         self.mail_notification = self.registry('mail.notification')
104         self.mail_followers = self.registry('mail.followers')
105         self.res_users = self.registry('res.users')
106         self.res_partner = self.registry('res.partner')
107
108         # Install mock SMTP gateway
109         self.build_email_real = self.registry('ir.mail_server').build_email
110         self.registry('ir.mail_server').build_email = self._mock_build_email
111         self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
112
113         # groups@.. will cause the creation of new mail groups
114         self.mail_group_model_id = self.ir_model.search(self.cr, self.uid, [('model', '=', 'mail.group')])[0]
115         self.mail_alias.create(self.cr, self.uid, {'alias_name': 'groups',
116                                                    'alias_model_id': self.mail_group_model_id})
117         # create a 'pigs' group that will be used through the various tests
118         self.group_pigs_id = self.mail_group.create(self.cr, self.uid,
119             {'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
120
121     def test_00_message_process(self):
122         cr, uid = self.cr, self.uid
123         # Incoming mail creates a new mail_group "frogs"
124         self.assertEqual(self.mail_group.search(cr, uid, [('name', '=', 'frogs')]), [])
125         mail_frogs = MAIL_TEMPLATE.format(to='groups@example.com, other@gmail.com', subject='frogs', extra='')
126         self.mail_thread.message_process(cr, uid, None, mail_frogs)
127         frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'frogs')])
128         self.assertTrue(len(frog_groups) == 1)
129
130         # Previously-created group can be emailed now - it should have an implicit alias group+frogs@...
131         frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
132         group_messages = frog_group.message_ids
133         self.assertTrue(len(group_messages) == 1, 'New group should only have the original message')
134         mail_frog_news = MAIL_TEMPLATE.format(to='Friendly Frogs <group+frogs@example.com>', subject='news', extra='')
135         self.mail_thread.message_process(cr, uid, None, mail_frog_news)
136         frog_group.refresh()
137         self.assertTrue(len(frog_group.message_ids) == 2, 'Group should contain 2 messages now')
138
139         # Even with a wrong destination, a reply should end up in the correct thread
140         mail_reply = MAIL_TEMPLATE.format(to='erroneous@example.com>', subject='Re: news',
141                                           extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
142         self.mail_thread.message_process(cr, uid, None, mail_reply)
143         frog_group.refresh()
144         self.assertTrue(len(frog_group.message_ids) == 3, 'Group should contain 3 messages now')
145
146         # No model passed and no matching alias must raise
147         mail_spam = MAIL_TEMPLATE.format(to='noone@example.com', subject='spam', extra='')
148         self.assertRaises(Exception,
149                           self.mail_thread.message_process,
150                           cr, uid, None, mail_spam)
151
152         # plain text content should be wrapped and stored as html
153         test_msg_id = '<deadcafe.1337@smtp.agrolait.com>'
154         mail_text = MAIL_TEMPLATE_PLAINTEXT.format(to='groups@example.com', subject='frogs', extra='', msg_id=test_msg_id)
155         self.mail_thread.message_process(cr, uid, None, mail_text)
156         new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id','=',test_msg_id)])[0])
157         self.assertEqual(new_mail.body, '\n<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>\n',
158                          'plaintext mail incorrectly parsed')
159
160     def test_10_many2many_reference_field(self):
161         """ Tests designed for the many2many_reference field (follower_ids).
162             We will test to perform writes using the many2many commands 0, 3, 4,
163             5 and 6. """
164         cr, uid = self.cr, self.uid
165         user_admin = self.res_users.browse(cr, uid, uid)
166         group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
167
168         # Create partner Bert Poilu
169         partner_bert_id = self.res_partner.create(cr, uid, {'name': 'Bert Poilu'})
170
171         # Create 'disturbing' values in mail.followers: same res_id, other res_model; same res_model, other res_id
172         group_dummy_id = self.mail_group.create(cr, uid,
173             {'name': 'Dummy group'})
174         self.mail_followers.create(cr, uid,
175             {'res_model': 'mail.thread', 'res_id': self.group_pigs_id, 'partner_id': partner_bert_id})
176         self.mail_followers.create(cr, uid,
177             {'res_model': 'mail.group', 'res_id': group_dummy_id, 'partner_id': partner_bert_id})
178
179         # Pigs just created: should be only Admin as follower
180         follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
181         self.assertEqual(follower_ids, set([user_admin.partner_id.id]), 'Admin should be the only Pigs fan')
182
183         # Subscribe Bert through a '4' command
184         group_pigs.write({'message_follower_ids': [(4, partner_bert_id)]})
185         group_pigs.refresh()
186         follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
187         self.assertEqual(follower_ids, set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the only Pigs fans')
188
189         # Unsubscribe Bert through a '3' command
190         group_pigs.write({'message_follower_ids': [(3, partner_bert_id)]})
191         group_pigs.refresh()
192         follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
193         self.assertEqual(follower_ids, set([user_admin.partner_id.id]), 'Admin should be the only Pigs fan')
194
195         # Set followers through a '6' command
196         group_pigs.write({'message_follower_ids': [(6, 0, [partner_bert_id])]})
197         group_pigs.refresh()
198         follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
199         self.assertEqual(follower_ids, set([partner_bert_id]), 'Bert should be the only Pigs fan')
200
201         # Add a follower created on the fly through a '0' command
202         group_pigs.write({'message_follower_ids': [(0, 0, {'name': 'Patrick Fiori'})]})
203         partner_patrick_id = self.res_partner.search(cr, uid, [('name', '=', 'Patrick Fiori')])[0]
204         group_pigs.refresh()
205         follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
206         self.assertEqual(follower_ids, set([partner_bert_id, partner_patrick_id]), 'Bert and Patrick should be the only Pigs fans')
207
208         # Finally, unlink through a '5' command
209         group_pigs.write({'message_follower_ids': [(5, 0)]})
210         group_pigs.refresh()
211         follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
212         self.assertFalse(follower_ids, 'Pigs group should not have fans anymore')
213
214         # Test dummy data has not been altered
215         fol_obj_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.thread'), ('res_id', '=', self.group_pigs_id)])
216         follower_ids = set([follower.partner_id.id for follower in self.mail_followers.browse(cr, uid, fol_obj_ids)])
217         self.assertEqual(follower_ids, set([partner_bert_id]), 'Bert should be the follower of dummy mail.thread data')
218         fol_obj_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', group_dummy_id)])
219         follower_ids = set([follower.partner_id.id for follower in self.mail_followers.browse(cr, uid, fol_obj_ids)])
220         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')
221
222     def test_11_message_followers(self):
223         """ Tests designed for the subscriber API. """
224         cr, uid = self.cr, self.uid
225         user_admin = self.res_users.browse(cr, uid, uid)
226         group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
227
228         # Create user Raoul
229         user_raoul_id = self.res_users.create(cr, uid, {'name': 'Raoul Grosbedon', 'login': 'raoul'})
230         user_raoul = self.res_users.browse(cr, uid, user_raoul_id)
231
232         # Subscribe Raoul three times (niak niak) through message_subscribe_users
233         group_pigs.message_subscribe_users([user_raoul_id, user_raoul_id])
234         group_pigs.message_subscribe_users([user_raoul_id])
235         group_pigs.refresh()
236         follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
237         self.assertEqual(len(follower_ids), 2, 'There should be 2 Pigs fans')
238         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')
239
240         # Unsubscribe Raoul twice through message_unsubscribe_users
241         group_pigs.message_unsubscribe_users([user_raoul_id, user_raoul_id])
242         group_pigs.refresh()
243         follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
244         self.assertEqual(follower_ids, [user_admin.partner_id.id], 'Admin must be the only Pigs fan')
245
246     def test_20_message_post(self):
247         """ Tests designed for message_post. """
248         cr, uid = self.cr, self.uid
249         self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
250         user_admin = self.res_users.browse(cr, uid, uid)
251         group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
252
253         # 0 - Admin
254         p_a_id = user_admin.partner_id.id
255         # 1 - Bert Tartopoils, with email, should receive emails for comments and emails
256         p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
257         # 2 - Carine Poilvache, with email, should never receive emails
258         p_c_id = self.res_partner.create(cr, uid, {'name': 'Carine Poilvache', 'email': 'c@c', 'notification_email_send': 'email'})
259         # 3 - Dédé Grosbedon, without email, to test email verification; should receive emails for every message
260         p_d_id = self.res_partner.create(cr, uid, {'name': 'Dédé Grosbedon', 'notification_email_send': 'all'})
261
262         # Subscribe #1, #2
263         group_pigs.message_subscribe([p_b_id, p_c_id])
264
265         # Mail data
266         _subject = 'Pigs'
267         _mail_subject = '%s posted on %s' % (user_admin.name, group_pigs.name)
268         _body1 = 'Pigs rules'
269         _mail_body1 = 'Pigs rules\n<pre>Admin</pre>\n'
270         _mail_bodyalt1 = 'Pigs rules\nAdmin'
271         _body2 = '<html>Pigs rules</html>'
272         _mail_body2 = html_sanitize('<html>Pigs rules\n<pre>Admin</pre>\n</html>')
273         _mail_bodyalt2 = 'Pigs rules\nAdmin'
274         _attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
275
276         # CASE1: post comment, body and subject specified
277         msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body1, subject=_subject, type='comment')
278         message = self.mail_message.browse(cr, uid, msg_id)
279         sent_email = self._build_email_kwargs
280         # Test: notifications have been deleted
281         self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg_id)]), 'mail.mail notifications should have been auto-deleted!')
282         # Test: mail_message: subject is _subject, body is _body1 (no formatting done)
283         self.assertEqual(message.subject, _subject, 'mail.message subject incorrect')
284         self.assertEqual(message.body, _body1, 'mail.message body incorrect')
285         # Test: sent_email: email send by server: correct subject, body; body_alternative
286         self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
287         self.assertEqual(sent_email['body'], _mail_body1, 'sent_email body incorrect')
288         self.assertEqual(sent_email['body_alternative'], _mail_bodyalt1, 'sent_email body_alternative is incorrect')
289         # Test: mail_message: partner_ids = group followers
290         message_pids = set([partner.id for partner in message.partner_ids])
291         test_pids = set([p_a_id, p_b_id, p_c_id])
292         self.assertEqual(test_pids, message_pids, 'mail.message partners incorrect')
293         # Test: notification linked to this message = group followers = partner_ids
294         notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
295         notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
296         self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
297         # Test: sent_email: email_to should contain b@b, not c@c (pref email), not a@a (writer)
298         self.assertEqual(sent_email['email_to'], ['b@b'], 'sent_email email_to is incorrect')
299
300         # CASE2: post an email with attachments, parent_id, partner_ids
301         # TESTS: automatic subject, signature in body_html, attachments propagation
302         msg_id2 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body2, type='email',
303             partner_ids=[(6, 0, [p_d_id])], parent_id=msg_id, attachments=_attachments)
304         message = self.mail_message.browse(cr, uid, msg_id2)
305         sent_email = self._build_email_kwargs
306         self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg_id2)]), 'mail.mail notifications should have been auto-deleted!')
307
308         # Test: mail_message: subject is False, body is _body2 (no formatting done), parent_id is msg_id
309         self.assertEqual(message.subject, False, 'mail.message subject incorrect')
310         self.assertEqual(message.body, html_sanitize(_body2), 'mail.message body incorrect')
311         self.assertEqual(message.parent_id.id, msg_id, 'mail.message parent_id incorrect')
312         # Test: sent_email: email send by server: correct subject, body, body_alternative
313         self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
314         self.assertEqual(sent_email['body'], _mail_body2, 'sent_email body incorrect')
315         self.assertEqual(sent_email['body_alternative'], _mail_bodyalt2, 'sent_email body_alternative incorrect')
316         # Test: mail_message: partner_ids = group followers
317         message_pids = set([partner.id for partner in message.partner_ids])
318         test_pids = set([p_a_id, p_b_id, p_c_id, p_d_id])
319         self.assertEqual(message_pids, test_pids, 'mail.message partners incorrect')
320         # Test: notifications linked to this message = group followers = partner_ids
321         notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
322         notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
323         self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
324         # Test: sent_email: email_to should contain b@b, c@c, not a@a (writer)
325         self.assertEqual(set(sent_email['email_to']), set(['b@b', 'c@c']), 'sent_email email_to incorrect')
326         # Test: attachments
327         for attach in message.attachment_ids:
328             self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
329             self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
330             self.assertIn((attach.name, attach.datas.decode('base64')), _attachments,
331                 'mail.message attachment name / data incorrect')
332
333     def test_21_message_compose_wizard(self):
334         """ Tests designed for the mail.compose.message wizard. """
335         cr, uid = self.cr, self.uid
336         mail_compose = self.registry('mail.compose.message')
337         self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
338         user_admin = self.res_users.browse(cr, uid, uid)
339         group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
340         group_bird_id = self.mail_group.create(cr, uid, {'name': 'Bird', 'description': 'Bird resistance'})
341         group_bird = self.mail_group.browse(cr, uid, group_bird_id)
342
343         # Mail data
344         _subject = 'Pigs'
345         _body_text = 'Pigs rules'
346         _msg_reply = 'Re: Pigs'
347         _msg_body = '<pre>Pigs rules</pre>'
348         _attachments = [
349             {'name': 'First', 'datas_fname': 'first.txt', 'datas': 'My first attachment'.encode('base64')},
350             {'name': 'Second', 'datas_fname': 'second.txt', 'datas': 'My second attachment'.encode('base64')}
351             ]
352         _attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')]
353
354         # Create partners
355         # 0 - Admin
356         p_a_id = user_admin.partner_id.id
357         # 1 - Bert Tartopoils, with email, should receive emails for comments and emails
358         p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
359         # 2 - Carine Poilvache, with email, should never receive emails
360         p_c_id = self.res_partner.create(cr, uid, {'name': 'Carine Poilvache', 'email': 'c@c', 'notification_email_send': 'email'})
361         # 3 - Dédé Grosbedon, without email, to test email verification; should receive emails for every message
362         p_d_id = self.res_partner.create(cr, uid, {'name': 'Dédé Grosbedon', 'notification_email_send': 'all'})
363
364         # Subscribe #1
365         group_pigs.message_subscribe([p_b_id])
366
367         # ----------------------------------------
368         # CASE1: comment on group_pigs
369         # ----------------------------------------
370
371         # 1. Comment group_pigs with body_text and subject
372         compose_id = mail_compose.create(cr, uid,
373             {'subject': _subject, 'body_text': _body_text, 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
374             {'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id})
375         compose = mail_compose.browse(cr, uid, compose_id)
376         # Test: mail.compose.message: composition_mode, model, res_id
377         self.assertEqual(compose.composition_mode,  'comment', 'mail.compose.message incorrect composition_mode')
378         self.assertEqual(compose.model,  'mail.group', 'mail.compose.message incorrect model')
379         self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
380
381         # 2. Post the comment, get created message
382         mail_compose.send_mail(cr, uid, [compose_id])
383         group_pigs.refresh()
384         message = group_pigs.message_ids[0]
385         # Test: mail.message: subject, body inside pre
386         self.assertEqual(message.subject,  False, 'mail.message incorrect subject')
387         self.assertEqual(message.body, _msg_body, 'mail.message incorrect body')
388         # Test: mail.message: partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
389         msg_pids = [partner.id for partner in message.partner_ids]
390         test_pids = [p_a_id, p_b_id, p_c_id, p_d_id]
391         notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
392         self.assertEqual(len(notif_ids), 4, 'mail.message: too much notifications created')
393         self.assertEqual(set(msg_pids), set(test_pids), 'mail.message partner_ids incorrect')
394
395         # ----------------------------------------
396         # CASE2: reply to last comment with attachments
397         # ----------------------------------------
398
399         # 1. Update last comment subject, reply with attachments
400         message.write({'subject': _subject})
401         compose_id = mail_compose.create(cr, uid,
402             {'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]},
403             {'default_composition_mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id, 'default_parent_id': message.id})
404         compose = mail_compose.browse(cr, uid, compose_id)
405         # Test: model, res_id, parent_id, content_subtype
406         self.assertEqual(compose.model,  'mail.group', 'mail.compose.message incorrect model')
407         self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
408         self.assertEqual(compose.parent_id.id, message.id, 'mail.compose.message incorrect parent_id')
409         self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype')
410
411         # 2. Post the comment, get created message
412         mail_compose.send_mail(cr, uid, [compose_id])
413         group_pigs.refresh()
414         message = group_pigs.message_ids[0]
415         # Test: mail.message: subject as Re:.., body in html
416         self.assertEqual(message.subject, _msg_reply, 'mail.message incorrect subject')
417         self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote></div>', message.body, 'mail.message body is incorrect')
418         # Test: mail.message: attachments
419         for attach in message.attachment_ids:
420             self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
421             self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
422             self.assertIn((attach.name, attach.datas.decode('base64')), _attachments_test,
423                 'mail.message attachment name / data incorrect')
424
425         # ----------------------------------------
426         # CASE3: mass_mail on Pigs and Bird
427         # ----------------------------------------
428
429         # 1. mass_mail on pigs and bird
430         compose_id = mail_compose.create(cr, uid,
431             {'subject': _subject, 'body': '${object.description}'},
432             {'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': -1,
433                 'active_ids': [self.group_pigs_id, group_bird_id]})
434         compose = mail_compose.browse(cr, uid, compose_id)
435         # Test: content_subtype is html
436         self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
437
438         # 2. Post the comment, get created message for each group
439         mail_compose.send_mail(cr, uid, [compose_id],
440             context={'default_res_id': -1, 'active_ids': [self.group_pigs_id, group_bird_id]})
441         group_pigs.refresh()
442         group_bird.refresh()
443         message1 = group_pigs.message_ids[0]
444         message2 = group_bird.message_ids[0]
445         # Test: Pigs and Bird did receive their message
446         test_msg_ids = self.mail_message.search(cr, uid, [], limit=2)
447         self.assertIn(message1.id, test_msg_ids, 'Pigs did not receive its mass mailing message')
448         self.assertIn(message2.id, test_msg_ids, 'Bird did not receive its mass mailing message')
449         # Test: mail.message: subject, body
450         self.assertEqual(message1.subject, _subject, 'mail.message subject incorrect')
451         self.assertEqual(message1.body, group_pigs.description, 'mail.message body incorrect')
452         self.assertEqual(message2.subject, _subject, 'mail.message subject incorrect')
453         self.assertEqual(message2.body, group_bird.description, 'mail.message body incorrect')
454
455     def test_30_message_read(self):
456         """ Tests designed for message_read. """
457         # TDE NOTE: this test is not finished, as the message_read method is not fully specified.
458         # It will be updated as soon as we have fixed specs !
459         cr, uid = self.cr, self.uid
460         group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
461         def _compare_structures(struct1, struct2, n=0):
462             # print '%scompare structure' % ('\t' * n)
463             self.assertEqual(len(struct1), len(struct2), 'message_read structure number of childs incorrect')
464             for x in range(len(struct1)):
465                 # print '%s' % ('\t' * n), struct1[x]['id'], struct2[x]['id'], struct1[x].get('subject') or ''
466                 self.assertEqual(struct1[x]['id'], struct2[x]['id'], 'message_read failure %s' % struct1[x].get('subject'))
467                 _compare_structures(struct1[x]['child_ids'], struct2[x]['child_ids'], n + 1)
468             # print '%send compare' % ('\t' * n)
469
470         # ----------------------------------------
471         # CASE1: Flattening test
472         # ----------------------------------------
473
474         # Create dummy message structure
475         import copy
476         tree = [{'id': 2, 'child_ids': [
477                     {'id': 6, 'child_ids': [
478                         {'id': 8, 'child_ids': []},
479                         ]},
480                     ]},
481                 {'id': 1, 'child_ids':[
482                     {'id': 7, 'child_ids': [
483                         {'id': 9, 'child_ids': []},
484                         ]},
485                     {'id': 4, 'child_ids': [
486                         {'id': 10, 'child_ids': []},
487                         {'id': 5, 'child_ids': []},
488                         ]},
489                     {'id': 3, 'child_ids': []},
490                     ]},
491                 ]
492         # Test: completely flat
493         new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 0)
494         self.assertEqual(len(new_tree), 10, 'message_read_tree_flatten wrong in flat')
495         # Test: 1 thread level
496         tree_test = [{'id': 2, 'child_ids': [
497                         {'id': 8, 'child_ids': []}, {'id': 6, 'child_ids': []},
498                     ]},
499                     {'id': 1, 'child_ids': [
500                         {'id': 10, 'child_ids': []}, {'id': 9, 'child_ids': []},
501                         {'id': 7, 'child_ids': []}, {'id': 5, 'child_ids': []},
502                         {'id': 4, 'child_ids': []}, {'id': 3, 'child_ids': []},
503                     ]},
504                     ]
505         new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 1)
506         _compare_structures(new_tree, tree_test)
507         # Test: 2 thread levels
508         new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 2)
509         _compare_structures(new_tree, tree)
510
511         # ----------------------------------------
512         # CASE2: message_read test
513         # ----------------------------------------
514
515         # 1. Add a few messages to pigs group
516         msgid1 = group_pigs.message_post(body='1', subject='1', parent_id=False)
517         msgid2 = group_pigs.message_post(body='2', subject='1-1', parent_id=msgid1)
518         msgid3 = group_pigs.message_post(body='3', subject='1-2', parent_id=msgid1)
519         msgid4 = group_pigs.message_post(body='4', subject='2', parent_id=False)
520         msgid5 = group_pigs.message_post(body='5', subject='1-1-1', parent_id=msgid2)
521         msgid6 = group_pigs.message_post(body='6', subject='2-1', parent_id=msgid4)
522
523         # Test: read all messages flat
524         tree_test = [{'id': msgid6, 'child_ids': []}, {'id': msgid5, 'child_ids': []},
525                         {'id': msgid4, 'child_ids': []}, {'id': msgid3, 'child_ids': []},
526                         {'id': msgid2, 'child_ids': []}, {'id': msgid1, 'child_ids': []}]
527         tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=0, limit=10)
528         _compare_structures(tree, tree_test)
529         # Test: read with 1 level of thread
530         tree_test = [{'id': msgid4, 'child_ids': [{'id': msgid6, 'child_ids': []}, ]},
531                     {'id': msgid1, 'child_ids': [
532                         {'id': msgid5, 'child_ids': []}, {'id': msgid3, 'child_ids': []},
533                         {'id': msgid2, 'child_ids': []},
534                     ]},
535                     ]
536         tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=1, limit=10)
537         _compare_structures(tree, tree_test)
538         # Test: read with 2 levels of thread
539         tree_test = [{'id': msgid4, 'child_ids': [{'id': msgid6, 'child_ids': []}, ]},
540                     {'id': msgid1, 'child_ids': [
541                         {'id': msgid3, 'child_ids': []},
542                         {'id': msgid2, 'child_ids': [{'id': msgid5, 'child_ids': []}, ]},
543                     ]},
544                     ]
545         tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=2, limit=10)
546         _compare_structures(tree, tree_test)
547
548         # 2. Test expandables
549         # TDE FIXME: add those tests when expandables are specified and implemented
550
551     def test_40_needaction(self):
552         """ Tests for mail.message needaction. """
553         cr, uid = self.cr, self.uid
554         group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
555         user_admin = self.res_users.browse(cr, uid, uid)
556
557         # Demo values: check unread notification = needaction on mail.message
558         notif_ids = self.mail_notification.search(cr, uid, [
559             ('partner_id', '=', user_admin.partner_id.id),
560             ('read', '=', False)
561             ])
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')
564
565         # Post 4 message on group_pigs
566         for dummy in range(4):
567             group_pigs.message_post(body='My Body')
568
569         # Check there are 4 new needaction on mail.message
570         notif_ids = self.mail_notification.search(cr, uid, [
571             ('partner_id', '=', user_admin.partner_id.id),
572             ('read', '=', False)
573             ])
574         na_count = self.mail_message._needaction_count(cr, uid, domain=[])
575         self.assertEqual(len(notif_ids), na_count, 'unread notifications count does not match needaction count')
576
577         # Check there are 4 needaction on mail.message with particular domain
578         na_count = self.mail_message._needaction_count(cr, uid, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
579         self.assertEqual(na_count, 4, 'posted message count does not match needaction count')
580
581     def test_50_thread_parent_resolution(self):
582         """Verify parent/child relationships are correctly established when processing incoming mails"""
583         cr, uid = self.cr, self.uid
584         group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
585         msg1 = group_pigs.message_post(body='My Body', subject='1')
586         msg2 = group_pigs.message_post(body='My Body', subject='2')
587         msg1, msg2 = self.mail_message.browse(cr, uid, [msg1, msg2])
588         self.assertTrue(msg1.message_id, "New message should have a proper message_id")
589
590         # Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms
591         # 1. In-Reply-To header
592         reply_msg = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1',
593                                          extra='In-Reply-To: %s' % msg1.message_id)
594         self.mail_thread.message_process(cr, uid, None, reply_msg)
595         # 2. References header
596         reply_msg2 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: Re: 1',
597                                          extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id)
598         self.mail_thread.message_process(cr, uid, None, reply_msg2)
599         # 3. Subject contains [<ID>] + model passed to message+process -> only attached to group, not to mail
600         reply_msg3 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com',
601                                           extra='', subject='Re: [%s] 1' % self.group_pigs_id)
602         self.mail_thread.message_process(cr, uid, 'mail.group', reply_msg3)
603         group_pigs.refresh()
604         msg1.refresh()
605         self.assertEqual(5, len(group_pigs.message_ids), 'group should contain 5 messages')
606         self.assertEqual(2, len(msg1.child_ids), 'msg1 should have 2 children now')