Skip to content
Snippets Groups Projects
Commit ab42c572 authored by Thibault Delavallée's avatar Thibault Delavallée
Browse files

[IMP] test_mail{..mailing}: improve tooling (recipient / email check)

Improve low-level checks of recipients in mail asserts. Sometimes 'email_to'
cannot be easily deduced from given input (partners, records customers, ...)
when some record -> email transformations are involved e.g. when dealing
with multiple emails input, double encapsulation, ...

Those tools are about to be used in upcoming tests and fixes.

Task-3438381 (TestMail: backport tools)
Prepares Task-2612945 (Mail: Defensive email formatting)

Part-of: odoo/odoo#129508
parent 66d79551
No related branches found
No related tags found
No related merge requests found
......@@ -257,7 +257,13 @@ class MockEmail(common.BaseCase):
if mail.id == mail_id:
break
else:
raise AssertionError('mail.mail not found for ID %s' % (mail_id))
debug_info = '\n'.join(
f'From: {mail.author_id} ({mail.email_from}) - ID {mail.id} (State: {mail.state})'
for mail in filtered
)
raise AssertionError(
f'mail.mail not found for ID {mail_id} / message {mail_message} / status {status} / author {author}\n{debug_info}'
)
return mail
def _find_mail_mail_wpartners(self, recipients, status, mail_message=None, author=None):
......@@ -275,7 +281,14 @@ 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 / author %s' % (mail_message, status, recipients.ids, author))
debug_info = '\n'.join(
f'From: {mail.author_id} ({mail.email_from}) - To: {sorted(mail.recipient_ids.ids)} (State: {mail.state})'
for mail in filtered
)
recipients_info = f'Missing: {[r.name for r in recipients if r.id not in filtered.recipient_ids.ids]}'
raise AssertionError(
f'mail.mail not found for message {mail_message} / status {status} / recipients {sorted(recipients.ids)} / author {author}\n{recipients_info}\n{debug_info}'
)
return mail
def _find_mail_mail_wemail(self, email_to, status, mail_message=None, author=None):
......@@ -293,7 +306,13 @@ class MockEmail(common.BaseCase):
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])))
debug_info = '\n'.join(
f'From: {mail.author_id} ({mail.email_from}) - To: {mail.email_to} / {sorted(mail.recipient_ids.mapped("email"))} (State: {mail.state})'
for mail in filtered
)
raise AssertionError(
f'mail.mail not found for message {mail_message} / status {status} / email_to {email_to} / author {author}\n{debug_info}'
)
return mail
def _find_mail_mail_wrecord(self, record, status=None, mail_message=None, author=None):
......@@ -306,7 +325,13 @@ class MockEmail(common.BaseCase):
if mail.model == record._name and mail.res_id == record.id:
break
else:
raise AssertionError('mail.mail not found for record %s in %s' % (record, repr([m.email_to for m in self._new_mails])))
debug_info = '\n'.join(
f'From: {mail.author_id} ({mail.email_from}) - Model{mail.model} / ResId {mail.res_id} (State: {mail.state})'
for mail in filtered
)
raise AssertionError(
f'mail.mail not found for message {mail_message} / status {status} / record {record.model}, {record.id} / author {author}\n{debug_info}'
)
return mail
def _find_sent_email(self, email_from, emails_to, subject=None):
......@@ -330,8 +355,8 @@ class MockEmail(common.BaseCase):
# GATEWAY ASSERTS
# ------------------------------------------------------------
def _assertMailMail(self, mail, recipients_list,
status, author=None,
def _assertMailMail(self, mail, recipients_list, status,
email_to_recipients=None, author=None,
content=None, fields_values=None, email_values=None):
""" Assert mail.mail record values and maybe related emails. Allow
asserting their content. Records to check are the one generated when
......@@ -343,6 +368,9 @@ class MockEmail(common.BaseCase):
``_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 email_to_recipients: used for assertSentEmail to find email based
on 'email_to' when doing the match directly based on recipients_list
being partners it nos easy (e.g. multi emails, ...);
: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 /
......@@ -364,15 +392,20 @@ class MockEmail(common.BaseCase):
'Mail: expected %s for %s, got %s' % (fvalue, fname, mail[fname])
)
if status == 'sent':
for recipient in recipients_list:
if email_to_recipients:
recipients = email_to_recipients # already formatted
else:
recipients = [[r] for r in recipients_list] # one partner -> list of a single email
for recipient in recipients:
with self.subTest(recipient=recipient):
self.assertSentEmail(
email_values['email_from'] if email_values and email_values.get('email_from') else author,
[recipient],
recipient,
**(email_values or {})
)
def assertMailMail(self, recipients, status,
email_to_recipients=None,
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. This
......@@ -388,13 +421,15 @@ class MockEmail(common.BaseCase):
found_mail = self._find_mail_mail_wpartners(recipients, status, mail_message=mail_message, author=author)
self.assertTrue(bool(found_mail))
self._assertMailMail(
found_mail, recipients,
status, author=author,
content=content, fields_values=fields_values, email_values=email_values,
found_mail, recipients, status,
email_to_recipients=email_to_recipients,
author=author, content=content,
fields_values=fields_values, email_values=email_values,
)
return found_mail
def assertMailMailWEmails(self, emails, status,
email_to_recipients=None,
mail_message=None, author=None,
content=None, fields_values=None, email_values=None):
""" Assert mail.mail records are created and maybe sent as emails. This
......@@ -411,13 +446,15 @@ class MockEmail(common.BaseCase):
found_mail = self._find_mail_mail_wemail(email_to, status, mail_message=mail_message, author=author)
self.assertTrue(bool(found_mail))
self._assertMailMail(
found_mail, [email_to],
status, author=author,
content=content, fields_values=fields_values, email_values=email_values,
found_mail, [email_to], status,
email_to_recipients=email_to_recipients,
author=author, content=content,
fields_values=fields_values, email_values=email_values,
)
return found_mail
def assertMailMailWRecord(self, record, recipients, status,
email_to_recipients=None,
mail_message=None, author=None,
content=None, fields_values=None, email_values=None):
""" Assert mail.mail records are created and maybe sent as emails. This
......@@ -433,13 +470,15 @@ class MockEmail(common.BaseCase):
found_mail = self._find_mail_mail_wrecord(record, mail_message=mail_message, author=author)
self.assertTrue(bool(found_mail))
self._assertMailMail(
found_mail, recipients,
status, author=author,
content=content, fields_values=fields_values, email_values=email_values,
found_mail, recipients, status,
email_to_recipients=email_to_recipients,
author=author, content=content,
fields_values=fields_values, email_values=email_values,
)
return found_mail
def assertMailMailWId(self, mail_id, status,
email_to_recipients=None,
author=None,
content=None, fields_values=None):
""" Assert mail.mail records are created and maybe sent as emails. Allow
......@@ -455,8 +494,10 @@ class MockEmail(common.BaseCase):
self.assertTrue(bool(found_mail))
self._assertMailMail(
found_mail, [], # generally used when recipients are Falsy
status, author=author,
content=content, fields_values=fields_values,
status,
email_to_recipients=email_to_recipients,
author=author, content=content,
fields_values=fields_values,
)
return found_mail
......@@ -507,7 +548,7 @@ class MockEmail(common.BaseCase):
"""
direct_check = ['attachments', 'body_alternative', 'email_from', 'references', 'reply_to', 'subject']
content_check = ['body_alternative_content', 'body_content', 'references_content']
other_check = ['body', 'attachments_info']
other_check = ['body', 'attachments_info', 'email_to']
expected = {}
for fname in direct_check + content_check + other_check:
......@@ -522,12 +563,15 @@ class MockEmail(common.BaseCase):
else:
expected['email_from'] = author
email_to_list = []
for email_to in recipients:
if isinstance(email_to, self.env['res.partner'].__class__):
email_to_list.append(formataddr((email_to.name, email_to.email)))
else:
email_to_list.append(email_to)
if 'email_to' in values:
email_to_list = values['email_to']
else:
email_to_list = []
for email_to in recipients:
if isinstance(email_to, self.env['res.partner'].__class__):
email_to_list.append(formataddr((email_to.name, email_to.email or 'False')))
else:
email_to_list.append(email_to)
expected['email_to'] = email_to_list
# fetch mail
......
......@@ -32,8 +32,8 @@ class MassMailCase(MailCase, MockLinkTracker):
)
def assertMailTraces(self, recipients_info, mailing, records,
check_mail=True, sent_unlink=False, author=None,
mail_links_info=None):
check_mail=True, sent_unlink=False,
author=None, mail_links_info=None):
""" Check content of traces. Traces are fetched based on a given mailing
and records. Their content is compared to recipients_info structure that
holds expected information. Links content may be checked, notably to
......@@ -48,6 +48,7 @@ class MassMailCase(MailCase, MockLinkTracker):
'record: linked record,
# MAIL.MAIL
'content': optional content that should be present in mail.mail body_html;
'email_to_recipients': optional, see '_assertMailMail';
'failure_type': optional failure reason;
}, { ... }]
......@@ -79,6 +80,12 @@ class MassMailCase(MailCase, MockLinkTracker):
('mass_mailing_id', 'in', mailing.ids),
('res_id', 'in', records.ids)
])
debug_info = '\n'.join(
(
f'Trace: to {t.email} - state {t.state}'
for t in traces
)
)
# ensure trace coherency
self.assertTrue(all(s.model == records._name for s in traces))
......@@ -90,6 +97,7 @@ class MassMailCase(MailCase, MockLinkTracker):
for recipient_info, link_info, record in zip(recipients_info, mail_links_info, records):
partner = recipient_info.get('partner', self.env['res.partner'])
email = recipient_info.get('email')
email_to_recipients = recipient_info.get('email_to_recipients')
state = recipient_info.get('state', 'sent')
record = record or recipient_info.get('record')
content = recipient_info.get('content')
......@@ -103,7 +111,9 @@ class MassMailCase(MailCase, MockLinkTracker):
)
self.assertTrue(
len(recipient_trace) == 1,
'MailTrace: email %s (recipient %s, state: %s, record: %s): found %s records (1 expected)' % (email, partner, state, record, len(recipient_trace))
'MailTrace: email %s (recipient %s, state: %s, record: %s): found %s records (1 expected)\n%s' % (
email, partner, state, record,
len(recipient_trace), debug_info)
)
self.assertTrue(bool(recipient_trace.mail_mail_id_int))
if 'failure_type' in recipient_info or state in ('ignored', 'exception', 'canceled', 'bounced'):
......@@ -122,12 +132,30 @@ class MassMailCase(MailCase, MockLinkTracker):
if state == 'sent' and sent_unlink:
self.assertSentEmail(author, [partner])
else:
self.assertMailMail(partner, state_mapping[state], author=author, content=content, fields_values=fields_values)
self.assertMailMail(
partner, state_mapping[state],
author=author,
content=content,
email_to_recipients=email_to_recipients,
fields_values=fields_values,
)
# specific if email is False -> could have troubles finding it if several falsy traces
elif not email and state in ('ignored', 'canceled', 'bounced'):
self.assertMailMailWId(recipient_trace.mail_mail_id_int, state_mapping[state], author=author, content=content, fields_values=fields_values)
self.assertMailMailWId(
recipient_trace.mail_mail_id_int, state_mapping[state],
author=author,
content=content,
email_to_recipients=email_to_recipients,
fields_values=fields_values,
)
else:
self.assertMailMailWEmails([email], state_mapping[state], author=author, content=content, fields_values=fields_values)
self.assertMailMailWEmails(
[email], state_mapping[state],
author=author,
content=content,
email_to_recipients=email_to_recipients,
fields_values=fields_values,
)
if link_info:
trace_mail = self._find_mail_mail_wrecord(record)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment