diff --git a/addons/account/tests/test_account_incoming_supplier_invoice.py b/addons/account/tests/test_account_incoming_supplier_invoice.py index 373fcbab346..47a8efa31da 100644 --- a/addons/account/tests/test_account_incoming_supplier_invoice.py +++ b/addons/account/tests/test_account_incoming_supplier_invoice.py @@ -12,7 +12,7 @@ class TestAccountIncomingSupplierInvoice(AccountTestInvoicingCommon): def setUpClass(cls, chart_template_ref=None): super().setUpClass(chart_template_ref=chart_template_ref) - cls.env['ir.config_parameter'].sudo().set_param('mail.catchall.domain', 'test-company.odoo.com') + cls.env['ir.config_parameter'].sudo().set_param('mail.catchall.domain', 'test.mycompany.com') cls.internal_user = cls.env['res.users'].create({ 'name': 'Internal User', diff --git a/addons/crm/tests/test_crm_lead_notification.py b/addons/crm/tests/test_crm_lead_notification.py index 7cc3143d7b6..7cecc31d9a6 100644 --- a/addons/crm/tests/test_crm_lead_notification.py +++ b/addons/crm/tests/test_crm_lead_notification.py @@ -49,7 +49,6 @@ class NewLeadNotification(TestCrmCommon): crm_team_model = self.env['ir.model'].search([('model', '=', 'crm.team')]) crm_lead_model = self.env['ir.model'].search([('model', '=', 'crm.lead')]) - self.env["ir.config_parameter"].sudo().set_param("mail.catchall.domain", 'aqualung.com') crm_team0 = self.env['crm.team'].create({ 'name': 'crm team 0', @@ -83,7 +82,7 @@ Date: Thu, 27 Dec 2018 16:27:45 +0100 Message-ID: Subject: sale team 0 in company 0 From: A client -To: sale_team_0@aqualung.com +To: sale_team_0@test.mycompany.com Content-Type: multipart/alternative; boundary="000000000000a47519057e029630" --000000000000a47519057e029630 @@ -104,7 +103,7 @@ Date: Thu, 27 Dec 2018 16:27:45 +0100 Message-ID: Subject: sale team 1 in company 1 From: B client -To: sale_team_1@aqualung.com +To: sale_team_1@test.mycompany.com Content-Type: multipart/alternative; boundary="000000000000a47519057e029630" --000000000000a47519057e029630 diff --git a/addons/google_calendar/models/google_sync.py b/addons/google_calendar/models/google_sync.py index 49f5e0edeb4..c7721e291fd 100644 --- a/addons/google_calendar/models/google_sync.py +++ b/addons/google_calendar/models/google_sync.py @@ -310,7 +310,7 @@ class GoogleSync(models.AbstractModel): email not in [partner.email_normalized for partner in partners]] if remaining: partners += self.env['mail.thread']._mail_find_partner_from_emails(remaining, records=self, force_create=True) - unsorted_partners = self.env['res.partner'].browse([p.id for p in partners]) + unsorted_partners = self.env['res.partner'].browse([p.id for p in partners if p.id]) # partners needs to be sorted according to the emails order provided by google k = {value: idx for idx, value in enumerate(emails)} result = unsorted_partners.sorted(key=lambda p: k.get(p.email_normalized, -1)) diff --git a/addons/google_calendar/tests/test_sync_google2odoo.py b/addons/google_calendar/tests/test_sync_google2odoo.py index e6732f2a8f6..4c32f605e3d 100644 --- a/addons/google_calendar/tests/test_sync_google2odoo.py +++ b/addons/google_calendar/tests/test_sync_google2odoo.py @@ -1303,10 +1303,9 @@ class TestSyncGoogle2Odoo(TestSyncGoogle): @patch_api def test_alias_email_sync_recurrence(self): - catchall_domain = self.env['ir.config_parameter'].sudo().get_param("mail.catchall.domain") + self.env['ir.config_parameter'].sudo().set_param('mail.catchall.domain', 'test.mycompany.com') alias_model = self.env['ir.model'].search([('model', '=', 'calendar.event')]) - self.env['mail.alias'].create({'alias_name': 'sale', 'alias_model_id': alias_model.id}) - alias_email = 'sale@%s' % catchall_domain if catchall_domain else 'sale@' + mail_alias = self.env['mail.alias'].create({'alias_name': 'sale', 'alias_model_id': alias_model.id}) google_id = 'oj44nep1ldf8a3ll02uip0c9aa' base_event = self.env['calendar.event'].create({ @@ -1333,7 +1332,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle): 'reminders': {'useDefault': True}, "attendees": [ { - "email": alias_email, "state": "accepted", + "email": mail_alias.display_name, + "state": "accepted", }, ], 'updated': self.now, diff --git a/addons/mail/tests/common.py b/addons/mail/tests/common.py index 2757ef48099..055eb69de80 100644 --- a/addons/mail/tests/common.py +++ b/addons/mail/tests/common.py @@ -84,7 +84,7 @@ class MockEmail(common.BaseCase, MockSmtplibCase): @classmethod def _init_mail_gateway(cls): - cls.alias_domain = 'test.com' + cls.alias_domain = 'test.mycompany.com' cls.alias_catchall = 'catchall.test' cls.alias_bounce = 'bounce.test' cls.default_from = 'notifications' @@ -92,13 +92,15 @@ class MockEmail(common.BaseCase, MockSmtplibCase): cls.env['ir.config_parameter'].set_param('mail.catchall.domain', cls.alias_domain) cls.env['ir.config_parameter'].set_param('mail.catchall.alias', cls.alias_catchall) cls.env['ir.config_parameter'].set_param('mail.default.from', cls.default_from) + + # mailer daemon email preformatting 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', + {'from_filter': cls.alias_domain, 'name': 'Domain Based Server', 'smtp_encryption': 'none', 'smtp_host': 'smtp_host', @@ -1087,25 +1089,26 @@ class MailCommon(common.TransactionCase, MailCase): @classmethod def setUpClass(cls): 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.partner_admin = cls.env.ref('base.partner_admin') + cls.company_admin = cls.user_admin.company_id + cls.company_admin.write({'email': 'company@example.com'}) with patch.object(Users, '_notify_security_setting_update', side_effect=lambda *args, **kwargs: None): 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'}) # have root available at hand, just in case cls.user_root = cls.env.ref('base.user_root') cls.partner_root = cls.user_root.partner_id + # give default values for all email aliases and domain + cls._init_mail_gateway() + cls._init_outgoing_gateway() + + # by default avoid rendering restriction complexity cls.env['ir.config_parameter'].set_param('mail.restrict.template.rendering', False) # test standard employee @@ -1164,6 +1167,7 @@ class MailCommon(common.TransactionCase, MailCase): """ Create another company, add it to admin and create an user that belongs to that new company. It allows to test flows with users from different companies. """ + # new company cls.company_2 = cls.env['res.company'].create({ 'currency_id': cls.env.ref('base.CAD').id, 'email': 'company_2@test.example.com', @@ -1171,6 +1175,7 @@ class MailCommon(common.TransactionCase, MailCase): }) cls.user_admin.write({'company_ids': [(4, cls.company_2.id)]}) + # employee specific to second company cls.user_employee_c2 = mail_new_test_user( cls.env, login='employee_c2', groups='base.group_user', @@ -1189,7 +1194,7 @@ class MailCommon(common.TransactionCase, MailCase): 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', + groups='base.group_user,base.group_erp_manager,mail.group_mail_template_editor,base.group_partner_manager', login='erp_manager', name='Etchenne Tchagada', notification_type='inbox', diff --git a/addons/test_mail/data/test_mail_data.py b/addons/test_mail/data/test_mail_data.py index 5597df0c611..c823fa60888 100644 --- a/addons/test_mail/data/test_mail_data.py +++ b/addons/test_mail/data/test_mail_data.py @@ -219,7 +219,7 @@ Date: Sun, 26 Mar 2023 05:23:22 +0200 Message-ID: {msg_id} Subject: {subject} From: "Sylvie Lelitre" -To: groups@test.com +To: groups@test.mycompany.com Content-Type: multipart/mixed; boundary="000000000000b951de05f7c47a9e" --000000000000b951de05f7c47a9e diff --git a/addons/test_mail/tests/__init__.py b/addons/test_mail/tests/__init__.py index 80cd71ffd88..29b8297d59d 100644 --- a/addons/test_mail/tests/__init__.py +++ b/addons/test_mail/tests/__init__.py @@ -3,6 +3,7 @@ from . import test_invite from . import test_ir_actions from . import test_mail_activity +from . import test_mail_alias from . import test_mail_composer from . import test_mail_composer_mixin from . import test_mail_followers diff --git a/addons/test_mail/tests/test_mail_alias.py b/addons/test_mail/tests/test_mail_alias.py new file mode 100644 index 00000000000..c853615f410 --- /dev/null +++ b/addons/test_mail/tests/test_mail_alias.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import exceptions +from odoo.addons.mail.tests.common import MailCommon +from odoo.tests import tagged +from odoo.tests.common import users +from odoo.tools import mute_logger + + +class TestMailAliasCommon(MailCommon): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._activate_multi_company() + + +@tagged('mail_gateway', 'mail_alias', 'multi_company') +class TestMailAlias(TestMailAliasCommon): + """ Test alias model features, constraints and behavior. """ + + def test_alias_domain_allowed_validation(self): + """ Check the validation of `mail.catchall.domain.allowed` system parameter""" + for value in [',', ',,', ', ,']: + with self.assertRaises(exceptions.ValidationError, + msg="The value '%s' should not be allowed" % value): + self.env['ir.config_parameter'].set_param('mail.catchall.domain.allowed', value) + + for value, expected in [ + ('', False), + ('hello.com', 'hello.com'), + ('hello.com,,', 'hello.com'), + ('hello.com,bonjour.com', 'hello.com,bonjour.com'), + ('hello.COM, BONJOUR.com', 'hello.com,bonjour.com'), + ]: + self.env['ir.config_parameter'].set_param('mail.catchall.domain.allowed', value) + self.assertEqual(self.env['ir.config_parameter'].get_param('mail.catchall.domain.allowed'), expected) + + def test_alias_name_unique(self): + alias_model_id = self.env['ir.model']._get('mail.test.gateway').id + catchall_alias = self.env['ir.config_parameter'].sudo().get_param('mail.catchall.alias') + bounce_alias = self.env['ir.config_parameter'].sudo().get_param('mail.bounce.alias') + + # test you cannot create aliases matching bounce / catchall + with self.assertRaises(exceptions.UserError), self.cr.savepoint(): + self.env['mail.alias'].create({'alias_model_id': alias_model_id, 'alias_name': catchall_alias}) + with self.assertRaises(exceptions.UserError), self.cr.savepoint(): + self.env['mail.alias'].create({'alias_model_id': alias_model_id, 'alias_name': bounce_alias}) + + new_mail_alias = self.env['mail.alias'].create({ + 'alias_model_id': alias_model_id, + 'alias_name': 'unused.test.alias' + }) + + # test that re-using catchall and bounce alias raises UserError + with self.assertRaises(exceptions.UserError), self.cr.savepoint(): + new_mail_alias.write({ + 'alias_name': catchall_alias + }) + with self.assertRaises(exceptions.UserError), self.cr.savepoint(): + new_mail_alias.write({ + 'alias_name': bounce_alias + }) + + new_mail_alias.write({'alias_name': 'another.unused.test.alias'}) + + # test that duplicating an alias should have blank name + copy_new_mail_alias = new_mail_alias.copy() + self.assertFalse(copy_new_mail_alias.alias_name) + + # cannot set catchall / bounce to used alias + with self.assertRaises(exceptions.UserError), self.cr.savepoint(): + self.env['ir.config_parameter'].sudo().set_param('mail.catchall.alias', new_mail_alias.alias_name) + with self.assertRaises(exceptions.UserError), self.cr.savepoint(): + self.env['ir.config_parameter'].sudo().set_param('mail.bounce.alias', new_mail_alias.alias_name) + + def test_alias_name_sanitize(self): + alias = self.env['mail.alias'].create({ + 'alias_model_id': self.env['ir.model']._get('mail.test.container').id, + 'alias_name': 'bidule...inc.', + }) + self.assertEqual(alias.alias_name, 'bidule.inc', 'Emails cannot start or end with a dot, there cannot be a sequence of dots.') + + alias = self.env['mail.alias'].create({ + 'alias_model_id': self.env['ir.model']._get('mail.test.container').id, + 'alias_name': 'b4r+_#_R3wl$$', + }) + self.assertEqual(alias.alias_name, 'b4r+_-_r3wl-', 'Disallowed chars should be replaced by hyphens') + + with self.assertRaises(exceptions.ValidationError): + alias.write({'alias_defaults': "{'custom_field': brokendict"}) + + +@tagged('mail_gateway', 'mail_alias', 'multi_company') +class TestMailAliasMixin(TestMailAliasCommon): + """ Test alias mixin implementation, synchornization of alias records + based on owner records. """ + + @users('employee') + @mute_logger('odoo.addons.base.models.ir_model') + def test_alias_creation(self): + record = self.env['mail.test.container'].create({ + 'name': 'Test Record', + 'alias_name': 'alias.test', + 'alias_contact': 'followers', + }) + self.assertEqual(record.alias_id.alias_model_id, self.env['ir.model']._get('mail.test.container')) + self.assertEqual(record.alias_id.alias_force_thread_id, record.id) + self.assertEqual(record.alias_id.alias_parent_model_id, self.env['ir.model']._get('mail.test.container')) + self.assertEqual(record.alias_id.alias_parent_thread_id, record.id) + self.assertEqual(record.alias_id.alias_name, 'alias.test') + self.assertEqual(record.alias_id.alias_contact, 'followers') + + record.write({ + 'alias_name': 'better.alias.test', + 'alias_defaults': "{'default_name': 'defaults'}" + }) + self.assertEqual(record.alias_id.alias_name, 'better.alias.test') + self.assertEqual(record.alias_id.alias_defaults, "{'default_name': 'defaults'}") + + with self.assertRaises(exceptions.AccessError): + record.write({ + 'alias_force_thread_id': 0, + }) + + with self.assertRaises(exceptions.AccessError): + record.write({ + 'alias_model_id': self.env['ir.model']._get('mail.test.gateway').id, + }) + + with self.assertRaises(exceptions.ValidationError): + record.write({'alias_defaults': "{'custom_field': brokendict"}) + + @users('employee') + def test_alias_mixin_copy_content(self): + self.assertFalse(self.env.user.has_group('base.group_system'), 'Test user should not have Administrator access') + + record = self.env['mail.test.container'].create({ + 'name': 'Test Record', + 'alias_name': 'test.record', + 'alias_contact': 'followers', + 'alias_bounced_content': False, + }) + self.assertFalse(record.alias_bounced_content) + record_copy = record.copy() + self.assertFalse(record_copy.alias_bounced_content) + + new_content = '

Bounced Content

' + record_copy.write({'alias_bounced_content': new_content}) + self.assertEqual(record_copy.alias_bounced_content, new_content) + record_copy2 = record_copy.copy() + self.assertEqual(record_copy2.alias_bounced_content, new_content) diff --git a/addons/test_mail/tests/test_mail_composer.py b/addons/test_mail/tests/test_mail_composer.py index b74993a726b..e2a3917cc94 100644 --- a/addons/test_mail/tests/test_mail_composer.py +++ b/addons/test_mail/tests/test_mail_composer.py @@ -2278,7 +2278,7 @@ class TestComposerResultsMass(TestMailComposer): default_template_id=self.template.id) )) composer_form.reply_to_mode = 'new' - composer_form.reply_to = "{{ '\"' + object.name + '\" <%s>' % 'dynamic.reply.to@test.com' }}" + composer_form.reply_to = "{{ '\"' + object.name + '\" <%s>' % 'dynamic.reply.to@test.mycompany.com' }}" composer = composer_form.save() self.assertTrue(composer.reply_to_force_new) with self.mock_mail_gateway(mail_unlink_sent=False): @@ -2294,7 +2294,7 @@ class TestComposerResultsMass(TestMailComposer): 'email_from': self.partner_employee_2.email_formatted, 'reply_to': formataddr(( f'{record.name}', - 'dynamic.reply.to@test.com' + 'dynamic.reply.to@test.mycompany.com' )), 'subject': 'TemplateSubject %s' % record.name, }, @@ -2302,7 +2302,7 @@ class TestComposerResultsMass(TestMailComposer): 'email_from': self.partner_employee_2.email_formatted, 'reply_to': formataddr(( f'{record.name}', - 'dynamic.reply.to@test.com' + 'dynamic.reply.to@test.mycompany.com' )), }, ) diff --git a/addons/test_mail/tests/test_mail_followers.py b/addons/test_mail/tests/test_mail_followers.py index 0f6f1c1b6be..d3fff6e69cb 100644 --- a/addons/test_mail/tests/test_mail_followers.py +++ b/addons/test_mail/tests/test_mail_followers.py @@ -464,7 +464,7 @@ class AdvancedResponsibleNotifiedTest(MailCommon): def test_auto_subscribe_notify_email(self): """ Responsible is notified when assigned """ - partner = self.env['res.partner'].create({"name": "demo1", "email": "demo1@test.com"}) + partner = self.env['res.partner'].create({"name": "demo1", "email": "demo1@test.mycompany.com"}) notified_user = self.env['res.users'].create({ 'login': 'demo1', 'partner_id': partner.id, diff --git a/addons/test_mail/tests/test_mail_gateway.py b/addons/test_mail/tests/test_mail_gateway.py index 40857f8ef19..75f144db9ef 100644 --- a/addons/test_mail/tests/test_mail_gateway.py +++ b/addons/test_mail/tests/test_mail_gateway.py @@ -16,7 +16,6 @@ from odoo.addons.test_mail.data.test_mail_data import MAIL_TEMPLATE from odoo.addons.test_mail.models.test_mail_models import MailTestGateway from odoo.sql_db import Cursor from odoo.tests import tagged -from odoo.tests.common import users from odoo.tools import email_split_and_format, formataddr, mute_logger @@ -87,7 +86,7 @@ class TestEmailParsing(MailCommon): def test_message_parse_eml(self): # Test that the parsing of mail with embedded emails as eml(msg) which generates empty attachments, can be processed. - mail = self.format(test_mail_data.MAIL_EML_ATTACHMENT, email_from='"Sylvie Lelitre" ', to='generic@test.com') + mail = self.format(test_mail_data.MAIL_EML_ATTACHMENT, email_from='"Sylvie Lelitre" ', to=f'generic@{self.alias_domain}') self.env['mail.thread'].message_parse(self.from_string(mail)) def test_message_parse_eml_bounce_headers(self): @@ -131,7 +130,7 @@ class TestEmailParsing(MailCommon): def test_message_parse_plaintext(self): """ Incoming email in plaintext should be stored as html """ - mail = self.format(test_mail_data.MAIL_TEMPLATE_PLAINTEXT, email_from='"Sylvie Lelitre" ', to='generic@test.com') + mail = self.format(test_mail_data.MAIL_TEMPLATE_PLAINTEXT, email_from='"Sylvie Lelitre" ', to=f'generic@{self.alias_domain}') res = self.env['mail.thread'].message_parse(self.from_string(mail)) self.assertIn('
\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n
', res['body']) @@ -139,140 +138,6 @@ class TestEmailParsing(MailCommon): # Test that the parsing of XHTML mails does not fail self.env['mail.thread'].message_parse(self.from_string(test_mail_data.MAIL_XHTML)) -@tagged('mail_gateway') -class TestMailAlias(MailCommon): - - @users('employee') - @mute_logger('odoo.addons.base.models.ir_model') - def test_alias_creation(self): - record = self.env['mail.test.container'].create({ - 'name': 'Test Record', - 'alias_name': 'alias.test', - 'alias_contact': 'followers', - }) - self.assertEqual(record.alias_id.alias_model_id, self.env['ir.model']._get('mail.test.container')) - self.assertEqual(record.alias_id.alias_force_thread_id, record.id) - self.assertEqual(record.alias_id.alias_parent_model_id, self.env['ir.model']._get('mail.test.container')) - self.assertEqual(record.alias_id.alias_parent_thread_id, record.id) - self.assertEqual(record.alias_id.alias_name, 'alias.test') - self.assertEqual(record.alias_id.alias_contact, 'followers') - - record.write({ - 'alias_name': 'better.alias.test', - 'alias_defaults': "{'default_name': 'defaults'}" - }) - self.assertEqual(record.alias_id.alias_name, 'better.alias.test') - self.assertEqual(record.alias_id.alias_defaults, "{'default_name': 'defaults'}") - - with self.assertRaises(exceptions.AccessError): - record.write({ - 'alias_force_thread_id': 0, - }) - - with self.assertRaises(exceptions.AccessError): - record.write({ - 'alias_model_id': self.env['ir.model']._get('mail.test.gateway').id, - }) - - with self.assertRaises(exceptions.ValidationError): - record.write({'alias_defaults': "{'custom_field': brokendict"}) - - def test_alias_domain_allowed_validation(self): - """ Check the validation of `mail.catchall.domain.allowed` system parameter""" - for value in [',', ',,', ', ,']: - with self.assertRaises(exceptions.ValidationError, - msg="The value '%s' should not be allowed" % value): - self.env['ir.config_parameter'].set_param('mail.catchall.domain.allowed', value) - - for value, expected in [ - ('', False), - ('hello.com', 'hello.com'), - ('hello.com,,', 'hello.com'), - ('hello.com,bonjour.com', 'hello.com,bonjour.com'), - ('hello.COM, BONJOUR.com', 'hello.com,bonjour.com'), - ]: - self.env['ir.config_parameter'].set_param('mail.catchall.domain.allowed', value) - self.assertEqual(self.env['ir.config_parameter'].get_param('mail.catchall.domain.allowed'), expected) - - def test_alias_sanitize(self): - alias = self.env['mail.alias'].create({ - 'alias_model_id': self.env['ir.model']._get('mail.test.container').id, - 'alias_name': 'bidule...inc.', - }) - self.assertEqual(alias.alias_name, 'bidule.inc', 'Emails cannot start or end with a dot, there cannot be a sequence of dots.') - - def test_alias_setup(self): - alias = self.env['mail.alias'].create({ - 'alias_model_id': self.env['ir.model']._get('mail.test.container').id, - 'alias_name': 'b4r+_#_R3wl$$', - }) - self.assertEqual(alias.alias_name, 'b4r+_-_r3wl-', 'Disallowed chars should be replaced by hyphens') - - with self.assertRaises(exceptions.ValidationError): - alias.write({'alias_defaults': "{'custom_field': brokendict"}) - - def test_alias_name_unique(self): - alias_model_id = self.env['ir.model']._get('mail.test.gateway').id - catchall_alias = self.env['ir.config_parameter'].sudo().get_param('mail.catchall.alias') - bounce_alias = self.env['ir.config_parameter'].sudo().get_param('mail.bounce.alias') - - # test you cannot create aliases matching bounce / catchall - with self.assertRaises(exceptions.UserError), self.cr.savepoint(): - self.env['mail.alias'].create({'alias_model_id': alias_model_id, 'alias_name': catchall_alias}) - with self.assertRaises(exceptions.UserError), self.cr.savepoint(): - self.env['mail.alias'].create({'alias_model_id': alias_model_id, 'alias_name': bounce_alias}) - - new_mail_alias = self.env['mail.alias'].create({ - 'alias_model_id': alias_model_id, - 'alias_name': 'unused.test.alias' - }) - - # test that re-using catchall and bounce alias raises UserError - with self.assertRaises(exceptions.UserError), self.cr.savepoint(): - new_mail_alias.write({ - 'alias_name': catchall_alias - }) - with self.assertRaises(exceptions.UserError), self.cr.savepoint(): - new_mail_alias.write({ - 'alias_name': bounce_alias - }) - - new_mail_alias.write({'alias_name': 'another.unused.test.alias'}) - - # test that duplicating an alias should have blank name - copy_new_mail_alias = new_mail_alias.copy() - self.assertFalse(copy_new_mail_alias.alias_name) - - # cannot set catchall / bounce to used alias - with self.assertRaises(exceptions.UserError), self.cr.savepoint(): - self.env['ir.config_parameter'].sudo().set_param('mail.catchall.alias', new_mail_alias.alias_name) - with self.assertRaises(exceptions.UserError), self.cr.savepoint(): - self.env['ir.config_parameter'].sudo().set_param('mail.bounce.alias', new_mail_alias.alias_name) - - -@tagged('mail_gateway') -class TestMailAliasMixin(MailCommon): - - @users('employee') - def test_alias_mixin_copy_content(self): - self.assertFalse(self.env.user.has_group('base.group_system'), 'Test user should not have Administrator access') - - record = self.env['mail.test.container'].create({ - 'name': 'Test Record', - 'alias_name': 'test.record', - 'alias_contact': 'followers', - 'alias_bounced_content': False, - }) - self.assertFalse(record.alias_bounced_content) - record_copy = record.copy() - self.assertFalse(record_copy.alias_bounced_content) - - new_content = '

Bounced Content

' - record_copy.write({'alias_bounced_content': new_content}) - self.assertEqual(record_copy.alias_bounced_content, new_content) - record_copy2 = record_copy.copy() - self.assertEqual(record_copy2.alias_bounced_content, new_content) - @tagged('mail_gateway') class TestMailgateway(MailCommon): @@ -280,7 +145,9 @@ class TestMailgateway(MailCommon): @classmethod def setUpClass(cls): super(TestMailgateway, cls).setUpClass() - cls.test_model = cls.env['ir.model']._get('mail.test.gateway') + cls._activate_multi_company() + + cls.mail_test_gateway_model = cls.env['ir.model']._get('mail.test.gateway') cls.email_from = '"Sylvie Lelitre" ' cls.test_record = cls.env['mail.test.gateway'].with_context(cls._test_context).create({ @@ -294,10 +161,11 @@ class TestMailgateway(MailCommon): }) # groups@.. will cause the creation of new mail.test.gateway cls.alias = cls.env['mail.alias'].create({ + 'alias_contact': 'everyone', + 'alias_model_id': cls.mail_test_gateway_model.id, 'alias_name': 'groups', 'alias_user_id': False, - 'alias_model_id': cls.test_model.id, - 'alias_contact': 'everyone'}) + }) # Set a first message on public group to test update and hierarchy cls.fake_email = cls._create_gateway_message(cls.test_record, '123456') @@ -325,7 +193,7 @@ class TestMailgateway(MailCommon): @mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models') def test_message_process_alias_basic(self): """ Test details of created message going through mailgateway """ - record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Specific') + record = self.format_and_process(MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Specific') # Test: one group created by mailgateway administrator as user_id is not set self.assertEqual(len(record), 1, 'message_process: a new mail.test should have been created') @@ -342,7 +210,7 @@ class TestMailgateway(MailCommon): @mute_logger('odoo.addons.mail.models.mail_thread') def test_message_process_cid(self): - record = self.format_and_process(test_mail_data.MAIL_MULTIPART_IMAGE, self.email_from, 'groups@test.com') + record = self.format_and_process(test_mail_data.MAIL_MULTIPART_IMAGE, self.email_from, f'groups@{self.alias_domain}') message = record.message_ids[0] for attachment in message.attachment_ids: self.assertIn('/web/image/%s' % attachment.id, message.body) @@ -363,7 +231,7 @@ class TestMailgateway(MailCommon): }) with self.mock_mail_gateway(): - record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, 'groups@test.com') + record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, f'groups@{self.alias_domain}') self.assertEqual(record.message_ids[0].author_id, self.partner_1, 'message_process: recognized email -> author_id') @@ -376,7 +244,7 @@ class TestMailgateway(MailCommon): # just an email -> no follower with self.mock_mail_gateway(): record2 = self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'groups@test.com', + MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Another Email') self.assertEqual(record2.message_ids[0].author_id, self.env['res.partner']) @@ -389,7 +257,7 @@ class TestMailgateway(MailCommon): # archived partner -> no follower with self.mock_mail_gateway(): record3 = self.format_and_process( - MAIL_TEMPLATE, partner_archived.email_formatted, 'groups@test.com', + MAIL_TEMPLATE, partner_archived.email_formatted, f'groups@{self.alias_domain}', subject='Archived Partner') self.assertEqual(record3.message_ids[0].author_id, self.env['res.partner']) @@ -405,7 +273,7 @@ class TestMailgateway(MailCommon): odoobot.email = 'odoobot@example.com' with self.mock_mail_gateway(): record4 = self.format_and_process( - MAIL_TEMPLATE, odoobot.email_formatted, 'groups@test.com', + MAIL_TEMPLATE, odoobot.email_formatted, f'groups@{self.alias_domain}', subject='Odoobot Automatic Answer') self.assertEqual(record4.message_ids[0].author_id, odoobot) @@ -422,7 +290,7 @@ class TestMailgateway(MailCommon): @mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models') def test_message_process_email_email_from(self): """ Incoming email: not recognized author: email_from, no author_id, no followers """ - record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com') + record = self.format_and_process(MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}') self.assertFalse(record.message_ids[0].author_id, 'message_process: unrecognized email -> no author_id') self.assertEqual(record.message_ids[0].email_from, self.email_from) self.assertEqual(len(record.message_partner_ids), 0, @@ -432,7 +300,7 @@ class TestMailgateway(MailCommon): def test_message_process_email_author(self): """ Incoming email: recognized author: email_from, author_id, added as follower """ with self.mock_mail_gateway(): - record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, 'groups@test.com', subject='Test1') + record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, f'groups@{self.alias_domain}', subject='Test1') self.assertEqual(record.message_ids[0].author_id, self.partner_1, 'message_process: recognized email -> author_id') @@ -441,45 +309,48 @@ class TestMailgateway(MailCommon): # Email recognized if partner has a formatted email self.partner_1.write({'email': '"Valid Lelitre" <%s>' % self.partner_1.email}) - record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email, 'groups@test.com', subject='Test2') + record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email, f'groups@{self.alias_domain}', subject='Test2') self.assertEqual(record.message_ids[0].author_id, self.partner_1, 'message_process: recognized email -> author_id') self.assertEqual(record.message_ids[0].email_from, self.partner_1.email) self.assertNotSentEmail() # No notification / bounce should be sent - @mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models.unlink') + @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_thread', 'odoo.models.unlink', 'odoo.tests') def test_message_process_email_partner_find(self): """ Finding the partner based on email, based on partner / user / follower """ self.alias.write({'alias_force_thread_id': self.test_record.id}) from_1 = self.env['res.partner'].create({'name': 'Brice Denisse', 'email': 'from.test@example.com'}) - self.format_and_process(MAIL_TEMPLATE, from_1.email_formatted, 'groups@test.com') + self.format_and_process(MAIL_TEMPLATE, from_1.email_formatted, f'groups@{self.alias_domain}') self.assertEqual(self.test_record.message_ids[0].author_id, from_1) self.test_record.message_unsubscribe([from_1.id]) from_2 = mail_new_test_user(self.env, login='B', groups='base.group_user', name='User Denisse', email='from.test@example.com') - self.format_and_process(MAIL_TEMPLATE, from_1.email_formatted, 'groups@test.com') + self.format_and_process(MAIL_TEMPLATE, from_1.email_formatted, f'groups@{self.alias_domain}') self.assertEqual(self.test_record.message_ids[0].author_id, from_2.partner_id) self.test_record.message_unsubscribe([from_2.partner_id.id]) from_3 = self.env['res.partner'].create({'name': 'FOllower Denisse', 'email': 'from.test@example.com'}) self.test_record.message_subscribe([from_3.id]) - self.format_and_process(MAIL_TEMPLATE, from_1.email_formatted, 'groups@test.com') + self.format_and_process(MAIL_TEMPLATE, from_1.email_formatted, f'groups@{self.alias_domain}') self.assertEqual(self.test_record.message_ids[0].author_id, from_3) @mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models') def test_message_process_email_author_exclude_alias(self): """ Do not set alias as author to avoid including aliases in discussions """ - from_1 = self.env['res.partner'].create({'name': 'Brice Denisse', 'email': 'from.test@test.com'}) + from_1 = self.env['res.partner'].create({ + 'name': 'Brice Denisse', + 'email': f'from.test@{self.alias_domain}', + }) self.env['mail.alias'].create({ 'alias_name': 'from.test', 'alias_model_id': self.env['ir.model']._get('mail.test.gateway').id }) - record = self.format_and_process(MAIL_TEMPLATE, from_1.email_formatted, 'groups@test.com') + record = self.format_and_process(MAIL_TEMPLATE, from_1.email_formatted, f'groups@{self.alias_domain}') self.assertFalse(record.message_ids[0].author_id) self.assertEqual(record.message_ids[0].email_from, from_1.email_formatted) @@ -497,9 +368,9 @@ class TestMailgateway(MailCommon): # Test: custom bounced content with self.mock_mail_gateway(): - record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Should Bounce') + record = self.format_and_process(MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Should Bounce') self.assertFalse(record, 'message_process: should have bounced') - self.assertSentEmail('"MAILER-DAEMON" ', ['whatever-2a840@postmaster.twitter.com'], body_content='

