diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py index 6667447cd39..b8887ec2c73 100644 --- a/addons/mail/models/mail_activity.py +++ b/addons/mail/models/mail_activity.py @@ -472,20 +472,20 @@ class MailActivity(models.Model): def action_done(self): """ Wrapper without feedback because web button add context as parameter, therefore setting context to feedback """ - messages, next_activities = self._action_done() - return messages.ids and messages.ids[0] or False + return self.action_feedback() def action_feedback(self, feedback=False, attachment_ids=None): - self = self.with_context(clean_context(self.env.context)) - messages, next_activities = self._action_done(feedback=feedback, attachment_ids=attachment_ids) - return messages.ids and messages.ids[0] or False + messages, _next_activities = self.with_context( + clean_context(self.env.context) + )._action_done(feedback=feedback, attachment_ids=attachment_ids) + return messages[0].id if messages else False def action_done_schedule_next(self): """ Wrapper without feedback because web button add context as parameter, therefore setting context to feedback """ return self.action_feedback_schedule_next() - def action_feedback_schedule_next(self, feedback=False): + def action_feedback_schedule_next(self, feedback=False, attachment_ids=None): ctx = dict( clean_context(self.env.context), default_previous_activity_type_id=self.activity_type_id.id, @@ -493,7 +493,7 @@ class MailActivity(models.Model): default_res_id=self.res_id, default_res_model=self.res_model, ) - messages, next_activities = self._action_done(feedback=feedback) # will unlink activity, dont access self after that + _messages, next_activities = self._action_done(feedback=feedback, attachment_ids=attachment_ids) # will unlink activity, dont access self after that if next_activities: return False return { diff --git a/addons/mail/models/mail_activity_mixin.py b/addons/mail/models/mail_activity_mixin.py index d718aa21c8c..7d656bc96b6 100644 --- a/addons/mail/models/mail_activity_mixin.py +++ b/addons/mail/models/mail_activity_mixin.py @@ -475,7 +475,7 @@ class MailActivityMixin(models.AbstractModel): activities.write(write_vals) return activities - def activity_feedback(self, act_type_xmlids, user_id=None, feedback=None): + def activity_feedback(self, act_type_xmlids, user_id=None, feedback=None, attachment_ids=None): """ Set activities as done, limiting to some activity types and optionally to a given user. """ if self.env.context.get('mail_activity_automation_skip'): @@ -488,7 +488,7 @@ class MailActivityMixin(models.AbstractModel): return False activities = self.activity_search(act_type_xmlids, user_id=user_id) if activities: - activities.action_feedback(feedback=feedback) + activities.action_feedback(feedback=feedback, attachment_ids=attachment_ids) return True def activity_unlink(self, act_type_xmlids, user_id=None): diff --git a/addons/mail/tests/common.py b/addons/mail/tests/common.py index 2fe5f1bcb1b..4277c53892b 100644 --- a/addons/mail/tests/common.py +++ b/addons/mail/tests/common.py @@ -11,10 +11,9 @@ from contextlib import contextmanager from functools import partial from lxml import html from unittest.mock import patch -from smtplib import SMTPServerDisconnected -from odoo import exceptions -from odoo.addons.base.models.ir_mail_server import IrMailServer, MailDeliveryException +from odoo.addons.base.models.ir_mail_server import IrMailServer +from odoo.addons.base.tests.common import MockSmtplibCase from odoo.addons.bus.models.bus import ImBus, json_dump from odoo.addons.mail.models.mail_mail import MailMail from odoo.addons.mail.models.mail_message import Message @@ -22,10 +21,14 @@ from odoo.addons.mail.models.mail_notification import MailNotification from odoo.tests import common, new_test_user from odoo.tools import formataddr, pycompat -mail_new_test_user = partial(new_test_user, context={'mail_create_nolog': True, 'mail_create_nosubscribe': True, 'mail_notrack': True, 'no_reset_password': True}) +mail_new_test_user = partial(new_test_user, context={'mail_create_nolog': True, + 'mail_create_nosubscribe': True, + 'mail_notrack': True, + 'no_reset_password': True, + 'mail_channel_nosubscribe': True}) -class MockEmail(common.BaseCase): +class MockEmail(common.BaseCase, MockSmtplibCase): """ Tools, helpers and asserts for mailgateway-related tests Useful reminders @@ -39,37 +42,19 @@ class MockEmail(common.BaseCase): # ------------------------------------------------------------ @contextmanager - def mock_mail_gateway(self, mail_unlink_sent=False, sim_error=None): + def mock_mail_gateway(self, mail_unlink_sent=False): build_email_origin = IrMailServer.build_email + send_email_origin = IrMailServer.send_email mail_create_origin = MailMail.create mail_unlink_origin = MailMail.unlink self.mail_unlink_sent = mail_unlink_sent self._init_mail_mock() - def _ir_mail_server_connect(model, *args, **kwargs): - if sim_error and sim_error == 'connect_smtp_notfound': - raise exceptions.UserError( - "Missing SMTP Server\nPlease define at least one SMTP server, or provide the SMTP parameters explicitly.") - if sim_error and sim_error == 'connect_failure': - raise Exception("Some exception") - return None - def _ir_mail_server_build_email(model, *args, **kwargs): self._mails.append(kwargs) self._mails_args.append(args) return build_email_origin(model, *args, **kwargs) - def _ir_mail_server_send_email(model, message, *args, **kwargs): - if '@' not in message['To']: - raise AssertionError(model.NO_VALID_RECIPIENT) - if sim_error and sim_error == 'send_assert': - raise AssertionError('AssertionError') - elif sim_error and sim_error == 'send_disconnect': - raise SMTPServerDisconnected('SMTPServerDisconnected') - elif sim_error and sim_error == 'send_delivery': - raise MailDeliveryException('MailDeliveryException') - return message['Message-Id'] - def _mail_mail_create(model, *args, **kwargs): res = mail_create_origin(model, *args, **kwargs) self._new_mails += res.sudo() @@ -80,11 +65,13 @@ class MockEmail(common.BaseCase): return mail_unlink_origin(model, *args, **kwargs) return True - with patch.object(IrMailServer, 'connect', autospec=True, wraps=IrMailServer, side_effect=_ir_mail_server_connect) as ir_mail_server_connect_mock, \ - patch.object(IrMailServer, 'build_email', autospec=True, wraps=IrMailServer, side_effect=_ir_mail_server_build_email) as ir_mail_server_build_email_mock, \ - patch.object(IrMailServer, 'send_email', autospec=True, wraps=IrMailServer, side_effect=_ir_mail_server_send_email) as ir_mail_server_send_email_mock, \ - patch.object(MailMail, 'create', autospec=True, wraps=MailMail, side_effect=_mail_mail_create) as _mail_mail_create_mock, \ - patch.object(MailMail, 'unlink', autospec=True, wraps=MailMail, side_effect=_mail_mail_unlink) as mail_mail_unlink_mock: + with self.mock_smtplib_connection(), \ + patch.object(IrMailServer, 'build_email', autospec=True, wraps=IrMailServer, side_effect=_ir_mail_server_build_email) as build_email_mocked, \ + patch.object(IrMailServer, 'send_email', autospec=True, wraps=IrMailServer, side_effect=send_email_origin) as send_email_mocked, \ + patch.object(MailMail, 'create', autospec=True, wraps=MailMail, side_effect=_mail_mail_create), \ + patch.object(MailMail, 'unlink', autospec=True, wraps=MailMail, side_effect=_mail_mail_unlink): + self.build_email_mocked = build_email_mocked + self.send_email_mocked = send_email_mocked yield def _init_mail_mock(self): @@ -92,10 +79,6 @@ class MockEmail(common.BaseCase): self._mails_args = [] self._new_mails = self.env['mail.mail'].sudo() - # ------------------------------------------------------------ - # GATEWAY TOOLS - # ------------------------------------------------------------ - @classmethod def _init_mail_gateway(cls): cls.alias_domain = 'test.com' @@ -108,6 +91,26 @@ class MockEmail(common.BaseCase): cls.env['ir.config_parameter'].set_param('mail.default.from', cls.default_from) cls.mailer_daemon_email = formataddr(('MAILER-DAEMON', '%s@%s' % (cls.alias_bounce, cls.alias_domain))) + @classmethod + def _init_outgoing_gateway(cls): + cls.env['ir.mail_server'].search([]).unlink() + cls.mail_server_domain, cls.mail_server_global = cls.env['ir.mail_server'].create([ + {'from_filter': 'test.com', + 'name': 'Domain Based Server', + 'smtp_encryption': 'none', + 'smtp_host': 'smtp_host', + }, + {'from_filter': False, + 'name': 'No FromFilter Server', + 'smtp_encryption': 'none', + 'smtp_host': 'smtp_host', + } + ]) + + # ------------------------------------------------------------ + # GATEWAY TOOLS + # ------------------------------------------------------------ + def format(self, template, to='groups@example.com, other@gmail.com', subject='Frogs', email_from='Sylvie Lelitre ', return_path='', cc='', extra='', msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>', @@ -316,7 +319,7 @@ class MockEmail(common.BaseCase): # ------------------------------------------------------------ def assertMailMail(self, recipients, status, - check_mail_mail=True, mail_message=None, author=None, + mail_message=None, author=None, content=None, fields_values=None, email_values=None): """ Assert mail.mail records are created and maybe sent as emails. Allow asserting their content. Records to check are the one generated when @@ -335,8 +338,6 @@ class MockEmail(common.BaseCase): :param email_values: if given, should be a dictionary of keys / values allowing to check sent email additional values (if any). See ``assertSentEmail``; - - :param check_mail_mail: deprecated, use ``assertSentEmail`` if False """ found_mail = self._find_mail_mail_wpartners(recipients, status, mail_message=mail_message, author=author) self.assertTrue(bool(found_mail)) @@ -370,8 +371,6 @@ class MockEmail(common.BaseCase): :param email_values: if given, should be a dictionary of keys / values allowing to check sent email additional values (if any). See ``assertSentEmail``; - - :param check_mail_mail: deprecated, use ``assertSentEmail`` if False """ for email_to in emails: found_mail = self._find_mail_mail_wemail(email_to, status, mail_message=mail_message, author=author) @@ -410,6 +409,16 @@ class MockEmail(common.BaseCase): found_mail[fname], fvalue, 'Mail: expected %s for %s, got %s' % (fvalue, fname, found_mail[fname])) + def assertMessageFields(self, message, fields_values): + """ Just a quick helper to check a mail.message content by giving directly + a dict for fields. Allows to hide a lot of assertEqual under a simple + call with a dictionary of expected values. """ + for fname, fvalue in fields_values.items(): + self.assertEqual( + message[fname], fvalue, + 'Message: expected %s for %s, got %s' % (fvalue, fname, message[fname]) + ) + def assertNoMail(self, recipients, mail_message=None, author=None): """ Check no mail.mail and email was generated during gateway mock. """ try: @@ -435,8 +444,10 @@ class MockEmail(common.BaseCase): :param values: dictionary of additional values to check email content; """ base_expected = {} - for fname in ['reply_to', 'subject', 'attachments', 'body', 'references', - 'body_content', 'body_alternative_content', 'references_content']: + for fname in ['author_id', 'reply_to', + 'subject', 'body', 'body_content', 'body_alternative_content', + 'references', 'references_content', + 'attachments']: if fname in values: base_expected[fname] = values[fname] @@ -461,9 +472,14 @@ class MockEmail(common.BaseCase): debug_info = '-'.join('From: %s-To: %s' % (mail['email_from'], mail['email_to']) for mail in self._mails) if not bool(sent_mail) else '' self.assertTrue(bool(sent_mail), 'Expected mail from %s to %s not found in %s' % (expected['email_from'], expected['email_to'], debug_info)) - for val in ['reply_to', 'subject', 'references', 'attachments']: + for val in ['reply_to', 'subject', 'references']: if val in expected: self.assertEqual(expected[val], sent_mail[val], 'Value for %s: expected %s, received %s' % (val, expected[val], sent_mail[val])) + if 'attachments' in expected: + self.assertEqual( + sorted(expected['attachments']), sorted(sent_mail['attachments']), + 'Value for %s: expected %s, received %s' % ('attachments', expected['attachments'], sent_mail['attachments']) + ) if 'attachments_info' in values: attachments = sent_mail['attachments'] for attachment_info in values['attachments_info']: @@ -473,12 +489,18 @@ class MockEmail(common.BaseCase): if attachment_info.get('type'): self.assertEqual(attachment[2], attachment_info['type']) self.assertEqual(len(values['attachments_info']), len(attachments)) - for val in ['body']: + for val in ['body', 'body_alternative']: if val in expected: - self.assertHtmlEqual(expected[val], sent_mail[val], 'Value for %s: expected %s, received %s' % (val, expected[val], sent_mail[val])) - for val in ['body_content', 'body_alternative', 'references_content']: + self.assertHtmlEqual( + expected[val], sent_mail[val], + 'Value for %s: expected %s, received %s' % (val, expected[val], sent_mail[val]) + ) + for val in ['body_content', 'body_alternative_content', 'references_content']: if val in expected: - self.assertIn(expected[val], sent_mail[val[:-8]], 'Value for %s: %s does not contain %s' % (val, sent_mail[val[:-8]], expected[val])) + self.assertIn( + expected[val], sent_mail[val[:-8]], + 'Value for %s: %s does not contain %s' % (val, sent_mail[val[:-8]], expected[val]) + ) class MailCase(MockEmail): @@ -496,10 +518,11 @@ class MailCase(MockEmail): ("UNKNOWN", "Unknown error") """ _test_context = { + 'mail_channel_nosubscribe': True, 'mail_create_nolog': True, 'mail_create_nosubscribe': True, 'mail_notrack': True, - 'no_reset_password': True + 'no_reset_password': True, } @classmethod @@ -507,7 +530,7 @@ class MailCase(MockEmail): return record.with_context( mail_create_nolog=False, mail_create_nosubscribe=False, - mail_notrack=False + mail_notrack=False, ) def flush_tracking(self): @@ -627,19 +650,19 @@ class MailCase(MockEmail): # ------------------------------------------------------------ @contextmanager - def assertSinglePostNotifications(self, recipients_info, message_info=None, mail_unlink_sent=False, sim_error=None): + def assertSinglePostNotifications(self, recipients_info, message_info=None, mail_unlink_sent=False): """ Shortcut to assertMsgNotifications when having a single message to check. """ r_info = dict(message_info if message_info else {}) r_info.setdefault('content', '') r_info['notif'] = recipients_info - with self.assertPostNotifications([r_info], mail_unlink_sent=mail_unlink_sent, sim_error=sim_error): + with self.assertPostNotifications([r_info], mail_unlink_sent=mail_unlink_sent): yield @contextmanager - def assertPostNotifications(self, recipients_info, mail_unlink_sent=False, sim_error=None): + def assertPostNotifications(self, recipients_info, mail_unlink_sent=False): """ Check content of notifications. """ try: - with self.mock_mail_gateway(mail_unlink_sent=mail_unlink_sent, sim_error=sim_error), self.mock_bus(), self.mock_mail_app(): + with self.mock_mail_gateway(mail_unlink_sent=mail_unlink_sent), self.mock_bus(), self.mock_mail_app(): yield finally: done_msgs, done_notifs = self.assertMailNotifications(self._new_msgs, recipients_info) @@ -659,7 +682,7 @@ class MailCase(MockEmail): @contextmanager def assertNoNotifications(self): try: - with self.mock_mail_gateway(mail_unlink_sent=False, sim_error=None), self.mock_bus(), self.mock_mail_app(): + with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_bus(), self.mock_mail_app(): yield finally: self.assertFalse(bool(self._new_msgs)) @@ -891,9 +914,14 @@ class MailCommon(common.TransactionCase, MailCase): super(MailCommon, cls).setUpClass() # give default values for all email aliases and domain cls._init_mail_gateway() + cls._init_outgoing_gateway() # ensure admin configuration cls.user_admin = cls.env.ref('base.user_admin') - cls.user_admin.write({'notification_type': 'inbox'}) + cls.user_admin.write({ + 'country_id': cls.env.ref('base.be').id, + 'email': 'test.admin@test.example.com', + 'notification_type': 'inbox', + }) cls.partner_admin = cls.env.ref('base.partner_admin') cls.company_admin = cls.user_admin.company_id cls.company_admin.write({'email': 'company@example.com'}) @@ -905,9 +933,11 @@ class MailCommon(common.TransactionCase, MailCase): # test standard employee cls.user_employee = mail_new_test_user( - cls.env, login='employee', - groups='base.group_user,mail.group_mail_template_editor', + cls.env, company_id=cls.company_admin.id, + country_id=cls.env.ref('base.be').id, + groups='base.group_user,mail.group_mail_template_editor', + login='employee', name='Ernest Employee', notification_type='inbox', signature='--\nErnest' @@ -939,12 +969,26 @@ class MailCommon(common.TransactionCase, MailCase): groups='base.group_user', company_id=cls.company_2.id, company_ids=[(4, cls.company_2.id)], + email='enguerrand@example.com', name='Enguerrand Employee C2', notification_type='inbox', signature='--\nEnguerrand' ) cls.partner_employee_c2 = cls.user_employee_c2.partner_id + # test erp manager employee + cls.user_erp_manager = mail_new_test_user( + cls.env, + company_id=cls.company_2.id, + company_ids=[(6, 0, (cls.company_admin + cls.company_2).ids)], + email='etchenne@example.com', + groups='base.group_user,base.group_erp_manager,mail.group_mail_template_editor', + login='erp_manager', + name='Etchenne Tchagada', + notification_type='inbox', + signature='--\nEtchenne', + ) + @classmethod def _activate_multi_lang(cls, lang_code='es_ES', layout_arch_db=None, test_record=False, test_template=False): """ Summary of es_ES matching done here (a bit hardcoded to ease tests) @@ -1097,15 +1141,18 @@ class MailCommon(common.TransactionCase, MailCase): }) cls.env['ir.translation'].create(translations_tocreate) - def _generate_attachments_data(self, count, res_model=None, res_id=None): + def _generate_attachments_data(self, count, res_model=None, res_id=None, attach_values=None): # attachment visibility depends on what they are attached to + attach_values = attach_values or {} if res_model is None: res_model = self.template._name if res_id is None: res_id = self.template.id return [{ - 'name': '%02d.txt' % x, - 'datas': base64.b64encode(b'Att%02d' % x), + 'datas': base64.b64encode(b'AttContent_%02d' % x), + 'name': 'AttFileName_%02d.txt' % x, + 'mimetype': 'text/plain', 'res_model': res_model, 'res_id': res_id, + **attach_values, } for x in range(count)] diff --git a/addons/mail/tests/test_mail_channel_partner.py b/addons/mail/tests/test_mail_channel_partner.py index 8f6e8a66424..f7ba426648f 100644 --- a/addons/mail/tests/test_mail_channel_partner.py +++ b/addons/mail/tests/test_mail_channel_partner.py @@ -1,10 +1,14 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from functools import partial + from odoo.addons.mail.tests.common import mail_new_test_user from odoo.addons.mail.tests.common import MailCommon from odoo.exceptions import AccessError, UserError +mail_channel_new_test_user = partial(mail_new_test_user, context={'mail_channel_nosubscribe': False}) + class TestMailChannelMembers(MailCommon): @@ -22,23 +26,23 @@ class TestMailChannelMembers(MailCommon): 'res_id': cls.secret_group.id, }) - cls.user_1 = mail_new_test_user( + cls.user_1 = mail_channel_new_test_user( cls.env, login='user_1', name='User 1', groups='base.group_user,mail.secret_group') - cls.user_2 = mail_new_test_user( + cls.user_2 = mail_channel_new_test_user( cls.env, login='user_2', name='User 2', groups='base.group_user,mail.secret_group') - cls.user_3 = mail_new_test_user( + cls.user_3 = mail_channel_new_test_user( cls.env, login='user_3', name='User 3', groups='base.group_user,mail.secret_group') - cls.user_portal = mail_new_test_user( + cls.user_portal = mail_channel_new_test_user( cls.env, login='user_portal', name='User Portal', groups='base.group_portal') - cls.user_public = mail_new_test_user( + cls.user_public = mail_channel_new_test_user( cls.env, login='user_ublic', name='User Public', groups='base.group_public') diff --git a/addons/mail/tests/test_mail_composer.py b/addons/mail/tests/test_mail_composer.py index 11bde11084c..9b573efb5be 100644 --- a/addons/mail/tests/test_mail_composer.py +++ b/addons/mail/tests/test_mail_composer.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo.tests import users from odoo.addons.mail.tests.common import MailCommon +from odoo.tests import tagged, users +@tagged('mail_composer') class TestMailComposer(MailCommon): + @classmethod def setUpClass(cls): super(TestMailComposer, cls).setUpClass() @@ -47,9 +49,11 @@ class TestMailComposer(MailCommon): values = mail_compose_message.get_mail_values(self.partner_employee.ids) - self.assertIn(self.body_html, + self.assertIn( + self.body_html, values[self.partner_employee.id]['body_html'], - 'We must preserve (mso) comments in email html') + 'We must preserve (mso) comments in email html' + ) @users('employee') def test_mail_mass_mode_compose_with_mso(self): @@ -69,6 +73,8 @@ class TestMailComposer(MailCommon): values = composer.get_mail_values(self.partner_employee.ids) - self.assertIn(self.body_html, + self.assertIn( + self.body_html, values[self.partner_employee.id]['body_html'], - 'We must preserve (mso) comments in email html') + 'We must preserve (mso) comments in email html' + ) diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index ce079a23cae..3fca30f5caf 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -545,7 +545,7 @@ class MailComposer(models.TransientModel): elif template_id: values = self.generate_email_for_composer( template_id, [res_id], - ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'mail_server_id'] + ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'mail_server_id'] )[res_id] # transform attachments into attachment_ids; not attached to the document because this will # be done further in the posting process, allowing to clean database if email not send diff --git a/addons/test_mail/models/test_mail_models.py b/addons/test_mail/models/test_mail_models.py index 6520ad3adb8..16e7c0afd09 100644 --- a/addons/test_mail/models/test_mail_models.py +++ b/addons/test_mail/models/test_mail_models.py @@ -60,8 +60,10 @@ class MailTestActivity(models.Model): summary=action_summary ) - def action_close(self, action_feedback): - self.activity_feedback(['test_mail.mail_act_test_todo'], feedback=action_feedback) + def action_close(self, action_feedback, attachment_ids=None): + self.activity_feedback(['test_mail.mail_act_test_todo'], + feedback=action_feedback, + attachment_ids=attachment_ids) class MailTestTicket(models.Model): @@ -111,6 +113,18 @@ class MailTestTicket(models.Model): return super(MailTestTicket, self)._track_subtype(init_values) +class MailTestTicketMC(models.Model): + """ Just mail.test.ticket, but multi company. Kept as different model to + avoid messing with existing tests, notably performance, and ease backward + comparison. """ + _description = 'Ticket-like model' + _name = 'mail.test.ticket.mc' + _inherit = ['mail.test.ticket'] + + company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company) + container_id = fields.Many2one('mail.test.container.mc', tracking=True) + + class MailTestContainer(models.Model): """ This model can be used in tests when container records like projects or teams are required. """ @@ -144,6 +158,18 @@ class MailTestContainer(models.Model): values['alias_parent_thread_id'] = self.id return values +class MailTestContainerMC(models.Model): + """ Just mail.test.container, but multi company. Kept as different model to + avoid messing with existing tests, notably performance, and ease backward + comparison. """ + _description = 'Project-like model with alias (MC)' + _name = 'mail.test.container.mc' + _mail_post_access = 'read' + _inherit = ['mail.test.container'] + + company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company) + + class MailTestComposerMixin(models.Model): """ A simple invite-like wizard using the composer mixin, rendering on itself. """ diff --git a/addons/test_mail/security/ir.model.access.csv b/addons/test_mail/security/ir.model.access.csv index e283c89b36f..c212f163d61 100644 --- a/addons/test_mail/security/ir.model.access.csv +++ b/addons/test_mail/security/ir.model.access.csv @@ -11,12 +11,16 @@ access_mail_test_activity_portal,mail.test.activity.portal,model_mail_test_activ access_mail_test_activity_user,mail.test.activity.user,model_mail_test_activity,base.group_user,1,1,1,1 access_mail_test_field_type_portal,mail.test.field.type.portal,model_mail_test_field_type,base.group_portal,0,0,0,0 access_mail_test_field_type_user,mail.test.field.type.user,model_mail_test_field_type,base.group_user,1,1,1,1 -access_mail_test_ticket_portal,mail.test.ticket.portal,model_mail_test_ticket,base.group_portal,0,0,0,0 +access_mail_test_ticket_portal,mail.test.ticket.portal,model_mail_test_ticket,base.group_portal,1,0,0,0 access_mail_test_ticket_user,mail.test.ticket.user,model_mail_test_ticket,base.group_user,1,1,1,1 +access_mail_test_ticket_mc_portal,mail.test.ticket.mc.portal,model_mail_test_ticket_mc,base.group_portal,1,0,0,0 +access_mail_test_ticket_mc_user,mail.test.ticket.mc.user,model_mail_test_ticket_mc,base.group_user,1,1,1,1 access_mail_test_composer_mixin_all,mail.test.composer.mixin.all,model_mail_test_composer_mixin,,0,0,0,0 access_mail_test_composer_mixin_user,mail.test.composer.mixin.user,model_mail_test_composer_mixin,base.group_user,1,1,1,1 -access_mail_test_container_portal,mail.test.container_portal,model_mail_test_container,base.group_portal,1,1,0,0 +access_mail_test_container_portal,mail.test.container_portal,model_mail_test_container,base.group_portal,1,0,0,0 access_mail_test_container_user,mail.test.container.user,model_mail_test_container,base.group_user,1,1,1,1 +access_mail_test_container_mc_portal,mail.test.container.mc.portal,model_mail_test_container_mc,base.group_portal,1,0,0,0 +access_mail_test_container_mc_user,mail.test.container.mc.user,model_mail_test_container_mc,base.group_user,1,1,1,1 access_mail_test_cc_portal,mail.test.cc.portal,model_mail_test_cc,base.group_portal,1,0,0,0 access_mail_test_cc_user,mail.test.cc.user,model_mail_test_cc,base.group_user,1,1,1,1 access_mail_test_lang_portal,mail.test.lang.portal,model_mail_test_lang,base.group_portal,1,0,0,0 diff --git a/addons/test_mail/security/test_mail_security.xml b/addons/test_mail/security/test_mail_security.xml index 296cbdc02da..5873a5d09ae 100644 --- a/addons/test_mail/security/test_mail_security.xml +++ b/addons/test_mail/security/test_mail_security.xml @@ -1,11 +1,55 @@ - - Mail Test Multi Company - - - ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] - + + Mail Test Multi Company + + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + + + + Portal Mail Test Ticket + + [('message_partner_ids', 'in', [user.partner_id.id])] + + + + + + Mail Test Ticket Multi Company + + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + + Portal Mail Test Ticket Multi Company + + [('message_partner_ids', 'in', [user.partner_id.id])] + + + + + + Portal Mail Test Container + + [('message_partner_ids', 'in', [user.partner_id.id])] + + + + + + Mail Test Container Multi Company + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + + + Portal Mail Test Container Multi Company + + [('message_partner_ids', 'in', [user.partner_id.id])] + + diff --git a/addons/test_mail/tests/common.py b/addons/test_mail/tests/common.py index ae43c3ae437..88759108d48 100644 --- a/addons/test_mail/tests/common.py +++ b/addons/test_mail/tests/common.py @@ -9,27 +9,29 @@ class TestMailCommon(MailCommon): """ Main entry point for functional tests. """ @classmethod - def _create_channel_listener(cls): - cls.channel_listen = cls.env['mail.channel'].with_context(cls._test_context).create({'name': 'Listener'}) - - @classmethod - def _create_records_for_batch(cls, model, count): - # TDE note: to be cleaned in master + def _create_records_for_batch(cls, model, count, additional_values=None): + additional_values = additional_values or {} records = cls.env[model] partners = cls.env['res.partner'] country_id = cls.env.ref('base.be').id - partners = cls.env['res.partner'].with_context(**cls._test_context).create([{ - 'name': 'Partner_%s' % (x), - 'email': '_test_partner_%s@example.com' % (x), - 'country_id': country_id, - 'mobile': '047500%02d%02d' % (x, x) - } for x in range(count)]) + base_values = [ + {'name': 'Test_%s' % idx, + **additional_values, + } for idx in range(count) + ] - records = cls.env[model].with_context(**cls._test_context).create([{ - 'name': 'Test_%s' % (x), - 'customer_id': partners[x].id, - } for x in range(count)]) + if 'customer_id' in cls.env[model]: + partners = cls.env['res.partner'].with_context(**cls._test_context).create([{ + 'name': 'Partner_%s' % idx, + 'email': '_test_partner_%s@example.com' % idx, + 'country_id': country_id, + 'mobile': '047500%02d%02d' % (idx, idx) + } for idx in range(count)]) + for values, partner in zip(base_values, partners): + values['customer_id'] = partner.id + + records = cls.env[model].with_context(**cls._test_context).create(base_values) cls.records = cls._reset_mail_context(records) cls.partners = partners diff --git a/addons/test_mail/tests/test_mail_composer.py b/addons/test_mail/tests/test_mail_composer.py index 01c6587ae17..c9ad9faf0e4 100644 --- a/addons/test_mail/tests/test_mail_composer.py +++ b/addons/test_mail/tests/test_mail_composer.py @@ -11,6 +11,7 @@ from odoo.tests import tagged from odoo.tests.common import users, Form from odoo.tools import mute_logger + @tagged('mail_composer') class TestMailComposer(TestMailCommon, TestRecipients): """ Test Composer internals """ @@ -18,7 +19,6 @@ class TestMailComposer(TestMailCommon, TestRecipients): @classmethod def setUpClass(cls): super(TestMailComposer, cls).setUpClass() - cls._init_mail_gateway() # ensure employee can create partners, necessary for templates cls.user_employee.write({ @@ -47,7 +47,10 @@ class TestMailComposer(TestMailCommon, TestRecipients): 'customer_id': cls.partner_1.id, 'user_id': cls.user_employee_2.id, }) - cls.test_records, cls.test_partners = cls._create_records_for_batch('mail.test.ticket', 2) + cls.test_records, cls.test_partners = cls._create_records_for_batch( + 'mail.test.ticket', 2, + additional_values={'user_id': cls.user_employee_2.id} + ) cls.test_report = cls.env['ir.actions.report'].create({ 'name': 'Test Report on mail test ticket', @@ -59,24 +62,17 @@ class TestMailComposer(TestMailCommon, TestRecipients): cls.test_from = '"John Doe" ' - cls.mail_server = cls.env['ir.mail_server'].create({ - 'name': 'Dummy Test Server', - 'smtp_host': 'smtp.pizza.moc', - 'smtp_port': 17, - 'smtp_encryption': 'ssl', - 'sequence': 666, - }) - cls.template = cls.env['mail.template'].create({ + 'auto_delete': True, 'name': 'TestTemplate', 'subject': 'TemplateSubject {{ object.name }}', 'body_html': '

