summaryrefslogtreecommitdiff
path: root/addons/test_mail/tests
diff options
context:
space:
mode:
authorMathieu Duckaerts-Antoine <dam@odoo.com>2018-05-24 15:27:28 +0200
committerThibault Delavallée <tde@odoo.com>2018-06-06 16:01:33 +0200
commit299ebb2cdf63de2e3967da0d2f3e7effd239812b (patch)
treef2681c0fc1e283acdfd5942392db8f17ddc7801d /addons/test_mail/tests
parenteeab18d753db21bca0636836c40df91268549390 (diff)
[IMP] mail: add moderation on channels
Purpose of this commit is to allow moderation on incoming messages in discussion channels. On some channels on which moderation is required messages should be in a pending moderation stage. Moderators can accept or refuse messages as well as always allow or ban messages coming from a given set of emails. Channels now have an option to be moderated. Moderators can be added on channels. They have access to a specific UI in Discuss to see and take action on messages waiting for moderation. Concerning mail.thread message that are pending moderation are not notified. It means nobody receives a notification about them. Moderation process calls the notification once the message is validated. Various features included in this commit : * a model is added to store the decision about emails, allow or ban; * access rights are updated so that only moderators can modify moderation fields on message; * specific bus notifications are send to moderated people as well as to moderators on incoming emails as well as when a decision is taken; * options are added on channels to send explanations to moderated emails; * options are added on channels to write and send guidelines explaining why and how moderation is performed; * a reminder is send daily to moderators with remaining messages to moderate; * discuss UI is adapted and a new channel is added below Inbox and Starred giving access to moderation tools; * chanenl UI is adapted allowing to moderate directly inside channels; This commit is linked to task ID 29521. Closes #21921.
Diffstat (limited to 'addons/test_mail/tests')
-rw-r--r--addons/test_mail/tests/__init__.py3
-rw-r--r--addons/test_mail/tests/common.py58
-rw-r--r--addons/test_mail/tests/test_mail_channel.py104
-rw-r--r--addons/test_mail/tests/test_mail_message.py105
-rw-r--r--addons/test_mail/tests/test_res_users.py34
5 files changed, 299 insertions, 5 deletions
diff --git a/addons/test_mail/tests/__init__.py b/addons/test_mail/tests/__init__.py
index dc9978773c4..411d73d5694 100644
--- a/addons/test_mail/tests/__init__.py
+++ b/addons/test_mail/tests/__init__.py
@@ -13,4 +13,5 @@ from . import test_invite
from . import test_ir_actions
from . import test_update_notification
from . import test_discuss
-from . import test_performance \ No newline at end of file
+from . import test_performance
+from . import test_res_users \ No newline at end of file
diff --git a/addons/test_mail/tests/common.py b/addons/test_mail/tests/common.py
index e536e15fa70..b526dce2906 100644
--- a/addons/test_mail/tests/common.py
+++ b/addons/test_mail/tests/common.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+from json import dumps, loads
import json
@@ -7,7 +8,7 @@ from email.utils import formataddr
from odoo import api
from odoo.addons.bus.models.bus import json_dump
-from odoo.tests import common
+from odoo.tests import common, tagged
class BaseFunctionalTest(common.SavepointCase):
@@ -244,6 +245,7 @@ class MockEmails(common.SingleTransactionCase):
super(MockEmails, cls).tearDownClass()
def _init_mock_build_email(self):
+ self.env['mail.mail'].search([]).unlink()
self._mails_args[:] = []
self._mails[:] = []
@@ -260,3 +262,57 @@ class MockEmails(common.SingleTransactionCase):
mail = self.format(template, to=to, subject=subject, cc=cc, extra=extra, email_from=email_from, msg_id=msg_id)
self.env['mail.thread'].with_context(mail_channel_noautofollow=True).message_process(model, mail)
return self.env[target_model].search([(target_field, '=', subject)])
+
+
+@tagged('moderation')
+class Moderation(MockEmails, BaseFunctionalTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(Moderation, cls).setUpClass()
+ Channel = cls.env['mail.channel']
+ Users = cls.env['res.users'].with_context(cls._quick_create_user_ctx)
+
+ cls.channel_moderation_1 = Channel.create({
+ 'name': 'Moderation_1',
+ 'email_send': True,
+ 'moderation': True
+ })
+ cls.channel_1 = cls.channel_moderation_1
+ cls.channel_moderation_2 = Channel.create({
+ 'name': 'Moderation_2',
+ 'email_send': True,
+ 'moderation': True
+ })
+ cls.channel_2 = cls.channel_moderation_2
+
+ cls.user_employee.write({'moderation_channel_ids': [(6, 0, [cls.channel_1.id])]})
+
+ cls.user_employee_2 = Users.create({
+ 'name': 'Roboute',
+ 'login': 'roboute',
+ 'email': 'roboute@guilliman.com',
+ 'groups_id': [(6, 0, [cls.env.ref('base.group_user').id])],
+ 'moderation_channel_ids': [(6, 0, [cls.channel_2.id])]
+ })
+ cls.partner_employee_2 = cls.user_employee_2.partner_id
+
+ cls.channel_moderation_1.write({'channel_last_seen_partner_ids': [(0, 0, {'partner_id': cls.partner_employee.id})]})
+ cls.channel_moderation_2.write({'channel_last_seen_partner_ids': [(0, 0, {'partner_id': cls.partner_employee_2.id})]})
+
+ def _create_new_message(self, channel_id, status='pending_moderation', author=None, body='', message_type="email"):
+ author = author if author else self.env.user.partner_id
+ message = self.env['mail.message'].create({
+ 'model': 'mail.channel',
+ 'res_id': channel_id,
+ 'message_type': 'email',
+ 'body': body,
+ 'moderation_status': status,
+ 'author_id': author.id,
+ 'email_from': formataddr((author.name, author.email)),
+ 'subtype_id': self.env['mail.message.subtype'].search([('name', '=', 'Discussions')]).id
+ })
+ return message
+
+ def _clear_bus(self):
+ self.env['bus.bus'].search([]).unlink()
diff --git a/addons/test_mail/tests/test_mail_channel.py b/addons/test_mail/tests/test_mail_channel.py
index 73fa55ebbcc..395fbd7b318 100644
--- a/addons/test_mail/tests/test_mail_channel.py
+++ b/addons/test_mail/tests/test_mail_channel.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-
from email.utils import formataddr
+from odoo.tests import tagged
from odoo.addons.test_mail.tests import common
-from odoo.exceptions import AccessError, except_orm
+from odoo.exceptions import AccessError, except_orm, ValidationError, UserError
from odoo.tools import mute_logger
@@ -195,3 +195,103 @@ class TestChannelFeatures(common.BaseFunctionalTest, common.MockEmails):
self.assertIn(
email['email_to'][0],
[formataddr((self.user_employee.name, self.user_employee.email)), formataddr((self.test_partner.name, self.test_partner.email))])
+
+
+@tagged('moderation')
+class TestChannelModeration(common.Moderation):
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestChannelModeration, cls).setUpClass()
+
+ def test_moderator_consistency(self):
+ with self.assertRaises(ValidationError):
+ self.channel_1.write({'moderator_ids': [(4, self.user_employee_2.id)]})
+
+ self.channel_1.write({'channel_partner_ids': [(4, self.partner_employee_2.id)]})
+ with self.assertRaises(ValidationError):
+ self.user_employee_2.write({'email': False})
+ self.channel_1.write({'moderator_ids': [(4, self.user_employee_2.id)]})
+
+ def test_channel_moderation_parameters(self):
+ with self.assertRaises(ValidationError):
+ self.channel_1.write({'email_send': False})
+
+ with self.assertRaises(ValidationError):
+ self.channel_1.write({'moderator_ids': [(5, 0)]})
+
+ def test_moderation_count(self):
+ self.assertEqual(self.channel_1.moderation_count, 0)
+ self.channel_1.write({'moderation_ids': [
+ (0, 0, {'email': 'test0@example.com', 'status': 'allow'}),
+ (0, 0, {'email': 'test1@example.com', 'status': 'ban'})
+ ]})
+ self.assertEqual(self.channel_1.moderation_count, 2)
+
+ @mute_logger('odoo.addons.mail.models.mail_channel', 'odoo.models.unlink')
+ def test_send_guidelines(self):
+ self.channel_1.write({'channel_partner_ids': [(4, self.partner_employee_2.id), (4, self.partner_admin.id)]})
+ self.channel_1._update_moderation_email([self.partner_admin.email], 'ban')
+ self._init_mock_build_email()
+ self.channel_1.sudo(self.user_employee).send_guidelines()
+ self.env['mail.mail'].process_email_queue()
+ self.assertEmails(False, self.partner_employee | self.partner_employee_2, email_from=self.env.user.company_id.catchall or self.env.user.company_id.email)
+
+ def test_send_guidelines_crash(self):
+ with self.assertRaises(UserError):
+ self.channel_1.sudo(self.user_employee_2).send_guidelines()
+
+ def test_update_moderation_email(self):
+ self.channel_1.write({'moderation_ids': [
+ (0, 0, {'email': 'test0@example.com', 'status': 'allow'}),
+ (0, 0, {'email': 'test1@example.com', 'status': 'ban'})
+ ]})
+ self.channel_1._update_moderation_email(['test0@example.com', 'test3@example.com'], 'ban')
+ self.assertEqual(len(self.channel_1.moderation_ids), 3)
+ self.assertTrue(all(status == 'ban' for status in self.channel_1.moderation_ids.mapped('status')))
+
+ def test_moderation_reset(self):
+ self._create_new_message(self.channel_1.id)
+ self._create_new_message(self.channel_1.id, status='accepted')
+ self._create_new_message(self.channel_2.id)
+ self.channel_1.write({'moderation': False})
+ self.assertEqual(self.env['mail.message'].search_count([
+ ('moderation_status', '=', 'pending_moderation'),
+ ('model', '=', 'mail.channel'),
+ ('res_id', '=', self.channel_1.id)
+ ]), 0)
+ self.assertEqual(self.env['mail.message'].search_count([
+ ('moderation_status', '=', 'pending_moderation'),
+ ('model', '=', 'mail.channel'),
+ ('res_id', '=', self.channel_2.id)
+ ]), 1)
+ self.channel_2.write({'moderation': False})
+ self.assertEqual(self.env['mail.message'].search_count([
+ ('moderation_status', '=', 'pending_moderation'),
+ ('model', '=', 'mail.channel'),
+ ('res_id', '=', self.channel_2.id)
+ ]), 0)
+
+ @mute_logger('odoo.models.unlink')
+ def test_message_post(self):
+ email1 = 'test0@example.com'
+ email2 = 'test1@example.com'
+
+ self.channel_1._update_moderation_email([email1], 'ban')
+ self.channel_1._update_moderation_email([email2], 'allow')
+
+ msg_admin = self.channel_1.message_post(message_type='email', subtype='mt_comment', author_id=self.partner_admin.id)
+ msg_moderator = self.channel_1.message_post(message_type='comment', subtype='mt_comment', author_id=self.partner_employee.id)
+ msg_email1 = self.channel_1.message_post(message_type='comment', subtype='mt_comment', email_from=formataddr(("MyName", email1)))
+ msg_email2 = self.channel_1.message_post(message_type='email', subtype='mt_comment', email_from=email2)
+ msg_notif = self.channel_1.message_post()
+
+ messages = self.env['mail.message'].search([('model', '=', 'mail.channel'), ('res_id', '=', self.channel_1.id)])
+ pending_messages = messages.filtered(lambda m: m.moderation_status == 'pending_moderation')
+ accepted_messages = messages.filtered(lambda m: m.moderation_status == 'accepted')
+
+ self.assertFalse(msg_email1)
+ self.assertEqual(msg_admin, pending_messages)
+ self.assertEqual(accepted_messages, msg_moderator | msg_email2 | msg_notif)
+ self.assertFalse(msg_admin.channel_ids)
+ self.assertEqual(msg_email2.channel_ids, self.channel_1)
diff --git a/addons/test_mail/tests/test_mail_message.py b/addons/test_mail/tests/test_mail_message.py
index 5d747e0a207..94540272b4e 100644
--- a/addons/test_mail/tests/test_mail_message.py
+++ b/addons/test_mail/tests/test_mail_message.py
@@ -5,6 +5,7 @@ import base64
from odoo.addons.test_mail.tests import common
from odoo.exceptions import AccessError, except_orm
from odoo.tools import mute_logger
+from odoo.tests import tagged
class TestMessageValues(common.BaseFunctionalTest, common.MockEmails):
@@ -213,6 +214,10 @@ class TestMessageAccess(common.BaseFunctionalTest, common.MockEmails):
messages = self.env['mail.message'].sudo(self.user_portal).search([('subject', 'like', '_ZTest')])
self.assertEqual(messages, msg4 | msg5)
+ # --------------------------------------------------
+ # READ
+ # --------------------------------------------------
+
@mute_logger('odoo.addons.base.models.ir_model', 'odoo.models')
def test_mail_message_access_read_crash(self):
# TODO: Change the except_orm to Warning ( Because here it's call check_access_rule
@@ -251,6 +256,16 @@ class TestMessageAccess(common.BaseFunctionalTest, common.MockEmails):
# Test: Bert reads the message, ok because linked to a doc he is allowed to read
self.message.sudo(self.user_employee).read()
+ def test_mail_message_access_read_crash_moderation(self):
+ # with self.assertRaises(AccessError):
+ self.message.write({'model': 'mail.channel', 'res_id': self.group_public.id, 'moderation_status': 'pending_moderation'})
+ # Test: Bert reads the message, ok because linked to a doc he is allowed to read
+ self.message.sudo(self.user_employee).read()
+
+ # --------------------------------------------------
+ # CREATE
+ # --------------------------------------------------
+
@mute_logger('odoo.addons.base.models.ir_model')
def test_mail_message_access_create_crash_public(self):
# Do: Bert creates a message on Pigs -> ko, no creation rights
@@ -284,6 +299,25 @@ class TestMessageAccess(common.BaseFunctionalTest, common.MockEmails):
self.message.write({'partner_ids': [(4, self.user_employee.partner_id.id)]})
self.env['mail.message'].sudo(self.user_employee).create({'model': 'mail.channel', 'res_id': self.group_private.id, 'body': 'Test', 'parent_id': self.message.id})
+ # --------------------------------------------------
+ # WRITE
+ # --------------------------------------------------
+
+ def test_mail_message_access_write_moderation(self):
+ """ Only moderators can modify pending messages """
+ self.group_public.write({
+ 'email_send': True,
+ 'moderation': True,
+ 'channel_partner_ids': [(4, self.partner_employee.id)],
+ 'moderator_ids': [(4, self.user_employee.id)],
+ })
+ self.message.write({'model': 'mail.channel', 'res_id': self.group_public.id, 'moderation_status': 'pending_moderation'})
+ self.message.sudo(self.user_employee).write({'moderation_status': 'accepted'})
+
+ def test_mail_message_access_write_crash_moderation(self):
+ self.message.write({'model': 'mail.channel', 'res_id': self.group_public.id, 'moderation_status': 'pending_moderation'})
+ with self.assertRaises(AccessError):
+ self.message.sudo(self.user_employee).write({'moderation_status': 'accepted'})
@mute_logger('openerp.addons.mail.models.mail_mail')
def test_mark_all_as_read(self):
@@ -350,4 +384,73 @@ class TestMessageAccess(common.BaseFunctionalTest, common.MockEmails):
portal_partner.env['mail.message'].mark_all_as_read(channel_ids=[], domain=[])
na_count = portal_partner.get_needaction_count()
- self.assertEqual(na_count, 0, "mark all read should conclude all needactions even inacessible ones") \ No newline at end of file
+ self.assertEqual(na_count, 0, "mark all read should conclude all needactions even inacessible ones")
+
+
+@tagged('moderation')
+class TestMessageModeration(common.Moderation):
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestMessageModeration, cls).setUpClass()
+
+ cls.msg_admin_pending_c1 = cls._create_new_message(cls, cls.channel_1.id, status='pending_moderation', author=cls.partner_admin)
+ cls.msg_admin_pending_c1_2 = cls._create_new_message(cls, cls.channel_1.id, status='pending_moderation', author=cls.partner_admin)
+ cls.msg_emp2_pending_c1 = cls._create_new_message(cls, cls.channel_1.id, status='pending_moderation', author=cls.partner_employee_2)
+
+ @mute_logger('odoo.models.unlink')
+ def test_moderate_accept(self):
+ self._clear_bus()
+ # 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).
+ self.assertFalse(self.msg_admin_pending_c1.channel_ids)
+ self.msg_admin_pending_c1.sudo(self.user_employee)._moderate('accept')
+ self.assertEqual(self.msg_admin_pending_c1.channel_ids, self.channel_1)
+ self.assertEqual(self.msg_admin_pending_c1.moderation_status, 'accepted')
+ self.assertEqual(self.msg_admin_pending_c1_2.moderation_status, 'pending_moderation')
+ self.assertBusNotification([(self.cr.dbname, 'mail.channel', self.channel_1.id)])
+
+ @mute_logger('odoo.models.unlink')
+ def test_moderate_allow(self):
+ self._clear_bus()
+ # 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).
+ self.assertFalse(self.msg_admin_pending_c1.channel_ids)
+ self.assertFalse(self.msg_admin_pending_c1_2.channel_ids)
+ self.msg_admin_pending_c1.sudo(self.user_employee)._moderate('allow')
+ self.assertEqual(self.msg_admin_pending_c1.channel_ids, self.channel_1)
+ self.assertEqual(self.msg_admin_pending_c1_2.channel_ids, self.channel_1)
+ self.assertEqual(self.msg_admin_pending_c1.moderation_status, 'accepted')
+ self.assertEqual(self.msg_admin_pending_c1_2.moderation_status, 'accepted')
+ self.assertBusNotification([
+ (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):
+ self._init_mock_build_email()
+ (self.msg_admin_pending_c1 | self.msg_emp2_pending_c1).sudo(self.user_employee)._moderate_send_reject_email('Title', 'Message to author')
+ self.env['mail.mail'].process_email_queue()
+ self.assertEmails(self.partner_employee, self.partner_employee_2 | self.partner_admin, subject='Title', body_content='Message to author')
+
+ def test_moderate_discard(self):
+ self._clear_bus()
+ id1, id2 = self.msg_admin_pending_c1.id, self.msg_emp2_pending_c1.id # save ids because unlink will discard them
+ (self.msg_admin_pending_c1 | self.msg_emp2_pending_c1).sudo(self.user_employee)._moderate_discard()
+
+ self.assertBusNotification(
+ [(self.cr.dbname, 'res.partner', self.partner_admin.id),
+ (self.cr.dbname, 'res.partner', self.partner_employee_2.id),
+ (self.cr.dbname, 'res.partner', self.partner_employee.id)],
+ [{'type': 'deletion', 'message_ids': [id1]}, # admin: one message deleted because discarded
+ {'type': 'deletion', 'message_ids': [id2]}, # employee_2: one message delete because discarded
+ {'type': 'deletion', 'message_ids': [id1, id2]}] # employee: two messages deleted because moderation done
+ )
+
+ @mute_logger('odoo.models.unlink')
+ def test_notify_moderators(self):
+ # create pending messages in another channel to have two notification to push
+ msg_emp_pending_c2 = self._create_new_message(self.channel_2.id, status='pending_moderation', author=self.partner_employee)
+
+ self.env['mail.message']._notify_moderators()
+ self.assertEmails(False, self.partner_employee | self.partner_employee_2, subject='Message are pending moderation', email_from=self.env.user.company_id.catchall or self.env.user.company_id.email)
diff --git a/addons/test_mail/tests/test_res_users.py b/addons/test_mail/tests/test_res_users.py
new file mode 100644
index 00000000000..4ae3ca00265
--- /dev/null
+++ b/addons/test_mail/tests/test_res_users.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.addons.test_mail.tests import common
+from odoo.tests import tagged
+
+
+@tagged('moderation')
+class TestMessageModeration(common.Moderation):
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestMessageModeration, cls).setUpClass()
+
+ def test_is_moderator(self):
+ self.assertTrue(self.user_employee.is_moderator)
+ self.assertFalse(self.user_admin.is_moderator)
+ self.assertTrue(self.user_employee_2.is_moderator)
+
+ def test_moderation_counter(self):
+ self._create_new_message(self.channel_1.id, status='pending_moderation', author=self.partner_admin)
+ self._create_new_message(self.channel_1.id, status='accepted', author=self.partner_admin)
+ self._create_new_message(self.channel_1.id, status='accepted', author=self.partner_employee)
+ self._create_new_message(self.channel_1.id, status='pending_moderation', author=self.partner_employee)
+ self._create_new_message(self.channel_1.id, status='accepted', author=self.partner_employee_2)
+
+ self.assertEqual(self.user_employee.moderation_counter, 2)
+ self.assertEqual(self.user_employee_2.moderation_counter, 0)
+ self.assertEqual(self.user_admin.moderation_counter, 0)
+
+ self.channel_1.write({'channel_partner_ids': [(4, self.partner_employee_2.id)], 'moderator_ids': [(4, self.user_employee_2.id)]})
+ self.assertEqual(self.user_employee.moderation_counter, 2)
+ self.assertEqual(self.user_employee_2.moderation_counter, 0)
+ self.assertEqual(self.user_admin.moderation_counter, 0)