[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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
|
||||
@@ -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)')
|
||||
|
||||
@@ -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([
|
||||
{
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user