TemplateBody

', 'partner_to': '{{ object.customer_id.id if object.customer_id else "" }}', 'email_to': '{{ (object.email_from if not object.customer_id else "") }}', 'email_from': '{{ (object.user_id.email_formatted or user.email_formatted) }}', + 'mail_server_id': cls.mail_server_domain.id, 'model_id': cls.env['ir.model']._get('mail.test.ticket').id, - 'mail_server_id': cls.mail_server.id, - 'auto_delete': True, + 'reply_to': '{{ ctx.get("custom_reply_to") or "info@test.example.com" }}', }) def _get_web_context(self, records, add_web=True, **values): @@ -108,15 +104,28 @@ class TestComposerForm(TestMailComposer): @users('employee') def test_mail_composer_comment(self): - composer_form = Form(self.env['mail.compose.message'].with_context(self._get_web_context(self.test_record, add_web=True))) + composer_form = Form(self.env['mail.compose.message'].with_context( + self._get_web_context(self.test_record, add_web=True) + )) + # auto_delete not displayed currently in view + # self.assertFalse(composer_form.auto_delete) + # self.assertFalse(composer_form.auto_delete_message) + # author_id not displayed currently in view + # self.assertEqual(composer_form.author_id, self.env.user.partner_id) + self.assertFalse(composer_form.body) + self.assertEqual(composer_form.composition_mode, 'comment') + self.assertEqual(composer_form.email_from, self.env.user.email_formatted) + self.assertFalse(composer_form.mail_server_id) + self.assertEqual(composer_form.model, self.test_record._name) + self.assertFalse(composer_form.partner_ids) + # record name not displayed currently in view + # self.assertEqual(composer_form.record_name, self.test_record.name, 'MailComposer: comment mode should compute record name') + self.assertFalse(composer_form.reply_to) + self.assertFalse(composer_form.reply_to_force_new) + self.assertEqual(composer_form.res_id, self.test_record.id) self.assertEqual( composer_form.subject, 'Re: %s' % self.test_record.name, 'MailComposer: comment mode should have default subject Re: record_name') - # record name not displayed currently in view - # self.assertEqual(composer_form.record_name, self.test_record.name, 'MailComposer: comment mode should compute record name') - self.assertFalse(composer_form.reply_to_force_new) - self.assertEqual(composer_form.composition_mode, 'comment') - self.assertEqual(composer_form.model, self.test_record._name) @users('employee') def test_mail_composer_comment_attachments(self): @@ -156,7 +165,7 @@ class TestComposerForm(TestMailComposer): report_attachments = [att for att in composer_form.attachment_ids if att not in template_1_attachments] self.assertEqual(len(report_attachments), 1) tpl_attachments = composer_form.attachment_ids[:] - report_attachments[0] - self.assertEqual(tpl_attachments, self.env['ir.attachment']) + self.assertFalse(tpl_attachments) # change back to template 1 composer_form.template_id = template_1 @@ -170,29 +179,77 @@ class TestComposerForm(TestMailComposer): composer_form.template_id = self.env['mail.template'] self.assertEqual(len(composer_form.attachment_ids), 0) + @users('employee') + def test_mail_composer_comment_wtpl(self): + composer_form = Form(self.env['mail.compose.message'].with_context( + self._get_web_context(self.test_record, add_web=True, default_template_id=self.template.id) + )) + # auto_delete not displayed currently in view + # self.assertTrue(composer_form.auto_delete) + # self.assertFalse(composer_form.auto_delete_message) + # author_id not displayed currently in view + # self.assertEqual(composer_form.author_id, self.env.user.partner_id) + self.assertEqual(composer_form.body, '

TemplateBody %s

' % self.test_record.name) + self.assertEqual(composer_form.composition_mode, 'comment') + self.assertEqual(composer_form.email_from, self.user_employee_2.email_formatted) + self.assertEqual(composer_form.mail_server_id, self.mail_server_domain) + self.assertEqual(composer_form.model, self.test_record._name) + self.assertEqual(composer_form.partner_ids[:], self.partner_1) + # record name not displayed currently in view + # self.assertEqual(composer_form.record_name, self.test_record.name, 'MailComposer: comment mode should compute record name') + self.assertEqual(composer_form.reply_to, 'info@test.example.com') + self.assertFalse(composer_form.reply_to_force_new) + self.assertEqual(composer_form.res_id, self.test_record.id) + self.assertEqual(composer_form.subject, 'TemplateSubject %s' % self.test_record.name) + @users('employee') def test_mail_composer_mass(self): - composer_form = Form(self.env['mail.compose.message'].with_context(self._get_web_context(self.test_records, add_web=True))) - self.assertFalse(composer_form.subject, 'MailComposer: mass mode should have void default subject if no template') + composer_form = Form(self.env['mail.compose.message'].with_context( + self._get_web_context(self.test_records, add_web=True) + )) + # auto_delete not displayed currently in view + # self.assertFalse(composer_form.auto_delete) + # self.assertFalse(composer_form.auto_delete_message) + # author_id not displayed currently in view + # self.assertEqual(composer_form.author_id, self.env.user.partner_id) + self.assertFalse(composer_form.body) + self.assertEqual(composer_form.composition_mode, 'mass_mail') + self.assertEqual(composer_form.email_from, self.env.user.email_formatted) + self.assertFalse(composer_form.mail_server_id) + self.assertEqual(composer_form.model, self.test_records._name) # record name not displayed currently in view # self.assertFalse(composer_form.record_name, 'MailComposer: mass mode should have void record name') + self.assertFalse(composer_form.reply_to) self.assertFalse(composer_form.reply_to_force_new) - self.assertEqual(composer_form.composition_mode, 'mass_mail') - self.assertEqual(composer_form.model, self.test_records._name) + self.assertEqual(composer_form.res_id, self.test_records[0].id, + 'MailComposer: even in mass mode web active_id presence may add a res_id') + self.assertFalse(composer_form.subject, 'MailComposer: mass mode should have void default subject if no template') @users('employee') def test_mail_composer_mass_wtpl(self): - ctx = self._get_web_context(self.test_records, add_web=True, default_template_id=self.template.id) - composer_form = Form(self.env['mail.compose.message'].with_context(ctx)) - self.assertEqual(composer_form.subject, self.template.subject, - 'MailComposer: mass mode should have template raw subject if template') + composer_form = Form(self.env['mail.compose.message'].with_context( + self._get_web_context(self.test_records, add_web=True, default_template_id=self.template.id) + )) + # auto_delete not displayed currently in view + # self.assertTrue(composer_form.auto_delete) + # self.assertFalse(composer_form.auto_delete_message) + # author_id not displayed currently in view + # self.assertEqual(composer_form.author_id, self.env.user.partner_id) self.assertEqual(composer_form.body, self.template.body_html, 'MailComposer: mass mode should have template raw body if template') + self.assertEqual(composer_form.composition_mode, 'mass_mail') + self.assertEqual(composer_form.email_from, self.template.email_from, + 'MailComposer: mass mode should have template raw email_from if template') + self.assertEqual(composer_form.mail_server_id, self.mail_server_domain) + self.assertEqual(composer_form.model, self.test_records._name) # record name not displayed currently in view # self.assertFalse(composer_form.record_name, 'MailComposer: mass mode should have void record name') + self.assertEqual(composer_form.reply_to, self.template.reply_to) self.assertFalse(composer_form.reply_to_force_new) - self.assertEqual(composer_form.composition_mode, 'mass_mail') - self.assertEqual(composer_form.model, self.test_records._name) + self.assertEqual(composer_form.res_id, self.test_records[0].id, + 'MailComposer: even in mass mode web active_id presence may add a res_id') + self.assertEqual(composer_form.subject, self.template.subject, + 'MailComposer: mass mode should have template raw subject if template') @tagged('mail_composer') @@ -294,6 +351,10 @@ class TestComposerInternals(TestMailComposer): self.assertEqual(composer.subject, 'My amazing subject') self.assertEqual(composer.body, '

Test Body

') self.assertEqual(composer.mail_server_id.id, False) + if composition_mode == 'comment': + self.assertEqual(composer.record_name, self.test_record.name) + else: + self.assertFalse(composer.record_name) # changing template should update its content composer.write({'template_id': self.template.id}) @@ -305,10 +366,12 @@ class TestComposerInternals(TestMailComposer): self.assertEqual(composer.subject, 'TemplateSubject %s' % self.test_record.name) self.assertEqual(composer.body, '

TemplateBody %s

' % self.test_record.name) self.assertEqual(composer.mail_server_id, self.template.mail_server_id) + self.assertEqual(composer.record_name, self.test_record.name) else: self.assertEqual(composer.subject, self.template.subject) self.assertEqual(composer.body, self.template.body_html) self.assertEqual(composer.mail_server_id, self.template.mail_server_id) + self.assertFalse(composer.record_name) # manual values is kept over template composer.write({'subject': 'Back to my amazing subject'}) @@ -322,17 +385,19 @@ class TestComposerInternals(TestMailComposer): # values are reset if composition_mode == 'comment': self.assertEqual(composer.subject, 'Re: %s' % self.test_record.name) - self.assertEqual(composer.body, '') + self.assertFalse(composer.body) # TDE FIXME: server id is kept, not sure why # self.assertFalse(composer.mail_server_id.id) self.assertEqual(composer.mail_server_id, self.template.mail_server_id) + self.assertEqual(composer.record_name, self.test_record.name) else: # values are reset TDE FIXME: strange for subject self.assertEqual(composer.subject, 'Back to my amazing subject') - self.assertEqual(composer.body, '') + self.assertFalse(composer.body) # TDE FIXME: server id is kept, not sure why # self.assertFalse(composer.mail_server_id.id) self.assertEqual(composer.mail_server_id, self.template.mail_server_id) + self.assertFalse(composer.record_name) # 2. check with default ctx['default_template_id'] = self.template.id @@ -347,10 +412,12 @@ class TestComposerInternals(TestMailComposer): self.assertEqual(composer.subject, 'TemplateSubject %s' % self.test_record.name) self.assertEqual(composer.body, '

TemplateBody %s

' % self.test_record.name) self.assertEqual(composer.mail_server_id, self.template.mail_server_id) + self.assertEqual(composer.record_name, self.test_record.name) else: self.assertEqual(composer.subject, self.template.subject) self.assertEqual(composer.body, self.template.body_html) self.assertEqual(composer.mail_server_id, self.template.mail_server_id) + self.assertFalse(composer.record_name) # 3. check at create ctx.pop('default_template_id') @@ -365,10 +432,12 @@ class TestComposerInternals(TestMailComposer): self.assertEqual(composer.subject, 'TemplateSubject %s' % self.test_record.name) self.assertEqual(composer.body, '

TemplateBody %s

' % self.test_record.name) self.assertEqual(composer.mail_server_id, self.template.mail_server_id) + self.assertEqual(composer.record_name, self.test_record.name) else: self.assertEqual(composer.subject, self.template.subject) self.assertEqual(composer.body, self.template.body_html) self.assertEqual(composer.mail_server_id, self.template.mail_server_id) + self.assertFalse(composer.record_name) # 4. template + user input ctx['default_template_id'] = self.template.id @@ -376,15 +445,155 @@ class TestComposerInternals(TestMailComposer): 'subject': 'My amazing subject', 'body': '

Test Body

', 'mail_server_id': False, + 'record_name': 'CustomName', }) # creation values are taken self.assertEqual(composer.subject, 'My amazing subject') self.assertEqual(composer.body, '

Test Body

') self.assertEqual(composer.mail_server_id.id, False) + self.assertEqual(composer.record_name, 'CustomName') @users('employee') - @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail') + @mute_logger('odoo.models.unlink') + def test_mail_composer_recipients(self): + """ Test content management (partner_ids, reply_to) in both comment and + mass mailing mode. Template update is also tested. Add some tests for + partner creation based on unknown emails as this is part of the process. """ + base_recipients = self.partner_1 + self.partner_2 + self.template.write({ + 'email_cc': 'test.cc.1@test.example.com, test.cc.2@test.example.com' + }) + + for composition_mode in ['comment', 'mass_mail']: + self.assertFalse( + self.env['res.partner'].search([ + ('email_normalized', 'in', ['test.cc.1@test.example.com', + 'test.cc.2@test.example.com']) + ]) + ) + if composition_mode == 'comment': + ctx = self._get_web_context(self.test_record, add_web=False) + else: + ctx = self._get_web_context(self.test_records, add_web=False) + + # 1. check without template + template update + composer = self.env['mail.compose.message'].with_context(ctx).create({ + 'body': '

Test Body