What Is Dead May Never Die

') + self.assertSentEmail(f'"MAILER-DAEMON" <{self.alias_bounce}@{self.alias_domain}>', ['whatever-2a840@postmaster.twitter.com'], body_content='

What Is Dead May Never Die

') for empty_content in [ '


', '


', '


', @@ -515,11 +386,12 @@ class TestMailgateway(MailCommon): # Test: with "empty" bounced content (simulate view, putting always '


' in html field) with self.mock_mail_gateway(): - record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Should Bounce') + record = self.format_and_process(MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Should Bounce') self.assertFalse(record, 'message_process: should have bounced') # Check if default (hardcoded) value is in the mail content self.assertSentEmail( - '"MAILER-DAEMON" ', ['whatever-2a840@postmaster.twitter.com'], + f'"MAILER-DAEMON" <{self.alias_bounce}@{self.alias_domain}>', + ['whatever-2a840@postmaster.twitter.com'], body_content='

Dear Sender,

The message below could not be accepted by the address %s' % self.alias.display_name.lower() ) @@ -567,7 +439,7 @@ class TestMailgateway(MailCommon): self.assertEqual(alias_valid.alias_status, 'not_tested') with self.mock_mail_gateway(), patch('odoo.addons.mail.models.mail_alias.Alias._set_alias_invalid', autospec=True) as _set_alias_invalid_mock: - record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'valid@test.com', subject='Valid', + record = self.format_and_process(MAIL_TEMPLATE, self.email_from, f'valid@{self.alias_domain}', subject='Valid', target_model=test_model_track.model) _set_alias_invalid_mock.assert_not_called() self.assertNotSentEmail() @@ -578,7 +450,7 @@ class TestMailgateway(MailCommon): container_custom.unlink() with self.assertRaises(Exception), patch('odoo.addons.mail.models.mail_alias.Alias._set_alias_invalid', autospec=True) as _set_alias_invalid_mock: - self.format_and_process(MAIL_TEMPLATE, self.email_from, 'valid@test.com', subject='Invalid', + self.format_and_process(MAIL_TEMPLATE, self.email_from, f'valid@{self.alias_domain}', subject='Invalid', target_model=test_model_track.model) # method executed in another transaction, so we cannot test its result directly but just below @@ -591,11 +463,11 @@ class TestMailgateway(MailCommon): alias._set_alias_invalid(message, message_dict) self.assertEqual(alias_valid.alias_status, 'invalid') - self.assertSentEmail('"MAILER-DAEMON" ', + self.assertSentEmail(f'"MAILER-DAEMON" <{self.alias_bounce}@{self.alias_domain}>', [alias_valid.alias_user_id.partner_id.email_formatted], subject='Re: Invalid') # Not sent to self.email_from because a return path is present in MAIL_TEMPLATE - self.assertSentEmail('"MAILER-DAEMON" ', + self.assertSentEmail(f'"MAILER-DAEMON" <{self.alias_bounce}@{self.alias_domain}>', ['whatever-2a840@postmaster.twitter.com'], subject='Re: Invalid', body=alias_valid._get_alias_invalid_body(message_dict)) @@ -611,7 +483,7 @@ class TestMailgateway(MailCommon): self.assertEqual(self.alias.alias_status, 'not_tested') - record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Specific') + record = self.format_and_process(MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Specific') _set_alias_invalid.assert_not_called() self.assertEqual(self.alias.alias_status, 'valid') @@ -623,7 +495,7 @@ class TestMailgateway(MailCommon): self.alias.write({'alias_defaults': '""'}) self.assertEqual(self.alias.alias_status, 'not_tested', 'Updating alias_defaults must reset status') - record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Specific2') + record = self.format_and_process(MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Specific2') _set_alias_invalid.assert_not_called() self.assertEqual(len(record), 1) @@ -639,7 +511,7 @@ class TestMailgateway(MailCommon): """ Test alias ownership """ self.alias.write({'alias_user_id': self.user_employee.id}) - record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com') + record = self.format_and_process(MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}') self.assertEqual(len(record), 1) res = record.get_metadata()[0].get('create_uid') or [None] self.assertEqual(res[0], self.user_employee.id) @@ -649,7 +521,7 @@ class TestMailgateway(MailCommon): """ Incoming email: everyone: new record + message_new """ self.alias.write({'alias_contact': 'everyone'}) - record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Specific') + record = self.format_and_process(MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Specific') self.assertEqual(len(record), 1) self.assertEqual(len(record.message_ids), 1) @@ -660,9 +532,9 @@ class TestMailgateway(MailCommon): # Test: no group created, email bounced with self.mock_mail_gateway(): - record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Should Bounce') + record = self.format_and_process(MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Should Bounce') self.assertFalse(record) - self.assertSentEmail('"MAILER-DAEMON" ', ['whatever-2a840@postmaster.twitter.com'], subject='Re: Should Bounce') + self.assertSentEmail(f'"MAILER-DAEMON" <{self.alias_bounce}@{self.alias_domain}>', ['whatever-2a840@postmaster.twitter.com'], subject='Re: Should Bounce') @mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') def test_message_process_alias_followers_bounce(self): @@ -675,22 +547,30 @@ class TestMailgateway(MailCommon): # Test: unknown on followers alias -> bounce with self.mock_mail_gateway(): - record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Should Bounce') + record = self.format_and_process(MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Should Bounce') self.assertFalse(record, 'message_process: should have bounced') - self.assertSentEmail('"MAILER-DAEMON" ', ['whatever-2a840@postmaster.twitter.com'], subject='Re: Should Bounce') + self.assertSentEmail( + f'"MAILER-DAEMON" <{self.alias_bounce}@{self.alias_domain}>', + ['whatever-2a840@postmaster.twitter.com'], + subject='Re: Should Bounce' + ) # Test: partner on followers alias -> bounce self._init_mail_mock() with self.mock_mail_gateway(): - record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, 'groups@test.com', subject='Should Bounce') + record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, f'groups@{self.alias_domain}', subject='Should Bounce') self.assertFalse(record, 'message_process: should have bounced') - self.assertSentEmail('"MAILER-DAEMON" ', ['whatever-2a840@postmaster.twitter.com'], subject='Re: Should Bounce') + self.assertSentEmail( + f'"MAILER-DAEMON" <{self.alias_bounce}@{self.alias_domain}>', + ['whatever-2a840@postmaster.twitter.com'], + subject='Re: Should Bounce' + ) @mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models') def test_message_process_alias_partner(self): """ Incoming email from a known partner on a Partners alias -> ok (+ test on alias.user_id) """ self.alias.write({'alias_contact': 'partners'}) - record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, 'groups@test.com') + record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, f'groups@{self.alias_domain}') # Test: one group created by alias user self.assertEqual(len(record), 1) @@ -705,7 +585,7 @@ class TestMailgateway(MailCommon): 'alias_parent_thread_id': self.test_record.id, }) self.test_record.message_subscribe(partner_ids=[self.partner_1.id]) - record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, 'groups@test.com') + record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, f'groups@{self.alias_domain}') # Test: one group created by Raoul (or Sylvie maybe, if we implement it) self.assertEqual(len(record), 1) @@ -718,7 +598,7 @@ class TestMailgateway(MailCommon): self.test_record.message_subscribe(partner_ids=[self.partner_1.id]) with self.mock_mail_gateway(): record = self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'groups@test.com>', + MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', msg_id='<1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>', subject='Re: cats') # Test: no new group + new message @@ -743,7 +623,7 @@ class TestMailgateway(MailCommon): return DEFAULT with patch.object(MailTestGateway, 'check_access_rights', autospec=True, side_effect=_employee_crash): - record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_formatted, 'groups@test.com', subject='NoEmployeeAllowed') + record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_formatted, f'groups@{self.alias_domain}', subject='NoEmployeeAllowed') self.assertEqual(record.create_uid, self.user_employee) self.assertEqual(record.message_ids[0].subject, 'NoEmployeeAllowed') self.assertEqual(record.message_ids[0].create_uid, self.user_employee) @@ -751,19 +631,19 @@ class TestMailgateway(MailCommon): @mute_logger('odoo.addons.mail.models.mail_thread') def test_message_process_create_uid_email(self): - record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_formatted, 'groups@test.com', subject='Email Found') + record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_formatted, f'groups@{self.alias_domain}', subject='Email Found') self.assertEqual(record.create_uid, self.user_employee) self.assertEqual(record.message_ids[0].subject, 'Email Found') self.assertEqual(record.message_ids[0].create_uid, self.user_employee) self.assertEqual(record.message_ids[0].author_id, self.user_employee.partner_id) - record = self.format_and_process(MAIL_TEMPLATE, 'Another name <%s>' % self.user_employee.email, 'groups@test.com', subject='Email OtherName') + record = self.format_and_process(MAIL_TEMPLATE, 'Another name <%s>' % self.user_employee.email, f'groups@{self.alias_domain}', subject='Email OtherName') self.assertEqual(record.create_uid, self.user_employee) self.assertEqual(record.message_ids[0].subject, 'Email OtherName') self.assertEqual(record.message_ids[0].create_uid, self.user_employee) self.assertEqual(record.message_ids[0].author_id, self.user_employee.partner_id) - record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_normalized, 'groups@test.com', subject='Email SimpleEmail') + record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_normalized, f'groups@{self.alias_domain}', subject='Email SimpleEmail') self.assertEqual(record.create_uid, self.user_employee) self.assertEqual(record.message_ids[0].subject, 'Email SimpleEmail') self.assertEqual(record.message_ids[0].create_uid, self.user_employee) @@ -772,13 +652,13 @@ class TestMailgateway(MailCommon): @mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models.unlink') def test_message_process_create_uid_email_follower(self): self.alias.write({ - 'alias_parent_model_id': self.test_model.id, + 'alias_parent_model_id': self.mail_test_gateway_model.id, 'alias_parent_thread_id': self.test_record.id, }) follower_user = mail_new_test_user(self.env, login='better', groups='base.group_user', name='Ernest Follower', email=self.user_employee.email) self.test_record.message_subscribe(follower_user.partner_id.ids) - record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_formatted, 'groups@test.com', subject='FollowerWinner') + record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_formatted, f'groups@{self.alias_domain}', subject='FollowerWinner') self.assertEqual(record.create_uid, follower_user) self.assertEqual(record.message_ids[0].subject, 'FollowerWinner') self.assertEqual(record.message_ids[0].create_uid, follower_user) @@ -787,7 +667,7 @@ class TestMailgateway(MailCommon): # name order win self.test_record.message_unsubscribe(follower_user.partner_id.ids) self.test_record.flush_recordset() - record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_formatted, 'groups@test.com', subject='FirstFoundWinner') + record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_formatted, f'groups@{self.alias_domain}', subject='FirstFoundWinner') self.assertEqual(record.create_uid, self.user_employee) self.assertEqual(record.message_ids[0].subject, 'FirstFoundWinner') self.assertEqual(record.message_ids[0].create_uid, self.user_employee) @@ -1014,7 +894,11 @@ class TestMailgateway(MailCommon): '"My Super Catchall" <%s@%s>' % (self.alias_catchall, self.alias_domain), subject='Should Bounce') self.assertFalse(record) - self.assertSentEmail('"MAILER-DAEMON" ', ['whatever-2a840@postmaster.twitter.com'], subject='Re: Should Bounce') + self.assertSentEmail( + f'"MAILER-DAEMON" <{self.alias_bounce}@{self.alias_domain}>', + ['whatever-2a840@postmaster.twitter.com'], + subject='Re: Should Bounce' + ) @mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models') def test_message_route_write_to_catchall_other_recipients_first(self): @@ -1052,8 +936,7 @@ class TestMailgateway(MailCommon): self.assertEqual(self.partner_1.message_bounce, 0) self.assertEqual(self.test_record.message_bounce, 0) - bounced_mail_id = 4442 - bounce_email_to = '%s@%s' % ('bounce.test', 'test.com') + bounce_email_to = f'{self.alias_bounce}@{self.alias_domain}' record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, bounce_email_to, subject='Undelivered Mail Returned to Sender') self.assertFalse(record) # No information found in bounce email -> not possible to do anything except avoiding email @@ -1066,7 +949,7 @@ class TestMailgateway(MailCommon): self.assertEqual(self.partner_1.message_bounce, 0) self.assertEqual(self.test_record.message_bounce, 0) - record = self.format_and_process(MAIL_TEMPLATE, 'MAILER-DAEMON@example.com', 'groups@test.com', subject='Undelivered Mail Returned to Sender') + record = self.format_and_process(MAIL_TEMPLATE, 'MAILER-DAEMON@example.com', f'groups@{self.alias_domain}', subject='Undelivered Mail Returned to Sender') self.assertFalse(record) # No information found in bounce email -> not possible to do anything except avoiding email self.assertEqual(self.partner_1.message_bounce, 0) @@ -1083,7 +966,7 @@ class TestMailgateway(MailCommon): # no notification to find, won't be able to find the correct recipient extra = self.fake_email.message_id - record = self.format_and_process(email, self.partner_1.email_formatted, 'bounce.test@%test.com', subject='Undelivered Mail Returned to Sender', extra=extra) + record = self.format_and_process(email, self.partner_1.email_formatted, f'{self.alias_bounce}@{self.alias_domain}', subject='Undelivered Mail Returned to Sender', extra=extra) self.assertFalse(record) self.assertEqual(self.partner_1.message_bounce, 0) self.assertEqual(self.test_record.message_bounce, 0) @@ -1094,7 +977,7 @@ class TestMailgateway(MailCommon): "res_partner_id": self.partner_1.id, "mail_message_id": self.fake_email.id, }) - record = self.format_and_process(email, self.partner_1.email_formatted, 'bounce.test@%test.com', subject='Undelivered Mail Returned to Sender', extra=extra) + record = self.format_and_process(email, self.partner_1.email_formatted, f'{self.alias_bounce}@{self.alias_domain}', subject='Undelivered Mail Returned to Sender', extra=extra) self.assertFalse(record) self.assertEqual(self.partner_1.message_bounce, 1) self.assertEqual(self.test_record.message_bounce, 1) @@ -1105,8 +988,7 @@ class TestMailgateway(MailCommon): self.assertEqual(self.partner_1.message_bounce, 0) self.assertEqual(self.test_record.message_bounce, 0) - bounced_mail_id = 4442 - bounce_email_to = '%s@%s' % ('bounce.test', 'test.com') + bounce_email_to = f'{self.alias_bounce}@{self.alias_domain}' record = self.format_and_process(test_mail_data.MAIL_BOUNCE, self.partner_1.email_formatted, bounce_email_to, subject='Undelivered Mail Returned to Sender') self.assertFalse(record) # Missing in reply to message_id -> cannot find original record @@ -1124,8 +1006,7 @@ class TestMailgateway(MailCommon): 'res_partner_id': self.partner_1.id, }) - bounced_mail_id = 4442 - bounce_email_to = '%s@%s' % ('bounce.test', 'test.com') + bounce_email_to = f'{self.alias_bounce}@{self.alias_domain}' extra = self.fake_email.message_id record = self.format_and_process(test_mail_data.MAIL_BOUNCE, self.partner_1.email_formatted, bounce_email_to, subject='Undelivered Mail Returned to Sender', extra=extra) self.assertFalse(record) @@ -1144,8 +1025,7 @@ class TestMailgateway(MailCommon): self.assertEqual(self.partner_1.message_bounce, 0) self.assertEqual(self.test_record.message_bounce, 0) - bounced_mail_id = 4442 - bounce_email_to = '%s@%s' % ('bounce.test', 'test.com') + bounce_email_to = f'{self.alias_bounce}@{self.alias_domain}' extra = self.fake_email.message_id record = self.format_and_process(test_mail_data.MAIL_BOUNCE, 'Whatever ', bounce_email_to, subject='Undelivered Mail Returned to Sender', extra=extra) self.assertFalse(record) @@ -1159,7 +1039,7 @@ class TestMailgateway(MailCommon): self.assertEqual(self.test_record.message_bounce, 0) extra = self.fake_email.message_id - record = self.format_and_process(test_mail_data.MAIL_BOUNCE, 'Whatever ', 'groups@test.com', subject='Undelivered Mail Returned to Sender', extra=extra) + record = self.format_and_process(test_mail_data.MAIL_BOUNCE, 'Whatever ', f'groups@{self.alias_domain}', subject='Undelivered Mail Returned to Sender', extra=extra) self.assertFalse(record) self.assertEqual(self.partner_1.message_bounce, 0) self.assertEqual(self.test_record.message_bounce, 1) @@ -1170,7 +1050,7 @@ class TestMailgateway(MailCommon): email = email.replace('MAILER-DAEMON@mail2.test.ironsky', 'email@mail2.test.ironsky') self.assertIn('report-type=delivery-status', email) extra = self.fake_email.message_id - record = self.format_and_process(email, 'Whatever ', 'groups@test.com', subject='Undelivered Mail Returned to Sender', extra=extra) + record = self.format_and_process(email, 'Whatever ', f'groups@{self.alias_domain}', subject='Undelivered Mail Returned to Sender', extra=extra) self.assertFalse(record) self.assertEqual(self.partner_1.message_bounce, 0) self.assertEqual(self.test_record.message_bounce, 2) @@ -1199,7 +1079,7 @@ class TestMailgateway(MailCommon): extra = self.fake_email.message_id for i in range(10): - record = self.format_and_process(test_mail_data.MAIL_BOUNCE, 'A third name <%s>' % self.partner_1.email, 'groups@test.com', subject='Undelivered Mail Returned to Sender', extra=extra) + record = self.format_and_process(test_mail_data.MAIL_BOUNCE, 'A third name <%s>' % self.partner_1.email, f'groups@{self.alias_domain}', subject='Undelivered Mail Returned to Sender', extra=extra) self.assertFalse(record) self.assertEqual(self.partner_1.message_bounce, 10) self.assertEqual(self.test_record.message_bounce, 0) @@ -1217,7 +1097,7 @@ class TestMailgateway(MailCommon): }) extra = self.fake_email.message_id - record = self.format_and_process(test_mail_data.MAIL_BOUNCE, self.partner_1.email_formatted, 'groups@test.com', subject='Undelivered Mail Returned to Sender', extra=extra) + record = self.format_and_process(test_mail_data.MAIL_BOUNCE, self.partner_1.email_formatted, f'groups@{self.alias_domain}', subject='Undelivered Mail Returned to Sender', extra=extra) self.assertFalse(record) self.assertEqual(self.partner_1.message_bounce, 1) self.assertEqual(self.test_record.message_bounce, 0) @@ -1273,7 +1153,7 @@ class TestMailgateway(MailCommon): """ Incoming email using in-rely-to should go into the right destination even with a wrong destination """ init_msg_count = len(self.test_record.message_ids) self.format_and_process( - MAIL_TEMPLATE, 'valid.other@gmail.com', 'erroneous@test.com>', + MAIL_TEMPLATE, 'valid.other@gmail.com', f'erroneous@{self.alias_domain}', subject='Re: news', extra='In-Reply-To:\r\n\t%s\n' % self.fake_email.message_id) self.assertEqual(len(self.test_record.message_ids), init_msg_count + 1) @@ -1284,7 +1164,7 @@ class TestMailgateway(MailCommon): """ Incoming email using references should go into the right destination even with a wrong destination """ init_msg_count = len(self.test_record.message_ids) self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'erroneous@test.com', + MAIL_TEMPLATE, self.email_from, f'erroneous@{self.alias_domain}', extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % self.fake_email.message_id) self.assertEqual(len(self.test_record.message_ids), init_msg_count + 1) @@ -1310,7 +1190,7 @@ class TestMailgateway(MailCommon): # reply to reply1 using multiple references self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'groups@test.com', + MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Reply to reply1', extra=f'References: {reply1.message_id} {self.fake_email.message_id}' ) @@ -1320,7 +1200,7 @@ class TestMailgateway(MailCommon): # ordering should not impact self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'groups@test.com', + MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Reply to reply1 (order issue)', extra=f'References: {self.fake_email.message_id} {reply1.message_id}' ) @@ -1330,7 +1210,7 @@ class TestMailgateway(MailCommon): # history with last one being a note self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'groups@test.com', + MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Reply to reply1_1', extra=f'References: {reply1_1.message_id} {self.fake_email.message_id}' ) @@ -1341,7 +1221,7 @@ class TestMailgateway(MailCommon): # messed up history (two child branches): gateway initial parent is newest one # (then may change with flattening when posting on record) self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'groups@test.com', + MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Reply to reply2_1 (with noise)', extra=f'References: {reply1_1.message_id} {reply2_1.message_id}' ) @@ -1377,7 +1257,7 @@ class TestMailgateway(MailCommon): ) self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'test.gateway@test.com', + MAIL_TEMPLATE, self.email_from, f'test.gateway@{self.alias_domain}', subject='Reply to reply1', extra=f'References: {reply1.message_id}' ) @@ -1386,7 +1266,7 @@ class TestMailgateway(MailCommon): self.assertEqual(new_msg.subtype_id, self.env.ref('mail.mt_comment'), 'Mail: parent should be a comment') self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'test.gateway@test.com', + MAIL_TEMPLATE, self.email_from, f'test.gateway@{self.alias_domain}', subject='Reply to reply1_1 (with noise)', extra=f'References: {reply1_1.message_id} {reply1.message_id} {reply1.message_id}' ) @@ -1395,7 +1275,7 @@ class TestMailgateway(MailCommon): self.assertEqual(new_msg.subtype_id, self.env.ref('mail.mt_note'), 'Mail: reply to a note should be a note') self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'test.gateway@test.com', + MAIL_TEMPLATE, self.email_from, f'test.gateway@{self.alias_domain}', subject='Reply to reply2_1 (with noise)', extra=f'References: {reply2_1.message_id} {reply1_1.message_id}' ) @@ -1405,7 +1285,7 @@ class TestMailgateway(MailCommon): # no references: new discussion thread started self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'test.gateway@test.com', + MAIL_TEMPLATE, self.email_from, f'test.gateway@{self.alias_domain}', subject='New thread', extra='References:' ) @@ -1416,7 +1296,7 @@ class TestMailgateway(MailCommon): # mixed up references: newer message wins self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'test.gateway@test.com', + MAIL_TEMPLATE, self.email_from, f'test.gateway@{self.alias_domain}', subject='New thread', extra=f'References: {new_thread.message_id} {reply1_1.message_id}' ) @@ -1433,7 +1313,7 @@ class TestMailgateway(MailCommon): }) init_msg_count = len(self.test_record.message_ids) self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'erroneous@test.com', + MAIL_TEMPLATE, self.email_from, f'erroneous@{self.alias_domain}', extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % self.fake_email.message_id) self.assertEqual(len(self.test_record.message_ids), init_msg_count + 1) @@ -1454,7 +1334,7 @@ class TestMailgateway(MailCommon): }) init_msg_count = len(self.test_record.message_ids) self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'erroneous@test.com', + MAIL_TEMPLATE, self.email_from, f'erroneous@{self.alias_domain}', extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % buggy_message_id) self.assertEqual(len(self.test_record.message_ids), init_msg_count + 1) @@ -1471,7 +1351,7 @@ class TestMailgateway(MailCommon): }) init_msg_count = len(self.test_record.message_ids) res_test = self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'test.alias@test.com', + MAIL_TEMPLATE, self.email_from, f'test.alias@{self.alias_domain}', subject='My Dear Forward', extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % self.fake_email.message_id, target_model='mail.test.container') @@ -1491,7 +1371,7 @@ class TestMailgateway(MailCommon): }) init_msg_count = len(self.test_record.message_ids) res_test = self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'test.alias@test.com', + MAIL_TEMPLATE, self.email_from, f'test.alias@{self.alias_domain}', subject='My Dear Forward', extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % self.fake_email.message_id, target_model='mail.test.container') @@ -1510,8 +1390,11 @@ class TestMailgateway(MailCommon): }) init_msg_count = len(self.test_record.message_ids) res_test = self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'catchall.test@test.com', cc='test.alias@test.com', - subject='My Dear Forward', extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % self.fake_email.message_id, + MAIL_TEMPLATE, self.email_from, + f'{self.alias_catchall}@{self.alias_domain}', + cc=f'test.alias@{self.alias_domain}', + subject='My Dear Forward', + extra=f'References: <2233@a.com>\r\n\t<3edss_dsa@b.com> {self.fake_email.message_id}', target_model='mail.test.container') self.assertEqual(len(self.test_record.message_ids), init_msg_count + 1) @@ -1527,14 +1410,18 @@ class TestMailgateway(MailCommon): reply_to_force_new=False, subtype_xmlid='mail.mt_comment', ) - self.assertEqual(record_msg.reply_to, formataddr(('%s %s' % (self.user_employee.company_id.name, first_record.name), '%s@%s' % ('catchall.test', 'test.com')))) + self.assertEqual( + record_msg.reply_to, + formataddr((f'{self.user_employee.company_id.name} {first_record.name}', + f'{self.alias_catchall}@{self.alias_domain}')) + ) mail_msg = first_record.message_post( subject='Replies to Record', - reply_to='groups@test.com', + reply_to=f'groups@{self.alias_domain}', reply_to_force_new=True, subtype_xmlid='mail.mt_comment', ) - self.assertEqual(mail_msg.reply_to, 'groups@test.com') + self.assertEqual(mail_msg.reply_to, f'groups@{self.alias_domain}') # reply to mail but should be considered as a new mail for alias msgID = '' @@ -1619,7 +1506,7 @@ class TestMailgateway(MailCommon): """ Incoming email with ref holding model / res_id but that does not match any message in the thread: must raise since OpenERP saas-3 """ self.assertRaises(ValueError, self.format_and_process, MAIL_TEMPLATE, - self.partner_1.email_formatted, 'noone@test.com', subject='spam', + self.partner_1.email_formatted, f'noone@{self.alias_domain}', subject='spam', extra='In-Reply-To: <12321321-openerp-%d-mail.test.gateway@%s>' % (self.test_record.id, socket.gethostname())) # when 6.1 messages are present, compat mode is available @@ -1629,7 +1516,7 @@ class TestMailgateway(MailCommon): self.assertRaises( ValueError, self.format_and_process, MAIL_TEMPLATE, - self.partner_1.email_formatted, 'noone@test.com>', subject='spam', + self.partner_1.email_formatted, f'noone@{self.alias_domain}>', subject='spam', extra='In-Reply-To: <12321321-openerp-%d-mail.test.gateway@%s>' % (self.test_record.id, socket.gethostname())) # Test created messages @@ -1642,19 +1529,19 @@ class TestMailgateway(MailCommon): self.alias.write({'alias_force_thread_id': self.test_record.id,}) # Post a base message - record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Re: super cats', msg_id='<123?456.diff1@agrolait.com>') + record = self.format_and_process(MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Re: super cats', msg_id='<123.456.diff1@agrolait.com>') self.assertFalse(record) self.assertEqual(len(self.test_record.message_ids), 2) # Do: due to some issue, same email goes back into the mailgateway record = self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Re: news', - msg_id='<123?456.diff1@agrolait.com>', extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>\n') + MAIL_TEMPLATE, self.email_from, f'groups@{self.alias_domain}', subject='Re: news', + msg_id='<123.456.diff1@agrolait.com>', extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>\n') self.assertFalse(record) self.assertEqual(len(self.test_record.message_ids), 2) # Test: message_id is still unique - no_of_msg = self.env['mail.message'].search_count([('message_id', 'ilike', '<123?456.diff1@agrolait.com>')]) + no_of_msg = self.env['mail.message'].search_count([('message_id', 'ilike', '<123.456.diff1@agrolait.com>')]) self.assertEqual(no_of_msg, 1, 'message_process: message with already existing message_id should not have been duplicated') @@ -1663,7 +1550,7 @@ class TestMailgateway(MailCommon): """ Incoming email with model that does not accepts incoming emails must raise """ self.assertRaises(ValueError, self.format_and_process, - MAIL_TEMPLATE, self.email_from, 'noone@test.com', + MAIL_TEMPLATE, self.email_from, f'noone@{self.alias_domain}', subject='spam', extra='', model='res.country') @mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models') @@ -1671,14 +1558,14 @@ class TestMailgateway(MailCommon): """ Incoming email without model and without alias must raise """ self.assertRaises(ValueError, self.format_and_process, - MAIL_TEMPLATE, self.email_from, 'noone@test.com', + MAIL_TEMPLATE, self.email_from, f'noone@{self.alias_domain}', subject='spam', extra='') @mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models') def test_message_process_fallback(self): """ Incoming email with model that accepting incoming emails as fallback """ record = self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'noone@test.com', + MAIL_TEMPLATE, self.email_from, f'noone@{self.alias_domain}', subject='Spammy', extra='', model='mail.test.gateway') self.assertEqual(len(record), 1) self.assertEqual(record.name, 'Spammy') @@ -1691,7 +1578,7 @@ class TestMailgateway(MailCommon): for encoding in ['', 'UTF-8', 'UTF-16LE', 'UTF-32BE']: file_content_b64 = base64.b64encode(file_content.encode(encoding or 'utf-8')).decode() record = self.format_and_process(test_mail_data.MAIL_FILE_ENCODING, - self.email_from, 'groups@test.com', + self.email_from, f'groups@{self.alias_domain}', subject='Test Charset %s' % encoding or 'Unset', charset='; charset="%s"' % encoding if encoding else '', content=file_content_b64 @@ -1767,7 +1654,7 @@ class TestMailgateway(MailCommon): new_record, msg='The loop should have been detected and the record should not have been created') - self.assertSentEmail('"MAILER-DAEMON" ', [email_from]) + self.assertSentEmail(f'"MAILER-DAEMON" <{self.alias_bounce}@{self.alias_domain}>', [email_from]) self.assertIn('-loop-detection-bounce-email@', self._mails[0]['references'], msg='The "bounce email" tag must be in the reference') @@ -1806,7 +1693,7 @@ class TestMailgateway(MailCommon): """ Incoming email containing an xml attachment with unknown characters (�) but an ASCII charset should not raise an Exception. UTF-8 is used as a safe fallback. """ - record = self.format_and_process(test_mail_data.MAIL_MULTIPART_INVALID_ENCODING, self.email_from, 'groups@test.com') + record = self.format_and_process(test_mail_data.MAIL_MULTIPART_INVALID_ENCODING, self.email_from, f'groups@{self.alias_domain}') self.assertEqual(record.message_ids.attachment_ids.name, 'bis3_with_error_encoding_address.xml') # NB: the xml received by email contains b"Chauss\xef\xbf\xbd\xef\xbf\xbde" with "\xef\xbf\xbd" being the @@ -1823,7 +1710,7 @@ class TestMailgateway(MailCommon): """ For incoming email containing an xml attachment with omitted charset and containing an UTF8 payload we should parse the attachment using UTF-8. """ - record = self.format_and_process(test_mail_data.MAIL_MULTIPART_OMITTED_CHARSET, self.email_from, 'groups@test.com') + record = self.format_and_process(test_mail_data.MAIL_MULTIPART_OMITTED_CHARSET, self.email_from, f'groups@{self.alias_domain}') self.assertEqual(record.message_ids.attachment_ids.name, 'bis3.xml') self.assertEqual("Chaussée de Bruxelles", record.message_ids.attachment_ids.raw.decode()) @@ -1846,9 +1733,12 @@ class TestMailgateway(MailCommon): 'res_id': None, }) - self.env['mail.alias'].create({'alias_name': 'test', 'alias_model_id': self.env['ir.model']._get('mail.test.gateway').id}) + self.env['mail.alias'].create({ + 'alias_name': 'test', + 'alias_model_id': self.env['ir.model']._get('mail.test.gateway').id, + }) record = self.format_and_process( - MAIL_TEMPLATE, self.email_from, 'test@test.com', + MAIL_TEMPLATE, self.email_from, f'test@{self.alias_domain}', subject=message.message_id, extra=f'In-Reply-To:\r\n\t{message.message_id}\n', model=None) @@ -1874,7 +1764,7 @@ class TestMailThreadCC(MailCommon): @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', + record = self.format_and_process(MAIL_TEMPLATE, self.email_from, f'cc_record@{self.alias_domain}', cc='cc1@example.com, cc2@example.com', target_model='mail.test.cc') cc = email_split_and_format(record.email_cc) self.assertEqual(sorted(cc), ['cc1@example.com', 'cc2@example.com']) @@ -1884,7 +1774,7 @@ class TestMailThreadCC(MailCommon): record = self.env['mail.test.cc'].create({'email_cc': 'cc1 , cc2@example.com'}) self.alias.write({'alias_force_thread_id': record.id}) - self.format_and_process(MAIL_TEMPLATE, self.email_from, 'cc_record@test.com', + self.format_and_process(MAIL_TEMPLATE, self.email_from, f'cc_record@{self.alias_domain}', cc='cc2 , cc3@example.com', target_model='mail.test.cc') cc = email_split_and_format(record.email_cc) self.assertEqual(sorted(cc), ['"cc1" ', 'cc2@example.com', 'cc3@example.com'], 'new cc should have been added on record (unique)') @@ -1894,7 +1784,7 @@ class TestMailThreadCC(MailCommon): record = self.env['mail.test.cc'].create({}) self.alias.write({'alias_force_thread_id': record.id}) - self.format_and_process(MAIL_TEMPLATE, self.email_from, 'cc_record@test.com', + self.format_and_process(MAIL_TEMPLATE, self.email_from, f'cc_record@{self.alias_domain}', cc='cc2 , cc3@example.com', target_model='mail.test.cc') cc = email_split_and_format(record.email_cc) self.assertEqual(sorted(cc), ['"cc2" ', 'cc3@example.com'], 'new cc should have been added on record (unique)') diff --git a/addons/test_mail/tests/test_mail_mail.py b/addons/test_mail/tests/test_mail_mail.py index 77d365e0341..0c7cad69a8c 100644 --- a/addons/test_mail/tests/test_mail_mail.py +++ b/addons/test_mail/tests/test_mail_mail.py @@ -666,7 +666,10 @@ class TestMailMail(MailCommon): Batch are defined by the mail server and the email from field. """ - self.assertEqual(self.env['ir.mail_server']._get_default_from_address(), 'notifications@test.com') + self.assertEqual( + self.env['ir.mail_server']._get_default_from_address(), + f'notifications@{self.alias_domain}' + ) mail_values = { 'body_html': '

