diff --git a/addons/mail/tests/test_mail_channel.py b/addons/mail/tests/test_mail_channel.py index b358a15d808..a26b8da1d7c 100644 --- a/addons/mail/tests/test_mail_channel.py +++ b/addons/mail/tests/test_mail_channel.py @@ -387,6 +387,101 @@ class TestChannelModeration(MailCommon): self.assertEqual(channel.channel_last_seen_partner_ids.partner_id, self.partner_employee | self.partner_root) self.assertEqual(channel.moderator_ids, self.user_employee) + @mute_logger('odoo.models.unlink') + def test_message_moderate_ok(self): + msg_c1_admin1 = self._add_messages(self.channel, 'Body11', author=self.partner_admin, moderation_status='pending_moderation') + msg_c1_admin2 = self._add_messages(self.channel, 'Body12', author=self.partner_admin, moderation_status='pending_moderation') + msg_c1_emplo2 = self._add_messages(self.channel, 'Body21', author=self.partner_employee_2, moderation_status='pending_moderation') + + self._reset_bus() + self.assertFalse(msg_c1_admin1.channel_ids | msg_c1_admin2.channel_ids | msg_c1_emplo2.channel_ids) + + # accept + msg_c1_admin1.with_user(self.user_employee)._moderate('accept') + self.assertEqual(msg_c1_admin1.channel_ids, self.channel) + self.assertEqual(msg_c1_admin1.moderation_status, 'accepted') + self.assertEqual(msg_c1_admin2.moderation_status, 'pending_moderation') + self.assertBusNotifications([(self.cr.dbname, 'mail.channel', self.channel.id)]) + + # allow + self._reset_bus() + (msg_c1_admin1 | msg_c1_emplo2).with_user(self.user_employee)._moderate('allow') + self.assertEqual(msg_c1_admin1.channel_ids, self.channel) + self.assertEqual(msg_c1_admin2.channel_ids, self.channel) + self.assertEqual(msg_c1_emplo2.channel_ids, self.channel) + self.assertEqual(msg_c1_admin1.moderation_status, 'accepted') + self.assertEqual(msg_c1_admin2.moderation_status, 'accepted') + self.assertEqual(msg_c1_emplo2.moderation_status, 'accepted') + self.assertBusNotifications([ + (self.cr.dbname, 'mail.channel', self.channel.id), + (self.cr.dbname, 'mail.channel', self.channel.id)]) + + @mute_logger('odoo.models.unlink') + def test_message_moderate_reject(self): + msg_c1_admin1 = self._add_messages(self.channel, '

Body11

', author=self.partner_admin, moderation_status='pending_moderation') + msg_c1_admin2 = self._add_messages(self.channel, '

Body12

', author=self.partner_admin, moderation_status='pending_moderation') + msg_c1_emplo2 = self._add_messages(self.channel, '

Body21

', author=self.partner_employee_2, moderation_status='pending_moderation') + msg_c1_portal = self._add_messages(self.channel, '

Body12

', author=self.partner_portal, moderation_status='pending_moderation') + id2, id4 = msg_c1_admin2.id, msg_c1_portal.id # save ids because unlink will discard them + + self.assertFalse(msg_c1_admin1.channel_ids | msg_c1_admin2.channel_ids | msg_c1_emplo2.channel_ids | msg_c1_portal.channel_ids) + + # test reject: should also send a rejection email + with self.mock_mail_gateway(): + (msg_c1_admin1 | msg_c1_emplo2).with_user(self.user_employee)._moderate('reject', title='RejectTitle', comment='RejectComment') + self.assertEqual(len(self._new_mails), 2) + + self.assertMailMailWEmails([self.partner_admin.email_formatted, self.partner_employee_2.email_formatted], 'outgoing', 'RejectComment', + fields_values={'subject': 'RejectTitle', 'author_id': self.partner_employee}) + self.assertTrue(all('RejectComment' in body for body in self._new_mails.mapped('body_html'))) + self.assertTrue(all('Body' in body for body in self._new_mails.mapped('body_html'))) + + # test discard: silently remove + self._reset_bus() + (msg_c1_admin2 + msg_c1_portal).with_user(self.user_employee)._moderate_discard() + + # check all generated bus notifications + self.assertBusNotifications( + [(self.cr.dbname, 'res.partner', self.partner_admin.id), + (self.cr.dbname, 'res.partner', self.partner_employee.id), + (self.cr.dbname, 'res.partner', self.partner_portal.id)], + [{'type': 'deletion', 'message_ids': [id2]}, # author of 1 message + {'type': 'deletion', 'message_ids': [id2, id4]}, # moderator + {'type': 'deletion', 'message_ids': [id4]}] # author of 1 message + ) + + @mute_logger('odoo.models.unlink') + def test_message_notify_moderators(self): + # create pending messages in another channel to have two notification to push + msg_c1_admin1 = self._add_messages(self.channel, '