', + 'partner_ids': base_recipients.ids, + 'reply_to': False, + 'subject': 'My amazing subject', + }) + + # creation values are taken + self.assertEqual(composer.partner_ids, base_recipients) + self.assertFalse(composer.reply_to) + self.assertFalse(composer.reply_to_force_new) + self.assertEqual(composer.reply_to_mode, 'update') + + # changing template should update its content + composer.write({'template_id': self.template.id}) + # currently onchange necessary + composer._onchange_template_id_wrapper() + new_partners = self.env['res.partner'].search( + [('email_normalized', 'in', ['test.cc.1@test.example.com', + 'test.cc.2@test.example.com']) + ] + ) + + # values come from template + if composition_mode == 'comment': + self.assertEqual(len(new_partners), 2) + self.assertEqual(composer.partner_ids, self.partner_1 + new_partners, 'Template took customer_id as set on record') + self.assertEqual(composer.reply_to, 'info@test.example.com', 'Template was rendered') + self.assertFalse(composer.reply_to_force_new) # should not change in comment mode + else: + self.assertEqual(len(new_partners), 0) + self.assertEqual(composer.partner_ids, base_recipients, 'Mass mode: kept original values') + self.assertEqual(composer.reply_to, self.template.reply_to, 'Mass mode: raw template value') + self.assertFalse(composer.reply_to_force_new) # should probably become True, not supported currently + + # manual values is kept over template + composer.write({'partner_ids': [(5, 0), (4, self.partner_admin.id)]}) + self.assertEqual(composer.partner_ids, self.partner_admin) + + # reset template should reset values + composer.write({'template_id': False}) + # currently onchange necessary + composer._onchange_template_id_wrapper() + + # values are kepts, not sure why + if composition_mode == 'comment': + self.assertEqual(composer.partner_ids, self.partner_admin, 'Values are kept, not sure why') + self.assertEqual(composer.reply_to, 'info@test.example.com', 'Values are kept') + self.assertFalse(composer.reply_to_force_new) + else: + self.assertEqual(composer.partner_ids, self.partner_admin, 'Mass mode: kept current value') + self.assertEqual(composer.reply_to, '{{ ctx.get("custom_reply_to") or "info@test.example.com" }}', 'Mass mode: kept current value') + self.assertFalse(composer.reply_to_force_new) + + # 2. check with default + ctx['default_template_id'] = self.template.id + composer = self.env['mail.compose.message'].with_context(ctx).create({ + 'template_id': self.template.id, + }) + # currently onchange necessary + composer._onchange_template_id_wrapper() + + # values come from template + if composition_mode == 'comment': + self.assertEqual(composer.partner_ids, self.partner_1 + new_partners) + else: + self.assertFalse(composer.partner_ids) + if composition_mode == 'comment': + self.assertEqual(composer.reply_to, "info@test.example.com") + else: + self.assertEqual(composer.reply_to, self.template.reply_to) + self.assertFalse(composer.reply_to_force_new) # note: this should be updated with reply-to + self.assertEqual(composer.reply_to_mode, 'update') # note: this should be updated with reply-to + + # 3. check at create + ctx.pop('default_template_id') + composer = self.env['mail.compose.message'].with_context(ctx).create({ + 'template_id': self.template.id, + }) + # currently onchange necessary + composer._onchange_template_id_wrapper() + + # values come from template + if composition_mode == 'comment': + self.assertEqual(composer.partner_ids, self.partner_1 + new_partners) + else: + self.assertFalse(composer.partner_ids) + if composition_mode == 'comment': + self.assertEqual(composer.reply_to, "info@test.example.com") + else: + self.assertEqual(composer.reply_to, self.template.reply_to) + self.assertFalse(composer.reply_to_force_new) + self.assertEqual(composer.reply_to_mode, 'update') + + # 4. template + user input + ctx['default_template_id'] = self.template.id + composer = self.env['mail.compose.message'].with_context(ctx).create({ + 'body': '

Test Body

', + 'partner_ids': base_recipients.ids, + 'subject': 'My amazing subject', + 'reply_to': False, + }) + + # creation values are taken + self.assertEqual(composer.partner_ids, base_recipients) + self.assertFalse(composer.reply_to) + self.assertFalse(composer.reply_to_force_new) + self.assertEqual(composer.reply_to_mode, 'update') + + self.env['res.partner'].search([ + ('email_normalized', 'in', ['test.cc.1@test.example.com', + 'test.cc.2@test.example.com']) + ]).unlink() + + @users('employee') + @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') def test_mail_composer_parent(self): """ Test specific management in comment mode when having parent_id set: record_name, subject, parent's partners. """ @@ -397,11 +606,13 @@ class TestComposerInternals(TestMailComposer): }) # creation values taken from parent - self.assertEqual(composer.subject, 'Re: %s' % self.test_record.name) self.assertEqual(composer.body, '

Test Body

') self.assertEqual(composer.partner_ids, self.partner_1 + self.partner_2) + self.assertEqual(composer.record_name, self.test_record.name) + self.assertEqual(composer.subject, 'Re: %s' % self.test_record.name) @users('user_rendering_restricted') + @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') def test_mail_composer_rights_attachments(self): """ Ensure a user without write access to a template can send an email""" template_1 = self.template.copy({ @@ -437,9 +648,13 @@ class TestComposerInternals(TestMailComposer): sorted(self.test_record.message_ids[0].attachment_ids.mapped('name')), sorted(template_1_attachment_name)) + @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') def test_mail_composer_rights_portal(self): portal_user = self._create_portal_user() + # give read access to the record to portal (for check access rule) + self.test_record.message_subscribe(partner_ids=portal_user.partner_id.ids) + # patch check access rights for write access, required to post a message by default with patch.object(MailTestTicket, 'check_access_rights', return_value=True): self.env['mail.compose.message'].with_user(portal_user).with_context( self._get_web_context(self.test_record) @@ -487,7 +702,7 @@ class TestComposerResultsComment(TestMailComposer): notification and emails generated during this process. """ @users('employee') - @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail') + @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') def test_mail_composer_notifications_delete(self): """ Notifications are correctly deleted once sent """ composer = self.env['mail.compose.message'].with_context( @@ -504,9 +719,29 @@ class TestComposerResultsComment(TestMailComposer): self.assertEqual(message.notified_partner_ids, self.partner_employee_2 + self.partner_1 + self.partner_2) # global outgoing - self.assertEqual(len(self._new_mails), 2, 'Should have created 2 mail.mail (1 for users, 1 for customers)') self.assertEqual(len(self._mails), 3, 'Should have sent an email each recipient') - self.assertEqual(self._new_mails.exists(), self.env['mail.mail'], 'Should have deleted mail.mail records') + self.assertEqual(len(self._new_mails), 2, 'Should have created 2 mail.mail (1 for users, 1 for customers)') + self.assertFalse(self._new_mails.exists(), 'Should have deleted mail.mail records') + + # Check ``auto_delete`` field usage (note: currently not correctly managed) + composer = self.env['mail.compose.message'].with_context( + self._get_web_context(self.test_record), + ).create({ + 'auto_delete': False, + 'body': '

Test Body

', + 'partner_ids': [(4, self.partner_1.id), (4, self.partner_2.id)] + }) + with self.mock_mail_gateway(mail_unlink_sent=True): + composer._action_send_mail() + + # notifications + message = self.test_record.message_ids[0] + self.assertEqual(message.notified_partner_ids, self.partner_employee_2 + self.partner_1 + self.partner_2) + + # global outgoing + self.assertEqual(len(self._mails), 3, 'Should have sent an email each recipient') + self.assertEqual(len(self._new_mails), 2, 'Should have created 2 mail.mail (1 for users, 1 for customers)') + self.assertEqual(len(self._new_mails.exists()), 0, 'To fix: does not respect auto_delete') # ensure ``mail_auto_delete`` context key allow to override this behavior composer = self.env['mail.compose.message'].with_context( @@ -524,12 +759,12 @@ class TestComposerResultsComment(TestMailComposer): self.assertEqual(message.notified_partner_ids, self.partner_employee_2 + self.partner_1 + self.partner_2) # global outgoing - self.assertEqual(len(self._new_mails), 2, 'Should have created 2 mail.mail (1 for users, 1 for customers)') self.assertEqual(len(self._mails), 3, 'Should have sent an email each recipient') + self.assertEqual(len(self._new_mails), 2, 'Should have created 2 mail.mail (1 for users, 1 for customers)') self.assertEqual(len(self._new_mails.exists()), 2, 'Should not have deleted mail.mail records') @users('employee') - @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail') + @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') def test_mail_composer_recipients(self): """ Test partner_ids given to composer are given to the final message. """ composer = self.env['mail.compose.message'].with_context( @@ -541,8 +776,8 @@ class TestComposerResultsComment(TestMailComposer): composer._action_send_mail() message = self.test_record.message_ids[0] - self.assertEqual(message.body, '

Test Body

') self.assertEqual(message.author_id, self.user_employee.partner_id) + self.assertEqual(message.body, '

Test Body

') self.assertEqual(message.subject, 'Re: %s' % self.test_record.name) self.assertEqual(message.subtype_id, self.env.ref('mail.mt_comment')) self.assertEqual(message.partner_ids, self.partner_1 | self.partner_2) @@ -607,10 +842,11 @@ class TestComposerResultsComment(TestMailComposer): email_values={ 'body_content': 'TemplateBody %s' % self.test_record.name, 'email_from': self.test_record.user_id.email_formatted, # set by template + 'mail_server_id': self.mail_server_domain, 'subject': 'TemplateSubject %s' % self.test_record.name, 'attachments_info': [ - {'name': '00.txt', 'raw': b'Att00', 'type': 'text/plain'}, - {'name': '01.txt', 'raw': b'Att01', 'type': 'text/plain'}, + {'name': 'AttFileName_00.txt', 'raw': b'AttContent_00', 'type': 'text/plain'}, + {'name': 'AttFileName_01.txt', 'raw': b'AttContent_01', 'type': 'text/plain'}, {'name': 'TestReport for %s.html' % self.test_record.name, 'type': 'text/plain'}, ] }, @@ -622,10 +858,11 @@ class TestComposerResultsComment(TestMailComposer): email_values={ 'body_content': 'TemplateBody %s' % self.test_record.name, 'email_from': self.test_record.user_id.email_formatted, # set by template + 'mail_server_id': self.mail_server_domain, 'subject': 'TemplateSubject %s' % self.test_record.name, 'attachments_info': [ - {'name': '00.txt', 'raw': b'Att00', 'type': 'text/plain'}, - {'name': '01.txt', 'raw': b'Att01', 'type': 'text/plain'}, + {'name': 'AttFileName_00.txt', 'raw': b'AttContent_00', 'type': 'text/plain'}, + {'name': 'AttFileName_01.txt', 'raw': b'AttContent_01', 'type': 'text/plain'}, {'name': 'TestReport for %s.html' % self.test_record.name, 'type': 'text/plain'}, ] }, @@ -638,7 +875,7 @@ class TestComposerResultsComment(TestMailComposer): # attachments are copied on message and linked to document self.assertEqual( set(message.attachment_ids.mapped('name')), - set(['00.txt', '01.txt', 'TestReport for %s.html' % self.test_record.name]) + set(['AttFileName_00.txt', 'AttFileName_01.txt', 'TestReport for %s.html' % self.test_record.name]) ) self.assertEqual(set(message.attachment_ids.mapped('res_model')), set([self.test_record._name])) self.assertEqual(set(message.attachment_ids.mapped('res_id')), set(self.test_record.ids)) @@ -656,10 +893,73 @@ class TestComposerResultsMass(TestMailComposer): 'groups_id': [(4, cls.env.ref('base.group_partner_manager').id)], }) + + @users('employee') + @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') + def test_mail_composer_delete(self): + """ Check mail / msg delete support """ + # ensure initial data + self.assertTrue(self.template.auto_delete) + self.assertEqual(self.test_records.user_id, self.user_employee_2) + self.assertEqual(self.test_records.message_partner_ids, self.partner_employee_2) + + composer_form = Form(self.env['mail.compose.message'].with_context( + self._get_web_context(self.test_records, add_web=True, + default_template_id=self.template.id) + )) + composer = composer_form.save() + with self.mock_mail_gateway(mail_unlink_sent=True), self.mock_mail_app(): + composer._action_send_mail() + + self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record') + self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record') + self.assertFalse(self._new_mails.exists(), 'Should have deleted mail.mail records') + self.assertEqual(len(self._new_msgs), 2, 'Should have created 1 mail.mail per record') + self.assertEqual(self._new_msgs.exists(), self._new_msgs, 'Should not have deleted mail.message records') + + # force composer auto_delete field + composer_form = Form(self.env['mail.compose.message'].with_context( + self._get_web_context(self.test_records, add_web=True, + default_template_id=self.template.id) + )) + composer = composer_form.save() + composer.auto_delete = False + with self.mock_mail_gateway(mail_unlink_sent=True), self.mock_mail_app(): + composer._action_send_mail() + + self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record') + self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record') + # self.assertEqual(self._new_mails.exists(), self._new_mails, 'Should not have deleted mail.mail records') + self.assertFalse(self._new_mails.exists(), 'Template is forced over composer value, which is not correct') + self.assertEqual(len(self._new_msgs), 2, 'Should have created 1 mail.mail per record') + self.assertEqual(self._new_msgs.exists(), self._new_msgs, 'Should not have deleted mail.message records') + + # check composer auto_delete_message + composer_form = Form(self.env['mail.compose.message'].with_context( + self._get_web_context(self.test_records, add_web=True, + default_template_id=self.template.id) + )) + composer = composer_form.save() + composer.auto_delete_message = True + with self.mock_mail_gateway(mail_unlink_sent=True), self.mock_mail_app(): + composer._action_send_mail() + + # global outgoing + self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record') + self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record') + self.assertFalse(self._new_mails.exists(), 'Should have deleted mail.mail records') + self.assertEqual(len(self._new_msgs), 2, 'Should have created 1 mail.mail per record') + self.assertFalse(self._new_msgs.exists(), 'Should have deleted mail.message records') + @users('employee') @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') def test_mail_composer_wtpl(self): self.template.auto_delete = False # keep sent emails to check content + + # ensure initial data + self.assertEqual(self.test_records.user_id, self.user_employee_2) + self.assertEqual(self.test_records.message_partner_ids, self.partner_employee_2) + composer_form = Form(self.env['mail.compose.message'].with_context( self._get_web_context(self.test_records, add_web=True, default_template_id=self.template.id) @@ -679,15 +979,18 @@ class TestComposerResultsMass(TestMailComposer): # template is sent directly using customer field, meaning we have recipients self.assertMailMail(record.customer_id, 'sent', mail_message=message, - author=self.partner_employee) + author=self.partner_employee, + email_values={ + 'email_from': self.partner_employee_2.email_formatted, + }) # message content self.assertEqual(message.subject, 'TemplateSubject %s' % record.name) self.assertEqual(message.body, '

TemplateBody %s

' % record.name) self.assertEqual(message.author_id, self.user_employee.partner_id) # post-related fields are void - self.assertEqual(message.subtype_id, self.env['mail.message.subtype']) - self.assertEqual(message.partner_ids, self.env['res.partner']) + self.assertFalse(message.subtype_id) + self.assertFalse(message.partner_ids) @users('employee') @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') @@ -712,8 +1015,8 @@ class TestComposerResultsMass(TestMailComposer): self.assertEqual(len(attachs), 2) # ensure initial data - self.assertEqual(self.test_records.user_id, self.env['res.users']) - self.assertEqual(self.test_records.message_partner_ids, self.env['res.partner']) + self.assertEqual(self.test_records.user_id, self.user_employee_2) + self.assertEqual(self.test_records.message_partner_ids, self.partner_employee_2) # launch composer in mass mode composer_form = Form(self.env['mail.compose.message'].with_context( @@ -752,72 +1055,17 @@ class TestComposerResultsMass(TestMailComposer): author=self.partner_employee, email_values={ 'body_content': 'TemplateBody %s' % record.name, + 'email_from': self.partner_employee_2.email_formatted, + 'mail_server_id': self.mail_server_global.id, 'subject': 'TemplateSubject %s' % record.name, - # 'attachments': [ - # ('00.txt', b'Att00', 'text/plain'), - # ('01.txt', b'Att01', 'text/plain'), - # ('report.test_mail.mail_test_ticket_test_template.html', b'My second attachment', 'text/plain') - # ] + 'attachments_info': [ + {'name': 'AttFileName_00.txt', 'raw': b'AttContent_00', 'type': 'text/plain'}, + {'name': 'AttFileName_01.txt', 'raw': b'AttContent_01', 'type': 'text/plain'}, + {'name': 'TestReport for %s.html' % record.name, 'type': 'text/plain'}, + ] } ) - @users('employee') - @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') - def test_mail_composer_wtpl_delete(self): - self.template.auto_delete = True - composer_form = Form(self.env['mail.compose.message'].with_context( - self._get_web_context(self.test_records, add_web=True, - default_template_id=self.template.id) - )) - composer = composer_form.save() - with self.mock_mail_gateway(mail_unlink_sent=True): - composer._action_send_mail() - - # global outgoing - self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record') - self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record') - self.assertEqual(self._new_mails.exists(), self.env['mail.mail'], 'Should have deleted mail.mail records') - - for record in self.test_records: - # message copy is kept - message = record.message_ids[0] - - # template is sent directly using customer field - self.assertSentEmail(self.partner_employee, record.customer_id) - - # message content - self.assertEqual(message.subject, 'TemplateSubject %s' % record.name) - self.assertEqual(message.body, '

TemplateBody %s

' % record.name) - self.assertEqual(message.author_id, self.user_employee.partner_id) - # post-related fields are void - self.assertEqual(message.subtype_id, self.env['mail.message.subtype']) - self.assertEqual(message.partner_ids, self.env['res.partner']) - - @users('employee') - @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') - def test_mail_composer_wtpl_delete_notif(self): - self.template.auto_delete = True - composer_form = Form(self.env['mail.compose.message'].with_context( - self._get_web_context(self.test_records, add_web=True, - default_template_id=self.template.id, - default_auto_delete_message=True) - )) - composer = composer_form.save() - with self.mock_mail_gateway(mail_unlink_sent=True): - composer._action_send_mail() - - # global outgoing - self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record') - self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record') - self.assertEqual(self._new_mails.exists(), self.env['mail.mail'], 'Should have deleted mail.mail records') - - for record in self.test_records: - # message copy is unlinked - self.assertEqual(record.message_ids, self.env['mail.message'], 'Should have deleted mail.message records') - - # template is sent directly using customer field - self.assertSentEmail(self.partner_employee, record.customer_id) - @users('employee') @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') def test_mail_composer_wtpl_recipients(self): @@ -841,7 +1089,8 @@ class TestComposerResultsMass(TestMailComposer): for record in self.test_records: # template is sent directly using customer field - self.assertSentEmail(self.partner_employee, record.customer_id) + self.assertSentEmail(self.partner_employee_2.email_formatted, record.customer_id, + author_id=self.partner_employee) # 2: active_domain not taken into account if use_active_domain is False composer_form = Form(self.env['mail.compose.message'].with_context( diff --git a/addons/test_mail/tests/test_mail_composer_mixin.py b/addons/test_mail/tests/test_mail_composer_mixin.py index d13473f7801..5b85c34ed5c 100644 --- a/addons/test_mail/tests/test_mail_composer_mixin.py +++ b/addons/test_mail/tests/test_mail_composer_mixin.py @@ -12,7 +12,6 @@ class TestMailComposerMixin(TestMailCommon, TestRecipients): @classmethod def setUpClass(cls): super(TestMailComposerMixin, cls).setUpClass() - cls._init_mail_gateway() cls.mail_template = cls.env['mail.template'].create({ 'subject': 'Subject for {{ object.name }}', diff --git a/addons/test_mail/tests/test_mail_followers.py b/addons/test_mail/tests/test_mail_followers.py index 8e6a54c0501..4a1e0d94258 100644 --- a/addons/test_mail/tests/test_mail_followers.py +++ b/addons/test_mail/tests/test_mail_followers.py @@ -14,7 +14,6 @@ class BaseFollowersTest(TestMailCommon): super(BaseFollowersTest, cls).setUpClass() cls.test_record = cls.env['mail.test.simple'].with_context(cls._test_context).create({'name': 'Test', 'email_from': 'ignasse@example.com'}) cls._create_portal_user() - cls._create_channel_listener() # allow employee to update partners cls.user_employee.write({'groups_id': [(4, cls.env.ref('base.group_partner_manager').id)]}) diff --git a/addons/test_mail/tests/test_mail_gateway.py b/addons/test_mail/tests/test_mail_gateway.py index aff9f63bf71..d5e1e669325 100644 --- a/addons/test_mail/tests/test_mail_gateway.py +++ b/addons/test_mail/tests/test_mail_gateway.py @@ -247,8 +247,6 @@ class TestMailgateway(TestMailCommon): 'message_id': '<123456-openerp-%s-mail.test.gateway@%s>' % (cls.test_record.id, socket.gethostname()), }) - cls._init_mail_gateway() - # -------------------------------------------------- # Base low-level tests # -------------------------------------------------- @@ -1277,8 +1275,6 @@ class TestMailThreadCC(TestMailCommon): 'alias_model_id': cls.env['ir.model']._get('mail.test.cc').id, 'alias_contact': 'everyone'}) - cls._init_mail_gateway() - @mute_logger('odoo.addons.mail.models.mail_thread') def test_message_cc_new(self): record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'cc_record@test.com', diff --git a/addons/test_mail/tests/test_mail_mail.py b/addons/test_mail/tests/test_mail_mail.py index 04f48e1a992..90688b004b8 100644 --- a/addons/test_mail/tests/test_mail_mail.py +++ b/addons/test_mail/tests/test_mail_mail.py @@ -2,22 +2,25 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import psycopg2 +import smtplib + +from OpenSSL.SSL import Error as SSLError +from socket import gaierror, timeout from unittest.mock import call from odoo import api -from odoo.addons.base.tests.common import MockSmtplibCase +from odoo.addons.base.models.ir_mail_server import MailDeliveryException from odoo.addons.test_mail.tests.common import TestMailCommon from odoo.tests import common, tagged from odoo.tools import mute_logger @tagged('mail_mail') -class TestMailMail(TestMailCommon, MockSmtplibCase): +class TestMailMail(TestMailCommon): @classmethod def setUpClass(cls): super(TestMailMail, cls).setUpClass() - cls._init_mail_gateway() cls._init_mail_servers() cls.server_domain_2 = cls.env['ir.mail_server'].create({ @@ -31,9 +34,36 @@ class TestMailMail(TestMailCommon, MockSmtplibCase): 'email_from': 'ignasse@example.com', }).with_context({}) + cls.test_message = cls.test_record.message_post(body='

