[IMP] base, mail: cleanup ir_mail_server tests, improve test logs

RATIONALE

This prepares the move of ICP to mail before replacing them by dynamic alias
domains.

Cleanup tests: try to use loops with input / expected to better understand
the various test cases, add some comments, improve logs when failing to
find the right sent email. Rename tests to have a better test structure
when reading logs.

Remove a test from odoo/odoo@3b6c20805c that adds nothing except testing
the test suite.

OTHER ADDONS

In test_mail: have a specific class for testing servers as other data is
not necessary, and it allows to have a tag for it.

In mass mailing: concatenate test about server finding, several tests can be
done in a single unit test.

Task-3453577 (TestMail: Update Alias/Gateway tests for MC)
Prepares Task-3453347 (Mail: Move Mail ICP from Base to Mail)
Prepares Task-36879 (Mail: Support MultiCompany Aliases)

closes odoo/odoo#131492

Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Thibault Delavallée
2023-08-10 10:39:41 +02:00
parent c7ca317d59
commit 053290c6e7
4 changed files with 339 additions and 439 deletions
+21 -13
View File
@@ -28,12 +28,6 @@ class TestMailMail(MailCommon):
super(TestMailMail, cls).setUpClass()
cls._init_mail_servers()
cls.server_domain_2 = cls.env['ir.mail_server'].create({
'name': 'Server 2',
'smtp_host': 'test_2.com',
'from_filter': 'test_2.com',
})
cls.test_record = cls.env['mail.test.gateway'].with_context(cls._test_context).create({
'name': 'Test',
'email_from': 'ignasse@example.com',
@@ -660,6 +654,20 @@ class TestMailMail(MailCommon):
self.send_email_mocked.side_effect = _send_current
@tagged('mail_mail', 'mail_server')
class TestMailMailServer(MailCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls._init_mail_servers()
cls.server_domain_2 = cls.env['ir.mail_server'].create({
'from_filter': 'test_2.com',
'name': 'Server 2',
'smtp_host': 'test_2.com',
})
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_mail_mail_send_server(self):
"""Test that the mails are send in batch.
@@ -722,13 +730,13 @@ class TestMailMail(MailCommon):
any_order=True,
)
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=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)
self.assert_email_sent_smtp(message_from='user_1@test_2.com', emails_count=5, from_filter=self.server_domain.from_filter)
self.assertSMTPEmailsSent(message_from=f'"test" <notifications@{self.alias_domain}>',
emails_count=5, from_filter=self.server_notification.from_filter)
self.assertSMTPEmailsSent(message_from=f'"test_2" <notifications@{self.alias_domain}>',
emails_count=5, from_filter=self.server_notification.from_filter)
self.assertSMTPEmailsSent(message_from='user_1@test_2.com', emails_count=5, from_filter=self.server_domain_2.from_filter)
self.assertSMTPEmailsSent(message_from='user_2@test_2.com', emails_count=5, from_filter=self.server_domain_2.from_filter)
self.assertSMTPEmailsSent(message_from='user_1@test_2.com', emails_count=5, from_filter=self.server_domain.from_filter)
@tagged('mail_mail')
@@ -73,98 +73,48 @@ class TestMassMailingServer(TestMassMailCommon):
@users('user_marketing')
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.addons.mass_mailing.models.mailing')
def test_mass_mailing_server_batch(self):
def test_mass_mailing_server_find(self):
"""Test that the right mail server is chosen to send the mailing.
Test also the envelop and the SMTP headers.
"""
# Test sending mailing in batch
mailings = self.env['mailing.mailing'].create([{
'subject': 'Mailing',
'body_html': 'Body for <t t-out="object.name" />',
'email_from': 'specific_user@test.mycompany.com',
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
'mailing_model_id': self.env['ir.model']._get_id('mailing.test.optout'),
}, {
'subject': 'Mailing',
'body_html': 'Body for <t t-out="object.name" />',
'email_from': 'unknown_name@test.mycompany.com',
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
}])
with self.mock_smtplib_connection():
mailings.action_send_mail()
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.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.mycompany.com"
smtp_from=lambda x: 'bounce' in x,
message_from='unknown_name@test.mycompany.com',
from_filter=self.server_domain.from_filter,
emails_count=8,
)
@users('user_marketing')
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.addons.mass_mailing.models.mailing')
def test_mass_mailing_server_default(self):
# We do not have a mail server for this address email, so fall back to the
# "notifications@domain" email.
mailings = self.env['mailing.mailing'].create([{
'subject': 'Mailing',
'body_html': 'Body for <t t-out="object.name" />',
'email_from': '"Testing" <unknow_email@unknow_domain.com>',
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
}])
with self.mock_smtplib_connection():
mailings.action_send_mail()
self.assertEqual(self.find_mail_server_mocked.call_count, 1)
self.assert_email_sent_smtp(
smtp_from='notifications@test.mycompany.com',
message_from='"Testing" <notifications@test.mycompany.com>',
from_filter=self.server_notification.from_filter,
emails_count=8,
)
self.assertEqual(self.find_mail_server_mocked.call_count, 1, 'Must be called only once')
@users('user_marketing')
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.addons.mass_mailing.models.mailing')
def test_mass_mailing_server_forced(self):
# We force a mail server on one mailing
mailings = self.env['mailing.mailing'].create([{
'subject': 'Mailing',
'body_html': 'Body for <t t-out="object.name" />',
'email_from': self.server_user.from_filter,
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
'mailing_model_id': self.env['ir.model']._get_id('mailing.test.optout'),
}, {
'subject': 'Mailing',
'body_html': 'Body for <t t-out="object.name" />',
'email_from': '"Testing" <unknow_email@unknow_domain.com>',
'mailing_model_id': self.env['ir.model']._get_id('mailing.test.optout'),
}, {
'subject': 'Mailing Forced',
'body_html': 'Body for <t t-out="object.name" />',
'email_from': 'unknow_email@unknow_domain.com',
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
'mailing_model_id': self.env['ir.model']._get_id('mailing.test.optout'),
'mail_server_id': self.server_notification.id,
}])
with self.mock_smtplib_connection():
mailings.action_send_mail()
self.assertEqual(self.find_mail_server_mocked.call_count, 1, 'Must not be called when mail server is forced')
self.assertEqual(self.find_mail_server_mocked.call_count, 3, 'Must be called only once per mail from except when forced')
self.assert_email_sent_smtp(
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(
smtp_from='unknow_email@unknow_domain.com',
message_from='unknow_email@unknow_domain.com',
from_filter=self.server_notification.from_filter,
for (expected_smtp_from, expected_msg_from, expected_from_filter) in [
('specific_user@test.mycompany.com', 'specific_user@test.mycompany.com', self.server_user.from_filter),
(f'{self.alias_bounce}@{self.alias_domain}', 'unknown_name@test.mycompany.com', self.server_domain.from_filter),
# We do not have a mail server for this address email, so fall back to the "notifications@domain" email.
('notifications@test.mycompany.com', '"Testing" <notifications@test.mycompany.com>', self.server_notification.from_filter),
# forced sever
('unknow_email@unknow_domain.com', 'unknow_email@unknow_domain.com', self.server_notification.from_filter),
]:
self.assertSMTPEmailsSent(
smtp_from=expected_smtp_from,
message_from=expected_msg_from,
from_filter=expected_from_filter,
emails_count=8,
)
+15 -10
View File
@@ -354,7 +354,7 @@ class MockSmtplibCase:
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):
def assertSMTPEmailsSent(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.
If one of the parameter is None, it's just ignored and not used to retrieve the email.
@@ -369,22 +369,25 @@ class MockSmtplibCase:
"""
matching_emails = filter(
lambda email:
(smtp_from is None or (
smtp_from(email['smtp_from'])
if callable(smtp_from)
else smtp_from == email['smtp_from'])
)
(smtp_from is None or smtp_from == email['smtp_from'])
and (smtp_to_list is None or smtp_to_list == email['smtp_to_list'])
and (message_from is None or 'From: %s' % message_from in email['message'])
and (from_filter is None or from_filter == email['from_filter']),
self.emails,
)
debug_info = ''
matching_emails_count = len(list(matching_emails))
self.assertTrue(
matching_emails_count == emails_count,
msg='Emails not sent, %i emails match the condition but %i are expected' % (matching_emails_count, emails_count),
if matching_emails_count != emails_count:
debug_info = '\n'.join(
f"SMTP-From: {email['smtp_from']}, SMTP-To: ({email['smtp_to_list']}) - From_filter {email['from_filter']})"
for email in self.emails
)
self.assertEqual(
matching_emails_count, emails_count,
msg=f'Incorrect emails sent: {matching_emails_count} found, {emails_count} expected'
f'\nConditions: SMTP-From {smtp_from}, SMTP-To {smtp_to_list}, Msg-From {message_from}, From_filter {from_filter}'
f'\n{debug_info}'
)
@classmethod
@@ -392,9 +395,11 @@ class MockSmtplibCase:
cls.alias_bounce = 'bounce.test'
cls.alias_domain = 'test.mycompany.com'
cls.default_from = 'notifications'
cls.default_from_filter = False
cls.env['ir.config_parameter'].sudo().set_param('mail.catchall.domain', cls.alias_domain)
cls.env['ir.config_parameter'].sudo().set_param('mail.default.from', cls.default_from)
cls.env['ir.config_parameter'].sudo().set_param('mail.bounce.alias', cls.alias_bounce)
cls.env['ir.config_parameter'].sudo().set_param('mail.default.from_filter', cls.default_from_filter)
@classmethod
def _init_mail_servers(cls):
+281 -344
View File
@@ -3,6 +3,7 @@
import email.message
import email.policy
import itertools
from unittest.mock import patch
@@ -38,22 +39,26 @@ 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")
for (icp_from, icp_domain), expected_from in zip(
[
('icp', 'test.mycompany.com'),
('icp@another.company.com', 'test.mycompany.com'),
(False, 'test.mycompany.com'),
], [
"icp@test.mycompany.com",
"icp@another.company.com",
"settings@example.com",
],
):
with self.subTest(icp_from=icp_from, icp_domain=icp_domain):
ICP.set_param("mail.default.from", icp_from)
ICP.set_param("mail.catchall.domain", icp_domain)
message = self.env["ir.mail_server"].build_email(
False, "recipient@example.com", "Subject",
"The body of an email",
)
self.assertEqual(message["From"], expected_from)
@tagged('mail_server')
@@ -65,6 +70,17 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
cls._init_mail_config()
cls._init_mail_servers()
cls.default_bounce_address = f'{cls.alias_bounce}@{cls.alias_domain}'
cls.default_from_address = f'{cls.default_from}@{cls.alias_domain}'
def test_assert_base_values(self):
self.assertEqual(
self.env['ir.mail_server']._get_default_bounce_address(),
self.default_bounce_address)
self.assertEqual(
self.env['ir.mail_server']._get_default_from_address(),
self.default_from_address)
def test_bpo_34424_35805(self):
"""Ensure all email sent are bpo-34424 and bpo-35805 free"""
fake_smtp = _FakeSMTP()
@@ -108,10 +124,8 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
"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
def test_mail_server_match_from_filter(self):
""" Test the from_filter field on the "ir.mail_server". """
# Should match
tests = [
('admin@mail.example.com', 'mail.example.com'),
@@ -123,7 +137,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
('"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))
self.assertTrue(self.env['ir.mail_server']._match_from_filter(email, from_filter))
# Should not match
tests = [
@@ -136,7 +150,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
('"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))
self.assertFalse(self.env['ir.mail_server']._match_from_filter(email, from_filter))
def test_mail_body(self):
bodies = [
@@ -175,7 +189,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
@mute_logger('odoo.models.unlink')
def test_mail_server_priorities(self):
"""Test if we choose the right mail server to send an email.
""" Test if we choose the right mail server to send an email.
Priorities are
1. Forced mail server (e.g.: in mass mailing)
@@ -192,300 +206,246 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
# sanity checks
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.mycompany.com')
self.assertEqual(mail_server, self.server_user)
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.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.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.mycompany.com')
self.assertEqual(mail_server, self.server_user, 'Mail from is case insensitive')
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.mycompany.com')
self.assertEqual(mail_server, self.server_domain)
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.MYCOMPANY.COM')
self.assertEqual(mail_server, self.server_domain, '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.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.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.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>')
mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Example" <test@domain1.com>')
self.assertEqual(mail_server, self.server_user, "The domain matches, the server should be used")
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.mycompany.com>')
self.assertEqual(mail_server, self.server_domain)
self.assertEqual(mail_from, '"Test" <not_specific_user@test.mycompany.com>')
for email_from, (expected_mail_server, expected_email_from) in zip(
[
'specific_user@test.mycompany.com',
'"Name name@strange.name" <specific_user@test.mycompany.com>',
'specific_user@test.mycompany.com',
'unknown_email@test.mycompany.com',
'unknown_email@TEST.MYCOMPANY.COM',
'"Test" <test@unknown_domain.com>',
# server_user multiple from filter check
'"Test" <specific_user@test.mycompany.com>',
'"Example" <test@domain2.com>',
'"Example" <test@domain1.com>',
'"Test" <not_specific_user@test.mycompany.com>',
], [
(self.server_user, 'specific_user@test.mycompany.com'),
# must extract email from full name, must keep the given email from
(self.server_user, '"Name name@strange.name" <specific_user@test.mycompany.com>'),
# should not be case sensitive
(self.server_user, 'specific_user@test.mycompany.com'),
(self.server_domain, 'unknown_email@test.mycompany.com'),
# cover a different condition that the "email case insensitive" test: domain is case insensitive
(self.server_domain, 'unknown_email@TEST.MYCOMPANY.COM'),
# should take the notification email
(self.server_notification, 'notifications@test.mycompany.com'),
# server_user multipl from_filter check: can be used for a sepcific emial and 2 domain names
# 1. entire email matches, server should be used
(self.server_user, '"Test" <specific_user@test.mycompany.com>'),
# 2. domain matches, server should be used
(self.server_user, '"Example" <test@domain2.com>'),
# 3. other domain matches, server shold be used
(self.server_user, '"Example" <test@domain1.com>'),
# check that we take the domain server and not the "user specific" server
(self.server_domain, '"Test" <not_specific_user@test.mycompany.com>'),
],
):
with self.subTest(email_from=email_from):
mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from=email_from)
self.assertEqual(mail_server, expected_mail_server)
self.assertEqual(mail_from, expected_email_from)
# remove the notifications email to simulate a mis-configured Odoo database
# so we do not have the choice, we have to spoof the FROM
# (otherwise we can not send the email)
@mute_logger('odoo.models.unlink')
def test_mail_server_priorities_base_config(self):
""" Test if we choose the right mail server to send an email, without
ICP configuration. Simulates unconfigured Odoo DB so we have to spoof
the FROM otherwise we cannot send any email. """
self.env['ir.config_parameter'].sudo().set_param('mail.catchall.domain', False)
with mute_logger('odoo.addons.base.models.ir_mail_server'):
mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='test@unknown_domain.com')
self.assertEqual(mail_server.from_filter, False, 'No notifications email set, must be forced to spoof the FROM')
self.assertEqual(mail_from, 'test@unknown_domain.com')
self.env['ir.config_parameter'].sudo().set_param('mail.default.from', False)
for email_from, (expected_mail_server, expected_from_filter, expected_email_from) in zip(
[
'test@unknown_domain.com',
], [
(self.server_default, False, 'test@unknown_domain.com'),
],
):
with self.subTest(email_from=email_from):
mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='test@unknown_domain.com')
self.assertEqual(mail_server.from_filter, expected_from_filter, 'No notifications email set, must be forced to spoof the FROM')
self.assertEqual(mail_server, expected_mail_server)
self.assertEqual(mail_from, expected_email_from)
@mute_logger('odoo.models.unlink')
def test_mail_server_send_email(self):
IrMailServer = self.env['ir.mail_server']
default_bounce_adress = self.env['ir.mail_server']._get_default_bounce_address()
# A mail server is configured for the email
with self.mock_smtplib_connection():
message = self._build_email(mail_from='specific_user@test.mycompany.com')
IrMailServer.send_email(message)
for (mail_from, (expected_smtp_from, expected_msg_from, expected_from_filter)), provide_smtp in itertools.product(
zip(
[
'specific_user@test.mycompany.com',
'"Name" <test@unknown_domain.com>',
'test@unknown_domain.com',
'unknown_name@test.mycompany.com'
], [
# A mail server is configured for the email
('specific_user@test.mycompany.com', 'specific_user@test.mycompany.com', 'specific_user@test.mycompany.com'),
# No mail server are configured for the email address, so it will use the
# notifications email instead and encapsulate the old email
('notifications@test.mycompany.com', '"Name" <notifications@test.mycompany.com>', 'notifications@test.mycompany.com'),
# same situation, but the original email has no name part
('notifications@test.mycompany.com', '"test" <notifications@test.mycompany.com>', '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
(self.default_bounce_address, 'unknown_name@test.mycompany.com', 'test.mycompany.com'),
],
),
[False, True], # provide SMTP session or not: should not impact sending
):
with self.subTest(mail_from=mail_from):
with self.mock_smtplib_connection():
if provide_smtp:
smtp_session = IrMailServer.connect(smtp_from=mail_from)
message = self._build_email(mail_from=mail_from)
IrMailServer.send_email(message, smtp_session=smtp_session)
else:
message = self._build_email(mail_from=mail_from)
IrMailServer.send_email(message)
self.assertEqual(len(self.emails), 1)
self.assert_email_sent_smtp(
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,
# so it will use the notifications email instead and encapsulate the old email
with self.mock_smtplib_connection():
message = self._build_email(mail_from='"Name" <test@unknown_domain.com>')
IrMailServer.send_email(message)
self.assertEqual(len(self.emails), 1)
self.assert_email_sent_smtp(
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
with self.mock_smtplib_connection():
message = self._build_email(mail_from='test@unknown_domain.com')
IrMailServer.send_email(message)
self.assertEqual(len(self.emails), 1)
self.assert_email_sent_smtp(
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.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.mycompany.com',
from_filter='test.mycompany.com',
)
self.connect_mocked.assert_called_once()
self.assertEqual(len(self.emails), 1)
self.assertSMTPEmailsSent(
smtp_from=expected_smtp_from,
message_from=expected_msg_from,
from_filter=expected_from_filter,
)
# remove the notification 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.mycompany.com
# and so we will be able to use the bounce address
# 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)
self.server_notification.unlink()
for provide_smtp in [False, True]:
with self.mock_smtplib_connection():
if provide_smtp:
smtp_session = IrMailServer.connect(smtp_from='"Name" <test@unknown_domain.com>')
message = self._build_email(mail_from='"Name" <test@unknown_domain.com>')
IrMailServer.send_email(message, smtp_session=smtp_session)
else:
message = self._build_email(mail_from='"Name" <test@unknown_domain.com>')
IrMailServer.send_email(message)
self.assertEqual(len(self.emails), 1)
self.assert_email_sent_smtp(
smtp_from=default_bounce_adress,
message_from='"Name" <notifications@test.mycompany.com>',
from_filter='test.mycompany.com',
)
self.connect_mocked.assert_called_once()
self.assertEqual(len(self.emails), 1)
self.assertSMTPEmailsSent(
smtp_from=self.default_bounce_address,
message_from='"Name" <notifications@test.mycompany.com>',
from_filter='test.mycompany.com',
)
@mute_logger('odoo.models.unlink')
def test_mail_server_send_email_IDNA(self):
# Test that the mail from / recipient envelop are encoded using IDNA
self.env['ir.config_parameter'].sudo().set_param('mail.catchall.domain', 'ééééééé.com')
with self.mock_smtplib_connection():
message = self._build_email(mail_from='test@ééééééé.com')
IrMailServer.send_email(message)
self.env['ir.mail_server'].send_email(message)
self.assertEqual(len(self.emails), 1)
self.assert_email_sent_smtp(
smtp_from='bounce.test@xn--9caaaaaaa.com',
self.assertSMTPEmailsSent(
smtp_from=f'{self.alias_bounce}@xn--9caaaaaaa.com',
smtp_to_list=['dest@xn--example--i1a.com'],
message_from='test@=?utf-8?b?w6nDqcOpw6nDqcOpw6k=?=.com',
from_filter=False,
)
@mute_logger('odoo.models.unlink')
def test_mail_server_send_email_default_from(self):
# Test the case when the "mail.default.from" contains a full email address and not just the local part
# the domain of this default email address can be different than the catchall domain
self.env['ir.config_parameter'].sudo().set_param('mail.default.from', 'test@custom_domain.com')
self.server_default.from_filter = 'custom_domain.com'
_custom_server = self.env['ir.mail_server'].create({
'from_filter': 'custom_domain.com',
'name': 'Custom Domain Server',
'smtp_host': 'smtp_host',
'smtp_encryption': 'none',
})
with self.mock_smtplib_connection():
message = self._build_email(mail_from='"Name" <test@unknown_domain.com>')
IrMailServer.send_email(message)
for mail_from, (expected_smtp_from, expected_msg_from, expected_from_filter) in zip(
[
'"Name" <test@unknown_domain.com>',
], [
('test@custom_domain.com', '"Name" <test@custom_domain.com>', 'custom_domain.com'),
],
):
with self.subTest(mail_from=mail_from):
with self.mock_smtplib_connection():
message = self._build_email(mail_from=mail_from)
self.env['ir.mail_server'].send_email(message)
self.assert_email_sent_smtp(
smtp_from='test@custom_domain.com',
smtp_to_list=['dest@xn--example--i1a.com'],
message_from='"Name" <test@custom_domain.com>',
from_filter='custom_domain.com',
)
# 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.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.mycompany.com',
message_from='specific_user@test.mycompany.com',
from_filter='test.mycompany.com',
)
@mute_logger('odoo.models.unlink')
def test_mail_server_send_email_smtp_session(self):
"""Test all the cases when we provide the SMTP session.
The results must be the same as passing directly the parameter to "send_email".
"""
IrMailServer = self.env['ir.mail_server']
default_bounce_adress = self.env['ir.mail_server']._get_default_bounce_address()
# A mail server is configured for the email
with self.mock_smtplib_connection():
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.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,
# so it will use the notifications email instead and encapsulate the old email
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>')
IrMailServer.send_email(message, smtp_session=smtp_session)
self.connect_mocked.assert_called_once()
self.assert_email_sent_smtp(
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.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.mycompany.com',
from_filter='test.mycompany.com',
)
# remove the notification 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.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>')
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='"Name" <notifications@test.mycompany.com>',
from_filter='test.mycompany.com',
)
self.assertSMTPEmailsSent(
smtp_from=expected_smtp_from,
smtp_to_list=['dest@xn--example--i1a.com'],
message_from=expected_msg_from,
from_filter=expected_from_filter,
)
@mute_logger('odoo.models.unlink')
@patch.dict(config.options, {"from_filter": "test.mycompany.com", "smtp_server": "example.com"})
def test_mail_server_binary_arguments_domain(self):
def test_mail_server_config_bin(self):
"""Test the configuration provided in the odoo-bin arguments.
This config is used when no mail server exists.
"""
IrMailServer = self.env['ir.mail_server']
default_bounce_adress = self.env['ir.mail_server']._get_default_bounce_address()
# Remove all mail server so we will use the odoo-bin arguments
self.env['ir.mail_server'].search([]).unlink()
self.assertFalse(self.env['ir.mail_server'].search([]))
# Use an email in the domain of the "from_filter"
for email_from, (expected_smtp_from, expected_msg_from) in zip(
[
# inside "from_filter" domain
'specific_user@test.mycompany.com',
# outside "from_filter" domain
'test@unknown_domain.com',
], [
(self.default_bounce_address, 'specific_user@test.mycompany.com'),
# outside "from_filter" domain: we will use notifications emails in the
# headers, and bounce address in the envelope because the "from_filter"
# allows to use the entire domain
(self.default_bounce_address, '"test" <notifications@test.mycompany.com>'),
]
):
with self.subTest(email_from=email_from):
with self.mock_smtplib_connection():
message = self._build_email(mail_from=email_from)
IrMailServer.send_email(message)
self.connect_mocked.assert_called_once()
self.assertSMTPEmailsSent(
smtp_from=expected_smtp_from,
message_from=expected_msg_from,
from_filter='test.mycompany.com',
)
@mute_logger('odoo.models.unlink')
@patch.dict(config.options, {'from_filter': 'test.mycompany.com', 'smtp_server': 'example.com'})
def test_mail_server_config_bin_default_from_filter(self):
"""Test that the config parameter "mail.default.from_filter" overwrites
the odoo-bin argument "--from-filter" """
self.env['ir.config_parameter'].sudo().set_param('mail.default.from_filter', 'example.com')
IrMailServer = self.env['ir.mail_server']
# Remove all mail server so we will use the odoo-bin arguments
IrMailServer.search([]).unlink()
self.assertFalse(IrMailServer.search([]))
# Use an email in the domain of the config parameter "mail.default.from_filter"
with self.mock_smtplib_connection():
message = self._build_email(mail_from='specific_user@test.mycompany.com')
message = self._build_email(mail_from='specific_user@example.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.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.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.mycompany.com',
from_filter='test.mycompany.com',
)
# Use an email outside of the domain of the "from_filter"
# So we will use the notifications email in the headers and the bounce address
# in the envelop because the "from_filter" allows to use the entire domain
with self.mock_smtplib_connection():
message = self._build_email(mail_from='test@unknown_domain.com')
IrMailServer.send_email(message)
self.connect_mocked.assert_called_once()
self.assert_email_sent_smtp(
smtp_from=default_bounce_adress,
message_from='"test" <notifications@test.mycompany.com>',
from_filter='test.mycompany.com',
self.assertSMTPEmailsSent(
smtp_from='specific_user@example.com',
message_from='specific_user@example.com',
from_filter='example.com',
)
@mute_logger('odoo.models.unlink')
@@ -493,77 +453,79 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
"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):
def test_mail_server_config_bin_smtp_session(self):
"""Test the configuration provided in the odoo-bin arguments.
This config is used when no mail server exists.
Use a pre-configured SMTP session.
"""
IrMailServer = self.env['ir.mail_server']
default_bounce_adress = self.env['ir.mail_server']._get_default_bounce_address()
# Remove all mail server so we will use the odoo-bin arguments
self.env['ir.mail_server'].search([]).unlink()
self.assertFalse(self.env['ir.mail_server'].search([]))
# Use an email in the domain of the "from_filter"
with self.mock_smtplib_connection():
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)
for email_from, (expected_smtp_from, expected_msg_from) in zip(
[
# use an email in the domain of the "from_filter"
'specific_user@test.mycompany.com',
# Use an email outside of the domain of the "from_filter"
'test@unknown_domain.com',
], [
(self.default_bounce_address, 'specific_user@test.mycompany.com'),
# Use an email outside of the domain of the "from_filter"
# So we will use the notifications email in the headers and the bounce address
# in the envelop because the "from_filter" allows to use the entire domain
(self.default_bounce_address, '"test" <notifications@test.mycompany.com>')
]
):
with self.subTest(email_from=email_from):
with self.mock_smtplib_connection():
smtp_session = IrMailServer.connect(smtp_from=email_from)
message = self._build_email(mail_from=email_from)
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.mycompany.com',
from_filter='dummy@example.com, test.mycompany.com, dummy2@example.com',
)
self.connect_mocked.assert_called_once()
self.assertSMTPEmailsSent(
smtp_from=expected_smtp_from,
message_from=expected_msg_from,
from_filter='dummy@example.com, test.mycompany.com, dummy2@example.com',
)
# Use an email outside of the domain of the "from_filter"
# So we will use the notifications email in the headers and the bounce address
# in the envelop because the "from_filter" allows to use the entire domain
with self.mock_smtplib_connection():
smtp_session = IrMailServer.connect(smtp_from='test@unknown_domain.com')
message = self._build_email(mail_from='test@unknown_domain.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='"test" <notifications@test.mycompany.com>',
from_filter='dummy@example.com, test.mycompany.com, dummy2@example.com',
)
def test_mail_server_get_email_addresses(self):
def test_mail_server_get_test_email_addresses(self):
"""Test the email used to test the mail server connection."""
self.server_notification.from_filter = 'example_2.com, example_3.com'
test_server = self.env['ir.mail_server'].create({
'from_filter': 'example_2.com, example_3.com',
'name': 'Test Server',
'smtp_host': 'smtp_host',
'smtp_encryption': 'none',
})
self.env['ir.config_parameter'].set_param('mail.default.from', 'notifications@example.com')
email_from = self.server_notification._get_test_email_addresses()[0]
self.assertEqual(email_from, 'noreply@example_2.com')
self.env['ir.config_parameter'].set_param('mail.default.from', 'notifications')
email_from = self.server_notification._get_test_email_addresses()[0]
self.assertEqual(email_from, 'notifications@example_2.com')
self.server_notification.from_filter = 'dummy.com, full_email@example_2.com, dummy2.com'
self.env['ir.config_parameter'].set_param('mail.default.from', 'notifications')
email_from = self.server_notification._get_test_email_addresses()[0]
self.assertEqual(email_from, 'full_email@example_2.com')
self.env['ir.config_parameter'].set_param('mail.default.from', 'notifications@example.com')
email_from = self.server_notification._get_test_email_addresses()[0]
self.assertEqual(email_from, 'full_email@example_2.com')
self.env['ir.config_parameter'].set_param('mail.default.from', 'notifications@example.com')
self.server_notification.from_filter = 'example.com'
email_from = self.server_notification._get_test_email_addresses()[0]
self.assertEqual(email_from, 'notifications@example.com')
# check default.from / filter matching
for (default_from, from_filter), expected_test_email in zip(
[
('notifications@example.com', 'example_2.com, example_3.com'),
('notifications', 'example_2.com, example_3.com'),
('notifications@example.com', 'dummy.com, full_email@example_2.com, dummy2.com'),
('notifications', 'dummy.com, full_email@example_2.com, dummy2.com'),
('notifications@example.com', 'example.com'),
], [
'noreply@example_2.com',
'notifications@example_2.com',
'full_email@example_2.com',
'full_email@example_2.com',
'notifications@example.com',
],
):
with self.subTest(default_from=default_from, from_filter=from_filter):
self.env['ir.config_parameter'].set_param('mail.default.from', default_from)
test_server.from_filter = from_filter
email_from = test_server._get_test_email_addresses()[0]
self.assertEqual(email_from, expected_test_email)
@mute_logger('odoo.models.unlink')
@patch.dict(config.options, {'from_filter': 'fake.com', 'smtp_server': 'cli_example.com'})
def test_mail_server_configuration_cli(self):
def test_mail_server_config_cli(self):
"""Check the mail server when the "smtp_authentication" is "cli".
Should take the configuration from the odoo-bin argument.
@@ -589,7 +551,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
message = self._build_email(mail_from='test@cli_example.com')
IrMailServer.send_email(message)
self.assert_email_sent_smtp(
self.assertSMTPEmailsSent(
smtp_from='test@cli_example.com',
message_from='test@cli_example.com',
from_filter='dummy@example.com, cli_example.com, dummy2@example.com',
@@ -600,33 +562,8 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
message = self._build_email(mail_from='specific_user@test.mycompany.com')
IrMailServer.send_email(message)
self.assert_email_sent_smtp(
self.assertSMTPEmailsSent(
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.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"
"""
self.env['ir.config_parameter'].sudo().set_param('mail.default.from_filter', 'example.com')
IrMailServer = self.env['ir.mail_server']
# Remove all mail server so we will use the odoo-bin arguments
IrMailServer.search([]).unlink()
self.assertFalse(IrMailServer.search([]))
# Use an email in the domain of the config parameter "mail.default.from_filter"
with self.mock_smtplib_connection():
message = self._build_email(mail_from='specific_user@example.com')
IrMailServer.send_email(message)
self.assert_email_sent_smtp(
smtp_from='specific_user@example.com',
message_from='specific_user@example.com',
from_filter='example.com',
)