Test

', @@ -711,7 +714,7 @@ class TestMailMail(MailCommon): self.assertEqual(self.connect_mocked.call_count, 4, 'Must be called once per batch which share the same mail server and the same smtp from') self.connect_mocked.assert_has_calls( calls=[ - call(smtp_from='notifications@test.com', mail_server_id=self.server_notification.id), + call(smtp_from=f'notifications@{self.alias_domain}', mail_server_id=self.server_notification.id), call(smtp_from='user_1@test_2.com', mail_server_id=self.server_domain_2.id), call(smtp_from='user_2@test_2.com', mail_server_id=self.server_domain_2.id), call(smtp_from='user_1@test_2.com', mail_server_id=self.server_domain.id), @@ -719,9 +722,9 @@ class TestMailMail(MailCommon): any_order=True, ) - self.assert_email_sent_smtp(message_from='"test" ', + self.assert_email_sent_smtp(message_from=f'"test" ', emails_count=5, from_filter=self.server_notification.from_filter) - self.assert_email_sent_smtp(message_from='"test_2" ', + self.assert_email_sent_smtp(message_from=f'"test_2" ', emails_count=5, from_filter=self.server_notification.from_filter) self.assert_email_sent_smtp(message_from='user_1@test_2.com', emails_count=5, from_filter=self.server_domain_2.from_filter) self.assert_email_sent_smtp(message_from='user_2@test_2.com', emails_count=5, from_filter=self.server_domain_2.from_filter) diff --git a/addons/test_mail/tests/test_mail_multicompany.py b/addons/test_mail/tests/test_mail_multicompany.py index 0a924acf763..d22563743fe 100644 --- a/addons/test_mail/tests/test_mail_multicompany.py +++ b/addons/test_mail/tests/test_mail_multicompany.py @@ -12,17 +12,15 @@ from odoo.addons.mail.models.mail_message import Message from odoo.addons.mail.tests.common import MailCommon from odoo.addons.test_mail.tests.common import TestRecipients from odoo.exceptions import AccessError -from odoo.tests import tagged -from odoo.tests.common import users, HttpCase +from odoo.tests import tagged, users, HttpCase from odoo.tools import formataddr, mute_logger -@tagged('multi_company') -class TestMultiCompanySetup(MailCommon, TestRecipients): +class TestMailMCCommon(MailCommon, TestRecipients): @classmethod def setUpClass(cls): - super(TestMultiCompanySetup, cls).setUpClass() + super().setUpClass() cls._activate_multi_company() cls.test_model = cls.env['ir.model']._get('mail.test.gateway') @@ -45,11 +43,12 @@ class TestMultiCompanySetup(MailCommon, TestRecipients): 'email': 'valid.lelitre@agrolait.com', }) # groups@.. will cause the creation of new mail.test.gateway - cls.alias = cls.env['mail.alias'].create({ + cls.mail_alias = cls.env['mail.alias'].create({ + 'alias_contact': 'everyone', + 'alias_model_id': cls.test_model.id, 'alias_name': 'groups', 'alias_user_id': False, - 'alias_model_id': cls.test_model.id, - 'alias_contact': 'everyone'}) + }) # Set a first message on public group to test update and hierarchy cls.fake_email = cls.env['mail.message'].create({ @@ -63,20 +62,21 @@ class TestMultiCompanySetup(MailCommon, TestRecipients): }) def setUp(self): - super(TestMultiCompanySetup, self).setUp() + super().setUp() # patch registry to simulate a ready environment self.patch(self.env.registry, 'ready', True) self.flush_tracking() + +@tagged('multi_company') +class TestMultiCompanySetup(TestMailMCCommon): + @users('erp_manager') @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') def test_mail_composer_wtpl_populate_new_recipient(self): """ Test auto-populate of auto created partner with related record values when sending a mail with a template. """ - self.user_erp_manager.write({ - 'groups_id': [(4, self.env.ref('base.group_partner_manager').id)], - }) companies = self.company_admin + self.company_2 test_records = self.env['mail.test.ticket.mc'].create([ { diff --git a/addons/test_mail/tests/test_message_track.py b/addons/test_mail/tests/test_message_track.py index ee72f8b628d..152fd91a52c 100644 --- a/addons/test_mail/tests/test_message_track.py +++ b/addons/test_mail/tests/test_message_track.py @@ -208,7 +208,7 @@ class TestTracking(MailCommon): 'alias_contact': 'everyone', 'alias_defaults': custom_values}) record = self.format_and_process(MAIL_TEMPLATE, '"Sylvie Lelitre" ', - 'groups@test.com', target_field='customer_id', subject=custom_values['customer_id'], + 'groups@test.mycompany.com', target_field='customer_id', subject=custom_values['customer_id'], target_model='mail.test.ticket') with self.mock_mail_gateway(mail_unlink_sent=False): diff --git a/addons/test_mail/tests/test_performance.py b/addons/test_mail/tests/test_performance.py index 35872c7c587..818703e5ae3 100644 --- a/addons/test_mail/tests/test_performance.py +++ b/addons/test_mail/tests/test_performance.py @@ -247,7 +247,7 @@ class TestBaseMailPerformance(BaseMailPerformance): with self.assertQueryCount(admin=1, employee=1): rec.write({ 'name': 'Test2', - 'email_from': 'test@test.com', + 'email_from': 'test@test.mycompany.com', }) @@ -738,11 +738,10 @@ class TestMailAPIPerformance(BaseMailPerformance): for record in test_records: self.assertEqual( reply_to[record.id], - formataddr( - ("%s %s" % (self.env.user.company_id.name, record.name), - "%s@%s" % (record.alias_name, self.alias_domain) - ) - ) + formataddr(( + record.name, + f"{record.alias_name}@{self.alias_domain}" + )) ) diff --git a/addons/test_mass_mailing/tests/test_mailing_server.py b/addons/test_mass_mailing/tests/test_mailing_server.py index e03c95303c1..0a768f3e69c 100644 --- a/addons/test_mass_mailing/tests/test_mailing_server.py +++ b/addons/test_mass_mailing/tests/test_mailing_server.py @@ -8,7 +8,7 @@ from odoo.tests.common import users from odoo.tools import mute_logger -@tagged('mass_mailing') +@tagged('mass_mailing', 'mail_server') class TestMassMailingServer(TestMassMailCommon): @classmethod @@ -82,12 +82,12 @@ class TestMassMailingServer(TestMassMailCommon): mailings = self.env['mailing.mailing'].create([{ 'subject': 'Mailing', 'body_html': 'Body for ', - 'email_from': 'specific_user@test.com', + 'email_from': 'specific_user@test.mycompany.com', 'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id, }, { 'subject': 'Mailing', 'body_html': 'Body for ', - 'email_from': 'unknown_name@test.com', + 'email_from': 'unknown_name@test.mycompany.com', 'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id, }]) with self.mock_smtplib_connection(): @@ -95,17 +95,17 @@ class TestMassMailingServer(TestMassMailCommon): self.assertEqual(self.find_mail_server_mocked.call_count, 2, 'Must be called only once per mail from') self.assert_email_sent_smtp( - smtp_from='specific_user@test.com', - message_from='specific_user@test.com', + smtp_from='specific_user@test.mycompany.com', + message_from='specific_user@test.mycompany.com', from_filter=self.server_user.from_filter, emails_count=8, ) self.assert_email_sent_smtp( # Must use the bounce address here because the mail server - # is configured for the entire domain "test.com" + # is configured for the entire domain "test.mycompany.com" smtp_from=lambda x: 'bounce' in x, - message_from='unknown_name@test.com', + message_from='unknown_name@test.mycompany.com', from_filter=self.server_domain.from_filter, emails_count=8, ) @@ -127,8 +127,8 @@ class TestMassMailingServer(TestMassMailCommon): self.assertEqual(self.find_mail_server_mocked.call_count, 1) self.assert_email_sent_smtp( - smtp_from='notifications@test.com', - message_from='"Testing" ', + smtp_from='notifications@test.mycompany.com', + message_from='"Testing" ', from_filter=self.server_notification.from_filter, emails_count=8, ) @@ -156,8 +156,8 @@ class TestMassMailingServer(TestMassMailCommon): self.assertEqual(self.find_mail_server_mocked.call_count, 1, 'Must not be called when mail server is forced') self.assert_email_sent_smtp( - smtp_from='specific_user@test.com', - message_from='specific_user@test.com', + smtp_from='specific_user@test.mycompany.com', + message_from='specific_user@test.mycompany.com', from_filter=self.server_user.from_filter, emails_count=8, ) diff --git a/odoo/addons/base/tests/common.py b/odoo/addons/base/tests/common.py index c2c7b6ea409..86f35ae2f35 100644 --- a/odoo/addons/base/tests/common.py +++ b/odoo/addons/base/tests/common.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import threading + from contextlib import contextmanager from unittest.mock import patch @@ -337,6 +339,21 @@ class MockSmtplibCase: self.find_mail_server_mocked = find_mail_server_mocked yield + def _build_email(self, mail_from, return_path=None, **kwargs): + return self.env['ir.mail_server'].build_email( + body=kwargs.pop('body', 'body'), + email_from=mail_from, + email_to='dest@example-é.com', + headers={'Return-Path': return_path} if return_path else None, + subject='subject', + **kwargs, + ) + + def _send_email(self, msg, smtp_session): + with patch.object(threading.current_thread(), 'testing', False): + self.env['ir.mail_server'].send_email(msg, smtp_session=smtp_session) + return smtp_session.messages.pop() + def assert_email_sent_smtp(self, smtp_from=None, smtp_to_list=None, message_from=None, from_filter=None, emails_count=1): """Check that the given email has been sent. @@ -373,7 +390,7 @@ class MockSmtplibCase: @classmethod def _init_mail_config(cls): cls.alias_bounce = 'bounce.test' - cls.alias_domain = 'test.com' + cls.alias_domain = 'test.mycompany.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) @@ -395,15 +412,15 @@ class MockSmtplibCase: ) = cls.env['ir.mail_server'].create([ { 'name': 'Domain based server', - 'from_filter': 'test.com', + 'from_filter': 'test.mycompany.com', ** ir_mail_server_values, }, { 'name': 'User specific server', - 'from_filter': 'specific_user@test.com', + 'from_filter': 'specific_user@test.mycompany.com', ** ir_mail_server_values, }, { 'name': 'Server Notifications', - 'from_filter': 'notifications@test.com', + 'from_filter': 'notifications@test.mycompany.com', ** ir_mail_server_values, }, { 'name': 'Server No From Filter', diff --git a/odoo/addons/base/tests/test_ir_mail_server.py b/odoo/addons/base/tests/test_ir_mail_server.py index 47cdcc6cf2d..89a4415b4f3 100644 --- a/odoo/addons/base/tests/test_ir_mail_server.py +++ b/odoo/addons/base/tests/test_ir_mail_server.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import email.message +import email.policy + from unittest.mock import patch from odoo import tools @@ -12,6 +15,47 @@ from odoo.tools import mute_logger from odoo.tools import config +class _FakeSMTP: + """SMTP stub""" + def __init__(self): + self.messages = [] + self.from_filter = 'example.com' + + # Python 3 before 3.7.4 + def sendmail(self, smtp_from, smtp_to_list, message_str, + mail_options=(), rcpt_options=()): + self.messages.append(message_str) + + # Python 3.7.4+ + def send_message(self, message, smtp_from, smtp_to_list, + mail_options=(), rcpt_options=()): + self.messages.append(message.as_string()) + + +@tagged('mail_server') +class EmailConfigCase(TransactionCase): + + @patch.dict(config.options, {"email_from": "settings@example.com"}) + def test_default_email_from(self, *args): + """Email from setting is respected.""" + # ICP setting is more important + ICP = self.env["ir.config_parameter"].sudo() + ICP.set_param("mail.catchall.domain", "test.mycompany.com") + ICP.set_param("mail.default.from", "icp") + message = self.env["ir.mail_server"].build_email( + False, "recipient@example.com", "Subject", + "The body of an email", + ) + self.assertEqual(message["From"], "icp@test.mycompany.com") + # Without ICP, the config file/CLI setting is used + ICP.set_param("mail.default.from", False) + message = self.env["ir.mail_server"].build_email( + False, "recipient@example.com", "Subject", + "The body of an email", + ) + self.assertEqual(message["From"], "settings@example.com") + + @tagged('mail_server') class TestIrMailServer(TransactionCase, MockSmtplibCase): @@ -21,14 +65,49 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): cls._init_mail_config() cls._init_mail_servers() - def _build_email(self, mail_from, return_path=None): - return self.env['ir.mail_server'].build_email( - email_from=mail_from, - email_to='dest@example-é.com', - subject='subject', body='body', - headers={'Return-Path': return_path} if return_path else None + def test_bpo_34424_35805(self): + """Ensure all email sent are bpo-34424 and bpo-35805 free""" + fake_smtp = _FakeSMTP() + msg = email.message.EmailMessage(policy=email.policy.SMTP) + msg['From'] = '"Joé Doe" ' + msg['To'] = '"Joé Doe" ' + + # Message-Id & References fields longer than 77 chars (bpo-35805) + msg['Message-Id'] = '<929227342217024.1596730490.324691772460938-example-30661-some.reference@test-123.example.com>' + msg['References'] = '<345227342212345.1596730777.324691772483620-example-30453-other.reference@test-123.example.com>' + + msg_on_the_wire = self._send_email(msg, fake_smtp) + self.assertEqual(msg_on_the_wire, + 'From: =?utf-8?q?Jo=C3=A9?= Doe \r\n' + 'To: =?utf-8?q?Jo=C3=A9?= Doe \r\n' + 'Message-Id: <929227342217024.1596730490.324691772460938-example-30661-some.reference@test-123.example.com>\r\n' + 'References: <345227342212345.1596730777.324691772483620-example-30453-other.reference@test-123.example.com>\r\n' + '\r\n' ) + def test_content_alternative_correct_order(self): + """ + RFC-1521 7.2.3. The Multipart/alternative subtype + > the alternatives appear in an order of increasing faithfulness + > to the original content. In general, the best choice is the + > LAST part of a type supported by the recipient system's local + > environment. + + Also, the MIME-Version header should be present in BOTH the + enveloppe AND the parts + """ + fake_smtp = _FakeSMTP() + msg = self._build_email("test@example.com", body='

Hello world

', subtype='html') + msg_on_the_wire = self._send_email(msg, fake_smtp) + + self.assertGreater(msg_on_the_wire.index('text/html'), msg_on_the_wire.index('text/plain'), + "The html part should be preferred (=appear after) to the text part") + self.assertEqual(msg_on_the_wire.count('==============='), 2 + 2, # +2 for the header and the footer + "There should be 2 parts: one text and one html") + self.assertEqual(msg_on_the_wire.count('MIME-Version: 1.0'), 3, + "There should be 3 headers MIME-Version: one on the enveloppe, " + "one on the html part, one on the text part") + def test_match_from_filter(self): """Test the from_filter field on the "ir.mail_server".""" match_from_filter = self.env['ir.mail_server']._match_from_filter @@ -39,9 +118,9 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): ('admin@mail.example.com', 'mail.EXAMPLE.com'), ('admin@mail.example.com', 'admin@mail.example.com'), ('admin@mail.example.com', False), - ('"fake@test.com" ', 'mail.example.com'), - ('"fake@test.com" ', 'mail.example.com'), - ('"fake@test.com" ', 'test.com, mail.example.com, test2.com'), + ('"fake@test.mycompany.com" ', 'mail.example.com'), + ('"fake@test.mycompany.com" ', 'mail.example.com'), + ('"fake@test.mycompany.com" ', 'test.mycompany.com, mail.example.com, test2.com'), ] for email, from_filter in tests: self.assertTrue(match_from_filter(email, from_filter)) @@ -49,12 +128,12 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): # Should not match tests = [ ('admin@mail.example.com', 'test@mail.example.com'), - ('admin@mail.example.com', 'test.com'), + ('admin@mail.example.com', 'test.mycompany.com'), ('admin@mail.example.com', 'mail.éxample.com'), ('admin@mmail.example.com', 'mail.example.com'), ('admin@mail.example.com', 'mmail.example.com'), - ('"admin@mail.example.com" ', 'mail.example.com'), - ('"fake@test.com" ', 'test.com, wrong.mail.example.com, test3.com'), + ('"admin@mail.example.com" ', 'mail.example.com'), + ('"fake@test.mycompany.com" ', 'test.mycompany.com, wrong.mail.example.com, test3.com'), ] for email, from_filter in tests: self.assertFalse(match_from_filter(email, from_filter)) @@ -114,37 +193,37 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): self.assertTrue(self.env['ir.mail_server']._get_default_from_address(), 'Notifications email must be set for testing') self.assertTrue(self.env['ir.mail_server']._get_default_bounce_address(), 'Bounce email must be set for testing') - mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='specific_user@test.com') + mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='specific_user@test.mycompany.com') self.assertEqual(mail_server, self.server_user) - self.assertEqual(mail_from, 'specific_user@test.com') + self.assertEqual(mail_from, 'specific_user@test.mycompany.com') - mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Name name@strange.name" ') + mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Name name@strange.name" ') self.assertEqual(mail_server, self.server_user, 'Must extract email from full name') - self.assertEqual(mail_from, '"Name name@strange.name" ', 'Must keep the given mail from') + self.assertEqual(mail_from, '"Name name@strange.name" ', 'Must keep the given mail from') # Should not be case sensitive - mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='specific_user@test.com') + mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='specific_user@test.mycompany.com') self.assertEqual(mail_server, self.server_user, 'Mail from is case insensitive') - self.assertEqual(mail_from, 'specific_user@test.com', 'Should not change the mail from') + self.assertEqual(mail_from, 'specific_user@test.mycompany.com', 'Should not change the mail from') - mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='unknown_email@test.com') + mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='unknown_email@test.mycompany.com') self.assertEqual(mail_server, self.server_domain) - self.assertEqual(mail_from, 'unknown_email@test.com') + self.assertEqual(mail_from, 'unknown_email@test.mycompany.com') # Cover a different condition that the "email case insensitive" test - mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='unknown_email@TEST.COM') + mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='unknown_email@TEST.MYCOMPANY.COM') self.assertEqual(mail_server, self.server_domain, 'Domain is case insensitive') - self.assertEqual(mail_from, 'unknown_email@TEST.COM', 'Domain is case insensitive') + self.assertEqual(mail_from, 'unknown_email@TEST.MYCOMPANY.COM', 'Domain is case insensitive') mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Test" ') self.assertEqual(mail_server, self.server_notification, 'Should take the notification email') - self.assertEqual(mail_from, 'notifications@test.com') + self.assertEqual(mail_from, 'notifications@test.mycompany.com') # this mail server can not be used for a specific email address and 2 domain names - self.server_user.from_filter = "domain1.com, specific_user@test.com, domain2.com" - mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Test" ') + self.server_user.from_filter = "domain1.com, specific_user@test.mycompany.com, domain2.com" + mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Test" ') self.assertEqual(mail_server, self.server_user, "The entire email matches, the server should be used") - self.assertEqual(mail_from, '"Test" ') + self.assertEqual(mail_from, '"Test" ') mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Example" ') self.assertEqual(mail_server, self.server_user, "The domain matches, the server should be used") self.assertEqual(mail_from, '"Example" ') @@ -153,9 +232,9 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): self.assertEqual(mail_from, '"Example" ') # check that we take the domain server and not the "user specific" server - mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Test" ') + mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Test" ') self.assertEqual(mail_server, self.server_domain) - self.assertEqual(mail_from, '"Test" ') + self.assertEqual(mail_from, '"Test" ') # remove the notifications email to simulate a mis-configured Odoo database # so we do not have the choice, we have to spoof the FROM @@ -173,15 +252,15 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): # A mail server is configured for the email with self.mock_smtplib_connection(): - message = self._build_email(mail_from='specific_user@test.com') + message = self._build_email(mail_from='specific_user@test.mycompany.com') IrMailServer.send_email(message) self.assertEqual(len(self.emails), 1) self.assert_email_sent_smtp( - smtp_from='specific_user@test.com', - message_from='specific_user@test.com', - from_filter='specific_user@test.com', + smtp_from='specific_user@test.mycompany.com', + message_from='specific_user@test.mycompany.com', + from_filter='specific_user@test.mycompany.com', ) # No mail server are configured for the email address, @@ -193,9 +272,9 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): self.assertEqual(len(self.emails), 1) self.assert_email_sent_smtp( - smtp_from='notifications@test.com', - message_from='"Name" ', - from_filter='notifications@test.com', + smtp_from='notifications@test.mycompany.com', + message_from='"Name" ', + from_filter='notifications@test.mycompany.com', ) # Same situation, but the original email has no name part @@ -206,33 +285,33 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): self.assertEqual(len(self.emails), 1) self.assert_email_sent_smtp( - smtp_from='notifications@test.com', - message_from='"test" ', - from_filter='notifications@test.com', + smtp_from='notifications@test.mycompany.com', + message_from='"test" ', + from_filter='notifications@test.mycompany.com', ) # A mail server is configured for the entire domain name, so we can use the bounce # email address because the mail server supports it with self.mock_smtplib_connection(): - message = self._build_email(mail_from='unknown_name@test.com') + message = self._build_email(mail_from='unknown_name@test.mycompany.com') IrMailServer.send_email(message) self.assertEqual(len(self.emails), 1) self.assert_email_sent_smtp( smtp_from=default_bounce_adress, - message_from='unknown_name@test.com', - from_filter='test.com', + message_from='unknown_name@test.mycompany.com', + from_filter='test.mycompany.com', ) # remove the notification server - # so will use the mail server + # so will use the mail server self.server_notification.unlink() # The mail server configured for the notifications email has been removed - # but we can still use the mail server configured for test.com + # but we can still use the mail server configured for test.mycompany.com # and so we will be able to use the bounce address - # because we use the mail server for "test.com" + # because we use the mail server for "test.mycompany.com" with self.mock_smtplib_connection(): message = self._build_email(mail_from='"Name" ') IrMailServer.send_email(message) @@ -241,8 +320,8 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): self.assert_email_sent_smtp( smtp_from=default_bounce_adress, - message_from='"Name" ', - from_filter='test.com', + message_from='"Name" ', + from_filter='test.mycompany.com', ) # Test that the mail from / recipient envelop are encoded using IDNA @@ -279,14 +358,14 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): # Test when forcing the mail server and when smtp_encryption is "starttls" self.server_domain.smtp_encryption = "starttls" with self.mock_smtplib_connection(): - message = self._build_email(mail_from='specific_user@test.com') + message = self._build_email(mail_from='specific_user@test.mycompany.com') IrMailServer.send_email(message, mail_server_id=self.server_domain.id) self.connect_mocked.assert_called_once() self.assert_email_sent_smtp( - smtp_from='specific_user@test.com', - message_from='specific_user@test.com', - from_filter='test.com', + smtp_from='specific_user@test.mycompany.com', + message_from='specific_user@test.mycompany.com', + from_filter='test.mycompany.com', ) @mute_logger('odoo.models.unlink') @@ -300,15 +379,15 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): # A mail server is configured for the email with self.mock_smtplib_connection(): - smtp_session = IrMailServer.connect(smtp_from='specific_user@test.com') - message = self._build_email(mail_from='specific_user@test.com') + smtp_session = IrMailServer.connect(smtp_from='specific_user@test.mycompany.com') + message = self._build_email(mail_from='specific_user@test.mycompany.com') IrMailServer.send_email(message, smtp_session=smtp_session) self.connect_mocked.assert_called_once() self.assert_email_sent_smtp( - smtp_from='specific_user@test.com', - message_from='specific_user@test.com', - from_filter='specific_user@test.com', + smtp_from='specific_user@test.mycompany.com', + message_from='specific_user@test.mycompany.com', + from_filter='specific_user@test.mycompany.com', ) # No mail server are configured for the email address, @@ -320,31 +399,31 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): self.connect_mocked.assert_called_once() self.assert_email_sent_smtp( - smtp_from='notifications@test.com', - message_from='"Name" ', - from_filter='notifications@test.com', + smtp_from='notifications@test.mycompany.com', + message_from='"Name" ', + from_filter='notifications@test.mycompany.com', ) # A mail server is configured for the entire domain name, so we can use the bounce # email address because the mail server supports it with self.mock_smtplib_connection(): - smtp_session = IrMailServer.connect(smtp_from='unknown_name@test.com') - message = self._build_email(mail_from='unknown_name@test.com') + smtp_session = IrMailServer.connect(smtp_from='unknown_name@test.mycompany.com') + message = self._build_email(mail_from='unknown_name@test.mycompany.com') IrMailServer.send_email(message, smtp_session=smtp_session) self.connect_mocked.assert_called_once() self.assert_email_sent_smtp( smtp_from=default_bounce_adress, - message_from='unknown_name@test.com', - from_filter='test.com', + message_from='unknown_name@test.mycompany.com', + from_filter='test.mycompany.com', ) # remove the notification server - # so will use the mail server + # so will use the mail server self.server_notification.unlink() # The mail server configured for the notifications email has been removed - # but we can still use the mail server configured for test.com + # but we can still use the mail server configured for test.mycompany.com with self.mock_smtplib_connection(): smtp_session = IrMailServer.connect(smtp_from='"Name" ') message = self._build_email(mail_from='"Name" ') @@ -353,12 +432,12 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): self.connect_mocked.assert_called_once() self.assert_email_sent_smtp( smtp_from=default_bounce_adress, - message_from='"Name" ', - from_filter='test.com', + message_from='"Name" ', + from_filter='test.mycompany.com', ) @mute_logger('odoo.models.unlink') - @patch.dict(config.options, {"from_filter": "test.com", "smtp_server": "example.com"}) + @patch.dict(config.options, {"from_filter": "test.mycompany.com", "smtp_server": "example.com"}) def test_mail_server_binary_arguments_domain(self): """Test the configuration provided in the odoo-bin arguments. @@ -373,26 +452,26 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): # Use an email in the domain of the "from_filter" with self.mock_smtplib_connection(): - message = self._build_email(mail_from='specific_user@test.com') + message = self._build_email(mail_from='specific_user@test.mycompany.com') IrMailServer.send_email(message) self.connect_mocked.assert_called_once() self.assert_email_sent_smtp( smtp_from=default_bounce_adress, - message_from='specific_user@test.com', - from_filter='test.com', + message_from='specific_user@test.mycompany.com', + from_filter='test.mycompany.com', ) # Test if the domain name is normalized before comparison with self.mock_smtplib_connection(): - message = self._build_email(mail_from='specific_user@test.com') + message = self._build_email(mail_from='specific_user@test.mycompany.com') IrMailServer.send_email(message) self.connect_mocked.assert_called_once() self.assert_email_sent_smtp( smtp_from=default_bounce_adress, - message_from='specific_user@test.com', - from_filter='test.com', + message_from='specific_user@test.mycompany.com', + from_filter='test.mycompany.com', ) # Use an email outside of the domain of the "from_filter" @@ -405,13 +484,13 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): self.connect_mocked.assert_called_once() self.assert_email_sent_smtp( smtp_from=default_bounce_adress, - message_from='"test" ', - from_filter='test.com', + message_from='"test" ', + from_filter='test.mycompany.com', ) @mute_logger('odoo.models.unlink') @patch.dict(config.options, { - "from_filter": "dummy@example.com, test.com, dummy2@example.com", + "from_filter": "dummy@example.com, test.mycompany.com, dummy2@example.com", "smtp_server": "example.com", }) def test_mail_server_binary_arguments_domain_smtp_session(self): @@ -429,15 +508,15 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): # Use an email in the domain of the "from_filter" with self.mock_smtplib_connection(): - smtp_session = IrMailServer.connect(smtp_from='specific_user@test.com') - message = self._build_email(mail_from='specific_user@test.com') + smtp_session = IrMailServer.connect(smtp_from='specific_user@test.mycompany.com') + message = self._build_email(mail_from='specific_user@test.mycompany.com') IrMailServer.send_email(message, smtp_session=smtp_session) self.connect_mocked.assert_called_once() self.assert_email_sent_smtp( smtp_from=default_bounce_adress, - message_from='specific_user@test.com', - from_filter='dummy@example.com, test.com, dummy2@example.com', + message_from='specific_user@test.mycompany.com', + from_filter='dummy@example.com, test.mycompany.com, dummy2@example.com', ) # Use an email outside of the domain of the "from_filter" @@ -451,8 +530,8 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): self.connect_mocked.assert_called_once() self.assert_email_sent_smtp( smtp_from=default_bounce_adress, - message_from='"test" ', - from_filter='dummy@example.com, test.com, dummy2@example.com', + message_from='"test" ', + from_filter='dummy@example.com, test.mycompany.com, dummy2@example.com', ) def test_mail_server_get_email_addresses(self): @@ -518,17 +597,17 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): # other mail server still work with self.mock_smtplib_connection(): - message = self._build_email(mail_from='specific_user@test.com') + message = self._build_email(mail_from='specific_user@test.mycompany.com') IrMailServer.send_email(message) self.assert_email_sent_smtp( - smtp_from='specific_user@test.com', - message_from='specific_user@test.com', - from_filter='specific_user@test.com', + smtp_from='specific_user@test.mycompany.com', + message_from='specific_user@test.mycompany.com', + from_filter='specific_user@test.mycompany.com', ) @mute_logger('odoo.models.unlink') - @patch.dict(config.options, {'from_filter': 'test.com', 'smtp_server': 'example.com'}) + @patch.dict(config.options, {'from_filter': 'test.mycompany.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" diff --git a/odoo/addons/base/tests/test_mail.py b/odoo/addons/base/tests/test_mail.py index eb8371caf34..c10542d4de4 100644 --- a/odoo/addons/base/tests/test_mail.py +++ b/odoo/addons/base/tests/test_mail.py @@ -2,23 +2,18 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from markupsafe import Markup -from unittest.mock import patch -import email.policy -import email.message import re -import threading from odoo.addons.base.models.ir_mail_server import extract_rfc2822_addresses from odoo.addons.base.models.ir_qweb_fields import nl2br_enclose from odoo.tests import tagged -from odoo.tests.common import BaseCase, TransactionCase +from odoo.tests.common import BaseCase from odoo.tools import ( is_html_empty, html_to_inner_content, html_sanitize, append_content_to_html, plaintext2html, email_split, email_domain_normalize, misc, formataddr, prepend_html_content, - config, ) from . import test_mail_examples @@ -28,6 +23,24 @@ from . import test_mail_examples class TestSanitizer(BaseCase): """ Test the html sanitizer that filters html to remove unwanted attributes """ + def test_abrupt_close(self): + payload = """ -->""" + html_result = html_sanitize(payload) + self.assertNotIn('alert(1)', html_result) + + payload = """ -->""" + html_result = html_sanitize(payload) + self.assertNotIn('alert(1)', html_result) + + def test_abrut_malformed(self): + payload = """""" + html_result = html_sanitize(payload) + self.assertNotIn('alert(1)', html_result) + + payload = """""" + html_result = html_sanitize(payload) + self.assertNotIn('alert(1)', html_result) + def test_basic_sanitizer(self): cases = [ ("yop", "