Message

', subject='Subject') + cls.test_mail = cls.env['mail.mail'].create([{ + 'body': '

Body

', + 'email_from': False, + 'email_to': 'test@example.com', + 'is_notification': True, + 'subject': 'Subject', + }]) + cls.test_notification = cls.env['mail.notification'].create({ + 'is_read': False, + 'mail_mail_id': cls.test_mail.id, + 'mail_message_id': cls.test_message.id, + 'notification_type': 'email', + 'res_partner_id': cls.partner_employee.id, # not really used for matching except multi-recipients + }) + + cls.emails_falsy = [False, '', ' '] + cls.emails_invalid = ['buggy', 'buggy, wrong'] + cls.emails_invalid_ascii = ['raoul@example¢¡.com'] + cls.emails_valid = ['raoul¢¡@example.com', 'raoul@example.com'] + + def _reset_data(self): + self._init_mail_mock() + self.test_mail.write({'failure_reason': False, 'failure_type': False, 'state': 'outgoing'}) + self.test_notification.write({'failure_type': False, 'notification_status': 'ready'}) + + @mute_logger('odoo.addons.mail.models.mail_mail') - def test_mail_message_notify_from_mail_mail(self): - # Due ot post-commit hooks, store send emails in every step + def test_mail_mail_recipients(self): + """ Partner_ids is a field used from mail_message, but not from mail_mail. """ mail = self.env['mail.mail'].sudo().create({ 'body_html': '

Test

', 'email_to': 'test@example.com', @@ -44,6 +74,18 @@ class TestMailMail(TestMailCommon, MockSmtplibCase): self.assertSentEmail(mail.env.user.partner_id, ['test@example.com']) self.assertEqual(len(self._mails), 1) + mail = self.env['mail.mail'].sudo().create({ + 'body_html': '

Test

', + 'email_to': 'test@example.com', + 'recipient_ids': [(4, self.user_employee.partner_id.id)], + }) + with self.mock_mail_gateway(): + mail.send() + self.assertSentEmail(mail.env.user.partner_id, ['test@example.com']) + self.assertSentEmail(mail.env.user.partner_id, [self.user_employee.email_formatted]) + self.assertEqual(len(self._mails), 2) + + @mute_logger('odoo.addons.mail.models.mail_mail') def test_mail_mail_return_path(self): # mail without thread-enabled record base_values = { @@ -65,6 +107,354 @@ class TestMailMail(TestMailCommon, MockSmtplibCase): mail.send() self.assertEqual(self._mails[0]['headers']['Return-Path'], '%s@%s' % (self.alias_bounce, self.alias_domain)) + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_mail_mail_send_exceptions_origin(self): + """ Test various use case with exceptions and errors and see how they are + managed and stored at mail and notification level. """ + mail, notification = self.test_mail, self.test_notification + + # MailServer.build_email(): invalid from + self.env['ir.config_parameter'].set_param('mail.default.from', '') + self._reset_data() + with self.mock_mail_gateway(), mute_logger('odoo.addons.mail.models.mail_mail'): + mail.send(raise_exception=False) + self.assertFalse(self._mails[0]['email_from']) + self.assertIn( + 'You must either provide a sender address explicitly or configure using the combination of `mail.catchall.domain` and `mail.default.from` ICPs', + mail.failure_reason) + self.assertFalse(mail.failure_type, 'Mail: void from: no failure type, should be updated') + self.assertEqual(mail.state, 'exception') + self.assertEqual(notification.failure_type, 'unknown', 'Mail: void from: unknown failure type, should be updated') + self.assertEqual(notification.notification_status, 'exception') + + # MailServer.send_email(): _prepare_email_message: unexpected ASCII + # Force catchall domain to void otherwise bounce is set to postmaster-odoo@domain + self.env['ir.config_parameter'].set_param('mail.catchall.domain', '') + self._reset_data() + mail.write({'email_from': 'strange@example¢¡.com'}) + with self.mock_mail_gateway(): + mail.send(raise_exception=False) + self.assertEqual(self._mails[0]['email_from'], 'strange@example¢¡.com') + self.assertIn('Codepoint U+00A2 at position', mail.failure_reason) + self.assertFalse(mail.failure_type, 'Mail: bugged from (ascii): no failure type, should be updated') + self.assertEqual(mail.state, 'exception') + self.assertEqual(notification.failure_type, 'unknown', 'Mail: bugged from (ascii): unknown failure type, should be updated') + self.assertEqual(notification.notification_status, 'exception') + + # MailServer.send_email(): _prepare_email_message: unexpected ASCII based on catchall domain + self.env['ir.config_parameter'].set_param('mail.catchall.domain', 'domain¢¡.com') + self._reset_data() + mail.write({'email_from': 'test.user@example.com'}) + with self.mock_mail_gateway(): + mail.send(raise_exception=False) + self.assertEqual(self._mails[0]['email_from'], 'test.user@example.com') + self.assertIn('Codepoint U+00A2 at position', mail.failure_reason) + self.assertFalse(mail.failure_type, 'Mail: bugged catchall domain (ascii): no failure type, should be updated') + self.assertEqual(mail.state, 'exception') + self.assertEqual(notification.failure_type, 'unknown', 'Mail: bugged catchall domain (ascii): unknown failure type, should be updated') + self.assertEqual(notification.notification_status, 'exception') + + # MailServer.send_email(): _prepare_email_message: Malformed 'Return-Path' or 'From' address + self.env['ir.config_parameter'].set_param('mail.catchall.domain', '') + self._reset_data() + mail.write({'email_from': 'robert'}) + with self.mock_mail_gateway(): + mail.send(raise_exception=False) + self.assertEqual(self._mails[0]['email_from'], 'robert') + self.assertIn("Malformed 'Return-Path' or 'From' address: robert", mail.failure_reason) + self.assertFalse(mail.failure_type, 'Mail: bugged from (ascii): no failure type, should be updated') + self.assertEqual(mail.state, 'exception') + self.assertEqual(notification.failure_type, 'unknown', 'Mail: bugged from (ascii): unknown failure type, should be updated') + self.assertEqual(notification.notification_status, 'exception') + + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_mail_mail_send_exceptions_recipients_emails(self): + """ Test various use case with exceptions and errors and see how they are + managed and stored at mail and notification level. """ + mail, notification = self.test_mail, self.test_notification + + self.env['ir.config_parameter'].set_param('mail.catchall.domain', self.alias_domain) + self.env['ir.config_parameter'].set_param('mail.default.from', self.default_from) + + # MailServer.send_email(): _prepare_email_message: missing To + for email_to in self.emails_falsy: + self._reset_data() + mail.write({'email_to': email_to}) + with self.mock_mail_gateway(): + mail.send(raise_exception=False) + self.assertIn('Error without exception. Probably due do sending an email without computed recipients.', mail.failure_reason) + self.assertFalse(mail.failure_type, 'Mail: missing email_to: no failure type, should be updated') + self.assertEqual(mail.state, 'exception') + if email_to == ' ': + self.assertEqual(notification.failure_type, 'mail_email_missing') + self.assertEqual(notification.notification_status, 'exception') + else: + self.assertEqual(notification.failure_type, False, 'Mail: missing email_to: notification is wrongly set as sent') + self.assertEqual(notification.notification_status, 'sent', 'Mail: missing email_to: notification is wrongly set as sent') + + # MailServer.send_email(): _prepare_email_message: invalid To + for email_to, failure_type in zip(self.emails_invalid, + ['mail_email_missing', 'mail_email_missing']): + self._reset_data() + mail.write({'email_to': email_to}) + with self.mock_mail_gateway(): + mail.send(raise_exception=False) + self.assertIn('Error without exception. Probably due do sending an email without computed recipients.', mail.failure_reason) + self.assertFalse(mail.failure_type, 'Mail: invalid email_to: no failure type, should be updated') + self.assertEqual(mail.state, 'exception') + self.assertEqual(notification.failure_type, failure_type, 'Mail: invalid email_to: missing instead of invalid') + self.assertEqual(notification.notification_status, 'exception') + + # MailServer.send_email(): _prepare_email_message: invalid To (ascii) + for email_to in self.emails_invalid_ascii: + self._reset_data() + mail.write({'email_to': email_to}) + with self.mock_mail_gateway(): + mail.send(raise_exception=False) + self.assertIn('Codepoint U+00A2 at position', mail.failure_reason) + self.assertFalse(mail.failure_type, 'Mail: invalid (ascii) recipient partner: no failure type, should be updated') + self.assertEqual(mail.state, 'exception') + self.assertEqual(notification.failure_type, 'unknown', 'Mail: invalid (ascii) recipient partner: unknown failure type, should be updated') + self.assertEqual(notification.notification_status, 'exception') + + # MailServer.send_email(): _prepare_email_message: ok To (ascii or just ok) + for email_to in self.emails_valid: + self._reset_data() + mail.write({'email_to': email_to}) + with self.mock_mail_gateway(): + mail.send(raise_exception=False) + self.assertFalse(mail.failure_reason) + self.assertFalse(mail.failure_type) + self.assertEqual(mail.state, 'sent') + self.assertFalse(notification.failure_type) + self.assertEqual(notification.notification_status, 'sent') + + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_mail_mail_send_exceptions_recipients_partners(self): + """ Test various use case with exceptions and errors and see how they are + managed and stored at mail and notification level. """ + mail, notification = self.test_mail, self.test_notification + + mail.write({'email_from': 'test.user@test.example.com', 'email_to': False}) + partners_falsy = self.env['res.partner'].create([ + {'name': 'Name %s' % email, 'email': email} + for email in self.emails_falsy + ]) + partners_invalid = self.env['res.partner'].create([ + {'name': 'Name %s' % email, 'email': email} + for email in self.emails_invalid + ]) + partners_invalid_ascii = self.env['res.partner'].create([ + {'name': 'Name %s' % email, 'email': email} + for email in self.emails_invalid_ascii + ]) + partners_valid = self.env['res.partner'].create([ + {'name': 'Name %s' % email, 'email': email} + for email in self.emails_valid + ]) + + # void values + for partner in partners_falsy: + self._reset_data() + mail.write({'recipient_ids': [(5, 0), (4, partner.id)]}) + notification.write({'res_partner_id': partner.id}) + with self.mock_mail_gateway(): + mail.send(raise_exception=False) + self.assertEqual(mail.failure_reason, 'Error without exception. Probably due do sending an email without computed recipients.') + self.assertFalse(mail.failure_type, 'Mail: void recipient partner: no failure type, should be updated') + self.assertEqual(mail.state, 'exception') + self.assertEqual(notification.failure_type, 'mail_email_invalid', 'Mail: void recipient partner: should be missing, not invalid') + self.assertEqual(notification.notification_status, 'exception') + + # wrong values + for partner in partners_invalid: + self._reset_data() + mail.write({'recipient_ids': [(5, 0), (4, partner.id)]}) + notification.write({'res_partner_id': partner.id}) + with self.mock_mail_gateway(): + mail.send(raise_exception=False) + self.assertEqual(mail.failure_reason, 'Error without exception. Probably due do sending an email without computed recipients.') + self.assertFalse(mail.failure_type, 'Mail: invalid recipient partner: no failure type, should be updated') + self.assertEqual(mail.state, 'exception') + self.assertEqual(notification.failure_type, 'mail_email_invalid') + self.assertEqual(notification.notification_status, 'exception') + + # ascii ko + for partner in partners_invalid_ascii: + self._reset_data() + mail.write({'recipient_ids': [(5, 0), (4, partner.id)]}) + notification.write({'res_partner_id': partner.id}) + with self.mock_mail_gateway(): + mail.send(raise_exception=False) + self.assertIn('Codepoint U+00A2 at position', mail.failure_reason) + self.assertFalse(mail.failure_type, 'Mail: invalid (ascii) recipient partner: no failure type, should be updated') + self.assertEqual(mail.state, 'exception') + self.assertEqual(notification.failure_type, 'unknown', 'Mail: invalid (ascii) recipient partner: unknown failure type, should be updated') + self.assertEqual(notification.notification_status, 'exception') + + # ascii ok or just ok + for partner in partners_valid: + self._reset_data() + mail.write({'recipient_ids': [(5, 0), (4, partner.id)]}) + notification.write({'res_partner_id': partner.id}) + with self.mock_mail_gateway(): + mail.send(raise_exception=False) + self.assertFalse(mail.failure_reason) + self.assertFalse(mail.failure_type) + self.assertEqual(mail.state, 'sent') + self.assertFalse(notification.failure_type) + self.assertEqual(notification.notification_status, 'sent') + + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_mail_mail_send_exceptions_recipients_partners_mixed(self): + """ Test various use case with exceptions and errors and see how they are + managed and stored at mail and notification level. """ + mail, notification = self.test_mail, self.test_notification + + mail.write({'email_to': 'test@example.com'}) + partners_falsy = self.env['res.partner'].create([ + {'name': 'Name %s' % email, 'email': email} + for email in self.emails_falsy + ]) + partners_invalid = self.env['res.partner'].create([ + {'name': 'Name %s' % email, 'email': email} + for email in self.emails_invalid + ]) + partners_valid = self.env['res.partner'].create([ + {'name': 'Name %s' % email, 'email': email} + for email in self.emails_valid + ]) + + # valid to, missing email for recipient or wrong email for recipient + for partner in partners_falsy + partners_invalid: + self._reset_data() + mail.write({'recipient_ids': [(5, 0), (4, partner.id)]}) + notification.write({'res_partner_id': partner.id}) + with self.mock_mail_gateway(): + mail.send(raise_exception=False) + self.assertFalse(mail.failure_reason, 'Mail: at least one valid recipient, mail is sent to avoid send loops and spam') + self.assertFalse(mail.failure_type, 'Mail: at least one valid recipient, mail is sent to avoid send loops and spam') + self.assertEqual(mail.state, 'sent', 'Mail: at least one valid recipient, mail is sent to avoid send loops and spam') + self.assertEqual(notification.failure_type, 'mail_email_invalid', 'Mail: void email considered as invalid') + self.assertEqual(notification.notification_status, 'exception') + + # update to have valid partner and invalid partner + mail.write({'recipient_ids': [(5, 0), (4, partners_valid[1].id), (4, partners_falsy[0].id)]}) + notification.write({'res_partner_id': partners_valid[1].id}) + notification2 = notification.create({ + 'is_read': False, + 'mail_mail_id': mail.id, + 'mail_message_id': self.test_message.id, + 'notification_type': 'email', + 'res_partner_id': partners_falsy[0].id, + }) + + # missing to / invalid to + for email_to in self.emails_falsy + self.emails_invalid: + self._reset_data() + notification2.write({'failure_type': False, 'notification_status': 'ready'}) + mail.write({'email_to': email_to}) + with self.mock_mail_gateway(): + mail.send(raise_exception=False) + self.assertFalse(mail.failure_reason, 'Mail: at least one valid recipient, mail is sent to avoid send loops and spam') + self.assertFalse(mail.failure_type, 'Mail: at least one valid recipient, mail is sent to avoid send loops and spam') + self.assertEqual(mail.state, 'sent', 'Mail: at least one valid recipient, mail is sent to avoid send loops and spam') + self.assertFalse(notification.failure_type) + self.assertEqual(notification.notification_status, 'sent') + self.assertEqual(notification2.failure_type, 'mail_email_invalid') + self.assertEqual(notification2.notification_status, 'exception') + + # buggy to (ascii) + for email_to in self.emails_invalid_ascii: + self._reset_data() + notification2.write({'failure_type': False, 'notification_status': 'ready'}) + mail.write({'email_to': email_to}) + with self.mock_mail_gateway(): + mail.send(raise_exception=False) + self.assertIn('Codepoint U+00A2 at position', mail.failure_reason) + self.assertFalse(mail.failure_type, 'Mail: at least one valid recipient, mail is sent to avoid send loops and spam') + self.assertEqual(mail.state, 'exception') + self.assertEqual(notification.failure_type, 'unknown') + self.assertEqual(notification.notification_status, 'exception') + self.assertEqual(notification2.failure_type, 'unknown') + self.assertEqual(notification2.notification_status, 'exception') + + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_mail_mail_send_exceptions_raise_management(self): + """ Test various use case with exceptions and errors and see how they are + managed and stored at mail and notification level. """ + mail, notification = self.test_mail, self.test_notification + mail.write({'email_from': 'test.user@test.example.com', 'email_to': 'test@example.com'}) + + # SMTP connecting issues + with self.mock_mail_gateway(): + _connect_current = self.connect_mocked.side_effect + + # classic errors that may be raised during sending, just to test their current support + for error, msg in [ + (smtplib.SMTPServerDisconnected('SMTPServerDisconnected'), 'SMTPServerDisconnected'), + (smtplib.SMTPResponseException('code', 'SMTPResponseException'), 'code\nSMTPResponseException'), + (smtplib.SMTPNotSupportedError('SMTPNotSupportedError'), 'SMTPNotSupportedError'), + (smtplib.SMTPException('SMTPException'), 'SMTPException'), + (SSLError('SSLError'), 'SSLError'), + (gaierror('gaierror'), 'gaierror'), + (timeout('timeout'), 'timeout')]: + + def _connect(*args, **kwargs): + raise error + self.connect_mocked.side_effect = _connect + + mail.send(raise_exception=False) + self.assertFalse(mail.failure_type) + self.assertEqual(mail.failure_reason, msg) + self.assertEqual(mail.state, 'exception') + self.assertEqual(notification.failure_type, 'mail_smtp') + self.assertEqual(notification.notification_status, 'exception') + self._reset_data() + + self.connect_mocked.side_effect = _connect_current + + # SMTP sending issues + with self.mock_mail_gateway(): + _send_current = self.send_email_mocked.side_effect + self._reset_data() + mail.write({'email_to': 'test@example.com'}) + + # should always raise for those errors, even with raise_exception=False + for error, error_class in [ + (smtplib.SMTPServerDisconnected("Some exception"), smtplib.SMTPServerDisconnected), + (MemoryError("Some exception"), MemoryError)]: + def _send_email(*args, **kwargs): + raise error + self.send_email_mocked.side_effect = _send_email + + with self.assertRaises(error_class): + mail.send(raise_exception=False) + self.assertFalse(mail.failure_reason, 'SMTPServerDisconnected/MemoryError during Send raises and lead to a rollback') + self.assertFalse(mail.failure_type, 'SMTPServerDisconnected/MemoryError during Send raises and lead to a rollback') + self.assertEqual(mail.state, 'outgoing', 'SMTPServerDisconnected/MemoryError during Send raises and lead to a rollback') + self.assertFalse(notification.failure_type, 'SMTPServerDisconnected/MemoryError during Send raises and lead to a rollback') + self.assertEqual(notification.notification_status, 'ready', 'SMTPServerDisconnected/MemoryError during Send raises and lead to a rollback') + + # MailDeliveryException: should be catched; other issues are sub-catched under + # a MailDeliveryException and are catched + for error, msg in [ + (MailDeliveryException("Some exception"), 'Some exception'), + (ValueError("Unexpected issue"), 'Unexpected issue')]: + def _send_email(*args, **kwargs): + raise error + self.send_email_mocked.side_effect = _send_email + + self._reset_data() + mail.send(raise_exception=False) + self.assertEqual(mail.failure_reason, msg) + self.assertFalse(mail.failure_type, 'Mail: unlogged failure type to fix') + self.assertEqual(mail.state, 'exception') + self.assertEqual(notification.failure_type, 'unknown', 'Mail: generic failure type') + self.assertEqual(notification.notification_status, 'exception') + + self.send_email_mocked.side_effect = _send_current + @mute_logger('odoo.addons.mail.models.mail_mail') def test_mail_mail_send_server(self): """Test that the mails are send in batch. @@ -133,6 +523,7 @@ class TestMailMail(TestMailCommon, MockSmtplibCase): self.assert_email_sent_smtp(message_from='user_1@test_2.com', emails_count=5, from_filter=self.server_domain.from_filter) +@tagged('mail_mail') class TestMailMailRace(common.TransactionCase): @mute_logger('odoo.addons.mail.models.mail_mail') diff --git a/addons/test_mail/tests/test_mail_message.py b/addons/test_mail/tests/test_mail_message.py index f4bfe292a68..0ebbc5e9f05 100644 --- a/addons/test_mail/tests/test_mail_message.py +++ b/addons/test_mail/tests/test_mail_message.py @@ -18,7 +18,6 @@ class TestMessageValues(TestMailCommon): def setUpClass(cls): super(TestMessageValues, cls).setUpClass() - cls._init_mail_gateway() cls.alias_record = cls.env['mail.test.container'].with_context(cls._test_context).create({ 'name': 'Pigs', 'alias_name': 'pigs', diff --git a/addons/test_mail/tests/test_mail_multicompany.py b/addons/test_mail/tests/test_mail_multicompany.py index 18082651e69..58309a7482f 100644 --- a/addons/test_mail/tests/test_mail_multicompany.py +++ b/addons/test_mail/tests/test_mail_multicompany.py @@ -47,8 +47,6 @@ class TestMultiCompanySetup(TestMailCommon, TestRecipients): 'message_id': '<123456-openerp-%s-mail.test.gateway@%s>' % (cls.test_record.id, socket.gethostname()), }) - cls._init_mail_gateway() - @users('employee') def test_notify_reply_to_computation(self): test_record = self.env['mail.test.gateway'].browse(self.test_record.ids) diff --git a/addons/test_mail/tests/test_mail_thread_internals.py b/addons/test_mail/tests/test_mail_thread_internals.py index 58c7a0b2538..56b9258070a 100644 --- a/addons/test_mail/tests/test_mail_thread_internals.py +++ b/addons/test_mail/tests/test_mail_thread_internals.py @@ -103,6 +103,7 @@ class TestChatterTweaks(TestMailCommon, TestRecipients): self.assertTrue(record.name) +@tagged('mail_thread') class TestDiscuss(TestMailCommon, TestRecipients): @classmethod @@ -239,7 +240,12 @@ class TestDiscuss(TestMailCommon, TestRecipients): partner_ids=[self.user_employee.partner_id.id] ) self.assertFalse(message.has_error) - with self.mock_mail_gateway(sim_error='connect_smtp_notfound'): + + with self.mock_mail_gateway(): + def _connect(*args, **kwargs): + raise Exception("Some exception") + self.connect_mocked.side_effect = _connect + self.user_admin.notification_type = 'email' message2 = self.test_record.with_user(self.user_employee).message_post( body='Test', message_type='comment', subtype_xmlid='mail.mt_comment', diff --git a/addons/test_mail/tests/test_message_management.py b/addons/test_mail/tests/test_message_management.py index 57d24e47f92..e1e5ab203e9 100644 --- a/addons/test_mail/tests/test_message_management.py +++ b/addons/test_mail/tests/test_message_management.py @@ -34,8 +34,10 @@ class TestMailResend(TestMailCommon): def test_mail_resend_workflow(self): with self.assertSinglePostNotifications( [{'partner': partner, 'type': 'email', 'status': 'exception'} for partner in self.partners], - message_info={'message_type': 'notification'}, - sim_error='connect_failure'): + message_info={'message_type': 'notification'}): + def _connect(*args, **kwargs): + raise Exception("Some exception") + self.connect_mocked.side_effect = _connect message = self.test_record.with_user(self.user_admin).message_post(partner_ids=self.partners.ids, subtype_xmlid='mail.mt_comment', message_type='notification') wizard = self.env['mail.resend.message'].with_context({'mail_message_to_resend': message.id}).create({}) diff --git a/addons/test_mail/tests/test_message_post.py b/addons/test_mail/tests/test_message_post.py index 71352d2d5da..9659e516d7d 100644 --- a/addons/test_mail/tests/test_message_post.py +++ b/addons/test_mail/tests/test_message_post.py @@ -6,6 +6,7 @@ import base64 from unittest.mock import patch from odoo import tools +from odoo.addons.mail.tests.common import mail_new_test_user from odoo.addons.test_mail.data.test_mail_data import MAIL_TEMPLATE_PLAINTEXT from odoo.addons.test_mail.models.test_mail_models import MailTestSimple from odoo.addons.test_mail.tests.common import TestMailCommon, TestRecipients @@ -19,12 +20,29 @@ from markupsafe import escape @tagged('mail_post') -class TestMessagePost(TestMailCommon, TestRecipients): +class TestMessagePostCommon(TestMailCommon, TestRecipients): @classmethod def setUpClass(cls): - super(TestMessagePost, cls).setUpClass() - cls._create_portal_user() + super(TestMessagePostCommon, cls).setUpClass() + + # portal user, notably for ACLS / notifications + cls.user_portal = cls._create_portal_user() + cls.partner_portal = cls.user_portal.partner_id + + # another standard employee to test follow and notifications between two + # users (and not admin / user) + cls.user_employee_2 = mail_new_test_user( + cls.env, login='employee2', + groups='base.group_user', + company_id=cls.company_admin.id, + email='eglantine@example.com', # check: use a formatted email + name='Eglantine Employee2', + notification_type='email', + signature='--\nEglantine', + ) + cls.partner_employee_2 = cls.user_employee_2.partner_id + cls.test_record = cls.env['mail.test.simple'].with_context(cls._test_context).create({ 'name': 'Test', 'email_from': 'ignasse@example.com' @@ -45,8 +63,22 @@ class TestMessagePost(TestMailCommon, TestRecipients): }) cls.user_admin.write({'notification_type': 'email'}) + def setUp(self): + super(TestMessagePostCommon, self).setUp() + # send tracking and messages + patch registry to simulate a ready environment + # purpose is to avoid nondeterministic tests, notably because tracking is + # accumulated and sent at flush -> we want to test only the result of a + # given test, not setup + test + self.flush_tracking() + # see ``_message_auto_subscribe_notify`` + self.patch(self.env.registry, 'ready', True) + + +@tagged('mail_post') +class TestMailNotifyAPI(TestMessagePostCommon): + @users('employee') - def test_notify_email_layouts(self): + def test_email_notifiction_layouts(self): self.user_employee.write({'notification_type': 'email'}) test_record = self.env['mail.test.simple'].browse(self.test_record.ids) test_message = self.env['mail.message'].browse(self.test_message.ids) @@ -74,12 +106,13 @@ class TestMessagePost(TestMailCommon, TestRecipients): self.assertTrue(user_email) @users('employee') + @mute_logger('odoo.addons.mail.models.mail_mail') def test_notify_by_mail_add_signature(self): - self.test_track = self.env['mail.test.track'].with_context(self._test_context).with_user(self.user_employee).create({ + test_track = self.env['mail.test.track'].with_context(self._test_context).with_user(self.user_employee).create({ 'name': 'Test', 'email_from': 'ignasse@example.com' }) - self.test_track.user_id = self.env.user + test_track.user_id = self.env.user signature = self.env.user.signature @@ -87,7 +120,7 @@ class TestMessagePost(TestMailCommon, TestRecipients): self.assertIn("record.user_id.sudo().signature", template.arch) with self.mock_mail_gateway(): - self.test_track.message_post( + test_track.message_post( body="Test body", email_add_signature=True, email_layout_xmlid="mail.mail_notification_paynow", @@ -99,12 +132,12 @@ class TestMessagePost(TestMailCommon, TestRecipients): self.assertEqual(found_mail.body_html.count(signature), 1) with self.mock_mail_gateway(): - self.test_track.message_post( + test_track.message_post( body="Test body", email_add_signature=False, email_layout_xmlid="mail.mail_notification_paynow", mail_auto_delete=False, - partner_ids=[self.partner_1.id, self.partner_2.id] + partner_ids=[self.partner_1.id, self.partner_2.id], ) found_mail = self._new_mails self.assertNotIn(signature, found_mail.body_html) @@ -112,18 +145,22 @@ class TestMessagePost(TestMailCommon, TestRecipients): @users('employee') def test_notify_by_email_add_signature_no_author_user_or_no_user(self): - self.test_message.author_id = self.env['res.partner'].sudo().create({ - 'name': 'Steve', - }).id - template_values = self.test_record._notify_by_email_prepare_rendering_context(self.test_message, {}) + test_record = self.env['mail.test.simple'].browse(self.test_record.ids) + test_message = self.env['mail.message'].browse(self.test_message.ids) + test_message.write({ + 'author_id': self.env['res.partner'].sudo().create({ + 'name': 'Steve', + }).id + }) + template_values = test_record._notify_by_email_prepare_rendering_context(test_message, {}) self.assertNotEqual(escape(template_values['signature']), escape('