Body11

', author=self.partner_admin, moderation_status='pending_moderation') + msg_c1_admin2 = self._add_messages(self.channel, '

Body12

', author=self.partner_admin, moderation_status='pending_moderation') + msg_c1_portal = self._add_messages(self.channel, '

Body21

', author=self.partner_employee_2, moderation_status='pending_moderation') + + channel_2 = self.env['mail.channel'].create({ + 'name': 'Moderation_1', + 'email_send': True, + 'moderation': True, + 'channel_partner_ids': [(4, self.partner_admin.id)], + 'moderator_ids': [(4, self.user_admin.id)], + }) + msg_c2_portal = self._add_messages(channel_2, 'Body31', author=self.partner_employee_2, moderation_status='pending_moderation') + + # one notification for each moderator: employee (channel1), admin (channel2) + with self.assertPostNotifications([{ + 'content': 'Hello %s' % self.partner_employee.name, + 'message_type': 'user_notification', 'subtype': 'mail.mt_note', + 'notif': [{ + 'partner': self.partner_employee, + 'type': 'inbox'}] + }, { + 'content': 'Hello %s' % self.partner_admin.name, + 'message_type': 'user_notification', 'subtype': 'mail.mt_note', + 'notif': [{ + 'partner': self.partner_admin, + 'type': 'inbox'}] + }]): + self.env['mail.message']._notify_moderators() + @users('employee') def test_moderation_fields(self): channel = self.env['mail.channel'].browse(self.channel.ids) diff --git a/addons/test_mail/tests/test_mail_message.py b/addons/test_mail/tests/test_mail_message.py index 256921dd244..b24c552f855 100644 --- a/addons/test_mail/tests/test_mail_message.py +++ b/addons/test_mail/tests/test_mail_message.py @@ -378,180 +378,3 @@ class TestMessageAccess(TestMailCommon): self.message.write({'model': 'mail.channel', 'res_id': self.group_public.id, 'moderation_status': 'pending_moderation'}) with self.assertRaises(AccessError): self.message.with_user(self.user_employee).write({'moderation_status': 'accepted'}) - - @mute_logger('openerp.addons.mail.models.mail_mail') - def test_mark_all_as_read(self): - self.user_employee.notification_type = 'inbox' - emp_partner = self.user_employee.partner_id.with_user(self.user_employee) - - group_private = self.env['mail.channel'].with_context({ - 'mail_create_nolog': True, - 'mail_create_nosubscribe': True, - 'mail_channel_noautofollow': True, - }).create({ - 'name': 'Private', - 'description': 'Private James R.', - 'public': 'private', - 'alias_name': 'private', - 'alias_contact': 'followers'} - ).with_context({'mail_create_nosubscribe': False}) - - # mark all as read clear needactions - msg1 = group_private.message_post(body='Test', message_type='comment', subtype_xmlid='mail.mt_comment', partner_ids=[emp_partner.id]) - self._reset_bus() - emp_partner.env['mail.message'].mark_all_as_read(domain=[]) - self.assertBusNotifications([(self.cr.dbname, 'res.partner', emp_partner.id)], [{ 'type': 'mark_as_read', 'message_ids': [msg1.id] }]) - na_count = emp_partner.get_needaction_count() - self.assertEqual(na_count, 0, "mark all as read should conclude all needactions") - - # mark all as read also clear inaccessible needactions - msg2 = group_private.message_post(body='Zest', message_type='comment', subtype_xmlid='mail.mt_comment', partner_ids=[emp_partner.id]) - needaction_accessible = len(emp_partner.env['mail.message'].search([['needaction', '=', True]])) - self.assertEqual(needaction_accessible, 1, "a new message to a partner is readable to that partner") - - msg2.sudo().partner_ids = self.env['res.partner'] - emp_partner.env['mail.message'].search([['needaction', '=', True]]) - needaction_length = len(emp_partner.env['mail.message'].search([['needaction', '=', True]])) - self.assertEqual(needaction_length, 1, "message should still be readable when notified") - - na_count = emp_partner.get_needaction_count() - self.assertEqual(na_count, 1, "message not accessible is currently still counted") - - self._reset_bus() - emp_partner.env['mail.message'].mark_all_as_read(domain=[]) - self.assertBusNotifications([(self.cr.dbname, 'res.partner', emp_partner.id)], [{ 'type': 'mark_as_read', 'message_ids': [msg2.id] }]) - na_count = emp_partner.get_needaction_count() - self.assertEqual(na_count, 0, "mark all read should conclude all needactions even inacessible ones") - - @mute_logger('openerp.addons.mail.models.mail_mail') - def test_mark_all_as_read_share(self): - self.user_portal.notification_type = 'inbox' - portal_partner = self.user_portal.partner_id.with_user(self.user_portal) - - # mark all as read clear needactions - self.group_pigs.message_post(body='Test', message_type='comment', subtype_xmlid='mail.mt_comment', partner_ids=[portal_partner.id]) - portal_partner.env['mail.message'].mark_all_as_read(domain=[]) - na_count = portal_partner.get_needaction_count() - self.assertEqual(na_count, 0, "mark all as read should conclude all needactions") - - # mark all as read also clear inaccessible needactions - new_msg = self.group_pigs.message_post(body='Zest', message_type='comment', subtype_xmlid='mail.mt_comment', partner_ids=[portal_partner.id]) - needaction_accessible = len(portal_partner.env['mail.message'].search([['needaction', '=', True]])) - self.assertEqual(needaction_accessible, 1, "a new message to a partner is readable to that partner") - - new_msg.sudo().partner_ids = self.env['res.partner'] - needaction_length = len(portal_partner.env['mail.message'].search([['needaction', '=', True]])) - self.assertEqual(needaction_length, 1, "message should still be readable when notified") - - na_count = portal_partner.get_needaction_count() - self.assertEqual(na_count, 1, "message not accessible is currently still counted") - - portal_partner.env['mail.message'].mark_all_as_read(domain=[]) - na_count = portal_partner.get_needaction_count() - self.assertEqual(na_count, 0, "mark all read should conclude all needactions even inacessible ones") - - -@tagged('moderation') -class TestMessageModeration(TestMailCommon): - - @classmethod - def setUpClass(cls): - super(TestMessageModeration, cls).setUpClass() - - cls.channel_1 = cls.env['mail.channel'].create({ - 'name': 'Moderation_1', - 'email_send': True, - 'moderation': True - }) - cls.user_employee.write({'moderation_channel_ids': [(6, 0, [cls.channel_1.id])]}) - cls.user_portal = cls._create_portal_user() - - # A pending moderation message needs to have field channel_ids empty. Moderators - # need to be able to notify a pending moderation message (in a channel they moderate). - cls.msg_c1_admin1 = cls._add_messages(cls.channel_1, 'Body11', author=cls.partner_admin, moderation_status='pending_moderation') - cls.msg_c1_admin2 = cls._add_messages(cls.channel_1, 'Body12', author=cls.partner_admin, moderation_status='pending_moderation') - cls.msg_c1_portal = cls._add_messages(cls.channel_1, 'Body21', author=cls.partner_portal, moderation_status='pending_moderation') - - @mute_logger('odoo.models.unlink') - def test_moderate_accept(self): - self._reset_bus() - self.assertFalse(self.msg_c1_admin1.channel_ids | self.msg_c1_admin2.channel_ids | self.msg_c1_portal.channel_ids) - - self.msg_c1_admin1.with_user(self.user_employee)._moderate('accept') - self.assertEqual(self.msg_c1_admin1.channel_ids, self.channel_1) - self.assertEqual(self.msg_c1_admin1.moderation_status, 'accepted') - self.assertEqual(self.msg_c1_admin2.moderation_status, 'pending_moderation') - self.assertBusNotifications([(self.cr.dbname, 'mail.channel', self.channel_1.id)]) - - @mute_logger('odoo.models.unlink') - def test_moderate_allow(self): - self._reset_bus() - - self.msg_c1_admin1.with_user(self.user_employee)._moderate('allow') - self.assertEqual(self.msg_c1_admin1.channel_ids, self.channel_1) - self.assertEqual(self.msg_c1_admin2.channel_ids, self.channel_1) - self.assertEqual(self.msg_c1_admin1.moderation_status, 'accepted') - self.assertEqual(self.msg_c1_admin2.moderation_status, 'accepted') - self.assertBusNotifications([ - (self.cr.dbname, 'mail.channel', self.channel_1.id), - (self.cr.dbname, 'mail.channel', self.channel_1.id)]) - - @mute_logger('odoo.models.unlink') - def test_moderate_reject(self): - with self.mock_mail_gateway(): - (self.msg_c1_admin1 | self.msg_c1_portal).with_user(self.user_employee)._moderate_send_reject_email('Title', 'Message to author') - self.assertEqual(len(self._new_mails), 2) - for mail in self._new_mails: - self.assertEqual(mail.author_id, self.partner_employee) - self.assertEqual(mail.subject, 'Title') - self.assertEqual(mail.state, 'outgoing') - self.assertEqual( - set(self._new_mails.mapped('email_to')), - set([self.msg_c1_admin1.email_from, self.msg_c1_portal.email_from]) - ) - self.assertEqual( - set(self._new_mails.mapped('body_html')), - set(['
Message to author
\n%s\n' % self.msg_c1_admin1.body, '
Message to author
\n%s\n' % self.msg_c1_portal.body]) - ) # TDE note: \n are added by append content to html, because why not - - @mute_logger('odoo.models.unlink') - def test_moderate_discard(self): - self._reset_bus() - id1, id2, id3 = self.msg_c1_admin1.id, self.msg_c1_admin2.id, self.msg_c1_portal.id # save ids because unlink will discard them - (self.msg_c1_admin1 | self.msg_c1_admin2 | self.msg_c1_portal).with_user(self.user_employee)._moderate_discard() - - self.assertBusNotifications( - [(self.cr.dbname, 'res.partner', self.partner_admin.id), - (self.cr.dbname, 'res.partner', self.partner_employee.id), - (self.cr.dbname, 'res.partner', self.partner_portal.id)], - [{'type': 'deletion', 'message_ids': [id1, id2]}, # author of 2 messages - {'type': 'deletion', 'message_ids': [id1, id2, id3]}, # moderator - {'type': 'deletion', 'message_ids': [id3]}] # author of 1 message - ) - - @mute_logger('odoo.models.unlink') - def test_notify_moderators(self): - # create pending messages in another channel to have two notification to push - channel_2 = self.env['mail.channel'].create({ - 'name': 'Moderation_1', - 'email_send': True, - 'moderation': True - }) - self.user_admin.write({'moderation_channel_ids': [(6, 0, [channel_2.id])]}) - self.msg_c2_portal = self._add_messages(channel_2, 'Body31', author=self.partner_portal, moderation_status='pending_moderation') - - # one notification for each moderator: employee (channel1), admin (channel2) - with self.assertPostNotifications([{ - 'content': 'Hello %s' % self.partner_employee.name, - 'message_type': 'user_notification', 'subtype': 'mail.mt_note', - 'notif': [{ - 'partner': self.partner_employee, - 'type': 'inbox'}] - }, { - 'content': 'Hello %s' % self.partner_admin.name, - 'message_type': 'user_notification', 'subtype': 'mail.mt_note', - 'notif': [{ - 'partner': self.partner_admin, - 'type': 'inbox'}] - }]): - self.env['mail.message']._notify_moderators() diff --git a/addons/test_mail/tests/test_mail_thread_internals.py b/addons/test_mail/tests/test_mail_thread_internals.py index afdf252210c..307049cdaff 100644 --- a/addons/test_mail/tests/test_mail_thread_internals.py +++ b/addons/test_mail/tests/test_mail_thread_internals.py @@ -1,8 +1,12 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from unittest.mock import patch +from unittest.mock import DEFAULT from werkzeug.urls import url_parse, url_decode +from odoo import exceptions +from odoo.addons.test_mail.models.test_mail_models import MailTestSimple from odoo.addons.test_mail.tests.common import TestMailCommon, TestRecipients from odoo.tests.common import tagged, HttpCase from odoo.tools import mute_logger @@ -107,7 +111,54 @@ class TestDiscuss(TestMailCommon, TestRecipients): @classmethod def setUpClass(cls): super(TestDiscuss, cls).setUpClass() - cls.test_record = cls.env['mail.test.simple'].with_context(cls._test_context).create({'name': 'Test', 'email_from': 'ignasse@example.com'}) + cls.test_record = cls.env['mail.test.simple'].with_context(cls._test_context).create({ + 'name': 'Test', + 'email_from': 'ignasse@example.com' + }) + + @mute_logger('openerp.addons.mail.models.mail_mail') + def test_mark_all_as_read(self): + def _employee_crash(*args, **kwargs): + """ If employee is test employee, consider he has no access on document """ + recordset = args[0] + if recordset.env.uid == self.user_employee.id and not recordset.env.su: + if kwargs.get('raise_exception', True): + raise exceptions.AccessError('Hop hop hop Ernest, please step back.') + return False + return DEFAULT + + with patch.object(MailTestSimple, 'check_access_rights', autospec=True, side_effect=_employee_crash): + with self.assertRaises(exceptions.AccessError): + self.env['mail.test.simple'].with_user(self.user_employee).browse(self.test_record.ids).read(['name']) + + employee_partner = self.env['res.partner'].with_user(self.user_employee).browse(self.partner_employee.ids) + + # mark all as read clear needactions + msg1 = self.test_record.message_post(body='Test', message_type='comment', subtype_xmlid='mail.mt_comment', partner_ids=[employee_partner.id]) + self._reset_bus() + employee_partner.env['mail.message'].mark_all_as_read(domain=[]) + self.assertBusNotifications([(self.cr.dbname, 'res.partner', employee_partner.id)], [{ 'type': 'mark_as_read', 'message_ids': [msg1.id] }]) + na_count = employee_partner.get_needaction_count() + self.assertEqual(na_count, 0, "mark all as read should conclude all needactions") + + # mark all as read also clear inaccessible needactions + msg2 = self.test_record.message_post(body='Zest', message_type='comment', subtype_xmlid='mail.mt_comment', partner_ids=[employee_partner.id]) + needaction_accessible = len(employee_partner.env['mail.message'].search([['needaction', '=', True]])) + self.assertEqual(needaction_accessible, 1, "a new message to a partner is readable to that partner") + + msg2.sudo().partner_ids = self.env['res.partner'] + employee_partner.env['mail.message'].search([['needaction', '=', True]]) + needaction_length = len(employee_partner.env['mail.message'].search([['needaction', '=', True]])) + self.assertEqual(needaction_length, 1, "message should still be readable when notified") + + na_count = employee_partner.get_needaction_count() + self.assertEqual(na_count, 1, "message not accessible is currently still counted") + + self._reset_bus() + employee_partner.env['mail.message'].mark_all_as_read(domain=[]) + self.assertBusNotifications([(self.cr.dbname, 'res.partner', employee_partner.id)], [{ 'type': 'mark_as_read', 'message_ids': [msg2.id] }]) + na_count = employee_partner.get_needaction_count() + self.assertEqual(na_count, 0, "mark all read should conclude all needactions even inacessible ones") def test_set_message_done_user(self): with self.assertSinglePostNotifications([{'partner': self.partner_employee, 'type': 'inbox'}], message_info={'content': 'Test'}):