[MERGE][REF] test_mail: prepare tests for multicompany aliases support

RATIONALE

We plan to move from a single alias-domain configuration to a multi
company enabled support for alias domains and aliases. First step to
prepare this change is to cleanup some tests: prepare multi-company
environment, make tests clearer, lessen future diff when updating
alias and alias domain usage.

This merge is mainly about test move and internal content renaming.

Task-3453577 (TestMail: Update Alias/Gateway tests for MC)

Next planned steps

Prepares Task-3453343 (Mail: Cleanup Alias Usage)
Prepares Task-3453347 (Mail: Move Alias Domain Support)
Prepares Task-36879 (Mail: Support MultiCompany Aliases)

closes odoo/odoo#130768

Related: odoo/enterprise#45204
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2023-08-03 21:56:58 +02:00
committed by GitHub
19 changed files with 566 additions and 522 deletions
@@ -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',
@@ -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: <blablabla0>
Subject: sale team 0 in company 0
From: A client <client_a@someprovider.com>
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: <blablabla1>
Subject: sale team 1 in company 1
From: B client <client_b@someprovider.com>
To: sale_team_1@aqualung.com
To: sale_team_1@test.mycompany.com
Content-Type: multipart/alternative; boundary="000000000000a47519057e029630"
--000000000000a47519057e029630
+1 -1
View File
@@ -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))
@@ -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,
+15 -10
View File
@@ -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',
+1 -1
View File
@@ -219,7 +219,7 @@ Date: Sun, 26 Mar 2023 05:23:22 +0200
Message-ID: {msg_id}
Subject: {subject}
From: "Sylvie Lelitre" <test.sylvie.lelitre@agrolait.com>
To: groups@test.com
To: groups@test.mycompany.com
Content-Type: multipart/mixed; boundary="000000000000b951de05f7c47a9e"
--000000000000b951de05f7c47a9e
+1
View File
@@ -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
+153
View File
@@ -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 = '<p>Bounced Content</p>'
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)
+3 -3
View File
@@ -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'
)),
},
)
@@ -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,
+127 -237
View File
@@ -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" <test.sylvie.lelitre@agrolait.com>', to='generic@test.com')
mail = self.format(test_mail_data.MAIL_EML_ATTACHMENT, email_from='"Sylvie Lelitre" <test.sylvie.lelitre@agrolait.com>', 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" <test.sylvie.lelitre@agrolait.com>', to='generic@test.com')
mail = self.format(test_mail_data.MAIL_TEMPLATE_PLAINTEXT, email_from='"Sylvie Lelitre" <test.sylvie.lelitre@agrolait.com>', to=f'generic@{self.alias_domain}')
res = self.env['mail.thread'].message_parse(self.from_string(mail))
self.assertIn('<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>', 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 = '<p>Bounced Content</p>'
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" <test.sylvie.lelitre@agrolait.com>'
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" <bounce.test@test.com>', ['whatever-2a840@postmaster.twitter.com'], body_content='<p>What Is Dead May Never Die</p>')
self.assertSentEmail(f'"MAILER-DAEMON" <{self.alias_bounce}@{self.alias_domain}>', ['whatever-2a840@postmaster.twitter.com'], body_content='<p>What Is Dead May Never Die</p>')
for empty_content in [
'<p><br></p>', '<p><br> </p>', '<p><br /></p >',
@@ -515,11 +386,12 @@ class TestMailgateway(MailCommon):
# Test: with "empty" bounced content (simulate view, putting always '<p></br></p>' 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" <bounce.test@test.com>', ['whatever-2a840@postmaster.twitter.com'],
f'"MAILER-DAEMON" <{self.alias_bounce}@{self.alias_domain}>',
['whatever-2a840@postmaster.twitter.com'],
body_content='<p>Dear Sender,<br /><br />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" <bounce.test@test.com>',
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" <bounce.test@test.com>',
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" <bounce.test@test.com>', ['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" <bounce.test@test.com>', ['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" <bounce.test@test.com>', ['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" <bounce.test@test.com>', ['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 <what@ever.com>', 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 <what@ever.com>', 'groups@test.com', subject='Undelivered Mail Returned to Sender', extra=extra)
record = self.format_and_process(test_mail_data.MAIL_BOUNCE, 'Whatever <what@ever.com>', 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 <what@ever.com>', 'groups@test.com', subject='Undelivered Mail Returned to Sender', extra=extra)
record = self.format_and_process(email, 'Whatever <what@ever.com>', 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 = '<this.is.duplicate.test@iron.sky>'
@@ -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" <bounce.test@test.com>', [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("<Invoice>Chaussée de Bruxelles</Invoice>", 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 <cc1@example.com>, 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 <cc2@example.com>, cc3@example.com', target_model='mail.test.cc')
cc = email_split_and_format(record.email_cc)
self.assertEqual(sorted(cc), ['"cc1" <cc1@example.com>', '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 <cc2@example.com>, cc3@example.com', target_model='mail.test.cc')
cc = email_split_and_format(record.email_cc)
self.assertEqual(sorted(cc), ['"cc2" <cc2@example.com>', 'cc3@example.com'], 'new cc should have been added on record (unique)')
+7 -4
View File
@@ -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': '<p>Test</p>',
@@ -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" <notifications@test.com>',
self.assert_email_sent_smtp(message_from=f'"test" <notifications@{self.alias_domain}>',
emails_count=5, from_filter=self.server_notification.from_filter)
self.assert_email_sent_smtp(message_from='"test_2" <notifications@test.com>',
self.assert_email_sent_smtp(message_from=f'"test_2" <notifications@{self.alias_domain}>',
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)
@@ -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([
{
+1 -1
View File
@@ -208,7 +208,7 @@ class TestTracking(MailCommon):
'alias_contact': 'everyone',
'alias_defaults': custom_values})
record = self.format_and_process(MAIL_TEMPLATE, '"Sylvie Lelitre" <test.sylvie.lelitre@agrolait.com>',
'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):
+5 -6
View File
@@ -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}"
))
)
@@ -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 <t t-out="object.name" />',
'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 <t t-out="object.name" />',
'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" <notifications@test.com>',
smtp_from='notifications@test.mycompany.com',
message_from='"Testing" <notifications@test.mycompany.com>',
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,
)
+21 -4
View File
@@ -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',
+166 -87
View File
@@ -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" <joe@example.com>'
msg['To'] = '"Joé Doe" <joe@example.com>'
# 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 <joe@example.com>\r\n'
'To: =?utf-8?q?Jo=C3=A9?= Doe <joe@example.com>\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='<p>Hello world</p>', 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" <admin@mail.example.com>', 'mail.example.com'),
('"fake@test.com" <ADMIN@mail.example.com>', 'mail.example.com'),
('"fake@test.com" <ADMIN@mail.example.com>', 'test.com, mail.example.com, test2.com'),
('"fake@test.mycompany.com" <admin@mail.example.com>', 'mail.example.com'),
('"fake@test.mycompany.com" <ADMIN@mail.example.com>', 'mail.example.com'),
('"fake@test.mycompany.com" <ADMIN@mail.example.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" <fake@test.com>', 'mail.example.com'),
('"fake@test.com" <ADMIN@mail.example.com>', 'test.com, wrong.mail.example.com, test3.com'),
('"admin@mail.example.com" <fake@test.mycompany.com>', 'mail.example.com'),
('"fake@test.mycompany.com" <ADMIN@mail.example.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" <specific_user@test.com>')
mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Name name@strange.name" <specific_user@test.mycompany.com>')
self.assertEqual(mail_server, self.server_user, 'Must extract email from full name')
self.assertEqual(mail_from, '"Name name@strange.name" <specific_user@test.com>', 'Must keep the given mail from')
self.assertEqual(mail_from, '"Name name@strange.name" <specific_user@test.mycompany.com>', '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" <test@unknown_domain.com>')
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" <specific_user@test.com>')
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" <specific_user@test.mycompany.com>')
self.assertEqual(mail_server, self.server_user, "The entire email matches, the server should be used")
self.assertEqual(mail_from, '"Test" <specific_user@test.com>')
self.assertEqual(mail_from, '"Test" <specific_user@test.mycompany.com>')
mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Example" <test@domain2.com>')
self.assertEqual(mail_server, self.server_user, "The domain matches, the server should be used")
self.assertEqual(mail_from, '"Example" <test@domain2.com>')
@@ -153,9 +232,9 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
self.assertEqual(mail_from, '"Example" <test@domain1.com>')
# 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" <not_specific_user@test.com>')
mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Test" <not_specific_user@test.mycompany.com>')
self.assertEqual(mail_server, self.server_domain)
self.assertEqual(mail_from, '"Test" <not_specific_user@test.com>')
self.assertEqual(mail_from, '"Test" <not_specific_user@test.mycompany.com>')
# 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" <notifications@test.com>',
from_filter='notifications@test.com',
smtp_from='notifications@test.mycompany.com',
message_from='"Name" <notifications@test.mycompany.com>',
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" <notifications@test.com>',
from_filter='notifications@test.com',
smtp_from='notifications@test.mycompany.com',
message_from='"test" <notifications@test.mycompany.com>',
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 <notifications@test.com> will use the <test.com> mail server
# so <notifications@test.mycompany.com> will use the <test.mycompany.com> 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" <test@unknown_domain.com>')
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" <notifications@test.com>',
from_filter='test.com',
message_from='"Name" <notifications@test.mycompany.com>',
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" <notifications@test.com>',
from_filter='notifications@test.com',
smtp_from='notifications@test.mycompany.com',
message_from='"Name" <notifications@test.mycompany.com>',
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 <notifications@test.com> will use the <test.com> mail server
# so <notifications@test.mycompany.com> will use the <test.mycompany.com> 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" <test@unknown_domain.com>')
message = self._build_email(mail_from='"Name" <test@unknown_domain.com>')
@@ -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" <notifications@test.com>',
from_filter='test.com',
message_from='"Name" <notifications@test.mycompany.com>',
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" <notifications@test.com>',
from_filter='test.com',
message_from='"test" <notifications@test.mycompany.com>',
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" <notifications@test.com>',
from_filter='dummy@example.com, test.com, dummy2@example.com',
message_from='"test" <notifications@test.mycompany.com>',
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"
+34 -136
View File
@@ -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 = """<!--> <script> alert(1) </script> -->"""
html_result = html_sanitize(payload)
self.assertNotIn('alert(1)', html_result)
payload = """<!---> <script> alert(1) </script> -->"""
html_result = html_sanitize(payload)
self.assertNotIn('alert(1)', html_result)
def test_abrut_malformed(self):
payload = """<!--!> <script> alert(1) </script> -->"""
html_result = html_sanitize(payload)
self.assertNotIn('alert(1)', html_result)
payload = """<!---!> <script> alert(1) </script> -->"""
html_result = html_sanitize(payload)
self.assertNotIn('alert(1)', html_result)
def test_basic_sanitizer(self):
cases = [
("yop", "<p>yop</p>"), # 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 = '''<!-- malformed-close --!> <img src='x' onerror='alert(1)'></img> --> comment <!-- normal comment --> --> out of context balise --!>'''
html_result = html_sanitize(html)
self.assertNotIn('alert(1)', html_result)
def test_comment_multiline(self):
payload = """
<div> <!--
multi line comment
--!> </div> <script> alert(1) </script> -->
"""
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, "<html><body><div>test</div><div>test</div></body></html>")
@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" <joe@example.com>'
msg['To'] = '"Joé Doe" <joe@example.com>'
# 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 <joe@example.com>\r\n'
'To: =?utf-8?q?Jo=C3=A9?= Doe <joe@example.com>\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='<p>Hello world</p>', 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 = '''<!-- malformed-close --!> <img src='x' onerror='alert(1)'></img> --> comment <!-- normal comment --> --> out of context balise --!>'''
html_result = html_sanitize(html)
self.assertNotIn('alert(1)', html_result)
def test_multiline(self):
payload = """
<div> <!--
multi line comment
--!> </div> <script> alert(1) </script> -->
"""
html_result = html_sanitize(payload)
self.assertNotIn('alert(1)', html_result)
def test_abrupt_close(self):
payload = """<!--> <script> alert(1) </script> -->"""
html_result = html_sanitize(payload)
self.assertNotIn('alert(1)', html_result)
payload = """<!---> <script> alert(1) </script> -->"""
html_result = html_sanitize(payload)
self.assertNotIn('alert(1)', html_result)
def test_abrut_malformed(self):
payload = """<!--!> <script> alert(1) </script> -->"""
html_result = html_sanitize(payload)
self.assertNotIn('alert(1)', html_result)
payload = """<!---!> <script> alert(1) </script> -->"""
html_result = html_sanitize(payload)
self.assertNotIn('alert(1)', html_result)