--
Steve

')) self.test_message.author_id = None - template_values = self.test_record._notify_by_email_prepare_rendering_context(self.test_message, {}) + template_values = test_record._notify_by_email_prepare_rendering_context(test_message, {}) self.assertEqual(template_values['signature'], '') @users('employee') - def test_notify_by_email_prepare_rendering_contextt(self): + def test_notify_by_email_prepare_rendering_context(self): """ Verify that the template context company value is right after switching the env company or if a company_id is set on mail record. @@ -225,300 +262,495 @@ class TestMessagePost(TestMailCommon, TestRecipients): self.assertFalse(partner_info['has_button_access']) self.assertFalse(emp_info['has_button_access']) + +@tagged('mail_post', 'mail_notify') +class TestMessageNotify(TestMessagePostCommon): + + @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail') - def test_post_needaction(self): - (self.user_employee | self.user_admin).write({'notification_type': 'inbox'}) - with self.assertSinglePostNotifications([{'partner': self.partner_employee, 'type': 'inbox'}], {'content': 'Body'}): - self.test_record.message_post( - body='Body', message_type='comment', subtype_xmlid='mail.mt_comment', - partner_ids=[self.user_employee.partner_id.id]) - - self.test_record.message_subscribe([self.partner_1.id]) - with self.assertSinglePostNotifications([ - {'partner': self.partner_employee, 'type': 'inbox'}, - {'partner': self.partner_1, 'type': 'email'}], {'content': 'NewBody'}): - self.test_record.message_post( - body='NewBody', message_type='comment', subtype_xmlid='mail.mt_comment', - partner_ids=[self.user_employee.partner_id.id]) - - with self.assertSinglePostNotifications([ - {'partner': self.partner_1, 'type': 'email'}, - {'partner': self.partner_portal, 'type': 'email'}], {'content': 'ToPortal'}): - self.test_record.message_post( - body='ToPortal', message_type='comment', subtype_xmlid='mail.mt_comment', - partner_ids=[self.partner_portal.id]) - - def test_post_inactive_follower(self): - # In some case odoobot is follower of a record. - # Even if it shouldn't be the case, we want to be sure that odoobot is not notified - (self.user_employee | self.user_admin).write({'notification_type': 'inbox'}) - self.test_record._message_subscribe(self.user_employee.partner_id.ids) - with self.assertSinglePostNotifications([{'partner': self.partner_employee, 'type': 'inbox'}], {'content': 'Test'}): - self.test_record.message_post( - body='Test', message_type='comment', subtype_xmlid='mail.mt_comment') - - self.user_employee.active = False - # at this point, partner is still active and would receive an email notification - self.user_employee.partner_id._write({'active': False}) - with self.assertPostNotifications([{'content': 'Test', 'notif': []}]): - self.test_record.message_post( - body='Test', message_type='comment', subtype_xmlid='mail.mt_comment') - - @mute_logger('odoo.addons.mail.models.mail_mail') - def test_post_notifications(self): - _body, _subject = '

Test Body

', 'Test Subject' - - # subscribe second employee to the group to test notifications - self.test_record.message_subscribe(partner_ids=[self.user_admin.partner_id.id]) - - with self.assertSinglePostNotifications( - [{'partner': self.partner_1, 'type': 'email'}, - {'partner': self.partner_2, 'type': 'email'}, - {'partner': self.partner_admin, 'type': 'email'}], - {'content': _body}, - mail_unlink_sent=True): - msg = self.test_record.with_user(self.user_employee).message_post( - body=_body, subject=_subject, - message_type='comment', subtype_xmlid='mail.mt_comment', - partner_ids=[self.partner_1.id, self.partner_2.id] - ) - - # message content - self.assertEqual(msg.subject, _subject) - self.assertEqual(msg.body, _body) - self.assertEqual(msg.partner_ids, self.partner_1 | self.partner_2) - self.assertEqual(msg.notified_partner_ids, self.user_admin.partner_id | self.partner_1 | self.partner_2) - - # notifications emails should have been deleted - self.assertFalse(self.env['mail.mail'].sudo().search([('mail_message_id', '=', msg.id)]), - 'message_post: mail.mail notifications should have been auto-deleted') - - @mute_logger('odoo.addons.mail.models.mail_mail') - def test_post_notifications_keep_emails(self): - self.test_record.message_subscribe(partner_ids=[self.user_admin.partner_id.id]) - - msg = self.test_record.with_user(self.user_employee).message_post( - body='Test', subject='Test', - message_type='comment', subtype_xmlid='mail.mt_comment', - partner_ids=[self.partner_1.id, self.partner_2.id], - mail_auto_delete=False - ) - - # notifications emails should not have been deleted: one for customers, one for user - self.assertEqual(len(self.env['mail.mail'].sudo().search([('mail_message_id', '=', msg.id)])), 2) - - @mute_logger('odoo.addons.mail.models.mail_mail') - def test_post_notifications_emails_tweak(self): - pass - # we should check _notification_groups behavior, for emails and buttons - - @mute_logger('odoo.addons.mail.models.mail_mail') - def test_post_attachments(self): - _attachments = [ - ('List1', b'My first attachment'), - ('List2', b'My second attachment') - ] - _attach_1 = self.env['ir.attachment'].with_user(self.user_employee).create({ - 'name': 'Attach1', - 'datas': 'bWlncmF0aW9uIHRlc3Q=', - 'res_model': 'mail.compose.message', 'res_id': 0}) - _attach_2 = self.env['ir.attachment'].with_user(self.user_employee).create({ - 'name': 'Attach2', - 'datas': 'bWlncmF0aW9uIHRlc3Q=', - 'res_model': 'mail.compose.message', 'res_id': 0}) + def test_notify(self): + test_record = self.env['mail.test.simple'].browse(self.test_record.ids) with self.mock_mail_gateway(): - msg = self.test_record.with_user(self.user_employee).message_post( - body='Test', subject='Test', - message_type='comment', subtype_xmlid='mail.mt_comment', - attachment_ids=[_attach_1.id, _attach_2.id], - partner_ids=[self.partner_1.id], - attachments=_attachments, - ) - - # message attachments - self.assertEqual(len(msg.attachment_ids), 4) - self.assertEqual(set(msg.attachment_ids.mapped('res_model')), set([self.test_record._name])) - self.assertEqual(set(msg.attachment_ids.mapped('res_id')), set([self.test_record.id])) - self.assertEqual(set([base64.b64decode(x) for x in msg.attachment_ids.mapped('datas')]), - set([b'migration test', _attachments[0][1], _attachments[1][1]])) - self.assertTrue(set([_attach_1.id, _attach_2.id]).issubset(msg.attachment_ids.ids), - 'message_post: mail.message attachments duplicated') - - # notification email attachments - self.assertSentEmail(self.user_employee.partner_id, [self.partner_1]) - # self.assertEqual(len(self._mails), 1) - self.assertEqual(len(self._mails[0]['attachments']), 4) - self.assertIn(('List1', b'My first attachment', 'application/octet-stream'), self._mails[0]['attachments']) - self.assertIn(('List2', b'My second attachment', 'application/octet-stream'), self._mails[0]['attachments']) - self.assertIn(('Attach1', b'migration test', 'application/octet-stream'), self._mails[0]['attachments']) - self.assertIn(('Attach2', b'migration test', 'application/octet-stream'), self._mails[0]['attachments']) - - @mute_logger('odoo.addons.mail.models.mail_mail') - def test_post_answer(self): - with self.mock_mail_gateway(): - parent_msg = self.test_record.with_user(self.user_employee).message_post( - body='

Test

', subject='Test Subject', - message_type='comment', subtype_xmlid='mail.mt_comment') - - self.assertEqual(parent_msg.partner_ids, self.env['res.partner']) - self.assertNotSentEmail() - - with self.assertPostNotifications([{'content': '

Test Answer

', 'notif': [{'partner': self.partner_1, 'type': 'email'}]}]): - msg = self.test_record.with_user(self.user_employee).message_post( - body='

Test Answer

', - message_type='comment', subtype_xmlid='mail.mt_comment', - partner_ids=[self.partner_1.id], - parent_id=parent_msg.id) - - self.assertEqual(msg.parent_id.id, parent_msg.id) - self.assertEqual(msg.partner_ids, self.partner_1) - self.assertEqual(parent_msg.partner_ids, self.env['res.partner']) - - # check notification emails: references - self.assertSentEmail(self.user_employee.partner_id, [self.partner_1], ref_content='openerp-%d-mail.test.simple' % self.test_record.id) - # self.assertTrue(all('openerp-%d-mail.test.simple' % self.test_record.id in m['references'] for m in self._mails)) - - new_msg = self.test_record.with_user(self.user_employee).message_post( - body='

Test Answer Bis

', - message_type='comment', subtype_xmlid='mail.mt_comment', - parent_id=msg.id) - - self.assertEqual(new_msg.parent_id.id, parent_msg.id, 'message_post: flatten error') - self.assertEqual(new_msg.partner_ids, self.env['res.partner']) - - @mute_logger('odoo.addons.mail.models.mail_mail') - def test_post_email_with_multiline_subject(self): - _body, _body_alt, _subject = '

Test Body

', 'Test Body', '1st line\n2nd line' - msg = self.test_record.with_user(self.user_employee).message_post( - body=_body, subject=_subject, - message_type='comment', subtype_xmlid='mail.mt_comment', - partner_ids=[self.partner_1.id, self.partner_2.id] - ) - self.assertEqual(msg.subject, '1st line 2nd line') - - @mute_logger('odoo.addons.mail.models.mail_mail') - def test_post_portal_ok(self): - self.test_record.message_subscribe((self.partner_1 | self.user_employee.partner_id).ids) - - with self.assertPostNotifications([{'content': '

Test

', 'notif': [ - {'partner': self.partner_employee, 'type': 'inbox'}, - {'partner': self.partner_1, 'type': 'email'}]} - ]), patch.object(MailTestSimple, 'check_access_rights', return_value=True): - new_msg = self.test_record.with_user(self.user_portal).message_post( - body='

Test

', subject='Subject', - message_type='comment', subtype_xmlid='mail.mt_comment') - - self.assertEqual(new_msg.sudo().notified_partner_ids, (self.partner_1 | self.user_employee.partner_id)) - - def test_post_portal_crash(self): - with self.assertRaises(AccessError): - self.test_record.with_user(self.user_portal).message_post( - body='

Test