yop

"), # simple @@ -39,6 +52,20 @@ class TestSanitizer(BaseCase): html = html_sanitize(content) self.assertEqual(html, expected, 'html_sanitize is broken') + def test_comment_malformed(self): + html = ''' --> comment --> out of context balise --!>''' + html_result = html_sanitize(html) + self.assertNotIn('alert(1)', html_result) + + def test_comment_multiline(self): + payload = """ +
--> + """ + html_result = html_sanitize(payload) + self.assertNotIn('alert(1)', html_result) + def test_evil_malicious_code(self): # taken from https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet#Tests cases = [ @@ -438,6 +465,7 @@ class TestHtmlTools(BaseCase): self.assertEqual(result, "
test
test
") +@tagged('mail_tools') class TestEmailTools(BaseCase): """ Test some of our generic utility functions for emails """ @@ -492,133 +520,3 @@ class TestEmailTools(BaseCase): self.assertEqual(email_domain_normalize("Test.Com"), "test.com", "Should have normalized the domain") self.assertEqual(email_domain_normalize("email@test.com"), False, "The domain is not valid, should return False") self.assertEqual(email_domain_normalize(False), False, "The domain is not valid, should return False") - - -class EmailConfigCase(TransactionCase): - @patch.dict(config.options, {"email_from": "settings@example.com"}) - def test_default_email_from(self, *args): - """Email from setting is respected.""" - # ICP setting is more important - ICP = self.env["ir.config_parameter"].sudo() - ICP.set_param("mail.catchall.domain", "example.org") - ICP.set_param("mail.default.from", "icp") - message = self.env["ir.mail_server"].build_email( - False, "recipient@example.com", "Subject", - "The body of an email", - ) - self.assertEqual(message["From"], "icp@example.org") - # Without ICP, the config file/CLI setting is used - ICP.set_param("mail.default.from", False) - message = self.env["ir.mail_server"].build_email( - False, "recipient@example.com", "Subject", - "The body of an email", - ) - self.assertEqual(message["From"], "settings@example.com") - - -class _FakeSMTP: - """SMTP stub""" - def __init__(self): - self.messages = [] - self.from_filter = 'example.com' - - # Python 3 before 3.7.4 - def sendmail(self, smtp_from, smtp_to_list, message_str, - mail_options=(), rcpt_options=()): - self.messages.append(message_str) - - # Python 3.7.4+ - def send_message(self, message, smtp_from, smtp_to_list, - mail_options=(), rcpt_options=()): - self.messages.append(message.as_string()) - - -class TestEmailMessage(TransactionCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls._fake_smtp = _FakeSMTP() - - def build_email(self, **kwargs): - kwargs.setdefault('email_from', 'from@example.com') - kwargs.setdefault('email_to', 'to@example.com') - kwargs.setdefault('subject', 'subject') - return self.env['ir.mail_server'].build_email(**kwargs) - - def send_email(self, msg): - with patch.object(threading.current_thread(), 'testing', False): - self.env['ir.mail_server'].send_email(msg, smtp_session=self._fake_smtp) - return self._fake_smtp.messages.pop() - - def test_bpo_34424_35805(self): - """Ensure all email sent are bpo-34424 and bpo-35805 free""" - msg = email.message.EmailMessage(policy=email.policy.SMTP) - msg['From'] = '"Joé Doe" ' - msg['To'] = '"Joé Doe" ' - - # Message-Id & References fields longer than 77 chars (bpo-35805) - msg['Message-Id'] = '<929227342217024.1596730490.324691772460938-example-30661-some.reference@test-123.example.com>' - msg['References'] = '<345227342212345.1596730777.324691772483620-example-30453-other.reference@test-123.example.com>' - - msg_on_the_wire = self.send_email(msg) - self.assertEqual(msg_on_the_wire, - 'From: =?utf-8?q?Jo=C3=A9?= Doe \r\n' - 'To: =?utf-8?q?Jo=C3=A9?= Doe \r\n' - 'Message-Id: <929227342217024.1596730490.324691772460938-example-30661-some.reference@test-123.example.com>\r\n' - 'References: <345227342212345.1596730777.324691772483620-example-30453-other.reference@test-123.example.com>\r\n' - '\r\n' - ) - - def test_alternative_correct_order(self): - """ - RFC-1521 7.2.3. The Multipart/alternative subtype - > the alternatives appear in an order of increasing faithfulness - > to the original content. In general, the best choice is the - > LAST part of a type supported by the recipient system's local - > environment. - - Also, the MIME-Version header should be present in BOTH the - enveloppe AND the parts - """ - msg = self.build_email(body='

