[IMP] test_mail: improve multi-company / server config tests

Composer tests now use the multi-company enabled model by default, allowing to
test company-dependent behavior. This has no impact on current tests, as there
is no company-dependent fields on composer model, and all tests are anyway
run into the main company (except multi-company specific tests, suffixed
by '_mc' generally).

We therefore also add some multi-company oriented tests to check notably
return-path or environment companies in various scenarios. Go until the SMTP
generation to test mail server choice, smtp_from and filtering, notifications
email.

Followup of odoo/odoo#136318 and odoo/odoo@3ffa1a0611 notably.

Task-36879 (Mail: Support Multi Domains Aliases)

Part-of: odoo/odoo#76734
This commit is contained in:
Thibault Delavallée
2023-10-24 19:24:50 +00:00
parent c53aae845d
commit f153e52e30
5 changed files with 308 additions and 27 deletions
+1 -1
View File
@@ -104,7 +104,7 @@ 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)
cls.env['ir.config_parameter'].sudo().set_param('mail.default.from_filter', cls.default_from_filter)
cls.env['ir.config_parameter'].set_param('mail.default.from_filter', cls.default_from_filter)
# mailer daemon email preformatting
cls.mailer_daemon_email = formataddr(('MAILER-DAEMON', f'{cls.alias_bounce}@{cls.alias_domain}'))
+10 -8
View File
@@ -91,8 +91,8 @@ class TestIrMailServer(MailCommon):
# 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@test.mycompany.com>'),
(self.default_bounce_address, '"Formatted Name" <notifications.test@test.mycompany.com>'),
(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
@@ -146,7 +146,8 @@ class TestIrMailServer(MailCommon):
('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'),
(f'notifications@{self.alias_domain}', f'{self.alias_domain}'),
(f'notifications@{self.alias_domain}', f'{self.alias_domain}, example_2.com'),
# default relies on "odoo"
(False, 'example.com'),
# fallback on user email if no from_filter
@@ -159,7 +160,8 @@ class TestIrMailServer(MailCommon):
'notifications@example_2.com',
'full_email@example_2.com',
'full_email@example_2.com',
'notifications@example.com',
f'notifications@{self.alias_domain}',
f'notifications@{self.alias_domain}',
'odoo@example.com',
self.env.user.email,
self.env.user.email,
@@ -219,7 +221,7 @@ class TestIrMailServer(MailCommon):
(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, 'notifications.test@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>'),
@@ -247,9 +249,9 @@ class TestIrMailServer(MailCommon):
('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
('notifications.test@test.mycompany.com', '"Name" <notifications.test@test.mycompany.com>', self.mail_server_notification),
(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
('notifications.test@test.mycompany.com', '"test" <notifications.test@test.mycompany.com>', self.mail_server_notification),
(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),
@@ -294,7 +296,7 @@ class TestIrMailServer(MailCommon):
self.assertEqual(len(self.emails), 1)
self.assertSMTPEmailsSent(
smtp_from=self.default_bounce_address,
message_from='"Name" <notifications.test@test.mycompany.com>',
message_from=f'"Name" <{self.default_from}@{self.alias_domain}>',
mail_server=self.mail_server_domain,
)
+291 -10
View File
@@ -56,32 +56,32 @@ class TestMailComposer(MailCommon, TestRecipients):
)
cls.env.ref('mail.group_mail_template_editor').users -= cls.user_rendering_restricted
cls.test_record = cls.env['mail.test.ticket'].with_context(cls._test_context).create({
cls.test_record = cls.env['mail.test.ticket.mc'].with_context(cls._test_context).create({
'name': 'TestRecord',
'customer_id': cls.partner_1.id,
'user_id': cls.user_employee_2.id,
})
cls.test_records, cls.test_partners = cls._create_records_for_batch(
'mail.test.ticket', 2,
'mail.test.ticket.mc', 2,
additional_values={'user_id': cls.user_employee_2.id},
)
cls.test_report, cls.test_report_2, cls.test_report_3 = cls.env['ir.actions.report'].create([
{
'name': 'Test Report on Mail Test Ticket',
'model': 'mail.test.ticket',
'model': 'mail.test.ticket.mc',
'print_report_name': "'TestReport for %s' % object.name",
'report_type': 'qweb-pdf',
'report_name': 'test_mail.mail_test_ticket_test_template',
}, {
'name': 'Test Report 2 on Mail Test Ticket',
'model': 'mail.test.ticket',
'model': 'mail.test.ticket.mc',
'print_report_name': "'TestReport2 for %s' % object.name",
'report_type': 'qweb-pdf',
'report_name': 'test_mail.mail_test_ticket_test_template_2',
}, {
'name': 'Test Report 3 with variable data on Mail Test Ticket',
'model': 'mail.test.ticket',
'model': 'mail.test.ticket.mc',
'print_report_name': "'TestReport3 for %s' % object.name",
'report_type': 'qweb-pdf',
'report_name': 'test_mail.mail_test_ticket_test_variable_template',
@@ -100,7 +100,7 @@ class TestMailComposer(MailCommon, TestRecipients):
'email_from': '{{ (object.user_id.email_formatted or user.email_formatted) }}',
'lang': '{{ object.customer_id.lang }}',
'mail_server_id': cls.mail_server_domain.id,
'model_id': cls.env['ir.model']._get('mail.test.ticket').id,
'model_id': cls.env['ir.model']._get('mail.test.ticket.mc').id,
'reply_to': '{{ ctx.get("custom_reply_to") or "info@test.example.com" }}',
'scheduled_date': '{{ (object.create_date or datetime.datetime(2022, 12, 26, 18, 0, 0)) + datetime.timedelta(days=2) }}',
})
@@ -332,7 +332,7 @@ class TestComposerForm(TestMailComposer):
when nothing is given as context. """
composer_form = Form(self.env['mail.compose.message'].with_context(
default_composition_mode='comment',
default_model='mail.test.ticket',
default_model='mail.test.ticket.mc',
default_template_id=self.template.id,
))
self.assertTrue(composer_form.auto_delete, 'Should take composer value')
@@ -454,7 +454,7 @@ class TestComposerForm(TestMailComposer):
when nothing is given as context. """
composer_form = Form(self.env['mail.compose.message'].with_context(
default_composition_mode='mass_mail',
default_model='mail.test.ticket',
default_model='mail.test.ticket.mc',
default_template_id=self.template.id,
))
self.assertTrue(composer_form.auto_delete, 'Should take composer value')
@@ -1267,6 +1267,7 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase):
self.assertEqual(self.test_records.message_partner_ids, self.partner_employee_2)
self.assertEqual(self.test_records[0].customer_id.lang, 'en_US')
self.assertEqual(self.test_records[1].customer_id.lang, 'en_US')
self.assertEqual(self.test_records.company_id, self.company_admin)
self.assertEqual(len(self.test_partners), 2)
@@ -1426,6 +1427,88 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase):
self.assertEqual(message.subtype_id, self.env.ref('mail.mt_comment'))
self.assertEqual(message.partner_ids, self.partner_1 | self.partner_2)
@users('employee')
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_mail_composer_server_config(self):
""" Test various configuration to check behavior of outgoing mail
servers, notifications, .... """
# add access to second company to avoid MC rules on ticket model
self.env.user.company_ids = [(4, self.company_2.id)]
# initial data
self.assertEqual(self.env.company, self.company_admin)
self.assertEqual(self.user_admin.company_id, self.company_admin)
# update test configuration
test_records = self.test_records
test_companies = self.company_admin + self.company_2
for company, record in zip(test_companies, test_records):
record.company_id = company.id
# various from / servers configuration
self.template.write({'mail_server_id': False}) # allow server archive
server_other = self.env['ir.mail_server'].sudo().create({
'name': 'Server Other',
'from_filter': 'test.othercompany.com',
'sequence': 4,
'smtp_encryption': 'none',
'smtp_host': 'smtp_host',
})
servers_all = self.mail_servers + server_other
for (emails_from, servers_active, mail_config), (exp_smtp_from_lst, exp_msg_from_lst, exp_from_filter) in zip(
[
(
[f'user.from@{self.alias_domain}', 'user@other.domain.com'],
self.env['ir.mail_server'],
{'default_from': f'notifications@{self.alias_domain}', 'from_filter': self.alias_domain}
), # odoo-style configuration
], [
(
[f'{self.alias_bounce}@{self.alias_domain}', f'{self.alias_bounce}@{self.alias_domain}'],
[f'"{self.env.user.name}" <user.from@{self.alias_domain}>', f'"{self.env.user.name}" <notifications@{self.alias_domain}>'],
self.alias_domain
), # no spoof
],
):
with self.subTest(emails_from=emails_from,
servers_active=servers_active):
# update servers
servers_all.active = False
if servers_active:
servers_active.active = True
# update mail config
default_from = mail_config.get('default_from', self.default_from)
from_filter = mail_config.get('from_filter', self.default_from_filter)
self.env['ir.config_parameter'].sudo().set_param('mail.default.from', default_from)
self.env['ir.config_parameter'].sudo().set_param('mail.default.from_filter', from_filter)
for email_from, exp_smtp_from, exp_msg_from in zip(emails_from, exp_smtp_from_lst, exp_msg_from_lst):
self.env.user.email = email_from
# open a composer and run it in comment mode
composer = Form(self.env['mail.compose.message'].with_context(
default_composition_mode='comment',
default_force_send=True, # force sending emails directly to check SMTP
default_model=test_records._name,
default_res_ids=test_records.ids,
# avoid successive tests issues with followers
mail_create_nosubscribe=True,
))
composer.body = 'Hello {{ object.name }}'
composer.subject = 'My Subject'
composer = composer.save()
with self.mock_mail_gateway(mail_unlink_sent=False), \
self.mock_mail_app():
composer._action_send_mail()
self.assertSMTPEmailsSent(
smtp_from=exp_smtp_from,
smtp_to_list=[self.partner_employee_2.email_normalized],
emails_count=2, # same on both records
message_from=exp_msg_from,
from_filter=exp_from_filter,
)
@users('employee')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_message_schedule')
def test_mail_composer_wtpl_complete(self):
@@ -1761,6 +1844,105 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase):
self.assertEqual(set(message.attachment_ids.mapped('res_id')), set(test_record.ids))
self.assertTrue(all(attach not in message.attachment_ids for attach in attachs), 'Should have copied attachments')
@users('employee')
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_mail_composer_wtpl_mc(self):
""" Test specific to multi-company environment, notably company propagation
or aliases. """
# add access to second company to avoid MC rules on ticket model
self.env.user.company_ids = [(4, self.company_2.id)]
# initial data
self.assertEqual(self.env.company, self.company_admin)
self.assertEqual(self.user_admin.company_id, self.company_admin)
attachment_data = self._generate_attachments_data(2, self.template._name, self.template.id)
email_to_1 = 'test.to.1@test.example.com'
self.template.write({
'auto_delete': False, # keep sent emails to check content
'attachment_ids': [(0, 0, a) for a in attachment_data],
'email_from': False, # use current user as author
'email_layout_xmlid': 'mail.test_layout',
'email_to': email_to_1,
'mail_server_id': False, # let it find a server
'partner_to': '%s, {{ object.customer_id.id if object.customer_id else "" }}' % self.partner_admin.id,
})
attachs = self.env['ir.attachment'].search([('name', 'in', [a['name'] for a in attachment_data])])
self.assertEqual(len(attachs), 2)
for batch, companies, expected_companies in [
(
False, self.company_admin, self.company_admin
), (
True, self.company_admin + self.company_2, self.company_admin + self.company_2,
),
]:
with self.subTest(batch=batch,
companies=companies):
# update test configuration
test_records = self.test_records if batch else self.test_record
for company, record in zip(companies, test_records):
record.company_id = company.id
# open a composer and run it in comment mode
composer = Form(self.env['mail.compose.message'].with_context(
default_composition_mode='comment',
default_force_send=True, # force sending emails directly to check SMTP
default_model=test_records._name,
default_res_ids=test_records.ids,
default_template_id=self.template.id,
# avoid successive tests issues with followers
mail_create_nosubscribe=True,
)).save()
with self.mock_mail_gateway(mail_unlink_sent=False), \
self.mock_mail_app():
composer._action_send_mail()
new_partner = self.env['res.partner'].search([('email_normalized', '=', 'test.to.1@test.example.com')])
self.assertEqual(len(new_partner), 1)
# check output, company-specific values mainly for this test
for record, exp_company in zip(
test_records, expected_companies
):
message = record.message_ids[0]
for recipient in [self.partner_employee_2, new_partner, record.customer_id]:
self.assertMailMail(
recipient,
'sent',
author=self.partner_employee,
mail_message=message,
email_values={
'headers': {
'Return-Path': f'{self.alias_bounce}@{self.alias_domain}',
'X-Odoo-Objects': f'{record._name}-{record.id}',
},
'subject': f'TemplateSubject {record.name}',
},
fields_values={
'headers': {
'X-Odoo-Objects': f'{record._name}-{record.id}',
},
'mail_server_id': self.env['ir.mail_server'],
'subject': f'TemplateSubject {record.name}',
},
)
# to check behavior of extract_rfc2822_addresses
if recipient == new_partner:
smtp_to_list = ['"test.to.1@test.example.com"', 'test.to.1@test.example.com']
else:
smtp_to_list = [recipient.email_normalized]
if recipient != record.customer_id:
emails_count = len(test_records) # not distinguishable using this assert
else:
emails_count = 1
self.assertSMTPEmailsSent(
smtp_from=f'{self.default_from}@{self.alias_domain}',
smtp_to_list=smtp_to_list,
mail_server=self.mail_server_notification,
emails_count=emails_count,
)
@users('employee')
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_mail_composer_wtpl_recipients_email_fields(self):
@@ -2462,6 +2644,105 @@ class TestComposerResultsMass(TestMailComposer):
# self.assertIn(exp_button_es, sent_mail['body'])
self.assertIn(exp_button_en, sent_mail['body'])
@users('employee')
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_mail_composer_wtpl_mc(self):
""" Test specific to multi-company environment, notably company propagation
or aliases. """
# add access to second company to avoid MC rules on ticket model
self.env.user.company_ids = [(4, self.company_2.id)]
# initial data
self.assertEqual(self.env.company, self.company_admin)
self.assertEqual(self.user_admin.company_id, self.company_admin)
attachment_data = self._generate_attachments_data(2, self.template._name, self.template.id)
email_to_1 = 'test.to.1@test.example.com'
self.template.write({
'auto_delete': False, # keep sent emails to check content
'attachment_ids': [(0, 0, a) for a in attachment_data],
'email_from': False, # use current user as author
'email_layout_xmlid': 'mail.test_layout',
'email_to': email_to_1,
'mail_server_id': False, # let it find a server
'partner_to': '%s, {{ object.customer_id.id if object.customer_id else "" }}' % self.partner_admin.id,
})
attachs = self.env['ir.attachment'].search([('name', 'in', [a['name'] for a in attachment_data])])
self.assertEqual(len(attachs), 2)
for companies, expected_companies in [
(
self.company_admin + self.company_2,
self.company_admin + self.company_2,
),
]:
with self.subTest(companies=companies):
# update test configuration
test_records = self.test_records
for company, record in zip(companies, test_records):
record.company_id = company.id
# open a composer and run it in comment mode
composer = Form(self.env['mail.compose.message'].with_context(
default_composition_mode='mass_mail',
default_force_send=True, # force sending emails directly to check SMTP
default_model=test_records._name,
default_res_ids=test_records.ids,
default_template_id=self.template.id,
# avoid successive tests issues with followers
mail_create_nosubscribe=True,
)).save()
with self.mock_mail_gateway(mail_unlink_sent=False), \
self.mock_mail_app():
composer._action_send_mail()
new_partner = self.env['res.partner'].search([('email_normalized', '=', 'test.to.1@test.example.com')])
self.assertEqual(len(new_partner), 1)
# check output, company-specific values mainly for this test
for record, exp_company in zip(
test_records, expected_companies
):
# message copy is kept
message = record.message_ids[0]
recipients = record.customer_id + new_partner + self.partner_admin
self.assertMailMail(
record.customer_id + new_partner + self.partner_admin,
'sent',
author=self.partner_employee,
mail_message=message,
email_values={
'headers': {
'Return-Path': f'{self.alias_bounce}@{self.alias_domain}',
'X-Odoo-Objects': f'{record._name}-{record.id}',
},
'subject': f'TemplateSubject {record.name}',
},
fields_values={
'headers': {
'X-Odoo-Objects': f'{record._name}-{record.id}',
},
'mail_server_id': self.env['ir.mail_server'],
'subject': f'TemplateSubject {record.name}',
},
)
for recipient in recipients:
# to check behavior of extract_rfc2822_addresses
if recipient == new_partner:
smtp_to_list = ['"test.to.1@test.example.com"', 'test.to.1@test.example.com']
else:
smtp_to_list = [recipient.email_normalized]
if recipient != record.customer_id:
emails_count = len(test_records) # not distinguishable using this assert
else:
emails_count = 1
self.assertSMTPEmailsSent(
smtp_from=f'{self.default_from}@{self.alias_domain}',
smtp_to_list=smtp_to_list,
mail_server=self.mail_server_notification,
emails_count=emails_count,
)
@users('employee')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_mail_composer_wtpl_recipients(self):
@@ -2581,7 +2862,7 @@ class TestComposerResultsMass(TestMailComposer):
# 6: void is void: raise in comment mode, just don't send anything in mass mail mode
composer_form = Form(self.env['mail.compose.message'].with_context(
default_model='mail.test.ticket',
default_model='mail.test.ticket.mc',
default_template_id=self.template.id
))
composer = composer_form.save()
@@ -2592,7 +2873,7 @@ class TestComposerResultsMass(TestMailComposer):
composer_form = Form(self.env['mail.compose.message'].with_context(
default_composition_mode='mass_mail',
default_model='mail.test.ticket',
default_model='mail.test.ticket.mc',
default_template_id=self.template.id
))
composer = composer_form.save()
+1 -3
View File
@@ -128,9 +128,7 @@ class TestEmailParsing(MailCommon):
email_to='bounce@xxx.odoo.com',
delivered_to='bounce@xxx.odoo.com'
)
msg_dict = {}
msg = self.env['mail.thread']._message_parse_extract_bounce(self.from_string(incoming_bounce), msg_dict)
msg = self.env['mail.thread'].message_parse(self.from_string(incoming_bounce))
self.assertEqual(msg['bounced_email'], partner.email, "The sender email should be correctly parsed")
self.assertEqual(msg['bounced_partner'], partner, "A partner with this email should exist")
self.assertEqual(msg['bounced_msg_ids'][0], message.message_id, "The sender message-id should correctly parsed")
@@ -112,8 +112,8 @@ class TestMassMailingServer(TestMassMailCommon):
('unknow_email@unknow_domain.com', 'unknow_email@unknow_domain.com', self.mail_server_notification),
]:
self.assertSMTPEmailsSent(
smtp_from=expected_smtp_from,
message_from=expected_msg_from,
mail_server=expected_mail_server,
emails_count=8,
)
smtp_from=expected_smtp_from,
message_from=expected_msg_from,
mail_server=expected_mail_server,
emails_count=8,
)