', subject='Subject', - message_type='comment', subtype_xmlid='mail.mt_comment') - - @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_thread') - def test_post_internal(self): - self.test_record.message_subscribe([self.user_admin.partner_id.id]) - msg = self.test_record.with_user(self.user_employee).message_post( - body='My Body', subject='My Subject', - message_type='comment', subtype_xmlid='mail.mt_note') - self.assertEqual(msg.partner_ids, self.env['res.partner']) - self.assertEqual(msg.notified_partner_ids, self.env['res.partner']) - - self.format_and_process( - MAIL_TEMPLATE_PLAINTEXT, self.user_admin.email, 'not_my_businesss@example.com', - msg_id='<1198923581.41972151344608186800.JavaMail.diff1@agrolait.com>', - extra='In-Reply-To:\r\n\t%s\n' % msg.message_id, - target_model='mail.test.simple') - reply = self.test_record.message_ids - msg - self.assertTrue(reply) - self.assertEqual(reply.subtype_id, self.env.ref('mail.mt_note')) - self.assertEqual(reply.notified_partner_ids, self.user_employee.partner_id) - self.assertEqual(reply.parent_id, msg) - - def test_post_log(self): - new_note = self.test_record.with_user(self.user_employee)._message_log( - body='

Labrador

', - ) - - self.assertEqual(new_note.subtype_id, self.env.ref('mail.mt_note')) - self.assertEqual(new_note.body, '

Labrador

') - self.assertEqual(new_note.author_id, self.user_employee.partner_id) - self.assertEqual(new_note.email_from, formataddr((self.user_employee.name, self.user_employee.email))) - self.assertEqual(new_note.notified_partner_ids, self.env['res.partner']) - - def test_post_log_with_view(self): - new_note = self.test_record.with_user(self.user_employee)._message_log_with_view( - 'test_mail.mail_template_simple_test', values={'partner': self.user_employee.partner_id} - ) - - self.assertEqual(new_note.subtype_id, self.env.ref('mail.mt_note')) - self.assertTrue('

Hello %s,

' % self.user_employee.name in new_note.body) - self.assertEqual(new_note.author_id, self.user_employee.partner_id) - self.assertEqual(new_note.email_from, formataddr((self.user_employee.name, self.user_employee.email))) - self.assertEqual(new_note.notified_partner_ids, self.env['res.partner']) - - @mute_logger('odoo.addons.mail.models.mail_mail') - def test_post_notify(self): - self.user_employee.write({'notification_type': 'inbox'}) - - with self.mock_mail_gateway(): - new_notification = self.test_record.message_notify( - subject='This should be a subject', + new_notification = test_record.message_notify( body='

You have received a notification

', - partner_ids=[self.partner_1.id, self.partner_admin.id, self.user_employee.partner_id.id], + partner_ids=[self.partner_1.id, self.partner_admin.id, self.partner_employee_2.id], + subject='This should be a subject', ) - self.assertEqual(new_notification.subtype_id, self.env.ref('mail.mt_note')) - self.assertEqual(new_notification.message_type, 'user_notification') - self.assertEqual(new_notification.body, '

You have received a notification

') - self.assertEqual(new_notification.author_id, self.env.user.partner_id) - self.assertEqual(new_notification.email_from, formataddr((self.env.user.name, self.env.user.email))) - self.assertEqual(new_notification.notified_partner_ids, self.partner_1 | self.user_employee.partner_id | self.partner_admin) + self.assertMessageFields( + new_notification, + {'author_id': self.partner_employee, + 'body': '

You have received a notification

', + 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), + 'message_type': 'user_notification', + 'model': test_record._name, + 'notified_partner_ids': self.partner_1 | self.partner_employee_2 | self.partner_admin, + 'res_id': test_record.id, + 'subtype_id': self.env.ref('mail.mt_note'), + } + ) self.assertNotIn(new_notification, self.test_record.message_ids) - admin_mails = [x for x in self._mails if self.partner_admin.name in x.get('email_to')[0]] + admin_mails = [mail for mail in self._mails if self.partner_admin.name in mail.get('email_to')[0]] self.assertEqual(len(admin_mails), 1, 'There should be exactly one email sent to admin') - admin_mail = admin_mails[0].get('body') - admin_access_link = admin_mail[admin_mail.index('model='):admin_mail.index('/>') - 1] if 'model=' in admin_mail else None - + admin_mail_body = admin_mails[0].get('body') + admin_access_link = admin_mail_body[admin_mail_body.index('model='):admin_mail_body.index('/>') - 1] if 'model=' in admin_mail_body else None + self.assertIsNotNone(admin_access_link, 'The email sent to admin should contain an access link') self.assertIn('model=%s' % self.test_record._name, admin_access_link, 'The access link should contain a valid model argument') self.assertIn('res_id=%d' % self.test_record.id, admin_access_link, 'The access link should contain a valid res_id argument') partner_mails = [x for x in self._mails if self.partner_1.name in x.get('email_to')[0]] self.assertEqual(len(partner_mails), 1, 'There should be exactly one email sent to partner') - partner_mail = partner_mails[0].get('body') - self.assertNotIn('/mail/view?model=', partner_mail, 'The email sent to admin should not contain an access link') - # todo xdo add test message_notify on thread with followers and stuff + partner_mail_body = partner_mails[0].get('body') + self.assertNotIn('/mail/view?model=', partner_mail_body, 'The email sent to customer should not contain an access link') + + @users('employee') + def test_notify_batch(self): + """ Test notify in batch. Currently not supported. """ + test_records, _partners = self._create_records_for_batch('mail.test.simple', 10) + + with self.assertRaises(ValueError): + test_records.message_notify( + body='

Nice notification content

', + partner_ids=self.partner_employee_2.ids, + subject='Notify Subject', + ) + + @users('employee') + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_notify_from_user_id(self): + """ Test notify coming from user_id assignment. """ + test_record = self.env['mail.test.track'].create({ + 'company_id': self.env.user.company_id.id, + 'email_from': self.env.user.email_formatted, + 'name': 'Test UserId Track', + 'user_id': False, + }) + self.flush_tracking() + + with self.mock_mail_gateway(), self.mock_mail_app(): + test_record.write({'user_id': self.user_employee_2.id}) + self.flush_tracking() + + self.assertEqual(len(self._new_msgs), 2, 'Should have 2 messages: tracking and assignment') + assign_notif = self._new_msgs.filtered(lambda msg: msg.message_type == 'user_notification') + self.assertTrue(assign_notif) + self.assertMessageFields( + assign_notif, + {'author_id': self.partner_employee, + 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), + 'model': test_record._name, + 'notified_partner_ids': self.partner_employee_2, + 'res_id': test_record.id, + 'subtype_id': self.env.ref('mail.mt_note'), + } + ) + self.assertIn('Dear %s' % self.partner_employee_2.name, assign_notif.body) + + @users('employee') + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_notify_from_user_id_batch(self): + """ Test notify coming from user_id assignment. """ + test_records, _ = self._create_records_for_batch( + 'mail.test.track', 10, { + 'company_id': self.env.user.company_id.id, + 'email_from': self.env.user.email_formatted, + 'user_id': False, + } + ) + test_records = self.env['mail.test.track'].browse(test_records.ids) + self.flush_tracking() + + with self.mock_mail_gateway(), self.mock_mail_app(): + test_records.write({'user_id': self.user_employee_2.id}) + self.flush_tracking() + + self.assertEqual(len(self._new_msgs), 20, 'Should have 20 messages: 10 tracking and 10 assignments') + for test_record in test_records: + assign_notif = self._new_msgs.filtered(lambda msg: msg.message_type == 'user_notification' and msg.res_id == test_record.id) + self.assertTrue(assign_notif) + self.assertMessageFields( + assign_notif, + {'author_id': self.partner_employee, + 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), + 'model': test_record._name, + 'notified_partner_ids': self.partner_employee_2, + 'res_id': test_record.id, + 'subtype_id': self.env.ref('mail.mt_note'), + } + ) + + @users('employee') + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_notify_thread(self): + """ Test notify on ``mail.thread`` model, which is pushing a message to + people without having a document. """ + with self.mock_mail_gateway(): + new_notification = self.env['mail.thread'].message_notify( + body='

You have received a notification

', + partner_ids=[self.partner_1.id, self.partner_admin.id, self.partner_employee_2.id], + subject='This should be a subject', + ) + + self.assertMessageFields( + new_notification, + {'author_id': self.partner_employee, + 'body': '

You have received a notification

', + 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), + 'message_type': 'user_notification', + 'model': False, + 'res_id': False, + 'notified_partner_ids': self.partner_1 | self.partner_employee_2 | self.partner_admin, + 'subtype_id': self.env.ref('mail.mt_note'), + } + ) + + +@tagged('mail_post') +class TestMessagePost(TestMessagePostCommon): + + def test_initial_values(self): + self.assertFalse(self.test_record.message_ids) + self.assertFalse(self.test_record.message_follower_ids) + self.assertFalse(self.test_record.message_partner_ids) + + @users('employee') + def test_message_log(self): + test_record = self.env['mail.test.simple'].browse(self.test_record.ids) + test_record.message_subscribe(self.partner_employee_2.ids) + + new_note = test_record._message_log( + body='

Labrador

', + ) + self.assertMessageFields( + new_note, + {'author_id': self.partner_employee, + 'body': '

Labrador

', + 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), + 'is_internal': True, + 'message_type': 'notification', + 'model': test_record._name, + 'notified_partner_ids': self.env['res.partner'], + 'reply_to': formataddr((self.company_admin.name, '%s@%s' % (self.alias_catchall, self.alias_domain))), + 'res_id': test_record.id, + 'subtype_id': self.env.ref('mail.mt_note'), + } + ) + + @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') + @users('employee') + def test_message_post(self): + self.user_employee_2.write({'notification_type': 'inbox'}) + test_record = self.env['mail.test.simple'].browse(self.test_record.ids) + + with self.assertSinglePostNotifications( + [{'partner': self.partner_employee_2, 'type': 'inbox'}], + {'content': 'Body'} + ): + new_message = test_record.message_post( + body='Body', + message_type='comment', + subtype_xmlid='mail.mt_comment', + partner_ids=[self.partner_employee_2.id], + ) + + self.assertMessageFields( + new_message, + {'author_id': self.partner_employee, + 'body': '

Body

', + 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), + 'is_internal': False, + 'message_type': 'comment', + 'model': test_record._name, + 'notified_partner_ids': self.partner_employee_2, + 'reply_to': formataddr(("%s %s" % (self.company_admin.name, test_record.name), '%s@%s' % (self.alias_catchall, self.alias_domain))), + 'res_id': test_record.id, + 'subtype_id': self.env.ref('mail.mt_comment'), + } + ) + self.assertEqual(test_record.message_partner_ids, self.partner_employee) + + test_record.message_subscribe(self.partner_1.ids) + with self.assertSinglePostNotifications( + [{'partner': self.partner_employee_2, 'type': 'inbox'}, + {'partner': self.partner_1, 'type': 'email'}], + message_info={'content': 'NewBody'}, + mail_unlink_sent=True + ): + new_message = test_record.message_post( + body='NewBody', + message_type='comment', + subtype_xmlid='mail.mt_comment', + partner_ids=[self.partner_employee_2.id], + ) + + self.assertMessageFields( + new_message, + {'notified_partner_ids': self.partner_1 + self.partner_employee_2} + ) + # notifications emails should have been deleted + self.assertFalse(self.env['mail.mail'].sudo().search_count([('mail_message_id', '=', new_message.id)])) + + with self.assertSinglePostNotifications( + [{'partner': self.partner_1, 'type': 'email'}, + {'partner': self.partner_portal, 'type': 'email'}], + {'content': 'ToPortal'} + ): + test_record.message_post( + body='ToPortal', + message_type='comment', + subtype_xmlid='mail.mt_comment', + partner_ids=self.partner_portal.ids, + ) + + @users('employee') + def test_message_post_inactive_follower(self): + """ Test posting with inactive followers does not notify them (e.g. odoobot) """ + test_record = self.env['mail.test.simple'].browse(self.test_record.ids) + test_record._message_subscribe(self.user_employee_2.partner_id.ids) + self.user_employee_2.write({'active': False}) + self.partner_employee_2.write({'active': False}) + + with self.assertPostNotifications([{'content': 'Test', 'notif': []}]): + test_record.message_post( + body='Test', + message_type='comment', + subtype_xmlid='mail.mt_comment', + ) @mute_logger('odoo.addons.mail.models.mail_mail') - def test_post_post_w_template(self): - test_record = self.env['mail.test.simple'].with_context(self._test_context).create({'name': 'Test', 'email_from': 'ignasse@example.com'}) - self.user_employee.write({ - 'groups_id': [(4, self.env.ref('base.group_partner_manager').id)], + @users('employee') + def test_message_post_keep_emails(self): + test_record = self.env['mail.test.simple'].browse(self.test_record.ids) + test_record.message_subscribe(partner_ids=self.partner_employee_2.ids) + + with self.mock_mail_gateway(mail_unlink_sent=True): + msg = test_record.message_post( + body='Test', + mail_auto_delete=False, + message_type='comment', + partner_ids=[self.partner_1.id, self.partner_2.id], + subject='Test', + subtype_xmlid='mail.mt_comment', + ) + + # notifications emails should not have been deleted: one for customers, one for user + self.assertEqual(self.env['mail.mail'].sudo().search_count([('mail_message_id', '=', msg.id)]), 2) + + @mute_logger('odoo.addons.mail.models.mail_mail') + @users('employee') + def test_message_post_w_attachments(self): + _attachments = [ + ('List1', b'My first attachment'), + ('List2', b'My second attachment'), + ] + _attachment_records = self.env['ir.attachment'].create( + self._generate_attachments_data(3, res_id=0, res_model='mail.compose.message') + ) + _attachment_records[1].write({'mimetype': 'image/png'}) # to test message_main_attachment heuristic + + test_record = self.env['mail.test.simple'].browse(self.test_record.ids) + self.assertFalse(test_record.message_main_attachment_id) + + with self.mock_mail_gateway(): + msg = test_record.message_post( + attachments=_attachments, + attachment_ids=_attachment_records.ids, + body='Test', + message_type='comment', + partner_ids=[self.partner_1.id], + subject='Test', + subtype_xmlid='mail.mt_comment', + ) + + # updated message main attachment + self.assertEqual(test_record.message_main_attachment_id, _attachment_records[1], + 'MailThread: main attachment should be set to image/png') + + # message attachments + self.assertEqual(len(msg.attachment_ids), 5) + self.assertEqual(set(msg.attachment_ids.mapped('res_model')), set([self.test_record._name])) + self.assertEqual(set(msg.attachment_ids.mapped('res_id')), set([self.test_record.id])) + self.assertEqual(set(base64.b64decode(x) for x in msg.attachment_ids.mapped('datas')), + set([b'AttContent_00', b'AttContent_01', b'AttContent_02', _attachments[0][1], _attachments[1][1]])) + self.assertTrue(set(_attachment_records.ids).issubset(msg.attachment_ids.ids), + 'message_post: mail.message attachments duplicated') + + # notification email attachments + self.assertEqual(len(self._mails), 1) + self.assertSentEmail( + self.user_employee.partner_id, [self.partner_1], + attachments=[('List1', b'My first attachment', 'application/octet-stream'), + ('List2', b'My second attachment', 'application/octet-stream'), + ('AttFileName_00.txt', b'AttContent_00', 'text/plain'), + ('AttFileName_01.txt', b'AttContent_01', 'image/png'), + ('AttFileName_02.txt', b'AttContent_02', 'text/plain'), + ] + ) + + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_multiline_subject(self): + with self.mock_mail_gateway(): + msg = self.test_record.with_user(self.user_employee).message_post( + body='

Test Body

', + partner_ids=[self.partner_1.id, self.partner_2.id], + subject='1st line\n2nd line', + ) + self.assertEqual(msg.subject, '1st line 2nd line') + + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_portal_acls(self): + self.test_record.message_subscribe((self.partner_1 | self.user_employee.partner_id).ids) + + with self.assertPostNotifications( + [{'content': '

Test

', 'notif': [ + {'partner': self.partner_employee, 'type': 'inbox'}, + {'partner': self.partner_1, 'type': 'email'}]} + ] + ), patch.object(MailTestSimple, 'check_access_rights', return_value=True): + new_msg = self.test_record.with_user(self.user_portal).message_post( + body='

Test

', + message_type='comment', + subject='Subject', + subtype_xmlid='mail.mt_comment', + ) + self.assertEqual(new_msg.sudo().notified_partner_ids, (self.partner_1 | self.user_employee.partner_id)) + + with self.assertRaises(AccessError): + self.test_record.with_user(self.user_portal).message_post( + body='

Test

', + message_type='comment', + subject='Subject', + subtype_xmlid='mail.mt_comment', + ) + + @mute_logger('odoo.addons.mail.models.mail_mail') + @users('employee') + def test_post_answer(self): + test_record = self.env['mail.test.simple'].browse(self.test_record.ids) + + with self.mock_mail_gateway(): + parent_msg = test_record.message_post( + body='

Test

', + message_type='comment', + subject='Test Subject', + subtype_xmlid='mail.mt_comment', + ) + self.assertFalse(parent_msg.partner_ids) + self.assertNotSentEmail() + + with self.assertPostNotifications( + [{'content': '

Test Answer

', 'notif': [{'partner': self.partner_1, 'type': 'email'}]}] + ): + msg = test_record.message_post( + body='

Test Answer

', + message_type='comment', + parent_id=parent_msg.id, + partner_ids=[self.partner_1.id], + subtype_xmlid='mail.mt_comment', + ) + self.assertEqual(msg.parent_id, parent_msg) + self.assertEqual(msg.partner_ids, self.partner_1) + self.assertFalse(parent_msg.partner_ids) + + # check notification emails: references + self.assertSentEmail( + self.user_employee.partner_id, [self.partner_1], + ref_content='openerp-%d-mail.test.simple' % self.test_record.id + ) + + with self.mock_mail_gateway(): + new_msg = test_record.message_post( + body='

Test Answer Bis

', + message_type='comment', + parent_id=msg.id, + subtype_xmlid='mail.mt_comment', + ) + self.assertEqual(new_msg.parent_id, parent_msg, 'message_post: flatten error') + self.assertFalse(new_msg.partner_ids) + + @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_thread') + @users('employee') + def test_post_internal(self): + test_record = self.env['mail.test.simple'].browse(self.test_record.ids) + + test_record.message_subscribe([self.user_admin.partner_id.id]) + with self.mock_mail_gateway(): + msg = test_record.message_post( + body='My Body', + message_type='comment', + subject='My Subject', + subtype_xmlid='mail.mt_note', + ) + self.assertFalse(msg.is_internal, + 'Notes are not "internal" but replies will be. Subtype being internal should be sufficient from ACLs point of view.') + self.assertFalse(msg.partner_ids) + self.assertFalse(msg.notified_partner_ids) + + self.format_and_process( + MAIL_TEMPLATE_PLAINTEXT, self.user_admin.email, 'not_my_businesss@example.com', + msg_id='<1198923581.41972151344608186800.JavaMail.diff1@agrolait.example.com>', + extra='In-Reply-To:\r\n\t%s\n' % msg.message_id, + target_model='mail.test.simple') + reply = test_record.message_ids - msg + self.assertTrue(reply) + self.assertTrue(reply.is_internal) + self.assertEqual(reply.notified_partner_ids, self.user_employee.partner_id) + self.assertEqual(reply.parent_id, msg) + self.assertEqual(reply.subtype_id, self.env.ref('mail.mt_note')) + + +@tagged('mail_post') +class TestMessagePostHelpers(TestMessagePostCommon): + + @classmethod + def setUpClass(cls): + super(TestMessagePostHelpers, cls).setUpClass() + # ensure employee can create partners, necessary for templates + cls.user_employee.write({ + 'groups_id': [(4, cls.env.ref('base.group_partner_manager').id)], }) - _attachments = [{ - 'name': 'first.txt', - 'datas': base64.b64encode(b'My first attachment'), - 'res_model': 'res.partner', - 'res_id': self.user_admin.partner_id.id - }, { - 'name': 'second.txt', - 'datas': base64.b64encode(b'My second attachment'), - 'res_model': 'res.partner', - 'res_id': self.user_admin.partner_id.id - }] + + @users('employee') + def test_message_log_with_view(self): + test_record = self.env['mail.test.simple'].browse(self.test_record.ids) + new_note = test_record._message_log_with_view( + 'test_mail.mail_template_simple_test', + values={'partner': self.user_employee.partner_id} + ) + self.assertMessageFields( + new_note, + {'author_id': self.partner_employee, + 'body': '

Hello %s,

' % self.user_employee.name, + 'email_from': formataddr((self.partner_employee.name, self.partner_employee.email_normalized)), + 'is_internal': True, + 'message_type': 'notification', + 'model': test_record._name, + 'notified_partner_ids': self.env['res.partner'], + 'reply_to': formataddr((self.company_admin.name, '%s@%s' % (self.alias_catchall, self.alias_domain))), + 'res_id': test_record.id, + 'subtype_id': self.env.ref('mail.mt_note'), + } + ) + + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_message_post_w_template(self): + test_record = self.env['mail.test.simple'].with_context(self._test_context).create({'name': 'Test', 'email_from': 'ignasse@example.com'}) + + _attachments = self._generate_attachments_data(count=2, res_id=0, res_model='mail.template') email_1 = 'test1@example.com' email_2 = 'test2@example.com' email_3 = self.partner_1.email - self._create_template('mail.test.simple', { - 'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])], + template = self._create_template('mail.test.simple', { + 'attachment_ids': [(0, 0, attach_vals) for attach_vals in _attachments], 'partner_to': '%s,%s' % (self.partner_2.id, self.user_admin.partner_id.id), 'email_to': '%s, %s' % (email_1, email_2), 'email_cc': '%s' % email_3, }) + template.attachment_ids.write({'res_id': template.id}) + # admin should receive emails self.user_admin.write({'notification_type': 'email'}) # Force the attachments of the template to be in the natural order. @@ -534,25 +766,35 @@ class TestMessagePost(TestMailCommon, TestRecipients): [r], subject='About %s' % test_record.name, body_content=test_record.name, - attachments=[('first.txt', b'My first attachment', 'text/plain'), ('second.txt', b'My second attachment', 'text/plain')]) + attachments=[ + ('AttFileName_00.txt', b'AttContent_00', 'text/plain'), + ('AttFileName_01.txt', b'AttContent_01', 'text/plain'), + ] + ) @mute_logger('odoo.addons.mail.models.mail_mail') - def test_post_post_w_template_mass_mode(self): + def test_message_post_w_template_mass_mode(self): test_record = self.env['mail.test.simple'].with_context(self._test_context).create({'name': 'Test', 'email_from': 'ignasse@example.com'}) self.user_employee.write({ 'groups_id': [(4, self.env.ref('base.group_partner_manager').id)], }) - self._create_template('mail.test.simple', { - 'partner_to': '%s,%s' % (self.partner_2.id, self.user_admin.partner_id.id), - 'email_to': 'test@example.com', - 'email_cc': self.partner_1.email, + _attachments = self._generate_attachments_data(count=2, res_id=0, res_model='mail.template') + template = self._create_template('mail.test.simple', { + 'attachment_ids': [(0, 0, attach_vals) for attach_vals in _attachments], # After the HTML sanitizer, it will become "

Body for: link