Hello world

', subtype='html') - msg_on_the_wire = self.send_email(msg) - - self.assertGreater(msg_on_the_wire.index('text/html'), msg_on_the_wire.index('text/plain'), - "The html part should be preferred (=appear after) to the text part") - self.assertEqual(msg_on_the_wire.count('==============='), 2 + 2, # +2 for the header and the footer - "There should be 2 parts: one text and one html") - self.assertEqual(msg_on_the_wire.count('MIME-Version: 1.0'), 3, - "There should be 3 headers MIME-Version: one on the enveloppe, " - "one on the html part, one on the text part") - - def test_comment_malformed(self): - html = ''' --> comment --> out of context balise --!>''' - html_result = html_sanitize(html) - self.assertNotIn('alert(1)', html_result) - - def test_multiline(self): - payload = """ -
--> - """ - html_result = html_sanitize(payload) - self.assertNotIn('alert(1)', html_result) - - def test_abrupt_close(self): - payload = """ -->""" - html_result = html_sanitize(payload) - self.assertNotIn('alert(1)', html_result) - - payload = """ -->""" - html_result = html_sanitize(payload) - self.assertNotIn('alert(1)', html_result) - - def test_abrut_malformed(self): - payload = """""" - html_result = html_sanitize(payload) - self.assertNotIn('alert(1)', html_result) - - payload = """""" - html_result = html_sanitize(payload) - self.assertNotIn('alert(1)', html_result)