[FW][MERGE] (test_/mass_)mail: improve composer and mailing tests

Purpose of this merge is to

  * merge and clean mail.compose.message related tests currently located
    in two files;
  * improve those tests and add more detailed checks about generated
    mail.mail and sent emails;
  * add some docstrings and parameter explanation in mail helpers, tools
    and asserts;
  * test sent emails in mass mailing and marketing automation tests in
    addition to traces and mail.mail;

This merge targets 14 to have a coherent test suite for mail_* features
through 14+ versions.

Task ID-2500615
Prepares Task ID-2484296 (improve markers use in Jinja)
Prepares Task ID-27033 (support QWeb in templates)
Prepares Task ID-2377974 (clean trace and status management in mass mailing)

closes odoo/odoo#69052

Forward-port-of: odoo/odoo#69004
Forward-port-of: odoo/odoo#68874
Related: odoo/enterprise#17593
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2021-04-12 10:55:18 +02:00
committed by GitHub
16 changed files with 927 additions and 436 deletions
@@ -120,7 +120,8 @@ class TestMailSchedule(TestEventCommon, MockEmail):
self.assertEqual(len(self._new_mails), 2, 'event: should have 2 scheduled emails (1 / registration)')
self.assertMailMailWEmails(
[formataddr((reg1.name, reg1.email)), formataddr((reg2.name, reg2.email))],
'outgoing', '',
'outgoing',
content=None,
fields_values={'subject': 'Your registration at %s' % test_event.name,
'email_from': self.user_eventmanager.company_id.email_formatted,
})
@@ -157,7 +158,8 @@ class TestMailSchedule(TestEventCommon, MockEmail):
self.assertEqual(len(self._new_mails), 2, 'event: should have 2 scheduled emails (1 / registration)')
self.assertMailMailWEmails(
[formataddr((reg1.name, reg1.email)), formataddr((reg2.name, reg2.email))],
'outgoing', '',
'outgoing',
content=None,
fields_values={'subject': 'Your registration at %s' % test_event.name,
'email_from': self.user_eventmanager.company_id.email_formatted,
})
@@ -191,7 +193,8 @@ class TestMailSchedule(TestEventCommon, MockEmail):
self.assertEqual(len(self._new_mails), 2, 'event: should have scheduled 2 mails (1 / registration)')
self.assertMailMailWEmails(
[formataddr((reg1.name, reg1.email)), formataddr((reg2.name, reg2.email))],
'outgoing', '',
'outgoing',
content=None,
fields_values={'subject': '%s: tomorrow' % test_event.name,
'email_from': self.user_eventmanager.company_id.email_formatted,
})
@@ -265,7 +268,8 @@ class TestMailSchedule(TestEventCommon, MockEmail):
self.assertEqual(len(self._new_mails), 3, 'event: should have scheduled 3 mails, one for each registration')
self.assertMailMailWEmails(
[formataddr((reg1.name, reg1.email)), formataddr((reg2.name, reg2.email)), formataddr((reg3.name, reg3.email))],
'outgoing', '',
'outgoing',
content=None,
fields_values={'subject': '%s: today' % test_event.name,
'email_from': self.user_eventmanager.company_id.email_formatted,
})
+32 -11
View File
@@ -258,6 +258,19 @@ class MailRenderMixin(models.AbstractModel):
@api.model
def _render_template_qweb(self, template_src, model, res_ids, add_context=None):
""" Render a QWeb template.
:param str template_src: source QWeb template. It should be a string
XmlID allowing to fetch an ir.ui.view;
:param str model: see ``MailRenderMixin._render_field)``;
:param list res_ids: see ``MailRenderMixin._render_field)``;
:param dict add_context: additional context to give to renderer. It
allows to add values to base rendering context generated by
``MailRenderMixin._render_qweb_eval_context()``;
:return dict: {res_id: string of rendered template based on record}
"""
view = self.env.ref(template_src, raise_if_not_found=False) or self.env['ir.ui.view']
results = dict.fromkeys(res_ids, u"")
if not view:
@@ -370,15 +383,14 @@ class MailRenderMixin(models.AbstractModel):
@api.model
def _render_template(self, template_src, model, res_ids, engine='jinja', add_context=None, post_process=False):
""" Render the given string on records designed by model / res_ids using
the given rendering engine. Currently only jinja is supported.
the given rendering engine. Currently only jinja or qweb are supported.
:param str template_src: template text to render (jinja) or xml id of view (qweb)
this could be cleaned but hey, we are in a rush
:param str model: model name of records on which we want to perform rendering
:param list res_ids: list of ids of records (all belonging to same model)
:param string engine: jinja
:param post_process: perform rendered str / html post processing (see
``_render_template_postprocess``)
:param post_process: see ``MailRenderMixin._render_field``;
:return dict: {res_id: string of rendered template based on record}
"""
@@ -437,16 +449,25 @@ class MailRenderMixin(models.AbstractModel):
for lang, lang_res_ids in lang_to_res_ids.items()
)
def _render_field(self, field, res_ids, compute_lang=False, set_lang=False, post_process=False):
""" Given some record ids, render a given field of template rendered on
all records.
def _render_field(self, field, res_ids,
compute_lang=False, set_lang=False,
post_process=False):
""" Given some record ids, render a template located on field on all
records. ``field`` should be a field of self (i.e. ``body_html`` on
``mail.template``). res_ids are record IDs linked to ``model`` field
on self.
:param list res_ids: list of ids of records (all belonging to same model
defined by self.model)
:param compute_lang: compute rendering language based on template.lang
:param set_lang: force language
:param post_process: perform rendered str / html post processing (see
``_render_template_postprocess``)
defined by ``self.model``)
:param boolean compute_lang: compute language to render on translated
version of the template instead of default (probably english) one.
Language will be computed based on ``self.lang``;
:param string set_lang: force language for rendering. It should be a
valid lang code matching an activate res.lang. Checked only if
``compute_lang`` is False;
:param boolean post_process: perform a post processing on rendered result
(notably html links management). See``_render_template_postprocess``);
:return dict: {res_id: string of rendered template based on record}
"""
+1
View File
@@ -4,5 +4,6 @@
from . import test_mail_channel
from . import test_mail_channel_partner
from . import test_mail_full_composer
from . import test_mail_render
from . import test_res_partner
from . import test_update_notification
+162 -42
View File
@@ -8,6 +8,7 @@ import time
from collections import defaultdict
from contextlib import contextmanager
from functools import partial
from lxml import html
from unittest.mock import patch
from smtplib import SMTPServerDisconnected
@@ -41,8 +42,8 @@ class MockEmail(common.BaseCase):
build_email_origin = IrMailServer.build_email
mail_create_origin = MailMail.create
mail_unlink_origin = MailMail.unlink
self._init_mail_mock()
self.mail_unlink_sent = mail_unlink_sent
self._init_mail_mock()
def _ir_mail_server_connect(model, *args, **kwargs):
if sim_error and sim_error == 'connect_smtp_notfound':
@@ -151,8 +152,6 @@ class MockEmail(common.BaseCase):
return email.message_from_string(pycompat.to_text(text), policy=email.policy.SMTP)
def assertHtmlEqual(self, value, expected, message=None):
from lxml import html
tree = html.fragment_fromstring(value, parser=html.HTMLParser(encoding='utf-8'), create_parent='body')
# mass mailing: add base tag we have to remove
@@ -170,19 +169,38 @@ class MockEmail(common.BaseCase):
self.assertEqual(tree, expected_node)
# ------------------------------------------------------------
# GATEWAY ASSERTS
# GATEWAY GETTERS
# ------------------------------------------------------------
def _find_sent_mail_wemail(self, email_to):
for email in self._mails:
if set(email['email_to']) == set([email_to]):
""" Find a sent email with a given list of recipients. Email should match
exactly the recipients.
:return email: a dictionary mapping values given to ``build_email``;
"""
for sent_email in self._mails:
if set(sent_email['email_to']) == set([email_to]):
break
else:
raise AssertionError('sent mail not found for email_to %s' % (email_to))
return email
return sent_email
def _find_mail_mail_wpartners(self, recipients, status, mail_message=None, author=None):
""" Find a mail.mail record based on various parameters, notably a list
of recipients (partners).
:param recipients: a ``res.partner`` recordset Check all of them are in mail
recipients to find the right mail.mail record;
:param status: state of mail.mail. If not void use it to filter mail.mail
record;
:param mail_message: optional check/filter on mail_message_id field aka
a ``mail.message`` record;
:param author: optional check/filter on author_id field aka a ``res.partner``
record;
:return mail: a ``mail.mail`` record generated during the mock and matching
given parameters and filters;
"""
for mail in self._new_mails:
if author is not None and mail.author_id != author:
continue
@@ -193,10 +211,25 @@ class MockEmail(common.BaseCase):
if all(p in mail.recipient_ids for p in recipients):
break
else:
raise AssertionError('mail.mail not found for message %s / status %s / recipients %s' % (mail_message, status, recipients.ids))
raise AssertionError('mail.mail not found for message %s / status %s / recipients %s / author %s' % (mail_message, status, recipients.ids, author))
return mail
def _find_mail_mail_wemail(self, email_to, status, mail_message=None, author=None):
""" Find a mail.mail record based on various parameters, notably a list
of email to (string emails).
:param email_to: either matching mail.email_to value, either a mail sent
to a single recipient whose email is email_to;
:param status: state of mail.mail. If not void use it to filter mail.mail
record;
:param mail_message: optional check/filter on mail_message_id field aka
a ``mail.message`` record;
:param author: optional check/filter on author_id field aka a ``res.partner``
record;
:return mail: a ``mail.mail`` record generated during the mock and matching
given parameters and filters;
"""
for mail in self._new_mails:
if author is not None and mail.author_id != author:
continue
@@ -204,13 +237,17 @@ class MockEmail(common.BaseCase):
continue
if status and mail.state != status:
continue
if (mail.email_to == email_to and not mail.recipient_ids) or (not mail.email_to and mail.recipient_ids.email) == email_to:
if (mail.email_to == email_to and not mail.recipient_ids) or (not mail.email_to and mail.recipient_ids.email == email_to):
break
else:
raise AssertionError('mail.mail not found for email_to %s / status %s in %s' % (email_to, status, repr([m.email_to for m in self._new_mails])))
return mail
def _find_mail_mail_wrecord(self, record):
""" Find a mail.mail record based on model / res_id of a record.
:return mail: a ``mail.mail`` record generated during the mock;
"""
for mail in self._new_mails:
if mail.model == record._name and mail.res_id == record.id:
break
@@ -218,39 +255,81 @@ class MockEmail(common.BaseCase):
raise AssertionError('mail.mail not found for record %s in %s' % (record, repr([m.email_to for m in self._new_mails])))
return mail
def assertMailFailed(self, author, recipients, mail_message):
mail = self._find_mail(author, recipients, mail_message)
self.assertEqual(mail.state, 'exception')
# ------------------------------------------------------------
# GATEWAY ASSERTS
# ------------------------------------------------------------
def assertMailMail(self, recipients, status, check_mail_mail=True, mail_message=None, author=None, email_values=None, fields_values=None):
if check_mail_mail:
mail = self._find_mail_mail_wpartners(recipients, status, mail_message=mail_message, author=author)
self.assertTrue(bool(mail))
for fname, fvalue in (fields_values or {}).items():
self.assertEqual(mail[fname], fvalue)
def assertMailMail(self, recipients, status,
check_mail_mail=True, mail_message=None, author=None,
content=None, fields_values=None, email_values=None):
""" Assert mail.mail records are created and maybe sent as emails. Allow
asserting their content. Records to check are the one generated when
using mock (mail.mail and outgoing emails). This method takes partners
as source of record fetch and assert.
:param recipients: a ``res.partner`` recordset. See ``_find_mail_mail_wpartners``;
:param status: mail.mail state used to filter mails. If ``sent`` this method
also check that emails have been sent trough gateway;
:param mail_message: see ``_find_mail_mail_wpartners``;
:param author: see ``_find_mail_mail_wpartners``;;
:param content: if given, check it is contained within mail html body;
:param fields_values: if given, should be a dictionary of field names /
values allowing to check ``mail.mail`` additional values (subject,
reply_to, ...);
:param email_values: if given, should be a dictionary of keys / values
allowing to check sent email additional values (if any).
See ``assertSentEmail``;
:param check_mail_mail: deprecated, use ``assertSentEmail`` if False
"""
found_mail = self._find_mail_mail_wpartners(recipients, status, mail_message=mail_message, author=author)
self.assertTrue(bool(found_mail))
if content:
self.assertIn(content, found_mail.body_html)
for fname, fvalue in (fields_values or {}).items():
self.assertEqual(
found_mail[fname], fvalue,
'Mail: expected %s for %s, got %s' % (fvalue, fname, found_mail[fname]))
if status == 'sent':
for recipient in recipients:
self.assertSentEmail(author, [recipient], **email_values)
self.assertSentEmail(email_values['email_from'] if email_values and email_values.get('email_from') else author, [recipient], **(email_values or {}))
def assertMailMailWEmails(self, emails, status, content, fields_values=None):
""" Will check in self._new_mails to find a sent mail.mail. To use with
mail gateway mock.
def assertMailMailWEmails(self, emails, status,
mail_message=None, author=None,
content=None, fields_values=None, email_values=None):
""" Assert mail.mail records are created and maybe sent as emails. Allow
asserting their content. Records to check are the one generated when
using mock (mail.mail and outgoing emails). This method takes emails
as source of record fetch and assert.
:param emails: list of emails;
:param status: status of mail.mail;
:param content: content to check for each email;
:param fields_values: specific value to check on the mail.mail record;
:param emails: a list of emails. See ``_find_mail_mail_wemail``;
:param status: mail.mail state used to filter mails. If ``sent`` this method
also check that emails have been sent trough gateway;
:param mail_message: see ``_find_mail_mail_wemail``;
:param author: see ``_find_mail_mail_wemail``;;
:param content: if given, check it is contained within mail html body;
:param fields_values: if given, should be a dictionary of field names /
values allowing to check ``mail.mail`` additional values (subject,
reply_to, ...);
:param email_values: if given, should be a dictionary of keys / values
allowing to check sent email additional values (if any).
See ``assertSentEmail``;
:param check_mail_mail: deprecated, use ``assertSentEmail`` if False
"""
for email_to in emails:
sent_mail = self._find_mail_mail_wemail(email_to, status)
found_mail = self._find_mail_mail_wemail(email_to, status, mail_message=mail_message, author=author)
if content:
self.assertIn(content, sent_mail.body_html)
self.assertIn(content, found_mail.body_html)
for fname, fvalue in (fields_values or {}).items():
self.assertEqual(
sent_mail[fname], fvalue,
'Mail: expected %s for %s, got %s' % (fvalue, fname, sent_mail[fname]))
found_mail[fname], fvalue,
'Mail: expected %s for %s, got %s' % (fvalue, fname, found_mail[fname]))
if status == 'sent':
for email_to in emails:
self.assertSentEmail(email_values['email_from'] if email_values and email_values.get('email_from') else author, [email_to], **(email_values or {}))
def assertNoMail(self, author, recipients, mail_message=None):
def assertNoMail(self, recipients, mail_message=None, author=None):
try:
self._find_mail_mail_wpartners(recipients, False, mail_message=mail_message, author=author)
except AssertionError:
@@ -302,6 +381,15 @@ class MockEmail(common.BaseCase):
for val in ['reply_to', 'subject', 'references', 'attachments']:
if val in expected:
self.assertEqual(expected[val], sent_mail[val], 'Value for %s: expected %s, received %s' % (val, expected[val], sent_mail[val]))
if 'attachments_info' in values:
attachments = sent_mail['attachments']
for attachment_info in values['attachments_info']:
attachment = next(attach for attach in attachments if attach[0] == attachment_info['name'])
if attachment_info.get('raw'):
self.assertEqual(attachment[1], attachment_info['raw'])
if attachment_info.get('type'):
self.assertEqual(attachment[2], attachment_info['type'])
self.assertEqual(len(values['attachments_info']), len(attachments))
for val in ['body']:
if val in expected:
self.assertHtmlEqual(expected[val], sent_mail[val], 'Value for %s: expected %s, received %s' % (val, expected[val], sent_mail[val]))
@@ -591,18 +679,27 @@ class MailCase(MockEmail):
for recipients in email_groups.values():
partners = self.env['res.partner'].sudo().concat(*recipients)
if all(p in mail_groups['failure'] for p in partners):
self.assertMailMail(partners, 'exception', author=message.author_id, mail_message=message)
if not self.mail_unlink_sent:
self.assertMailMail(partners, 'exception',
author=message.author_id,
mail_message=message)
else:
for recipient in partners:
self.assertSentEmail(message.author_id, [recipient])
else:
check_mail_mail = not self.mail_unlink_sent
self.assertMailMail(
partners, 'sent',
author=message.author_id if message.author_id else message.email_from,
mail_message=message,
check_mail_mail=check_mail_mail,
email_values={'body_content': mbody}
)
if not self.mail_unlink_sent:
self.assertMailMail(
partners, 'sent',
author=message.author_id if message.author_id else message.email_from,
mail_message=message,
email_values={'body_content': mbody}
)
else:
for recipient in partners:
self.assertSentEmail(message.author_id if message.author_id else message.email_from, [recipient],
email_values={'body_content': mbody})
if not any(p for recipients in email_groups.values() for p in recipients):
self.assertNoMail(message.author_id, partners, message)
self.assertNoMail(partners, mail_message=message, author=message.author_id)
return done_msgs, done_notifs
@@ -655,6 +752,29 @@ class MailCase(MockEmail):
return bus_notifs
def assertNotified(self, message, recipients_info, is_complete=False):
""" Lightweight check for notifications (mail.notification).
:param recipients_info: list notified recipients: [
{'partner': res.partner record (may be empty),
'type': notification_type to check,
'is_read': is_read to check,
}, {...}]
"""
notifications = self._new_notifs.filtered(lambda notif: notif in message.notification_ids)
if is_complete:
self.assertEqual(len(notifications), len(recipients_info))
for rinfo in recipients_info:
recipient_notif = next(
(notif
for notif in notifications
if notif.res_partner_id == rinfo['partner']
), False
)
self.assertTrue(recipient_notif)
self.assertEqual(recipient_notif.is_read, rinfo['is_read'])
self.assertEqual(recipient_notif.notification_type, rinfo['type'])
def assertTracking(self, message, data):
tracking_values = message.sudo().tracking_value_ids
for field_name, value_type, old_value, new_value in data:
+6 -3
View File
@@ -508,8 +508,11 @@ class TestChannelModeration(MailCommon):
(msg_c1_admin1 | msg_c1_emplo2).with_user(self.user_employee)._moderate('reject', title='RejectTitle', comment='RejectComment')
self.assertEqual(len(self._new_mails), 2)
self.assertMailMailWEmails([self.partner_admin.email_formatted, self.partner_employee_2.email_formatted], 'outgoing', 'RejectComment',
fields_values={'subject': 'RejectTitle', 'author_id': self.partner_employee})
self.assertMailMailWEmails(
[self.partner_admin.email_formatted, self.partner_employee_2.email_formatted], 'outgoing',
content='RejectComment',
fields_values={'subject': 'RejectTitle', 'author_id': self.partner_employee}
)
self.assertTrue(all('RejectComment' in body for body in self._new_mails.mapped('body_html')))
self.assertTrue(all('Body' in body for body in self._new_mails.mapped('body_html')))
@@ -674,7 +677,7 @@ class TestChannelModeration(MailCommon):
})
# admin being banned, should not received any email
self.assertNoMail(self.partner_employee, self.partner_admin)
self.assertNoMail(self.partner_employee, author=self.partner_admin)
# only moderators can send guidelines
channel.write({
+191
View File
@@ -0,0 +1,191 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.mail.tests import common
from odoo.tests import tagged, users
@tagged('mail_render')
class TestMailRender(common.MailCommon):
@classmethod
def setUpClass(cls):
super(TestMailRender, cls).setUpClass()
# activate multi language support
cls.env['res.lang']._activate_lang('fr_FR')
cls.user_admin.write({'lang': 'en_US'})
# test records
cls.render_object = cls.env['res.partner'].create({
'name': 'TestRecord',
'lang': 'en_US',
})
cls.render_object_fr = cls.env['res.partner'].create({
'name': 'Element de Test',
'lang': 'fr_FR',
})
# some jinja templates
cls.base_jinja_bits = [
'<p>Hello</p>',
'<p>Hello ${object.name}</p>',
"""<p>
% set english = object.lang == 'en_US'
% if english
<span>English Speaker</span>
% else
<span>Other Speaker</span>
% endif
</p>"""
]
cls.base_jinja_bits_fr = [
'<p>Bonjour</p>',
'<p>Bonjour ${object.name}</p>',
"""<p>
% set english = object.lang == 'en_US'
% if english
<span>Narrateur Anglais</span>
% else
<span>Autre Narrateur</span>
% endif
</p>"""
]
# some qweb templates and their xml ids
cls.base_qweb_templates = cls.env['ir.ui.view'].create([
{'name': 'TestRender', 'type': 'qweb',
'arch': '<p>Hello</p>',
},
{'name': 'TestRender2', 'type': 'qweb',
'arch': '<p>Hello <t t-esc="object.name"/></p>',
},
{'name': 'TestRender3', 'type': 'qweb',
'arch': """<p>
<span t-if="object.lang == 'en_US'">English Speaker</span>
<span t-else="">Other Speaker</span>
</p>""",
},
])
cls.base_qweb_templates_data = cls.env['ir.model.data'].create([
{'name': template.name, 'module': 'mail',
'model': template._name, 'res_id': template.id,
} for template in cls.base_qweb_templates
])
cls.base_qweb_templates_xmlids = [
model_data.complete_name
for model_data in cls.base_qweb_templates_data
]
# render result
cls.base_rendered = [
'<p>Hello</p>',
'<p>Hello %s</p>' % cls.render_object.name,
"""<p>
<span>English Speaker</span>
</p>"""
]
cls.base_rendered_fr = [
'<p>Bonjour</p>',
'<p>Bonjour %s</p>' % cls.render_object_fr.name,
"""<p>
<span>Autre Narrateur</span>
</p>"""
]
# link to mail template
cls.test_template_jinja = cls.env['mail.template'].create({
'name': 'Test Template',
'subject': cls.base_jinja_bits[0],
'body_html': cls.base_jinja_bits[1],
'model_id': cls.env['ir.model']._get('res.partner').id,
'lang': '${object.lang}'
})
# some translations
cls.env['ir.translation'].create({
'type': 'model',
'name': 'mail.template,subject',
'lang': 'fr_FR',
'res_id': cls.test_template_jinja.id,
'src': cls.test_template_jinja.subject,
'value': cls.base_jinja_bits_fr[0],
})
cls.env['ir.translation'].create({
'type': 'model',
'name': 'mail.template,body_html',
'lang': 'fr_FR',
'res_id': cls.test_template_jinja.id,
'src': cls.test_template_jinja.body_html,
'value': cls.base_jinja_bits_fr[1],
})
cls.env['ir.model.data'].create({
'name': 'test_template_xmlid',
'module': 'mail',
'model': cls.test_template_jinja._name,
'res_id': cls.test_template_jinja.id,
})
@users('employee')
def test_render_jinja(self):
source = """<p>
% set line_statement_variable = 3
<span>We have ${line_statement_variable} cookies in stock</span>
<span>We have <% set block_variable = 4 %>${block_variable} cookies in stock</span>
</p>"""
partner = self.env['res.partner'].browse(self.render_object.ids)
result = self.env['mail.render.mixin']._render_template(
source,
partner._name,
partner.ids,
engine='jinja',
)[partner.id]
self.assertEqual(result, """<p>
<span>We have 3 cookies in stock</span>
<span>We have 4 cookies in stock</span>
</p>""")
@users('employee')
def test_render_mail_template_jinja(self):
template = self.env['mail.template'].browse(self.test_template_jinja.ids)
partner = self.env['res.partner'].browse(self.render_object.ids)
for fname, expected in zip(['subject', 'body_html'], self.base_rendered):
rendered = template._render_field(
fname,
partner.ids,
compute_lang=True
)[partner.id]
self.assertEqual(rendered, expected)
partner = self.env['res.partner'].browse(self.render_object_fr.ids)
for fname, expected in zip(['subject', 'body_html'], self.base_rendered_fr):
rendered = template._render_field(
fname,
partner.ids,
compute_lang=True
)[partner.id]
self.assertEqual(rendered, expected)
@users('employee')
def test_render_template_jinja(self):
partner = self.env['res.partner'].browse(self.render_object.ids)
for source, expected in zip(self.base_jinja_bits, self.base_rendered):
rendered = self.env['mail.render.mixin']._render_template(
source,
partner._name,
partner.ids,
engine='jinja',
)[partner.id]
self.assertEqual(rendered, expected)
@users('employee')
def test_render_template_qweb(self):
partner = self.env['res.partner'].browse(self.render_object.ids)
for source, expected in zip(self.base_qweb_templates_xmlids, self.base_rendered):
rendered = self.env['mail.render.mixin']._render_template(
source,
partner._name,
partner.ids,
engine='qweb',
)[partner.id].decode()
self.assertEqual(rendered, expected)
+17 -12
View File
@@ -7,7 +7,8 @@ from odoo.addons.mail.tests.common import MailCase, MailCommon, mail_new_test_us
class MassMailCase(MailCase, MockLinkTracker):
def assertMailTraces(self, recipients_info, mailing, records, check_mail=True):
def assertMailTraces(self, recipients_info, mailing, records,
check_mail=True, author=None):
""" Check content of traces.
:param recipients_info: list[{
@@ -19,6 +20,15 @@ class MassMailCase(MailCase, MockLinkTracker):
'failure_type': optional: UPDATE ME
}, { ... }]
"""
# map trace state to email state
state_mapping = {
'sent': 'sent',
'replied': 'sent', # replied imply something has been sent
'ignored': 'cancel',
'exception': 'exception',
'canceled': 'canceled',
}
traces = self.env['mailing.trace'].search([
('mass_mailing_id', 'in', mailing.ids),
('res_id', 'in', records.ids)
@@ -47,22 +57,17 @@ class MassMailCase(MailCase, MockLinkTracker):
)
if check_mail:
if author is None:
author = self.env.user.partner_id
fields_values = {'mailing_id': mailing}
if 'failure_type' in recipient_info:
fields_values['failure_type'] = recipient_info['failure_type']
if state == 'sent':
self.assertMailMailWEmails([email], 'sent', content, fields_values=fields_values)
elif state == 'replied': # replied imply something has been sent
self.assertMailMailWEmails([email], 'sent', content, fields_values=fields_values)
elif state == 'ignored':
self.assertMailMailWEmails([email], 'cancel', content, fields_values=fields_values)
elif state == 'exception':
self.assertMailMailWEmails([email], 'exception', content, fields_values=fields_values)
elif state == 'canceled':
self.assertMailMailWEmails([email], 'canceled', content, fields_values=fields_values)
if partner:
self.assertMailMail(partner, state_mapping[state], author=author, content=content, fields_values=fields_values)
else:
raise NotImplementedError()
self.assertMailMailWEmails([email], state_mapping[state], author=author, content=content, fields_values=fields_values)
class MassMailCommon(MailCommon, MassMailCase):
@@ -240,8 +240,8 @@ class TestMassMailFeatures(MassMailCommon, CronMixinCase):
mailing._process_mass_mailing_queue()
self.assertMailTraces(
[{'email': 'test1@example.com'},
{'email': 'test2@example.com', 'state': 'ignored'}],
[{'partner': partner_a},
{'partner': partner_b, 'state': 'ignored'}],
mailing, partner_a | partner_b, check_mail=True
)
+6 -3
View File
@@ -3,8 +3,9 @@
from odoo.tests.common import TransactionCase, users
from odoo.addons.mail.tests.common import mail_new_test_user
from odoo.tests import tagged
from odoo.exceptions import AccessError
from odoo.tests import tagged
from odoo.tools import mute_logger
@tagged('post_install')
@@ -26,7 +27,8 @@ class TestSmsTemplateAccessRights(TransactionCase):
cls.sms_templates = cls.env['sms.template'].create(vals)
@users('user_employee')
def test_access_rights_user_sms_template(self):
@mute_logger('odoo.models.unlink')
def test_access_rights_user(self):
# Check if a member of group_user can only read on sms.template
for sms_template in self.env['sms.template'].browse(self.sms_templates.ids):
self.assertTrue(bool(sms_template.name))
@@ -42,7 +44,8 @@ class TestSmsTemplateAccessRights(TransactionCase):
sms_template.unlink()
@users('user_system')
def test_access_rights_manager_sms_template(self):
@mute_logger('odoo.models.unlink', 'odoo.addons.base.models.ir_model')
def test_access_rights_system(self):
admin = self.env.ref('base.user_admin')
for sms_template in self.env['sms.template'].browse(self.sms_templates.ids):
self.assertTrue(bool(sms_template.name))
-1
View File
@@ -10,7 +10,6 @@ from . import test_mail_mail
from . import test_mail_gateway
from . import test_mail_template
from . import test_mail_thread_internals
from . import test_message_composer
from . import test_message_management
from . import test_message_post
from . import test_message_track
+490 -6
View File
@@ -3,7 +3,10 @@
import base64
from unittest.mock import patch
from odoo.addons.mail.tests.common import mail_new_test_user
from odoo.addons.test_mail.models.test_mail_models import MailTestTicket
from odoo.addons.test_mail.tests.common import TestMailCommon, TestRecipients
from odoo.tests import tagged
from odoo.tests.common import users, Form
@@ -18,9 +21,14 @@ class TestMailComposer(TestMailCommon, TestRecipients):
super(TestMailComposer, cls).setUpClass()
cls._init_mail_gateway()
# ensure employee can create partners, necessary for templates
cls.user_employee.write({
'groups_id': [(4, cls.env.ref('base.group_partner_manager').id)],
})
cls.user_employee_2 = mail_new_test_user(
cls.env, login='employee2', groups='base.group_user',
notification_type='inbox', email='eglantine@example.com',
notification_type='email', email='eglantine@example.com',
name='Eglantine Employee', signature='--\nEglantine')
cls.partner_employee_2 = cls.user_employee_2.partner_id
@@ -37,6 +45,7 @@ class TestMailComposer(TestMailCommon, TestRecipients):
'report_type': 'qweb-pdf',
'report_name': 'test_mail.mail_test_ticket_test_template',
})
cls.test_record_report = cls.test_report._render_qweb_pdf(cls.test_report.ids)
cls.test_from = '"John Doe" <john@example.com>'
@@ -54,9 +63,10 @@ class TestMailComposer(TestMailCommon, TestRecipients):
'body_html': '<p>TemplateBody ${object.name}</p>',
'partner_to': '${object.customer_id.id if object.customer_id else ""}',
'email_to': '${(object.email_from if not object.customer_id else "") | safe}',
'email_from': '${object.user_id.email_formatted | safe}',
'email_from': '${(object.user_id.email_formatted or user.email_formatted) | safe}',
'model_id': cls.env['ir.model']._get('mail.test.ticket').id,
'mail_server_id': cls.mail_server.id,
'auto_delete': True,
})
def _generate_attachments_data(self, count):
@@ -102,7 +112,59 @@ class TestComposerForm(TestMailComposer):
# self.assertEqual(composer_form.record_name, self.test_record.name, 'MailComposer: comment mode should compute record name')
self.assertFalse(composer_form.no_auto_thread)
self.assertEqual(composer_form.composition_mode, 'comment')
self.assertEqual(composer_form.model, self.test_records._name)
self.assertEqual(composer_form.model, self.test_record._name)
@users('employee')
def test_mail_composer_comment_attachments(self):
"""Tests that all attachments are added to the composer, static attachments
are not duplicated and while reports are re-generated, and that intermediary
attachments are dropped."""
attachment_data = self._generate_attachments_data(2)
template_1 = self.template.copy({
'attachment_ids': [(0, 0, a) for a in attachment_data],
'report_name': 'TestReport for ${object.name}.html', # test cursor forces html
'report_template': self.test_report.id,
})
template_1_attachments = template_1.attachment_ids
self.assertEqual(len(template_1_attachments), 2)
template_2 = self.template.copy({
'attachment_ids': False,
'report_template': self.test_report.id,
})
# begins without attachments
composer_form = Form(self.env['mail.compose.message'].with_context(
self._get_web_context(self.test_record, add_web=True, default_attachment_ids=[])
))
self.assertEqual(len(composer_form.attachment_ids), 0)
# change template: 2 static (attachment_ids) and 1 dynamic (report)
composer_form.template_id = template_1
self.assertEqual(len(composer_form.attachment_ids), 3)
report_attachments = [att for att in composer_form.attachment_ids if att not in template_1_attachments]
self.assertEqual(len(report_attachments), 1)
tpl_attachments = composer_form.attachment_ids[:] - report_attachments[0]
self.assertEqual(tpl_attachments, template_1_attachments)
# change template: 0 static (attachment_ids) and 1 dynamic (report)
composer_form.template_id = template_2
self.assertEqual(len(composer_form.attachment_ids), 1)
report_attachments = [att for att in composer_form.attachment_ids if att not in template_1_attachments]
self.assertEqual(len(report_attachments), 1)
tpl_attachments = composer_form.attachment_ids[:] - report_attachments[0]
self.assertEqual(tpl_attachments, self.env['ir.attachment'])
# change back to template 1
composer_form.template_id = template_1
self.assertEqual(len(composer_form.attachment_ids), 3)
report_attachments = [att for att in composer_form.attachment_ids if att not in template_1_attachments]
self.assertEqual(len(report_attachments), 1)
tpl_attachments = composer_form.attachment_ids[:] - report_attachments[0]
self.assertEqual(tpl_attachments, template_1_attachments)
# reset template
composer_form.template_id = self.env['mail.template']
self.assertEqual(len(composer_form.attachment_ids), 0)
@users('employee')
def test_mail_composer_mass(self):
@@ -139,6 +201,7 @@ class TestComposerInternals(TestMailComposer):
attachment_data = self._generate_attachments_data(3)
self.template.write({
'attachment_ids': [(0, 0, a) for a in attachment_data],
'report_name': 'TestReport for ${object.name}.html', # test cursor forces html
'report_template': self.test_report.id,
})
attachs = self.env['ir.attachment'].search([('name', 'in', [a['name'] for a in attachment_data])])
@@ -161,6 +224,7 @@ class TestComposerInternals(TestMailComposer):
self.assertIn(attach, composer.attachment_ids)
generated = composer.attachment_ids - attachs
self.assertEqual(len(generated), 1, 'MailComposer: should have 1 additional attachment for report')
self.assertEqual(generated.name, 'TestReport for %s.html' % self.test_record.name)
self.assertEqual(generated.res_model, 'mail.compose.message')
self.assertEqual(generated.res_id, 0)
@@ -207,9 +271,9 @@ class TestComposerInternals(TestMailComposer):
self.assertEqual(composer.email_from, self.test_from)
@users('employee')
def test_mail_composer_content_comment(self):
""" Test content management (subject, body, server) in comment mode.
Template update is also tested. """
def test_mail_composer_content(self):
""" Test content management (subject, body, server) in both comment and
mass mailing mode. Template update is also tested. """
for composition_mode in ['comment', 'mass_mail']:
if composition_mode == 'comment':
ctx = self._get_web_context(self.test_record, add_web=False)
@@ -314,3 +378,423 @@ class TestComposerInternals(TestMailComposer):
self.assertEqual(composer.subject, 'My amazing subject')
self.assertEqual(composer.body, '<p>Test Body</p>')
self.assertEqual(composer.mail_server_id.id, False)
@users('employee')
@mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail')
def test_mail_composer_parent(self):
""" Test specific management in comment mode when having parent_id set:
record_name, subject, parent's partners. """
parent = self.test_record.message_post(body='Test', partner_ids=(self.partner_1 + self.partner_2).ids)
composer = self.env['mail.compose.message'].with_context(
self._get_web_context(self.test_record, add_web=False, default_parent_id=parent.id)
).create({
'body': '<p>Test Body</p>',
})
# creation values taken from parent
self.assertEqual(composer.subject, 'Re: %s' % self.test_record.name)
self.assertEqual(composer.body, '<p>Test Body</p>')
self.assertEqual(composer.partner_ids, self.partner_1 + self.partner_2)
def test_mail_composer_rights_portal(self):
portal_user = self._create_portal_user()
with patch.object(MailTestTicket, 'check_access_rights', return_value=True):
self.env['mail.compose.message'].with_user(portal_user).with_context(
self._get_web_context(self.test_record)
).create({
'subject': 'Subject',
'body': '<p>Body text</p>',
'partner_ids': []
}).send_mail()
self.assertEqual(self.test_record.message_ids[0].body, '<p>Body text</p>')
self.assertEqual(self.test_record.message_ids[0].author_id, portal_user.partner_id)
self.env['mail.compose.message'].with_user(portal_user).with_context({
'default_composition_mode': 'comment',
'default_parent_id': self.test_record.message_ids.ids[0],
}).create({
'subject': 'Subject',
'body': '<p>Body text 2</p>'
}).send_mail()
self.assertEqual(self.test_record.message_ids[0].body, '<p>Body text 2</p>')
self.assertEqual(self.test_record.message_ids[0].author_id, portal_user.partner_id)
@users('employee')
def test_mail_composer_save_template(self):
self.env['mail.compose.message'].with_context(
self._get_web_context(self.test_record, add_web=False)
).create({
'subject': 'Template Subject',
'body': '<p>Template Body</p>',
}).save_as_template()
# Test: email_template subject, body_html, model
template = self.env['mail.template'].search([
('model', '=', self.test_record._name),
('subject', '=', 'Template Subject')
], limit=1)
self.assertEqual(template.name, "%s: %s" % (self.env['ir.model']._get(self.test_record._name).name, 'Template Subject'))
self.assertEqual(template.body_html, '<p>Template Body</p>', 'email_template incorrect body_html')
@tagged('mail_composer')
class TestComposerResultsComment(TestMailComposer):
""" Test global output of composer used in comment mode. Test notably
notification and emails generated during this process. """
@users('employee')
@mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail')
def test_mail_composer_recipients(self):
""" Test partner_ids given to composer are given to the final message. """
composer = self.env['mail.compose.message'].with_context(
self._get_web_context(self.test_record)
).create({
'body': '<p>Test Body</p>',
'partner_ids': [(4, self.partner_1.id), (4, self.partner_2.id)]
})
composer.send_mail()
message = self.test_record.message_ids[0]
self.assertEqual(message.body, '<p>Test Body</p>')
self.assertEqual(message.author_id, self.user_employee.partner_id)
self.assertEqual(message.subject, 'Re: %s' % self.test_record.name)
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.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_mail_composer_wtpl_complete(self):
""" Test a posting process using a complex template, holding several
additional recipients and attachments.
This tests notifies: 2 new email_to (+ 1 duplicated), 1 email_cc,
test_record followers and partner_admin added in partner_to."""
attachment_data = self._generate_attachments_data(2)
email_to_1 = 'test.to.1@test.example.com'
email_to_2 = 'test.to.2@test.example.com'
email_to_3 = 'test.to.1@test.example.com' # duplicate: should not sent twice the email
email_cc_1 = 'test.cc.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_to': '%s, %s, %s' % (email_to_1, email_to_2, email_to_3),
'email_cc': email_cc_1,
'partner_to': '%s, ${object.customer_id.id if object.customer_id else ""}' % self.partner_admin.id,
'report_name': 'TestReport for ${object.name}', # test cursor forces html
'report_template': self.test_report.id,
})
attachs = self.env['ir.attachment'].search([('name', 'in', [a['name'] for a in attachment_data])])
self.assertEqual(len(attachs), 2)
# ensure initial data
self.assertEqual(self.test_record.user_id, self.user_employee_2)
self.assertEqual(self.test_record.message_partner_ids, self.partner_employee_2)
# open a composer and run it in comment mode
composer_form = Form(self.env['mail.compose.message'].with_context(
self._get_web_context(self.test_record, add_web=True,
default_template_id=self.template.id)
))
composer = composer_form.save()
with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app():
composer.send_mail()
# check new partners have been created based on emails given
new_partners = self.env['res.partner'].search([
('email', 'in', [email_to_1, email_to_2, email_to_3, email_cc_1])
])
self.assertEqual(len(new_partners), 3)
self.assertEqual(set(new_partners.mapped('email')),
set(['test.to.1@test.example.com', 'test.to.2@test.example.com', 'test.cc.1@test.example.com'])
)
# global outgoing: one mail.mail (all customer recipients, then all employee recipients)
# and 5 emails, and 1 inbox notification (admin)
self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail')
self.assertEqual(len(self._mails), 5, 'Should have sent 5 emails, one per recipient')
# template is sent only to partners (email_to are transformed)
message = self.test_record.message_ids[0]
self.assertMailMail(self.partner_employee_2, 'sent',
mail_message=message,
author=self.partner_employee, # author != email_from (template sets only email_from)
email_values={
'body_content': 'TemplateBody %s' % self.test_record.name,
'email_from': self.test_record.user_id.email_formatted, # set by template
'subject': 'TemplateSubject %s' % self.test_record.name,
'attachments_info': [
{'name': '00.txt', 'raw': b'Att00', 'type': 'text/plain'},
{'name': '01.txt', 'raw': b'Att01', 'type': 'text/plain'},
{'name': 'TestReport for %s.html' % self.test_record.name, 'type': 'text/plain'},
]
},
fields_values={},
)
self.assertMailMail(self.test_record.customer_id + new_partners, 'sent',
mail_message=message,
author=self.partner_employee, # author != email_from (template sets only email_from)
email_values={
'body_content': 'TemplateBody %s' % self.test_record.name,
'email_from': self.test_record.user_id.email_formatted, # set by template
'subject': 'TemplateSubject %s' % self.test_record.name,
'attachments_info': [
{'name': '00.txt', 'raw': b'Att00', 'type': 'text/plain'},
{'name': '01.txt', 'raw': b'Att01', 'type': 'text/plain'},
{'name': 'TestReport for %s.html' % self.test_record.name, 'type': 'text/plain'},
]
},
fields_values={},
)
# message is posted and notified admin
self.assertEqual(message.subtype_id, self.env.ref('mail.mt_comment'))
self.assertNotified(message, [{'partner': self.partner_admin, 'is_read': False, 'type': 'inbox'}])
# attachments are copied on message and linked to document
self.assertEqual(
set(message.attachment_ids.mapped('name')),
set(['00.txt', '01.txt', 'TestReport for %s.html' % self.test_record.name])
)
self.assertEqual(set(message.attachment_ids.mapped('res_model')), set([self.test_record._name]))
self.assertEqual(set(message.attachment_ids.mapped('res_id')), set(self.test_record.ids))
self.assertTrue(all(attach not in message.attachment_ids for attach in attachs), 'Should have copied attachments')
@tagged('mail_composer')
class TestComposerResultsMass(TestMailComposer):
@classmethod
def setUpClass(cls):
super(TestComposerResultsMass, cls).setUpClass()
# ensure employee can create partners, necessary for templates
cls.user_employee.write({
'groups_id': [(4, cls.env.ref('base.group_partner_manager').id)],
})
@users('employee')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_mail_composer_wtpl(self):
self.template.auto_delete = False # keep sent emails to check content
composer_form = Form(self.env['mail.compose.message'].with_context(
self._get_web_context(self.test_records, add_web=True,
default_template_id=self.template.id)
))
composer = composer_form.save()
with self.mock_mail_gateway(mail_unlink_sent=True):
composer.send_mail()
# global outgoing
self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record')
self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record')
for record in self.test_records:
# message copy is kept
message = record.message_ids[0]
# template is sent directly using customer field, meaning we have recipients
self.assertMailMail(record.customer_id, 'sent',
mail_message=message,
author=self.partner_employee)
# message content
self.assertEqual(message.subject, 'TemplateSubject %s' % record.name)
self.assertEqual(message.body, '<p>TemplateBody %s</p>' % record.name)
self.assertEqual(message.author_id, self.user_employee.partner_id)
# post-related fields are void
self.assertEqual(message.subtype_id, self.env['mail.message.subtype'])
self.assertEqual(message.partner_ids, self.env['res.partner'])
@users('employee')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_mail_composer_wtpl_complete(self):
""" Test a composer in mass mode with a quite complete template, containing
notably email-based recipients and attachments. """
attachment_data = self._generate_attachments_data(2)
email_to_1 = 'test.to.1@test.example.com'
email_to_2 = 'test.to.2@test.example.com'
email_to_3 = 'test.to.1@test.example.com' # duplicate: should not sent twice the email
email_cc_1 = 'test.cc.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_to': '%s, %s, %s' % (email_to_1, email_to_2, email_to_3),
'email_cc': email_cc_1,
'partner_to': '%s, ${object.customer_id.id if object.customer_id else ""}' % self.partner_admin.id,
'report_name': 'TestReport for ${object.name}', # test cursor forces html
'report_template': self.test_report.id,
})
attachs = self.env['ir.attachment'].search([('name', 'in', [a['name'] for a in attachment_data])])
self.assertEqual(len(attachs), 2)
# ensure initial data
self.assertEqual(self.test_records.user_id, self.env['res.users'])
self.assertEqual(self.test_records.message_partner_ids, self.env['res.partner'])
# launch composer in mass mode
composer_form = Form(self.env['mail.compose.message'].with_context(
self._get_web_context(self.test_records, add_web=True,
default_template_id=self.template.id)
))
composer = composer_form.save()
with self.mock_mail_gateway(mail_unlink_sent=False):
composer.send_mail()
new_partners = self.env['res.partner'].search([
('email', 'in', [email_to_1, email_to_2, email_to_3, email_cc_1])
])
self.assertEqual(len(new_partners), 3)
# global outgoing
self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record')
self.assertEqual(len(self._mails), 10, 'Should have sent 5 emails per record')
# hack to use assertEmails: filtering on from/to only is not sufficient to distinguish emails
_mails_records = [
[mail for mail in self._mails if '%s-%s' % (record.id, record._name) in mail['message_id']]
for record in self.test_records
]
_mails_record2 = [mail for mail in self._mails if '%s-%s' % (self.test_records[1].id, self.test_records._name) in mail['message_id']]
for record, _mails in zip(self.test_records, _mails_records):
# message copy is kept
message = record.message_ids[0]
# template is sent only to partners (email_to are transformed)
self._mails = _mails
self.assertMailMail(record.customer_id + new_partners + self.partner_admin,
'sent',
mail_message=message,
author=self.partner_employee,
email_values={
'body_content': 'TemplateBody %s' % record.name,
'subject': 'TemplateSubject %s' % record.name,
# 'attachments': [
# ('00.txt', b'Att00', 'text/plain'),
# ('01.txt', b'Att01', 'text/plain'),
# ('report.test_mail.mail_test_ticket_test_template.html', b'My second attachment', 'text/plain')
# ]
}
)
@users('employee')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_mail_composer_wtpl_delete(self):
self.template.auto_delete = True
composer_form = Form(self.env['mail.compose.message'].with_context(
self._get_web_context(self.test_records, add_web=True,
default_template_id=self.template.id)
))
composer = composer_form.save()
with self.mock_mail_gateway(mail_unlink_sent=True):
composer.send_mail()
# global outgoing
self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record')
self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record')
self.assertEqual(self._new_mails.exists(), self.env['mail.mail'], 'Should have deleted mail.mail records')
for record in self.test_records:
# message copy is kept
message = record.message_ids[0]
# template is sent directly using customer field
self.assertSentEmail(self.partner_employee, record.customer_id)
# message content
self.assertEqual(message.subject, 'TemplateSubject %s' % record.name)
self.assertEqual(message.body, '<p>TemplateBody %s</p>' % record.name)
self.assertEqual(message.author_id, self.user_employee.partner_id)
# post-related fields are void
self.assertEqual(message.subtype_id, self.env['mail.message.subtype'])
self.assertEqual(message.partner_ids, self.env['res.partner'])
@users('employee')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_mail_composer_wtpl_delete_notif(self):
self.template.auto_delete = True
composer_form = Form(self.env['mail.compose.message'].with_context(
self._get_web_context(self.test_records, add_web=True,
default_template_id=self.template.id,
default_auto_delete_message=True)
))
composer = composer_form.save()
with self.mock_mail_gateway(mail_unlink_sent=True):
composer.send_mail()
# global outgoing
self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record')
self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record')
self.assertEqual(self._new_mails.exists(), self.env['mail.mail'], 'Should have deleted mail.mail records')
for record in self.test_records:
# message copy is unlinked
self.assertEqual(record.message_ids, self.env['mail.message'], 'Should have deleted mail.message records')
# template is sent directly using customer field
self.assertSentEmail(self.partner_employee, record.customer_id)
@users('employee')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_mail_composer_wtpl_recipients(self):
""" Test various combinations of recipients: active_domain, active_id,
active_ids, ... to ensure fallback behavior are working. """
# 1: active_domain
composer_form = Form(self.env['mail.compose.message'].with_context(
self._get_web_context(self.test_records, add_web=True,
default_template_id=self.template.id,
active_ids=[],
default_use_active_domain=True,
default_active_domain=[('id', 'in', self.test_records.ids)])
))
composer = composer_form.save()
with self.mock_mail_gateway(mail_unlink_sent=True):
composer.send_mail()
# global outgoing
self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record')
self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record')
for record in self.test_records:
# template is sent directly using customer field
self.assertSentEmail(self.partner_employee, record.customer_id)
# 2: active_domain not taken into account if use_active_domain is False
composer_form = Form(self.env['mail.compose.message'].with_context(
self._get_web_context(self.test_records, add_web=True,
default_template_id=self.template.id,
default_use_active_domain=False,
default_active_domain=[('id', 'in', -1)])
))
composer = composer_form.save()
with self.mock_mail_gateway(mail_unlink_sent=True):
composer.send_mail()
# global outgoing
self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record')
self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record')
# 3: fallback on active_id if not active_ids
composer_form = Form(self.env['mail.compose.message'].with_context(
self._get_web_context(self.test_records, add_web=True,
default_template_id=self.template.id,
active_ids=[])
))
composer = composer_form.save()
with self.mock_mail_gateway(mail_unlink_sent=False):
composer.send_mail()
# global outgoing
self.assertEqual(len(self._new_mails), 1, 'Should have created 1 mail.mail per record')
self.assertEqual(len(self._mails), 1, 'Should have sent 1 email per record')
# 3: void is void
composer_form = Form(self.env['mail.compose.message'].with_context(
default_model='mail.test.ticket',
default_template_id=self.template.id
))
composer = composer_form.save()
with self.mock_mail_gateway(mail_unlink_sent=False), self.assertRaises(ValueError):
composer.send_mail()
@@ -1,345 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from unittest.mock import patch
from odoo.addons.test_mail.tests.common import TestMailCommon, TestRecipients
from odoo.addons.test_mail.models.test_mail_models import MailTestSimple
from odoo.tests import tagged
from odoo.tools import mute_logger
@tagged('mail_composer')
class TestComposer(TestMailCommon, TestRecipients):
""" Test Composer integration and results """
@classmethod
def setUpClass(cls):
super(TestComposer, cls).setUpClass()
cls.test_record = cls.env['mail.test.simple'].with_context(cls._test_context).create({'name': 'Test', 'email_from': 'ignasse@example.com'})
cls._reset_mail_context(cls.test_record)
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_composer_comment(self):
composer = self.env['mail.compose.message'].with_context({
'default_composition_mode': 'comment',
'default_model': self.test_record._name,
'default_res_id': self.test_record.id,
}).with_user(self.user_employee).create({
'body': '<p>Test Body</p>',
'partner_ids': [(4, self.partner_1.id), (4, self.partner_2.id)]
})
composer.send_mail()
message = self.test_record.message_ids[0]
self.assertEqual(message.body, '<p>Test Body</p>')
self.assertEqual(message.author_id, self.user_employee.partner_id)
self.assertEqual(message.subject, 'Re: %s' % self.test_record.name)
self.assertEqual(message.subtype_id, self.env.ref('mail.mt_comment'))
self.assertEqual(message.partner_ids, self.partner_1 | self.partner_2)
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_composer_comment_parent(self):
parent = self.test_record.message_post(body='Test')
self.env['mail.compose.message'].with_context({
'default_composition_mode': 'comment',
'default_parent_id': parent.id
}).with_user(self.user_employee).create({
'body': '<p>Mega</p>',
}).send_mail()
message = self.test_record.message_ids[0]
self.assertEqual(message.body, '<p>Mega</p>')
self.assertEqual(message.parent_id, parent)
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_composer_mass_mail(self):
test_record_2 = self.env['mail.test.simple'].with_context(self._test_context).create({'name': 'Test2'})
composer = self.env['mail.compose.message'].with_context({
'default_composition_mode': 'mass_mail',
'default_model': self.test_record._name,
'default_res_id': False,
'active_ids': [self.test_record.id, test_record_2.id]
}).with_user(self.user_employee).create({
'subject': 'Testing ${object.name}',
'body': '<p>${object.name}</p>',
'partner_ids': [(4, self.partner_1.id), (4, self.partner_2.id)]
})
composer.with_context({
'default_res_id': -1,
'active_ids': [self.test_record.id, test_record_2.id]
}).send_mail()
# check mail_mail
mails = self.env['mail.mail'].sudo().search([('subject', 'ilike', 'Testing')])
for mail in mails:
self.assertEqual(mail.recipient_ids, self.partner_1 | self.partner_2,
'compose wizard: mail_mail mass mailing: mail.mail in mass mail incorrect recipients')
# check message on test_record
message1 = self.test_record.message_ids[0]
self.assertEqual(message1.subject, 'Testing %s' % self.test_record.name)
self.assertEqual(message1.body, '<p>%s</p>' % self.test_record.name)
# check message on test_record_2
message1 = test_record_2.message_ids[0]
self.assertEqual(message1.subject, 'Testing %s' % test_record_2.name)
self.assertEqual(message1.body, '<p>%s</p>' % test_record_2.name)
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_composer_mass_mail_active_domain(self):
test_record_2 = self.env['mail.test.simple'].with_context(self._test_context).create({'name': 'Test2'})
self.env['mail.compose.message'].with_context({
'default_composition_mode': 'mass_mail',
'default_model': self.test_record._name,
'default_use_active_domain': True,
'active_ids': [self.test_record.id],
'active_domain': [('name', 'in', ['%s' % self.test_record.name, '%s' % test_record_2.name])],
}).with_user(self.user_employee).create({
'subject': 'From Composer Test',
'body': '${object.name}',
}).send_mail()
self.assertEqual(self.test_record.message_ids[0].subject, 'From Composer Test')
self.assertEqual(test_record_2.message_ids[0].subject, 'From Composer Test')
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_message_compose_mass_mail_no_active_domain(self):
test_record_2 = self.env['mail.test.simple'].with_context(self._test_context).create({'name': 'Test2'})
self.env['mail.compose.message'].with_context({
'default_composition_mode': 'mass_mail',
'default_model': self.test_record._name,
'default_use_active_domain': False,
'active_ids': [self.test_record.id],
'active_domain': [('name', 'in', ['%s' % self.test_record.name, '%s' % test_record_2.name])],
}).with_user(self.user_employee).create({
'subject': 'From Composer Test',
'body': '${object.name}',
}).send_mail()
self.assertEqual(self.test_record.message_ids[0].subject, 'From Composer Test')
self.assertFalse(test_record_2.message_ids.ids)
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_message_compose_portal_ok(self):
portal_user = self._create_portal_user()
with patch.object(MailTestSimple, 'check_access_rights', return_value=True):
ComposerPortal = self.env['mail.compose.message'].with_user(portal_user)
ComposerPortal.with_context({
'default_composition_mode': 'comment',
'default_model': self.test_record._name,
'default_res_id': self.test_record.id,
}).create({
'subject': 'Subject',
'body': '<p>Body text</p>',
'partner_ids': []}).send_mail()
self.assertEqual(self.test_record.message_ids[0].body, '<p>Body text</p>')
self.assertEqual(self.test_record.message_ids[0].author_id, portal_user.partner_id)
ComposerPortal.with_context({
'default_composition_mode': 'comment',
'default_parent_id': self.test_record.message_ids.ids[0],
}).create({
'subject': 'Subject',
'body': '<p>Body text 2</p>'}).send_mail()
self.assertEqual(self.test_record.message_ids[0].body, '<p>Body text 2</p>')
self.assertEqual(self.test_record.message_ids[0].author_id, portal_user.partner_id)
@tagged('mail_composer')
class TestComposerWTpl(TestMailCommon, TestRecipients):
@classmethod
def setUpClass(cls):
super(TestComposerWTpl, cls).setUpClass()
cls.test_record = cls.env['mail.test.simple'].with_context(cls._test_context).create({'name': 'Test', 'email_from': 'ignasse@example.com'})
cls.user_employee.write({
'groups_id': [(4, cls.env.ref('base.group_partner_manager').id)],
})
cls._attachments = [{
'name': 'first.txt',
'datas': base64.b64encode(b'My first attachment'),
'res_model': 'res.partner',
'res_id': cls.user_admin.partner_id.id
}, {
'name': 'second.txt',
'datas': base64.b64encode(b'My second attachment'),
'res_model': 'res.partner',
'res_id': cls.user_admin.partner_id.id
}]
cls.email_1 = 'test1@example.com'
cls.email_2 = 'test2@example.com'
cls.email_3 = cls.partner_1.email
cls._create_template('mail.test.simple', {
'attachment_ids': [(0, 0, cls._attachments[0]), (0, 0, cls._attachments[1])],
'partner_to': '%s,%s' % (cls.partner_2.id, cls.user_admin.partner_id.id),
'email_to': '%s, %s' % (cls.email_1, cls.email_2),
'email_cc': '%s' % cls.email_3,
})
# admin should receive emails
cls.user_admin.write({'notification_type': 'email'})
# Force the attachments of the template to be in the natural order.
cls.email_template.invalidate_cache(['attachment_ids'], ids=cls.email_template.ids)
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_composer_w_template(self):
composer = self.env['mail.compose.message'].with_user(self.user_employee).with_context({
'default_composition_mode': 'comment',
'default_model': 'mail.test.simple',
'default_res_id': self.test_record.id,
'default_template_id': self.email_template.id,
}).create({'subject': 'Forget me subject', 'body': 'Dummy body'})
# perform onchange and send emails
values = composer.onchange_template_id(self.email_template.id, 'comment', self.test_record._name, self.test_record.id)['value']
composer.write(values)
with self.mock_mail_gateway(mail_unlink_sent=True):
composer.send_mail()
new_partners = self.env['res.partner'].search([('email', 'in', [self.email_1, self.email_2])])
self.assertMailMail(
[self.partner_1, self.partner_2, new_partners[0], new_partners[1], self.partner_admin],
'sent',
author=self.user_employee.partner_id,
mail_message=False,
check_mail_mail=False,
email_values={
'subject': 'About %s' % self.test_record.name,
'body_content': self.test_record.name,
'attachments': [
('first.txt', b'My first attachment', 'text/plain'),
('second.txt', b'My second attachment', 'text/plain')
]
}
)
def test_composer_template_onchange_attachments(self):
"""Tests that all attachments are added to the composer,
static attachments are not duplicated and while reports are re-generated,
and that intermediary attachments are dropped."""
composer = self.env['mail.compose.message'].with_context(default_attachment_ids=[]).create({})
report_template = self.env.ref('web.action_report_externalpreview')
template_1 = self.email_template.copy({
'report_template': report_template.id,
})
template_2 = self.email_template.copy({
'attachment_ids': False,
'report_template': report_template.id,
})
onchange_templates = [template_1, template_2, template_1, False]
attachments_onchange = [composer.attachment_ids]
# template_1 has two static attachments and one dynamically generated report,
# template_2 only has the report, so we should get 3, 1, 3 attachments
attachment_numbers = [0, 3, 1, 3, 0]
for template in onchange_templates:
onchange = composer.onchange_template_id(
template.id if template else False, 'comment', self.test_record._name, self.test_record.id
)
composer.update(onchange['value'])
attachments_onchange.append(composer.attachment_ids)
self.assertEqual(
[len(attachments) for attachments in attachments_onchange],
attachment_numbers,
)
self.assertTrue(
len(attachments_onchange[1] & attachments_onchange[3]) == 2,
"The two static attachments on the template should be common to the two onchanges"
)
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_composer_w_template_mass_mailing(self):
test_record_2 = self.env['mail.test.simple'].with_context(self._test_context).create({'name': 'Test2', 'email_from': 'laurie.poiret@example.com'})
composer = self.env['mail.compose.message'].with_user(self.user_employee).with_context({
'default_composition_mode': 'mass_mail',
# 'default_notify': True,
'default_notify': False,
'default_model': 'mail.test.simple',
'default_res_id': self.test_record.id,
'default_template_id': self.email_template.id,
'active_ids': [self.test_record.id, test_record_2.id]
}).create({})
values = composer.onchange_template_id(self.email_template.id, 'mass_mail', 'mail.test.simple', self.test_record.id)['value']
composer.write(values)
with self.mock_mail_gateway(mail_unlink_sent=True):
composer.send_mail()
new_partners = self.env['res.partner'].search([('email', 'in', [self.email_1, self.email_2])])
# hack to use assertEmails
self._mails_record1 = [dict(mail) for mail in self._mails if '%s-%s' % (self.test_record.id, self.test_record._name) in mail['message_id']]
self._mails_record2 = [dict(mail) for mail in self._mails if '%s-%s' % (test_record_2.id, test_record_2._name) in mail['message_id']]
self._mails = self._mails_record1
self.assertMailMail(
[self.partner_1, self.partner_2, new_partners[0], new_partners[1], self.partner_admin],
'sent',
author=self.user_employee.partner_id,
mail_message=False,
check_mail_mail=False,
email_values={
'subject': 'About %s' % self.test_record.name,
'body_content': self.test_record.name,
'attachments': [
('first.txt', b'My first attachment', 'text/plain'),
('second.txt', b'My second attachment', 'text/plain')
]
}
)
self._mails = self._mails_record2
self.assertMailMail(
[self.partner_1, self.partner_2, new_partners[0], new_partners[1], self.partner_admin],
'sent',
author=self.user_employee.partner_id,
mail_message=False,
check_mail_mail=False,
email_values={
'subject': 'About %s' % test_record_2.name,
'body_content': test_record_2.name,
'attachments': [
('first.txt', b'My first attachment', 'text/plain'),
('second.txt', b'My second attachment', 'text/plain')
]
}
)
message_1 = self.test_record.message_ids[0]
message_2 = test_record_2.message_ids[0]
# messages effectively posted
self.assertEqual(message_1.subject, 'About %s' % self.test_record.name)
self.assertEqual(message_2.subject, 'About %s' % test_record_2.name)
self.assertIn(self.test_record.name, message_1.body)
self.assertIn(test_record_2.name, message_2.body)
def test_composer_template_save(self):
self.env['mail.compose.message'].with_context({
'default_composition_mode': 'comment',
'default_model': 'mail.test.simple',
'default_res_id': self.test_record.id,
}).create({
'subject': 'Forget me subject',
'body': '<p>Dummy body</p>'
}).save_as_template()
# Test: email_template subject, body_html, model
last_template = self.env['mail.template'].search([('model', '=', 'mail.test.simple'), ('subject', '=', 'Forget me subject')], limit=1)
self.assertEqual(last_template.body_html, '<p>Dummy body</p>', 'email_template incorrect body_html')
+6 -4
View File
@@ -113,10 +113,12 @@ class TestMessagePost(TestMailCommon, TestRecipients):
# subscribe second employee to the group to test notifications
self.test_record.message_subscribe(partner_ids=[self.user_admin.partner_id.id])
with self.assertSinglePostNotifications([
{'partner': self.partner_1, 'type': 'email'},
{'partner': self.partner_2, 'type': 'email'},
{'partner': self.partner_admin, 'type': 'email'}], {'content': _body}, mail_unlink_sent=True):
with self.assertSinglePostNotifications(
[{'partner': self.partner_1, 'type': 'email'},
{'partner': self.partner_2, 'type': 'email'},
{'partner': self.partner_admin, 'type': 'email'}],
{'content': _body},
mail_unlink_sent=True):
msg = self.test_record.with_user(self.user_employee).message_post(
body=_body, subject=_subject,
message_type='comment', subtype_xmlid='mail.mt_comment',
@@ -75,6 +75,6 @@ class TestMassMailing(TestMailFullCommon):
'http://localhost:%s/mailing/%s/view' % (config['http_port'], mailing.id),
email['body'])
self.assertMailTraces([recipient_info], mailing, recipient, check_mail=True)
self.assertMailTraces([recipient_info], mailing, recipient, check_mail=True, author=self.env.user.partner_id)
self.assertEqual(mailing.ignored, 4)
+3 -1
View File
@@ -14,7 +14,9 @@ class TestMassMailCommon(MassMailCommon, TestMailCommon):
def setUpClass(cls):
super(TestMassMailCommon, cls).setUpClass()
cls.mailing_bl = cls.env['mailing.mailing'].create({
# enforce last update by user_marketing to match _process_mass_mailing_queue
# taking last writer as user running a batch
cls.mailing_bl = cls.env['mailing.mailing'].with_user(cls.user_marketing).create({
'name': 'SourceName',
'subject': 'MailingSubject',
'body_html': '<p>Hello ${object.name}</p>',
@@ -35,7 +35,8 @@ class TestAutoBlacklist(common.TestMassMailCommon):
self.assertMailTraces(
[{'email': 'test.record.00@test.example.com'}],
mailing, target, check_mail=True
mailing, target,
check_mail=True
)
# call bounced