" 'body_html': 'Body for: link', + 'email_to': 'test@example.com', + 'email_cc': self.partner_1.email, + 'partner_to': '%s,%s' % (self.partner_2.id, self.user_admin.partner_id.id), }) + template.attachment_ids.write({'res_id': template.id}) with self.mock_mail_gateway(): - test_record.with_user(self.user_employee).message_post_with_template(self.email_template.id, composition_mode='mass_mail') + test_record.with_user(self.user_employee).message_post_with_template( + template.id, + composition_mode='mass_mail' + ) new_partner = self.env['res.partner'].search([('email', '=', 'test@example.com')]) @@ -561,21 +803,15 @@ class TestMessagePost(TestMailCommon, TestRecipients): [new_partner], subject='About %s' % test_record.name, body_content=test_record.name, - attachments=[]) + attachments=[ + ('AttFileName_00.txt', b'AttContent_00', 'text/plain'), + ('AttFileName_01.txt', b'AttContent_01', 'text/plain'), + ] + ) @tagged('mail_post', 'post_install', '-at_install') -class TestMessagePostGlobal(TestMailCommon, TestRecipients): - - @classmethod - def setUpClass(cls): - super(TestMessagePostGlobal, cls).setUpClass() - cls.test_record = cls.env['mail.test.simple'].with_context(cls._test_context).create({ - 'email_from': 'ignasse@example.com', - 'name': 'Test', - }) - cls._reset_mail_context(cls.test_record) - cls.user_admin.write({'notification_type': 'email'}) +class TestMessagePostGlobal(TestMessagePostCommon): @users('employee') def test_message_post_return(self): @@ -586,8 +822,7 @@ class TestMessagePostGlobal(TestMailCommon, TestRecipients): message_id = call_kw(self.env['mail.test.simple'], 'message_post', [test_record.id], - {'body': 'test'} - ) + {'body': 'test'}) self.assertTrue(isinstance(message_id, int)) @@ -631,6 +866,7 @@ class TestMessagePostLang(TestMailCommon, TestRecipients): cls.partner_2.write({'lang': 'es_ES'}) @users('employee') + @mute_logger('odoo.addons.mail.models.mail_mail') def test_composer_lang_template(self): test_records = self.test_records.with_user(self.env.user) test_template = self.test_template.with_user(self.env.user) @@ -659,6 +895,7 @@ class TestMessagePostLang(TestMailCommon, TestRecipients): self.assertEqual('EnglishSubject for %s' % record.name, customer_email['subject'], 'Fixme: this should be translated') @users('employee') + @mute_logger('odoo.addons.mail.models.mail_mail') def test_layout_email_lang_context(self): test_records = self.test_records.with_user(self.env.user).with_context(lang='es_ES') test_records[1].message_subscribe(self.partner_2.ids) @@ -687,6 +924,7 @@ class TestMessagePostLang(TestMailCommon, TestRecipients): self.assertIn('Hello', body, 'Body of posted message should be present') @users('employee') + @mute_logger('odoo.addons.mail.models.mail_mail') def test_layout_email_lang_template(self): test_records = self.test_records.with_user(self.env.user) test_template = self.test_template.with_user(self.env.user) diff --git a/addons/test_mail/tests/test_performance.py b/addons/test_mail/tests/test_performance.py index 29a014a18a2..43721eef50e 100644 --- a/addons/test_mail/tests/test_performance.py +++ b/addons/test_mail/tests/test_performance.py @@ -1,70 +1,41 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. + import base64 from odoo.addons.base.tests.common import TransactionCaseWithUserDemo -from odoo.tests.common import users, warmup +from odoo.addons.mail.tests.common import MailCommon +from odoo.tests.common import users, warmup, Form from odoo.tests import tagged from odoo.tools import mute_logger, formataddr @tagged('mail_performance') -class BaseMailPerformance(TransactionCaseWithUserDemo): +class BaseMailPerformance(MailCommon, TransactionCaseWithUserDemo): @classmethod def setUpClass(cls): super(BaseMailPerformance, cls).setUpClass() - cls._quick_create_ctx = { - 'no_reset_password': True, - 'mail_create_nolog': True, - 'mail_create_nosubscribe': True, - 'mail_notrack': True, - 'mail_channel_nosubscribe': True, - } - cls.user_employee = cls.env['res.users'].with_context(cls._quick_create_ctx).create({ - 'country_id': cls.env.ref('base.be').id, - 'email': 'e.e@example.com', - 'groups_id': [(6, 0, [cls.env.ref('base.group_user').id, cls.env.ref('base.group_partner_manager').id])], - 'login': 'employee', - 'name': 'Ernest Employee', - 'notification_type': 'inbox', - 'signature': '--\nErnest', - }) - cls.admin = cls.env.user - cls.admin.write({ - 'country_id': cls.env.ref('base.be').id, - }) + # creating partners is required notably witn template usage + cls.user_employee.write({'groups_id': [(4, cls.env.ref('base.group_partner_manager').id)]}) - cls.customer = cls.env['res.partner'].with_context(cls._quick_create_ctx).create({ + cls.customer = cls.env['res.partner'].with_context(cls._test_context).create({ 'country_id': cls.env.ref('base.be').id, 'email': 'customer.test@example.com', 'name': 'Test Customer', 'mobile': '0456123456', }) + cls.test_attachments_vals = cls._generate_attachments_data( + cls, 3, res_model='mail.compose.message', res_id=0 + ) + def setUp(self): super(BaseMailPerformance, self).setUp() # patch registry to simulate a ready environment self.patch(self.env.registry, 'ready', True) - self._flush_tracking() - - def _init_mail_gateway(self): - # setup mail gateway - self.alias_domain = 'example.com' - self.alias_catchall = 'catchall.test' - self.alias_bounce = 'bounce.test' - self.default_from = 'notifications' - self.env['ir.config_parameter'].set_param('mail.bounce.alias', self.alias_bounce) - self.env['ir.config_parameter'].set_param('mail.catchall.domain', self.alias_domain) - self.env['ir.config_parameter'].set_param('mail.catchall.alias', self.alias_catchall) - self.env['ir.config_parameter'].set_param('mail.default.from', self.default_from) - - def _flush_tracking(self): - """ Force the creation of tracking values notably, and ensure tests are - reproducible. """ - self.env['base'].flush() - self.cr.flush() + self.flush_tracking() @tagged('mail_performance') @@ -113,8 +84,6 @@ class TestBaseMailPerformance(BaseMailPerformance): } ]) - self._init_mail_gateway() - @users('__system__', 'demo') @warmup def test_read_mail(self): @@ -215,7 +184,7 @@ class TestMailAPIPerformance(BaseMailPerformance): def setUp(self): super(TestMailAPIPerformance, self).setUp() - self.user_test = self.env['res.users'].with_context(self._quick_create_ctx).create({ + self.user_test = self.env['res.users'].with_context(self._test_context).create({ 'name': 'Paulette Testouille', 'login': 'paul', 'email': 'user.test.paulette@example.com', @@ -223,13 +192,11 @@ class TestMailAPIPerformance(BaseMailPerformance): 'groups_id': [(6, 0, [self.env.ref('base.group_user').id])], }) - self._init_mail_gateway() - # automatically follow activities, for backward compatibility concerning query count self.env.ref('mail.mt_activities').write({'default': True}) def _create_test_records(self): - self.test_record_full = self.env['mail.test.ticket'].with_context(self._quick_create_ctx).create({ + self.test_record_full = self.env['mail.test.ticket'].with_context(self._test_context).create({ 'name': 'TestRecord', 'customer_id': self.customer.id, 'user_id': self.user_test.id, @@ -243,6 +210,10 @@ class TestMailAPIPerformance(BaseMailPerformance): 'email_from': '{{ object.user_id.email_formatted }}', 'partner_to': '{{ object.customer_id.id }}', 'email_to': '{{ ("%s Customer <%s>" % (object.name, object.email_from)) }}', + 'attachment_ids': [ + (0, 0, dict(attachment, res_model='mail.template')) + for attachment in self.test_attachments_vals + ], }) @users('__system__', 'employee') @@ -268,9 +239,9 @@ class TestMailAPIPerformance(BaseMailPerformance): 'res_id': record.id, 'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id, }) - #read activity_type to normalize cache between enterprise and community - #voip module read activity_type during create leading to one less query in enterprise on action_feedback - category = activity.activity_type_id.category + # read activity_type to normalize cache between enterprise and community + # voip module read activity_type during create leading to one less query in enterprise on action_feedback + _category = activity.activity_type_id.category with self.assertQueryCount(__system__=17, employee=19): activity.action_feedback(feedback='Zizisse Done !') @@ -283,9 +254,9 @@ class TestMailAPIPerformance(BaseMailPerformance): with self.assertQueryCount(__system__=8, employee=8): activity = record.action_start('Test Start') - #read activity_type to normalize cache between enterprise and community - #voip module read activity_type during create leading to one less query in enterprise on action_close - category = activity.activity_type_id.category + # read activity_type to normalize cache between enterprise and community + # voip module read activity_type during create leading to one less query in enterprise on action_close + _category = activity.activity_type_id.category record.write({'name': 'Dupe write'}) @@ -296,7 +267,38 @@ class TestMailAPIPerformance(BaseMailPerformance): @users('__system__', 'employee') @warmup - @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail') + @mute_logger('odoo.models.unlink') + def test_adv_activity_mixin_w_attachments(self): + record = self.env['mail.test.activity'].create({'name': 'Test'}) + + attachments = self.env['ir.attachment'].create([ + dict(values, + res_model='mail.activity', + res_id=0) + for values in self.test_attachments_vals + ]) + + with self.assertQueryCount(__system__=8, employee=8): + activity = record.action_start('Test Start') + #read activity_type to normalize cache between enterprise and community + #voip module read activity_type during create leading to one less query in enterprise on action_close + _category = activity.activity_type_id.category + + record.write({'name': 'Dupe write'}) + + with self.assertQueryCount(__system__=20, employee=24): + record.action_close('Dupe feedback', attachment_ids=attachments.ids) + + # notifications + message = record.message_ids[0] + self.assertEqual( + set(message.attachment_ids.mapped('name')), + set(['AttFileName_00.txt', 'AttFileName_01.txt', 'AttFileName_02.txt']) + ) + + @users('__system__', 'employee') + @warmup + @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests') def test_mail_composer(self): self._create_test_records() test_record = self.env['mail.test.ticket'].browse(self.test_record_full.id) @@ -311,12 +313,64 @@ class TestMailAPIPerformance(BaseMailPerformance): 'partner_ids': [(4, customer_id)], }) - with self.assertQueryCount(__system__=32, employee=38): + with self.assertQueryCount(__system__=33, employee=39): composer._action_send_mail() @users('__system__', 'employee') @warmup - @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail') + @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests') + def test_mail_composer_attachments(self): + self._create_test_records() + test_record = self.env['mail.test.ticket'].browse(self.test_record_full.id) + customer = self.env['res.partner'].browse(self.customer.ids) + attachments = self.env['ir.attachment'].with_user(self.env.user).create(self.test_attachments_vals) + with self.assertQueryCount(__system__=3, employee=3): + composer = self.env['mail.compose.message'].with_context({ + 'default_composition_mode': 'comment', + 'default_model': test_record._name, + 'default_res_id': test_record.id, + }).create({ + 'attachment_ids': attachments.ids, + 'body': '

Test Body

', + 'partner_ids': [(4, customer.id)], + }) + + with self.assertQueryCount(__system__=36, employee=44): + composer._action_send_mail() + + @users('__system__', 'employee') + @warmup + @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests') + def test_mail_composer_form_attachments(self): + self._create_test_records() + test_record = self.env['mail.test.ticket'].browse(self.test_record_full.id) + customer = self.env['res.partner'].browse(self.customer.ids) + attachments = self.env['ir.attachment'].with_user(self.env.user).create(self.test_attachments_vals) + with self.assertQueryCount(__system__=15, employee=15): + composer_form = Form( + self.env['mail.compose.message'].with_context({ + 'default_composition_mode': 'comment', + 'default_model': test_record._name, + 'default_res_id': test_record.id, + }) + ) + composer_form.body = '

Test Body

' + composer_form.partner_ids.add(customer) + for attachment in attachments: + composer_form.attachment_ids.add(attachment) + composer = composer_form.save() + + with self.assertQueryCount(__system__=44, employee=52): + composer._action_send_mail() + + # notifications + message = test_record.message_ids[0] + self.assertEqual(message.attachment_ids, attachments) + self.assertEqual(message.notified_partner_ids, customer + self.user_test.partner_id) + + @users('__system__', 'employee') + @warmup + @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests') def test_mail_composer_nodelete(self): self._create_test_records() test_record = self.env['mail.test.ticket'].browse(self.test_record_full.id) @@ -332,16 +386,19 @@ class TestMailAPIPerformance(BaseMailPerformance): 'partner_ids': [(4, customer_id)], }) - with self.assertQueryCount(__system__=25, employee=31): + with self.assertQueryCount(__system__=26, employee=32): composer._action_send_mail() @users('__system__', 'employee') @warmup - @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') + @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests') def test_mail_composer_w_template(self): self._create_test_records() + self.test_template_full.write({'attachment_ids': [(5, 0)]}) + test_record = self.env['mail.test.ticket'].browse(self.test_record_full.id) test_template = self.env['mail.template'].browse(self.test_template_full.id) + with self.assertQueryCount(__system__=11, employee=12): composer = self.env['mail.compose.message'].with_context({ 'default_composition_mode': 'comment', @@ -351,19 +408,121 @@ class TestMailAPIPerformance(BaseMailPerformance): }).create({}) composer._onchange_template_id_wrapper() - with self.assertQueryCount(__system__=32, employee=38): + with self.assertQueryCount(__system__=33, employee=39): composer._action_send_mail() + # notifications + message = test_record.message_ids[0] + self.assertFalse(message.attachment_ids) + # remove created partner to ensure tests are the same each run self.env['res.partner'].sudo().search([('email', '=', 'nopartner.test@example.com')]).unlink() + @users('__system__', 'employee') + @warmup + @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests') + def test_mail_composer_w_template_attachments(self): + self._create_test_records() + + test_record = self.env['mail.test.ticket'].browse(self.test_record_full.id) + test_template = self.env['mail.template'].browse(self.test_template_full.id) + with self.assertQueryCount(__system__=12, employee=13): + composer = self.env['mail.compose.message'].with_context({ + 'default_composition_mode': 'comment', + 'default_model': test_record._name, + 'default_res_id': test_record.id, + 'default_template_id': test_template.id, + }).create({}) + composer._onchange_template_id_wrapper() + + with self.assertQueryCount(__system__=41, employee=52): + composer._action_send_mail() + + # notifications + message = test_record.message_ids[0] + self.assertEqual( + set(message.attachment_ids.mapped('name')), + set(['AttFileName_00.txt', 'AttFileName_01.txt', 'AttFileName_02.txt']) + ) + + # remove created partner to ensure tests are the same each run + self.env['res.partner'].sudo().search([('email', '=', 'nopartner.test@example.com')]).unlink() + + @users('__system__', 'employee') + @warmup + @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests') + def test_mail_composer_w_template_form(self): + self._create_test_records() + self.test_template_full.write({'attachment_ids': [(5, 0)]}) + + test_record = self.env['mail.test.ticket'].browse(self.test_record_full.id) + test_template = self.env['mail.template'].browse(self.test_template_full.id) + customer = self.env['res.partner'].browse(self.customer.ids) + with self.assertQueryCount(__system__=33, employee=33): + composer_form = Form( + self.env['mail.compose.message'].with_context({ + 'default_composition_mode': 'comment', + 'default_model': test_record._name, + 'default_res_id': test_record.id, + 'default_template_id': test_template.id, + }) + ) + composer = composer_form.save() + + with self.assertQueryCount(__system__=41, employee=47): + composer._action_send_mail() + + # notifications + new_partner = self.env['res.partner'].sudo().search([('email', '=', 'nopartner.test@example.com')]) + message = test_record.message_ids[0] + self.assertFalse(message.attachment_ids) + self.assertEqual(message.notified_partner_ids, customer + self.user_test.partner_id + new_partner) + + # remove created partner to ensure tests are the same each run + new_partner.unlink() + + @users('__system__', 'employee') + @warmup + @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests') + def test_mail_composer_w_template_form_attachments(self): + self._create_test_records() + + test_record = self.env['mail.test.ticket'].browse(self.test_record_full.id) + test_template = self.env['mail.template'].browse(self.test_template_full.id) + customer = self.env['res.partner'].browse(self.customer.ids) + with self.assertQueryCount(__system__=34, employee=34): + composer_form = Form( + self.env['mail.compose.message'].with_context({ + 'default_composition_mode': 'comment', + 'default_model': test_record._name, + 'default_res_id': test_record.id, + 'default_template_id': test_template.id, + }) + ) + composer = composer_form.save() + + with self.assertQueryCount(__system__=53, employee=70): + composer._action_send_mail() + + # notifications + new_partner = self.env['res.partner'].sudo().search([('email', '=', 'nopartner.test@example.com')]) + message = test_record.message_ids[0] + self.assertEqual( + set(message.attachment_ids.mapped('name')), + set(['AttFileName_00.txt', 'AttFileName_01.txt', 'AttFileName_02.txt']) + ) + self.assertEqual(message.notified_partner_ids, customer + self.user_test.partner_id + new_partner) + + # remove created partner to ensure tests are the same each run + new_partner.unlink() + @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') @users('__system__', 'employee') @warmup def test_message_assignation_email(self): self.user_test.write({'notification_type': 'email'}) record = self.env['mail.test.track'].create({'name': 'Test'}) - with self.assertQueryCount(__system__=38, employee=39): + with self.assertQueryCount(__system__=39, employee=40): record.write({ 'user_id': self.user_test.id, }) @@ -416,7 +575,7 @@ class TestMailAPIPerformance(BaseMailPerformance): def test_message_post_one_email_notification(self): record = self.env['mail.test.simple'].create({'name': 'Test'}) - with self.assertQueryCount(__system__=29, employee=32): + with self.assertQueryCount(__system__=30, employee=33): record.message_post( body='

Test Post Performances with an email ping

', partner_ids=self.customer.ids, @@ -511,7 +670,7 @@ class TestMailComplexPerformance(BaseMailPerformance): def setUp(self): super(TestMailComplexPerformance, self).setUp() - self.user_portal = self.env['res.users'].with_context(self._quick_create_ctx).create({ + self.user_portal = self.env['res.users'].with_context(self._test_context).create({ 'name': 'Olivia Portal', 'login': 'port', 'email': 'p.p@example.com', @@ -525,16 +684,14 @@ class TestMailComplexPerformance(BaseMailPerformance): 'customer_id': self.customer.id, 'alias_name': 'test-alias', }) - Partners = self.env['res.partner'].with_context(self._quick_create_ctx) + Partners = self.env['res.partner'].with_context(self._test_context) self.partners = self.env['res.partner'] for x in range(0, 10): self.partners |= Partners.create({'name': 'Test %s' % x, 'email': 'test%s@example.com' % x}) self.container.message_subscribe(self.partners.ids, subtype_ids=[ self.env.ref('mail.mt_comment').id, - self.env.ref('test_mail.st_mail_test_container_child_full').id] - ) - - self._init_mail_gateway() + self.env.ref('test_mail.st_mail_test_container_child_full').id + ]) # `test_complex_mail_mail_send` self.container.flush() @@ -557,11 +714,13 @@ class TestMailComplexPerformance(BaseMailPerformance): 'recipient_ids': [(4, pid) for pid in self.partners.ids], }) mail_ids = mail.ids - with self.assertQueryCount(__system__=8, employee=8): + with self.assertQueryCount(__system__=9, employee=9): self.env['mail.mail'].sudo().browse(mail_ids).send() self.assertEqual(mail.body_html, '

Test

') - self.assertEqual(mail.reply_to, formataddr(('%s %s' % (self.env.company.name, self.container.name), 'test-alias@example.com'))) + self.assertEqual(mail.reply_to, formataddr( + ('%s %s' % (self.env.company.name, self.container.name), 'test-alias@%s' % self.alias_domain) + )) @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') @users('__system__', 'employee') @@ -570,7 +729,7 @@ class TestMailComplexPerformance(BaseMailPerformance): self.container.message_subscribe(self.user_portal.partner_id.ids) record = self.container.with_user(self.env.user) - with self.assertQueryCount(__system__=53, employee=54): # about 20 (19?) queries per additional customer group + with self.assertQueryCount(__system__=54, employee=55): # about 20 (19?) queries per additional customer group record.message_post( body='

Test Post Performances

', message_type='comment', @@ -587,7 +746,7 @@ class TestMailComplexPerformance(BaseMailPerformance): record = self.container.with_user(self.env.user) template_id = self.env.ref('test_mail.mail_test_container_tpl').id - with self.assertQueryCount(__system__=61, employee=62): # about 20 (19 ?) queries per additional customer group + with self.assertQueryCount(__system__=62, employee=63): # about 20 (19 ?) queries per additional customer group record.message_post_with_template(template_id, message_type='comment', composition_mode='comment') self.assertEqual(record.message_ids[0].body, '

Adding stuff on %s

' % record.name) @@ -650,7 +809,7 @@ class TestMailComplexPerformance(BaseMailPerformance): }) rec1 = rec.with_context(active_test=False) # to see inactive records self.assertEqual(rec1.message_partner_ids, self.partners | self.env.user.partner_id) - with self.assertQueryCount(__system__=38, employee=39): + with self.assertQueryCount(__system__=39, employee=40): rec.write({'user_id': self.user_portal.id}) self.assertEqual(rec1.message_partner_ids, self.partners | self.env.user.partner_id | self.user_portal.partner_id) # write tracking message @@ -670,7 +829,7 @@ class TestMailComplexPerformance(BaseMailPerformance): customer_id = self.customer.id user_id = self.user_portal.id - with self.assertQueryCount(__system__=89, employee=90): + with self.assertQueryCount(__system__=91, employee=92): rec = self.env['mail.test.ticket'].create({ 'name': 'Test', 'container_id': container_id, @@ -699,7 +858,7 @@ class TestMailComplexPerformance(BaseMailPerformance): rec1 = rec.with_context(active_test=False) # to see inactive records self.assertEqual(rec1.message_partner_ids, self.user_portal.partner_id | self.env.user.partner_id) self.assertEqual(len(rec1.message_ids), 1) - with self.assertQueryCount(__system__=58, employee=58): + with self.assertQueryCount(__system__=59, employee=59): rec.write({ 'name': 'Test2', 'container_id': self.container.id, @@ -736,7 +895,7 @@ class TestMailComplexPerformance(BaseMailPerformance): rec1 = rec.with_context(active_test=False) # to see inactive records self.assertEqual(rec1.message_partner_ids, self.user_portal.partner_id | self.env.user.partner_id) - with self.assertQueryCount(__system__=65, employee=65): + with self.assertQueryCount(__system__=66, employee=66): rec.write({ 'name': 'Test2', 'container_id': container_id, @@ -769,7 +928,7 @@ class TestMailComplexPerformance(BaseMailPerformance): rec1 = rec.with_context(active_test=False) # to see inactive records self.assertEqual(rec1.message_partner_ids, self.partners | self.env.user.partner_id | self.user_portal.partner_id) - with self.assertQueryCount(__system__=28, employee=29): + with self.assertQueryCount(__system__=29, employee=30): rec.write({ 'name': 'Test2', 'customer_id': customer_id, @@ -922,7 +1081,7 @@ class TestMailComplexPerformance(BaseMailPerformance): messages.flush() messages.invalidate_cache() - with self.assertQueryCount(employee=13): + with self.assertQueryCount(employee=13): # tm only: 12 res = messages.message_format() self.assertEqual(len(res), 6) @@ -934,96 +1093,87 @@ class TestMailHeavyPerformancePost(BaseMailPerformance): super(TestMailHeavyPerformancePost, self).setUp() # record - self.record = self.env['mail.test.container'].with_context(mail_create_nosubscribe=True).create({ + self.record_container = self.env['mail.test.container'].with_context(mail_create_nosubscribe=True).create({ 'name': 'Test record', 'customer_id': self.customer.id, 'alias_name': 'test-alias', }) # followers - self.user_follower_email = self.env['res.users'].with_context(self._quick_create_ctx).create({ + self.user_follower_email = self.env['res.users'].with_context(self._test_context).create({ 'name': 'user_follower_email', 'login': 'user_follower_email', 'email': 'user_follower_email@example.com', 'notification_type': 'email', 'groups_id': [(6, 0, [self.env.ref('base.group_user').id])], }) - self.user_follower_inbox = self.env['res.users'].with_context(self._quick_create_ctx).create({ + self.user_follower_inbox = self.env['res.users'].with_context(self._test_context).create({ 'name': 'user_follower_inbox', 'login': 'user_follower_inbox', 'email': 'user_follower_inbox@example.com', 'notification_type': 'inbox', 'groups_id': [(6, 0, [self.env.ref('base.group_user').id])], }) - self.partner_follower = self.env['res.partner'].with_context(self._quick_create_ctx).create({ + self.partner_follower = self.env['res.partner'].with_context(self._test_context).create({ 'name': 'partner_follower', 'email': 'partner_follower@example.com', }) - self.record.message_subscribe([ + self.record_container.message_subscribe([ self.partner_follower.id, self.user_follower_inbox.partner_id.id, self.user_follower_email.partner_id.id ]) # partner_ids - self.user_inbox = self.env['res.users'].with_context(self._quick_create_ctx).create({ + self.user_inbox = self.env['res.users'].with_context(self._test_context).create({ 'name': 'user_inbox', 'login': 'user_inbox', 'email': 'user_inbox@example.com', 'notification_type': 'inbox', 'groups_id': [(6, 0, [self.env.ref('base.group_user').id])], }) - self.user_email = self.env['res.users'].with_context(self._quick_create_ctx).create({ + self.user_email = self.env['res.users'].with_context(self._test_context).create({ 'name': 'user_email', 'login': 'user_email', 'email': 'user_email@example.com', 'notification_type': 'email', 'groups_id': [(6, 0, [self.env.ref('base.group_user').id])], }) - self.partner = self.env['res.partner'].with_context(self._quick_create_ctx).create({ + self.partner = self.env['res.partner'].with_context(self._test_context).create({ 'name': 'partner', 'email': 'partner@example.com', }) - self.vals = [{ - 'datas': base64.b64encode(bytes("attachement content %s" % i, 'utf-8')), - 'name': 'fileText_test%s.txt' % i, - 'mimetype': 'text/plain', - 'res_model': 'mail.compose.message', - 'res_id': 0, - } for i in range(3)] - - self._init_mail_gateway() @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') @users('employee') @warmup def test_complete_message_post(self): # aims to cover as much features of message_post as possible - partner_ids = [self.user_inbox.partner_id.id, self.user_email.partner_id.id, self.partner.id] - record = self.record.with_user(self.env.user) - attachements = [ # not linear on number of attachements + recipients = self.user_inbox.partner_id + self.user_email.partner_id + self.partner + record_container = self.record_container.with_user(self.env.user) + attachments_vals = [ # not linear on number of attachments_vals ('attach tuple 1', "attachement tupple content 1"), ('attach tuple 2', "attachement tupple content 2", {'cid': 'cid1'}), ('attach tuple 3', "attachement tupple content 3", {'cid': 'cid2'}), ] - self.attachements = self.env['ir.attachment'].with_user(self.env.user).create(self.vals) - attachement_ids = self.attachements.ids - with self.assertQueryCount(employee=67): + attachments = self.env['ir.attachment'].with_user(self.env.user).create(self.test_attachments_vals) + with self.assertQueryCount(employee=68): self.cr.sql_log = self.warm and self.cr.sql_log_count - record.with_context({}).message_post( + record_container.with_context({}).message_post( body='

Test body

', subject='Test Subject', message_type='notification', subtype_xmlid=None, - partner_ids=partner_ids, + partner_ids=recipients.ids, parent_id=False, - attachments=attachements, - attachment_ids=attachement_ids, + attachments=attachments_vals, + attachment_ids=attachments.ids, email_add_signature=True, model_description=False, mail_auto_delete=True ) self.cr.sql_log = False - self.assertTrue(record.message_ids[0].body.startswith('

Test body + + + + TestRating: Multi Company + + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + + TestRating: Portal should follow + + [('message_partner_ids', 'in', [user.partner_id.id])] + + + + diff --git a/addons/test_mail_full/tests/__init__.py b/addons/test_mail_full/tests/__init__.py index 9b71f41ab53..3921c4e1343 100644 --- a/addons/test_mail_full/tests/__init__.py +++ b/addons/test_mail_full/tests/__init__.py @@ -2,10 +2,12 @@ from . import common from . import test_odoobot +from . import test_mail_performance from . import test_phone_blacklist from . import test_mass_mailing from . import test_mass_mailing_statistics from . import test_mass_sms +from . import test_rating from . import test_sms_composer from . import test_sms_management from . import test_sms_performance diff --git a/addons/test_mail_full/tests/test_mail_performance.py b/addons/test_mail_full/tests/test_mail_performance.py new file mode 100644 index 00000000000..550985a2a33 --- /dev/null +++ b/addons/test_mail_full/tests/test_mail_performance.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.addons.mail.tests.common import mail_new_test_user +from odoo.addons.test_mail.tests.test_performance import BaseMailPerformance +from odoo.tests.common import users, warmup +from odoo.tests import tagged +from odoo.tools import mute_logger + + +@tagged('mail_performance') +class TestMailPerformance(BaseMailPerformance): + + @classmethod + def setUpClass(cls): + super(TestMailPerformance, cls).setUpClass() + + # users / followers + cls.user_emp_email = mail_new_test_user( + cls.env, + company_id=cls.user_admin.company_id.id, + company_ids=[(4, cls.user_admin.company_id.id)], + email='user.emp.email@test.example.com', + login='user_emp_email', + groups='base.group_user,base.group_partner_manager', + name='Emmanuel Email', + notification_type='email', + signature='--\nEmmanuel', + ) + cls.user_portal = mail_new_test_user( + cls.env, + company_id=cls.user_admin.company_id.id, + company_ids=[(4, cls.user_admin.company_id.id)], + email='user.portal@test.example.com', + login='user_portal', + groups='base.group_portal', + name='Paul Portal', + ) + cls.customers = cls.env['res.partner'].create([ + {'country_id': cls.env.ref('base.be').id, + 'email': 'customer.full.test.1@example.com', + 'name': 'Test Full Customer 1', + 'mobile': '0456112233', + 'phone': '0456112233', + }, + {'country_id': cls.env.ref('base.be').id, + 'email': 'customer.full.test.2@example.com', + 'name': 'Test Full Customer 2', + 'mobile': '0456223344', + 'phone': '0456112233', + }, + ]) + + # record + cls.record_container = cls.env['mail.test.container.mc'].create({ + 'alias_name': 'test-alias', + 'customer_id': cls.customer.id, + 'name': 'Test Container', + }) + cls.record_ticket = cls.env['mail.test.ticket.mc'].create({ + 'email_from': 'email.from@test.example.com', + 'container_id': cls.record_container.id, + 'customer_id': False, + 'name': 'Test Ticket', + 'user_id': cls.user_emp_email.id, + }) + cls.record_ticket.message_subscribe(cls.customers.ids + cls.user_admin.partner_id.ids + cls.user_portal.partner_id.ids) + + def test_initial_values(self): + """ Simply ensure some values through all tests """ + record_ticket = self.env['mail.test.ticket.mc'].browse(self.record_ticket.ids) + self.assertEqual(record_ticket.message_partner_ids, + self.user_emp_email.partner_id + self.user_admin.partner_id + self.customers + self.user_portal.partner_id) + self.assertEqual(len(record_ticket.message_ids), 1) + + @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') + @users('employee') + @warmup + def test_message_post_w_followers(self): + """ Aims to cover as much features of message_post as possible """ + record_ticket = self.env['mail.test.ticket.mc'].browse(self.record_ticket.ids) + attachments = self.env['ir.attachment'].create(self.test_attachments_vals) + + with self.assertQueryCount(employee=102): # tmf: 93 / com: 100 + new_message = record_ticket.message_post( + attachment_ids=attachments.ids, + body='

Test Content

', + message_type='comment', + subject='Test Subject', + subtype_xmlid='mail.mt_comment', + ) + + self.assertEqual( + new_message.notified_partner_ids, + self.user_emp_email.partner_id + self.user_admin.partner_id + self.customers + self.user_portal.partner_id + ) diff --git a/addons/test_mail_full/tests/test_rating.py b/addons/test_mail_full/tests/test_rating.py new file mode 100644 index 00000000000..a0e867e73e2 --- /dev/null +++ b/addons/test_mail_full/tests/test_rating.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.addons.test_mail_full.tests.common import TestMailFullCommon, TestMailFullRecipients +from odoo.tests import tagged +from odoo.tests.common import users +from odoo.tools import mute_logger + + +@tagged('rating') +class TestRatingFlow(TestMailFullCommon, TestMailFullRecipients): + + @classmethod + def setUpClass(cls): + super(TestRatingFlow, cls).setUpClass() + + cls.record_rating = cls.env['mail.test.rating'].create({ + 'customer_id': cls.partner_1.id, + 'name': 'Test Rating', + 'user_id': cls.user_admin.id, + }) + + def test_initial_values(self): + record_rating = self.env['mail.test.rating'].browse(self.record_rating.ids) + self.assertFalse(record_rating.rating_ids) + self.assertEqual(record_rating.message_partner_ids, self.partner_admin) + self.assertEqual(len(record_rating.message_ids), 1) + + @users('employee') + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_rating_prepare(self): + record_rating = self.env['mail.test.rating'].browse(self.record_rating.ids) + + # prepare rating token + access_token = record_rating.rating_get_access_token() + record_rating.invalidate_cache(fnames=['rating_ids']) + + # check rating creation + rating = record_rating.rating_ids + self.assertEqual(rating.access_token, access_token) + self.assertFalse(rating.consumed) + self.assertFalse(rating.is_internal) + self.assertEqual(rating.partner_id, self.partner_1) + self.assertEqual(rating.rated_partner_id, self.user_admin.partner_id) + self.assertFalse(rating.rating) + + @users('employee') + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_rating_rating_apply(self): + record_rating = self.env['mail.test.rating'].browse(self.record_rating.ids) + + # prepare rating token + access_token = record_rating.rating_get_access_token() + record_rating.invalidate_cache(fnames=['rating_ids']) + + # apply a rate + with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app(): + record_rating.rating_apply(5, token=access_token, feedback='Top Feedback') + message = record_rating.message_ids[0] + rating = record_rating.rating_ids + + # check posted message + self.assertIn('Top Feedback', message.body) + self.assertIn('rating/static/src/img/rating_5.png', message.body) + self.assertEqual(message.author_id, self.partner_1) + self.assertFalse(message.rating_ids) + self.assertEqual(message.notified_partner_ids, self.partner_admin) + self.assertEqual(message.subtype_id, self.env.ref('mail.mt_comment')) + + # check rating update + self.assertTrue(rating.consumed) + self.assertEqual(rating.feedback, 'Top Feedback') + self.assertFalse(rating.message_id) + self.assertEqual(rating.rating, 5) diff --git a/addons/test_mail_full/tests/test_sms_performance.py b/addons/test_mail_full/tests/test_sms_performance.py index 24108442039..95439be6c76 100644 --- a/addons/test_mail_full/tests/test_sms_performance.py +++ b/addons/test_mail_full/tests/test_sms_performance.py @@ -14,14 +14,14 @@ class TestSMSPerformance(BaseMailPerformance, sms_common.SMSCase): def setUp(self): super(TestSMSPerformance, self).setUp() - self.test_record = self.env['mail.test.sms'].with_context(self._quick_create_ctx).create({ + self.test_record = self.env['mail.test.sms'].with_context(self._test_context).create({ 'name': 'Test', 'customer_id': self.customer.id, 'phone_nbr': '0456999999', }) # prepare recipients to test for more realistic workload - self.partners = self.env['res.partner'].with_context(self._quick_create_ctx).create([ + self.partners = self.env['res.partner'].with_context(self._test_context).create([ {'name': 'Test %s' % x, 'email': 'test%s@example.com' % x, 'mobile': '0456%s%s0000' % (x, x), @@ -29,8 +29,6 @@ class TestSMSPerformance(BaseMailPerformance, sms_common.SMSCase): } for x in range(0, 10) ]) - self._init_mail_gateway() - @mute_logger('odoo.addons.sms.models.sms_sms') @users('employee') @warmup @@ -87,13 +85,13 @@ class TestSMSMassPerformance(BaseMailPerformance, sms_common.MockSMS): records = self.env['mail.test.sms'] partners = self.env['res.partner'] for x in range(50): - partners += self.env['res.partner'].with_context(**self._quick_create_ctx).create({ + partners += self.env['res.partner'].with_context(**self._test_context).create({ 'name': 'Partner_%s' % (x), 'email': '_test_partner_%s@example.com' % (x), 'country_id': be_country_id, 'mobile': '047500%02d%02d' % (x, x) }) - records += self.env['mail.test.sms'].with_context(**self._quick_create_ctx).create({ + records += self.env['mail.test.sms'].with_context(**self._test_context).create({ 'name': 'Test_%s' % (x), 'customer_id': partners[x].id, }) diff --git a/addons/test_mass_mailing/tests/test_mailing_server.py b/addons/test_mass_mailing/tests/test_mailing_server.py index 0bc1d35c222..53986f491a5 100644 --- a/addons/test_mass_mailing/tests/test_mailing_server.py +++ b/addons/test_mass_mailing/tests/test_mailing_server.py @@ -2,20 +2,17 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon -from odoo.addons.base.tests.common import MockSmtplibCase from odoo.tests import tagged from odoo.tests.common import users from odoo.tools import mute_logger @tagged('mass_mailing') -class TestMassMailingServer(TestMassMailCommon, MockSmtplibCase): +class TestMassMailingServer(TestMassMailCommon): @classmethod def setUpClass(cls): super(TestMassMailingServer, cls).setUpClass() - cls._init_mail_gateway() - cls.env['ir.mail_server'].search([]).unlink() cls._init_mail_servers() cls.recipients = cls._create_mailing_test_records(model='mailing.test.optout', count=8) diff --git a/addons/test_mass_mailing/tests/test_performance.py b/addons/test_mass_mailing/tests/test_performance.py index d5f2d2ca6e4..57c1e674b43 100644 --- a/addons/test_mass_mailing/tests/test_performance.py +++ b/addons/test_mass_mailing/tests/test_performance.py @@ -2,39 +2,25 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.addons.mail.tests.common import mail_new_test_user -from odoo.tests.common import TransactionCase, users, warmup +from odoo.addons.test_mail.tests.test_performance import BaseMailPerformance +from odoo.tests.common import users, warmup from odoo.tests import tagged from odoo.tools import mute_logger -class TestMassMailPerformanceBase(TransactionCase): +class TestMassMailPerformanceBase(BaseMailPerformance): - def setUp(self): - super(TestMassMailPerformanceBase, self).setUp() + @classmethod + def setUpClass(cls): + super(TestMassMailPerformanceBase, cls).setUpClass() - self.user_employee = mail_new_test_user( - self.env, login='emp', - groups='base.group_user', - name='Ernest Employee', notification_type='inbox') - - self.user_marketing = mail_new_test_user( - self.env, login='marketing', + cls.user_marketing = mail_new_test_user( + cls.env, groups='base.group_user,mass_mailing.group_mass_mailing_user', - name='Martial Marketing', signature='--\nMartial') - - # setup mail gateway - self.alias_domain = 'example.com' - self.alias_catchall = 'catchall.test' - self.alias_bounce = 'bounce.test' - self.default_from = 'notifications' - self.env['ir.config_parameter'].set_param('mail.bounce.alias', self.alias_bounce) - self.env['ir.config_parameter'].set_param('mail.catchall.domain', self.alias_domain) - self.env['ir.config_parameter'].set_param('mail.catchall.alias', self.alias_catchall) - self.env['ir.config_parameter'].set_param('mail.default.from', self.default_from) - - # patch registry to simulate a ready environment - self.patch(self.env.registry, 'ready', True) - + login='marketing', + name='Martial Marketing', + signature='--\nMartial' + ) @tagged('mail_performance') class TestMassMailPerformance(TestMassMailPerformanceBase): @@ -60,8 +46,8 @@ class TestMassMailPerformance(TestMassMailPerformanceBase): 'mailing_domain': [('id', 'in', self.mm_recs.ids)], }) - # runbot needs +101 compared to local (1568) - with self.assertQueryCount(__system__=1670, marketing=1671): + # runbot needs +101 compared to local (1570) + with self.assertQueryCount(__system__=1672, marketing=1673): mailing.action_send_mail() self.assertEqual(mailing.sent, 50) @@ -100,8 +86,8 @@ class TestMassMailBlPerformance(TestMassMailPerformanceBase): 'mailing_domain': [('id', 'in', self.mm_recs.ids)], }) - # runbot needs +125 compared to local (1834) - with self.assertQueryCount(__system__=1960, marketing=1961): + # runbot needs +125 compared to local (1836) + with self.assertQueryCount(__system__=1962, marketing=1963): mailing.action_send_mail() self.assertEqual(mailing.sent, 50) diff --git a/odoo/addons/base/tests/common.py b/odoo/addons/base/tests/common.py index 0fd845209db..28ddff19b06 100644 --- a/odoo/addons/base/tests/common.py +++ b/odoo/addons/base/tests/common.py @@ -252,14 +252,14 @@ class MockSmtplibCase: self.testing_smtp_session = TestingSMTPSession() IrMailServer = self.env['ir.mail_server'] - connect = IrMailServer.connect - find_mail_server = IrMailServer._find_mail_server + connect_origin = IrMailServer.connect + find_mail_server_origin = IrMailServer._find_mail_server - with patch.object(type(IrMailServer), '_is_test_mode', lambda self: False), \ - patch('smtplib.SMTP_SSL', side_effect=lambda *args, **kwargs: self.testing_smtp_session), \ + with patch('smtplib.SMTP_SSL', side_effect=lambda *args, **kwargs: self.testing_smtp_session), \ patch('smtplib.SMTP', side_effect=lambda *args, **kwargs: self.testing_smtp_session), \ - patch.object(type(IrMailServer), 'connect', side_effect=connect) as connect_mocked, \ - patch.object(type(IrMailServer), '_find_mail_server', side_effect=find_mail_server) as find_mail_server_mocked: + patch.object(type(IrMailServer), '_is_test_mode', lambda self: False), \ + patch.object(type(IrMailServer), 'connect', wraps=IrMailServer, side_effect=connect_origin) as connect_mocked, \ + patch.object(type(IrMailServer), '_find_mail_server', side_effect=find_mail_server_origin) as find_mail_server_mocked: self.connect_mocked = connect_mocked self.find_mail_server_mocked = find_mail_server_mocked yield @@ -298,14 +298,16 @@ class MockSmtplibCase: ) @classmethod - def _init_mail_servers(cls): - cls.env['ir.config_parameter'].sudo().set_param('mail.catchall.domain', 'test.com') - cls.env['ir.config_parameter'].sudo().set_param('mail.default.from', 'notifications') - cls.env['ir.config_parameter'].sudo().set_param('mail.bounce.alias', 'bounce') - - cls.alias_bounce = 'bounce' + def _init_mail_config(cls): + cls.alias_bounce = 'bounce.test' cls.alias_domain = 'test.com' + cls.default_from = 'notifications' + cls.env['ir.config_parameter'].sudo().set_param('mail.catchall.domain', cls.alias_domain) + cls.env['ir.config_parameter'].sudo().set_param('mail.default.from', cls.default_from) + cls.env['ir.config_parameter'].sudo().set_param('mail.bounce.alias', cls.alias_bounce) + @classmethod + def _init_mail_servers(cls): cls.env['ir.mail_server'].search([]).unlink() ir_mail_server_values = { diff --git a/odoo/addons/base/tests/test_ir_mail_server.py b/odoo/addons/base/tests/test_ir_mail_server.py index 448f04c2d06..c2a330067f9 100644 --- a/odoo/addons/base/tests/test_ir_mail_server.py +++ b/odoo/addons/base/tests/test_ir_mail_server.py @@ -15,6 +15,7 @@ from odoo.tools import mute_logger class TestIrMailServer(TransactionCase, MockSmtplibCase): def setUp(self): + self._init_mail_config() self._init_mail_servers() def _build_email(self, mail_from, return_path=None): @@ -231,7 +232,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): self.assertEqual(len(self.emails), 1) self.assert_email_sent_smtp( - smtp_from='bounce@xn--9caaaaaaa.com', + smtp_from='bounce.test@xn--9caaaaaaa.com', smtp_to_list=['dest@xn--example--i1a.com'], message_from='test@=?utf-8?b?w6nDqcOpw6nDqcOpw6k=?=.com', from_filter=False, @@ -322,7 +323,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): ) @mute_logger('odoo.models.unlink') - @patch.dict("odoo.tools.config.options", {"from_filter": "test.com"}) + @patch.dict("odoo.tools.config.options", {"from_filter": "test.com", "smtp_server": "example.com"}) def test_mail_server_binary_arguments_domain(self): """Test the configuration provided in the odoo-bin arguments. @@ -374,7 +375,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): ) @mute_logger('odoo.models.unlink') - @patch.dict("odoo.tools.config.options", {"from_filter": "test.com"}) + @patch.dict("odoo.tools.config.options", {"from_filter": "test.com", "smtp_server": "example.com"}) def test_mail_server_binary_arguments_domain_smtp_session(self): """Test the configuration provided in the odoo-bin arguments. @@ -417,7 +418,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): ) @mute_logger('odoo.models.unlink') - @patch.dict('odoo.tools.config.options', {'from_filter': 'test.com'}) + @patch.dict('odoo.tools.config.options', {'from_filter': 'test.com', 'smtp_server': 'example.com'}) def test_mail_server_mail_default_from_filter(self): """Test that the config parameter "mail.default.from_filter" overwrite the odoo-bin argument "--from-filter"