1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
|
# -*- 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>',
# fallback when email_from is False, should default to notification email
False,
# 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'),
(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',
)
|