311 lines
16 KiB
Python
311 lines
16 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||
|
|
||
|
from unittest.mock import patch
|
||
|
|
||
|
from odoo.addons.mail.tests.common import MailCommon
|
||
|
from odoo.tests import tagged, users
|
||
|
from odoo.tools import config, mute_logger
|
||
|
|
||
|
|
||
|
@tagged('mail_server')
|
||
|
class TestIrMailServer(MailCommon):
|
||
|
|
||
|
@classmethod
|
||
|
def setUpClass(cls):
|
||
|
super().setUpClass()
|
||
|
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)
|
||
|
|
||
|
@patch.dict(config.options, {"email_from": "settings@example.com"})
|
||
|
def test_default_email_from(self, *args):
|
||
|
""" Check that default_from parameter of alias domain respected. """
|
||
|
for (default_from, domain_name), expected_from in zip(
|
||
|
[
|
||
|
('icp', 'test.mycompany.com'),
|
||
|
(False, 'test.mycompany.com'),
|
||
|
(False, False),
|
||
|
], [
|
||
|
"icp@test.mycompany.com",
|
||
|
"settings@example.com",
|
||
|
"settings@example.com",
|
||
|
],
|
||
|
):
|
||
|
with self.subTest(default_from=default_from, domain_name=domain_name):
|
||
|
if domain_name:
|
||
|
self.mail_alias_domain.name = domain_name
|
||
|
self.mail_alias_domain.default_from = default_from
|
||
|
self.env.company.alias_domain_id = self.mail_alias_domain
|
||
|
else:
|
||
|
self.env.company.alias_domain_id = False
|
||
|
message = self.env["ir.mail_server"].build_email(
|
||
|
False, "recipient@example.com", "Subject",
|
||
|
"The body of an email",
|
||
|
)
|
||
|
self.assertEqual(message["From"], expected_from)
|
||
|
|
||
|
@mute_logger('odoo.models.unlink')
|
||
|
@patch.dict(config.options, {
|
||
|
"from_filter": "dummy@example.com, test.mycompany.com, dummy2@example.com",
|
||
|
"smtp_server": "example.com",
|
||
|
})
|
||
|
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. Test with and without giving a
|
||
|
pre-configured SMTP session, should not impact results.
|
||
|
|
||
|
Also check "mail.default.from_filter" parameter usage that should overwrite
|
||
|
odoo-bin argument "--from-filter".
|
||
|
"""
|
||
|
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([]))
|
||
|
|
||
|
for mail_from, (expected_smtp_from, expected_msg_from) in zip(
|
||
|
[
|
||
|
# inside "from_filter" domain
|
||
|
'specific_user@test.mycompany.com',
|
||
|
'"Formatted Name" <specific_user@test.mycompany.com>',
|
||
|
'"Formatted Name" <specific_user@test.MYCOMPANY.com>',
|
||
|
'"Formatted Name" <SPECIFIC_USER@test.mycompany.com>',
|
||
|
# outside "from_filter" domain
|
||
|
'test@unknown_domain.com',
|
||
|
'"Formatted Name" <test@unknown_domain.com>',
|
||
|
], [
|
||
|
# inside "from_filter" domain: no rewriting
|
||
|
(self.default_bounce_address, 'specific_user@test.mycompany.com'),
|
||
|
(self.default_bounce_address, '"Formatted Name" <specific_user@test.mycompany.com>'),
|
||
|
(self.default_bounce_address, '"Formatted Name" <specific_user@test.MYCOMPANY.com>'),
|
||
|
(self.default_bounce_address, '"Formatted Name" <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, f'"test" <{self.default_from}@{self.alias_domain}>'),
|
||
|
(self.default_bounce_address, f'"Formatted Name" <{self.default_from}@{self.alias_domain}>'),
|
||
|
]
|
||
|
):
|
||
|
for provide_smtp in [False, True]: # providing smtp session should ont impact test
|
||
|
with self.subTest(mail_from=mail_from, provide_smtp=provide_smtp):
|
||
|
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.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="dummy@example.com, test.mycompany.com, dummy2@example.com",
|
||
|
)
|
||
|
|
||
|
# for from_filter in ICP, overwrite the one from odoo-bin
|
||
|
self.env['ir.config_parameter'].sudo().set_param('mail.default.from_filter', 'icp.example.com')
|
||
|
|
||
|
# 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@icp.example.com')
|
||
|
IrMailServer.send_email(message)
|
||
|
|
||
|
self.assertSMTPEmailsSent(
|
||
|
smtp_from='specific_user@icp.example.com',
|
||
|
message_from='specific_user@icp.example.com',
|
||
|
from_filter='icp.example.com',
|
||
|
)
|
||
|
|
||
|
@users('admin')
|
||
|
def test_mail_server_get_test_email_from(self):
|
||
|
""" Test the email used to test the mail server connection. Check
|
||
|
from_filter parsing / alias_domain.default_from support. """
|
||
|
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',
|
||
|
})
|
||
|
|
||
|
# check default.from / filter matching
|
||
|
for (default_from, from_filter), expected_test_email in zip(
|
||
|
[
|
||
|
('notifications', 'dummy.com, full_email@example_2.com, dummy2.com'),
|
||
|
('notifications', self.mail_alias_domain.name),
|
||
|
('notifications', f'{self.mail_alias_domain.name}, example_2.com'),
|
||
|
# default relies on "odoo"
|
||
|
(False, self.mail_alias_domain.name),
|
||
|
# fallback on user email if no from_filter
|
||
|
('notifications', ' '),
|
||
|
('notifications', ','),
|
||
|
('notifications', False),
|
||
|
(False, False),
|
||
|
], [
|
||
|
'full_email@example_2.com',
|
||
|
f'notifications@{self.mail_alias_domain.name}',
|
||
|
f'notifications@{self.mail_alias_domain.name}',
|
||
|
f'odoo@{self.mail_alias_domain.name}',
|
||
|
self.env.user.email,
|
||
|
self.env.user.email,
|
||
|
self.env.user.email,
|
||
|
self.env.user.email,
|
||
|
],
|
||
|
):
|
||
|
with self.subTest(default_from=default_from, from_filter=from_filter):
|
||
|
self.mail_alias_domain.default_from = default_from
|
||
|
test_server.from_filter = from_filter
|
||
|
email_from = test_server._get_test_email_from()
|
||
|
self.assertEqual(email_from, expected_test_email)
|
||
|
|
||
|
@mute_logger('odoo.models.unlink')
|
||
|
def test_mail_server_priorities(self):
|
||
|
""" Test if we choose the right mail server to send an email.
|
||
|
Priorities are
|
||
|
1. Forced mail server (e.g.: in mass mailing)
|
||
|
- If the "from_filter" of the mail server match the notification email
|
||
|
use the notifications email in the "From header"
|
||
|
- Otherwise spoof the "From" (because we force the mail server but we don't
|
||
|
know which email use to send it)
|
||
|
2. A mail server for which the "from_filter" match the "From" header
|
||
|
3. A mail server for which the "from_filter" match the domain of the "From" header
|
||
|
4. The mail server used for notifications
|
||
|
5. A mail server without "from_filter" (and so spoof the "From" header because we
|
||
|
do not know for which email address it can be used)
|
||
|
"""
|
||
|
# this mail server can now be used for a specific email address and 2 domain names
|
||
|
self.mail_server_user.from_filter = "domain1.com, specific_user@test.mycompany.com, domain2.com"
|
||
|
|
||
|
for email_from, (expected_mail_server, expected_email_from) in zip(
|
||
|
[
|
||
|
# matches user-specific server
|
||
|
'specific_user@test.mycompany.com',
|
||
|
# matches user-specific server (with formatting') -> should extract
|
||
|
# email from full name, must keep the given email_from
|
||
|
'"Name name@strange.name" <specific_user@test.mycompany.com>',
|
||
|
# case check
|
||
|
'SPECIFIC_USER@test.mycompany.com',
|
||
|
'specific_user@test.MYCOMPANY.com',
|
||
|
# matches domain-based server: domain is case insensitive
|
||
|
'unknown_email@test.mycompany.com',
|
||
|
'unknown_email@TEST.MYCOMPANY.COM',
|
||
|
'"Unknown" <unknown_email@test.mycompany.com>',
|
||
|
# fallback on notification email
|
||
|
'"Test" <test@unknown_domain.com>',
|
||
|
# mail_server_user multiple from_filter check: can be used for a
|
||
|
# specific email and 2 domain names -> check other domains in filter
|
||
|
'"Example" <test@domain2.com>',
|
||
|
'"Example" <test@domain1.com>',
|
||
|
], [
|
||
|
(self.mail_server_user, 'specific_user@test.mycompany.com'),
|
||
|
(self.mail_server_user, '"Name name@strange.name" <specific_user@test.mycompany.com>'),
|
||
|
(self.mail_server_user, 'SPECIFIC_USER@test.mycompany.com'),
|
||
|
(self.mail_server_user, 'specific_user@test.MYCOMPANY.com'),
|
||
|
(self.mail_server_domain, 'unknown_email@test.mycompany.com'),
|
||
|
(self.mail_server_domain, 'unknown_email@TEST.MYCOMPANY.COM'),
|
||
|
(self.mail_server_domain, '"Unknown" <unknown_email@test.mycompany.com>'),
|
||
|
(self.mail_server_notification, f'{self.default_from}@test.mycompany.com'),
|
||
|
# mail_server_user multiple from_filter check
|
||
|
(self.mail_server_user, '"Example" <test@domain2.com>'),
|
||
|
(self.mail_server_user, '"Example" <test@domain1.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)
|
||
|
|
||
|
@mute_logger('odoo.models.unlink', 'odoo.addons.base.models.ir_mail_server')
|
||
|
def test_mail_server_send_email(self):
|
||
|
""" Test main 'send_email' usage: check mail_server choice based on from
|
||
|
filters, encapsulation, spoofing. """
|
||
|
IrMailServer = self.env['ir.mail_server']
|
||
|
|
||
|
for mail_from, (expected_smtp_from, expected_msg_from, expected_mail_server) in zip(
|
||
|
[
|
||
|
'specific_user@test.mycompany.com',
|
||
|
'"Name" <test@unknown_domain.com>',
|
||
|
'test@unknown_domain.com',
|
||
|
'"Name" <unknown_name@test.mycompany.com>'
|
||
|
], [
|
||
|
# A mail server is configured for the email
|
||
|
('specific_user@test.mycompany.com', 'specific_user@test.mycompany.com', self.mail_server_user),
|
||
|
# No mail server are configured for the email address, so it will use the
|
||
|
# notifications email instead and encapsulate the old email
|
||
|
(f'{self.default_from}@{self.alias_domain}', f'"Name" <{self.default_from}@{self.alias_domain}>', self.mail_server_notification),
|
||
|
# same situation, but the original email has no name part
|
||
|
(f'{self.default_from}@{self.alias_domain}', f'"test" <{self.default_from}@{self.alias_domain}>', self.mail_server_notification),
|
||
|
# 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, '"Name" <unknown_name@test.mycompany.com>', self.mail_server_domain),
|
||
|
],
|
||
|
):
|
||
|
# test with and without providing an SMTP session, which should not impact test
|
||
|
for provide_smtp in [True, False]:
|
||
|
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.connect_mocked.assert_called_once()
|
||
|
self.assertEqual(len(self.emails), 1)
|
||
|
self.assertSMTPEmailsSent(
|
||
|
smtp_from=expected_smtp_from,
|
||
|
message_from=expected_msg_from,
|
||
|
mail_server=expected_mail_server,
|
||
|
)
|
||
|
|
||
|
# remove the notification server
|
||
|
# so <notifications.test@test.mycompany.com> will use the <test.mycompany.com> mail server
|
||
|
# 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"
|
||
|
self.mail_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.connect_mocked.assert_called_once()
|
||
|
self.assertEqual(len(self.emails), 1)
|
||
|
self.assertSMTPEmailsSent(
|
||
|
smtp_from=self.default_bounce_address,
|
||
|
message_from=f'"Name" <{self.default_from}@{self.alias_domain}>',
|
||
|
mail_server=self.mail_server_domain,
|
||
|
)
|
||
|
|
||
|
# miss-configured database, no mail servers from filter
|
||
|
# match the user / notification email
|
||
|
self.env['ir.mail_server'].search([]).from_filter = "random.domain"
|
||
|
self.mail_alias_domain.default_from = 'test'
|
||
|
self.mail_alias_domain.name = 'custom_domain.com'
|
||
|
with self.mock_smtplib_connection():
|
||
|
message = self._build_email(mail_from='specific_user@test.com')
|
||
|
IrMailServer.send_email(message)
|
||
|
|
||
|
self.connect_mocked.assert_called_once()
|
||
|
self.assertSMTPEmailsSent(
|
||
|
smtp_from='test@custom_domain.com',
|
||
|
message_from='"specific_user" <test@custom_domain.com>',
|
||
|
from_filter='random.domain',
|
||
|
)
|