mirror of
https://github.com/bringout/oca-ocb-mail.git
synced 2026-04-20 16:22:08 +02:00
19.0 vanilla
This commit is contained in:
parent
5df8c07b59
commit
daa394e8b0
2114 changed files with 564841 additions and 299642 deletions
|
|
@ -8,7 +8,7 @@ import werkzeug
|
|||
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo import tools
|
||||
from odoo.tools import email_normalize, mail
|
||||
from odoo.addons.link_tracker.tests.common import MockLinkTracker
|
||||
from odoo.addons.mail.tests.common import MailCase, MailCommon, mail_new_test_user
|
||||
from odoo.sql_db import Cursor
|
||||
|
|
@ -36,7 +36,7 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
)
|
||||
|
||||
def assertMailTraces(self, recipients_info, mailing, records,
|
||||
check_mail=True, sent_unlink=False,
|
||||
check_mail=True, is_cancel_not_sent=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
|
||||
|
|
@ -46,16 +46,22 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
|
||||
:param recipients_info: list[{
|
||||
# TRACE
|
||||
'email': (normalized) email used when sending email and stored on
|
||||
trace. May be empty, computed based on partner;
|
||||
'failure_type': optional failure type;
|
||||
'failure_reason': optional failure reason;
|
||||
'partner': res.partner record (may be empty),
|
||||
'email': email used when sending email (may be empty, computed based on partner),
|
||||
'trace_status': outgoing / sent / open / reply / bounce / error / cancel (sent by default),
|
||||
'record: linked record,
|
||||
'trace_status': outgoing / sent / open / reply / bounce / error / cancel (sent by default),
|
||||
# MAIL.MAIL
|
||||
'content': optional content that should be present in mail.mail body_html;
|
||||
'email_to_mail': optional email used for the mail, when different from the
|
||||
one stored on the trace itself;
|
||||
'email_to_recipients': optional, see '_assertMailMail';
|
||||
'failure_type': optional failure reason;
|
||||
one stored on the trace itself (see 'email_to' in assertMailMail);
|
||||
'email_to_recipients': optional email used ofr the outgoing email,
|
||||
see 'assertSentEmail';
|
||||
'failure_type': propagated from trace;
|
||||
'failure_reason': propagated from trace;
|
||||
'mail_values': other mail.mail values for assertMailMail;
|
||||
}, { ... }]
|
||||
|
||||
:param mailing: a mailing.mailing record from which traces have been
|
||||
|
|
@ -63,12 +69,15 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
:param records: records given to mailing that generated traces. It is
|
||||
used notably to find traces using their IDs;
|
||||
:param check_mail: if True, also check mail.mail records that should be
|
||||
linked to traces;
|
||||
linked to traces unless not sent (trace_status == 'cancel');
|
||||
:param is_cancel_not_sent: if True, also check that no mail.mail/mail.message
|
||||
related to "cancel trace" have been created and disable check_mail for those.
|
||||
:param sent_unlink: it True, sent mail.mail are deleted and we check gateway
|
||||
output result instead of actual mail.mail records;
|
||||
:param mail_links_info: if given, should follow order of ``recipients_info``
|
||||
and give details about links. See ``assertLinkShortenedHtml`` helper for
|
||||
more details about content to give;
|
||||
more details about content to give.
|
||||
Not tested for mail with trace status == 'cancel' if is_cancel_not_sent;
|
||||
:param author: author of sent mail.mail;
|
||||
"""
|
||||
# map trace state to email state
|
||||
|
|
@ -86,10 +95,8 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
('res_id', 'in', records.ids)
|
||||
])
|
||||
debug_info = '\n'.join(
|
||||
(
|
||||
f'Trace: to {t.email} - state {t.trace_status} - res_id {t.res_id}'
|
||||
for t in traces
|
||||
)
|
||||
f'Trace: to {t.email} - state {t.trace_status} - res_id {t.res_id}'
|
||||
for t in traces
|
||||
)
|
||||
|
||||
# ensure trace coherency
|
||||
|
|
@ -100,15 +107,36 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
if not mail_links_info:
|
||||
mail_links_info = [None] * len(recipients_info)
|
||||
for recipient_info, link_info, record in zip(recipients_info, mail_links_info, records):
|
||||
# check input
|
||||
invalid = set(recipient_info.keys()) - {
|
||||
'content',
|
||||
# email_to
|
||||
'email', 'email_to_mail', 'email_to_recipients',
|
||||
# mail.mail
|
||||
'mail_values',
|
||||
# email
|
||||
'email_values',
|
||||
# trace
|
||||
'partner', 'record', 'trace_status',
|
||||
'failure_type', 'failure_reason',
|
||||
}
|
||||
if invalid:
|
||||
raise AssertionError(f"assertMailTraces: invalid input {invalid}")
|
||||
|
||||
# recipients
|
||||
partner = recipient_info.get('partner', self.env['res.partner'])
|
||||
email = recipient_info.get('email')
|
||||
email_to_mail = recipient_info.get('email_to_mail') or email
|
||||
email_to_recipients = recipient_info.get('email_to_recipients')
|
||||
status = recipient_info.get('trace_status', 'sent')
|
||||
record = record or recipient_info.get('record')
|
||||
content = recipient_info.get('content')
|
||||
if email is None and partner:
|
||||
email = partner.email_normalized
|
||||
email_to_mail = recipient_info.get('email_to_mail') or email
|
||||
email_to_recipients = recipient_info.get('email_to_recipients')
|
||||
# trace
|
||||
failure_type = recipient_info.get('failure_type')
|
||||
failure_reason = recipient_info.get('failure_reason')
|
||||
status = recipient_info.get('trace_status', 'sent')
|
||||
# content
|
||||
content = recipient_info.get('content')
|
||||
record = record or recipient_info.get('record')
|
||||
|
||||
recipient_trace = traces.filtered(
|
||||
lambda t: (t.email == email or (not email and not t.email)) and \
|
||||
|
|
@ -121,22 +149,38 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
email, partner, status, record,
|
||||
len(recipient_trace), debug_info)
|
||||
)
|
||||
self.assertTrue(bool(recipient_trace.mail_mail_id_int))
|
||||
mail_not_created = is_cancel_not_sent and recipient_trace.trace_status == 'cancel'
|
||||
self.assertTrue(mail_not_created or bool(recipient_trace.mail_mail_id_int))
|
||||
if 'failure_type' in recipient_info or status in ('error', 'cancel', 'bounce'):
|
||||
self.assertEqual(recipient_trace.failure_type, recipient_info['failure_type'])
|
||||
self.assertEqual(recipient_trace.failure_type, failure_type)
|
||||
if 'failure_reason' in recipient_info:
|
||||
self.assertEqual(recipient_trace.failure_reason, failure_reason)
|
||||
if mail_not_created:
|
||||
self.assertFalse(recipient_trace.mail_mail_id_int)
|
||||
self.assertFalse(self.env['mail.mail'].sudo().search(
|
||||
[('model', '=', record._name), ('res_id', '=', record.id),
|
||||
('id', 'in', self._new_mails.ids)]))
|
||||
self.assertFalse(self.env['mail.message'].sudo().search(
|
||||
[('model', '=', record._name), ('res_id', '=', record.id),
|
||||
('id', 'in', self._new_mails.mail_message_id.ids)]))
|
||||
|
||||
if check_mail:
|
||||
if check_mail and not mail_not_created:
|
||||
if author is None:
|
||||
author = self.env.user.partner_id
|
||||
|
||||
# mail.mail specific values to check
|
||||
email_values = recipient_info.get('email_values', {})
|
||||
fields_values = {'mailing_id': mailing}
|
||||
if recipient_info.get('mail_values'):
|
||||
fields_values.update(recipient_info['mail_values'])
|
||||
if 'failure_type' in recipient_info:
|
||||
fields_values['failure_type'] = failure_type
|
||||
if 'failure_reason' in recipient_info:
|
||||
fields_values['failure_reason'] = recipient_info['failure_reason']
|
||||
fields_values['failure_reason'] = failure_reason
|
||||
if 'email_to_mail' in recipient_info:
|
||||
fields_values['email_to'] = recipient_info['email_to_mail']
|
||||
if partner:
|
||||
fields_values['recipient_ids'] = partner
|
||||
|
||||
# specific for partner: email_formatted is used
|
||||
if partner:
|
||||
|
|
@ -149,6 +193,7 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
content=content,
|
||||
email_to_recipients=email_to_recipients,
|
||||
fields_values=fields_values,
|
||||
email_values=email_values,
|
||||
)
|
||||
# specific if email is False -> could have troubles finding it if several falsy traces
|
||||
elif not email and status in ('cancel', 'bounce'):
|
||||
|
|
@ -158,6 +203,7 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
content=content,
|
||||
email_to_recipients=email_to_recipients,
|
||||
fields_values=fields_values,
|
||||
email_values=email_values,
|
||||
)
|
||||
else:
|
||||
self.assertMailMailWEmails(
|
||||
|
|
@ -166,9 +212,10 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
content=content,
|
||||
email_to_recipients=email_to_recipients,
|
||||
fields_values=fields_values,
|
||||
email_values=email_values,
|
||||
)
|
||||
|
||||
if link_info:
|
||||
if link_info and not mail_not_created:
|
||||
trace_mail = self._find_mail_mail_wrecord(record)
|
||||
for (anchor_id, url, is_shortened, add_link_params) in link_info:
|
||||
link_params = {'utm_medium': 'Email', 'utm_source': mailing.name}
|
||||
|
|
@ -184,7 +231,7 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
# TOOLS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def gateway_mail_bounce(self, mailing, record, bounce_base_values=None):
|
||||
def gateway_mail_trace_bounce(self, mailing, record, bounce_base_values=None):
|
||||
""" Generate a bounce at mailgateway level.
|
||||
|
||||
:param mailing: a ``mailing.mailing`` record on which we find a trace
|
||||
|
|
@ -192,27 +239,31 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
:param record: record which should bounce;
|
||||
:param bounce_base_values: optional values given to routing;
|
||||
"""
|
||||
record_email = record[record._primary_email]
|
||||
trace = mailing.mailing_trace_ids.filtered(
|
||||
lambda t: t.model == record._name and t.res_id == record.id
|
||||
)
|
||||
self.assertTrue(trace)
|
||||
self.assertEqual(trace.email, email_normalize(record_email))
|
||||
|
||||
parsed_bounce_values = {
|
||||
'email_from': 'some.email@external.example.com', # TDE check: email_from -> trace email ?
|
||||
'to': 'bounce@test.example.com', # TDE check: bounce alias ?
|
||||
'message_id': tools.generate_tracking_message_id('MailTest'),
|
||||
'message_id': mail.generate_tracking_message_id('MailTest'),
|
||||
'bounced_partner': self.env['res.partner'].sudo(),
|
||||
'bounced_message': self.env['mail.message'].sudo()
|
||||
'bounced_message': self.env['mail.message'].sudo(),
|
||||
'body': 'This is the bounce email',
|
||||
}
|
||||
if bounce_base_values:
|
||||
parsed_bounce_values.update(bounce_base_values)
|
||||
parsed_bounce_values.update({
|
||||
'bounced_email': trace.email,
|
||||
'bounced_msg_id': [trace.message_id],
|
||||
'bounced_msg_ids': [trace.message_id],
|
||||
})
|
||||
self.env['mail.thread']._routing_handle_bounce(False, parsed_bounce_values)
|
||||
return trace
|
||||
|
||||
def gateway_mail_click(self, mailing, record, click_label):
|
||||
def gateway_mail_trace_click(self, mailing, record, click_label):
|
||||
""" Simulate a click on a sent email.
|
||||
|
||||
:param mailing: a ``mailing.mailing`` record on which we find a trace
|
||||
|
|
@ -220,13 +271,16 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
:param record: record which should click;
|
||||
:param click_label: label of link on which we should click;
|
||||
"""
|
||||
record_email = record[record._primary_email]
|
||||
trace = mailing.mailing_trace_ids.filtered(
|
||||
lambda t: t.model == record._name and t.res_id == record.id
|
||||
)
|
||||
self.assertTrue(trace)
|
||||
self.assertEqual(trace.email, email_normalize(record_email))
|
||||
|
||||
email = self._find_sent_mail_wemail(trace.email)
|
||||
email = self._find_sent_email_wemail(record_email)
|
||||
self.assertTrue(bool(email))
|
||||
for (_url_href, link_url, _dummy, label) in re.findall(tools.HTML_TAG_URL_REGEX, email['body']):
|
||||
for (_url_href, link_url, _dummy, label) in re.findall(mail.HTML_TAG_URL_REGEX, email['body']):
|
||||
if label == click_label and '/r/' in link_url: # shortened link, like 'http://localhost:8069/r/LBG/m/53'
|
||||
parsed_url = werkzeug.urls.url_parse(link_url)
|
||||
path_items = parsed_url.path.split('/')
|
||||
|
|
@ -244,7 +298,7 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
raise AssertionError('url %s not found in mailing %s for record %s' % (click_label, mailing, record))
|
||||
return trace
|
||||
|
||||
def gateway_mail_open(self, mailing, record):
|
||||
def gateway_mail_trace_open(self, mailing, record):
|
||||
""" Simulate opening an email through blank.gif icon access. As we
|
||||
don't want to use the whole Http layer just for that we will just
|
||||
call 'set_opened()' on trace, until having a better option.
|
||||
|
|
@ -256,9 +310,28 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
trace = mailing.mailing_trace_ids.filtered(
|
||||
lambda t: t.model == record._name and t.res_id == record.id
|
||||
)
|
||||
self.assertTrue(trace)
|
||||
|
||||
trace.set_opened()
|
||||
return trace
|
||||
|
||||
def gateway_mail_trace_reply(self, mailing, record):
|
||||
""" Simulate replying to an email. As we don't want to use the whole
|
||||
mail and gateway layer just for that we will just call 'set_replied()'
|
||||
on trace.
|
||||
|
||||
:param mailing: a ``mailing.mailing`` record on which we find a trace
|
||||
to open;
|
||||
:param record: record which should open;
|
||||
"""
|
||||
trace = mailing.mailing_trace_ids.filtered(
|
||||
lambda t: t.model == record._name and t.res_id == record.id
|
||||
)
|
||||
self.assertTrue(trace)
|
||||
|
||||
trace.set_replied()
|
||||
return trace
|
||||
|
||||
@classmethod
|
||||
def _create_bounce_trace(cls, mailing, records, dt=None):
|
||||
if dt is None:
|
||||
|
|
@ -300,23 +373,35 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
def _create_mailing_list(cls):
|
||||
""" Shortcut to create mailing lists. Currently hardcoded, maybe evolve
|
||||
in a near future. """
|
||||
cls.mailing_list_1 = cls.env['mailing.list'].with_context(cls._test_context).create({
|
||||
'name': 'List1',
|
||||
'contact_ids': [
|
||||
(0, 0, {'name': 'Déboulonneur', 'email': 'fleurus@example.com'}),
|
||||
(0, 0, {'name': 'Gorramts', 'email': 'gorramts@example.com'}),
|
||||
(0, 0, {'name': 'Ybrant', 'email': 'ybrant@example.com'}),
|
||||
]
|
||||
})
|
||||
cls.mailing_list_2 = cls.env['mailing.list'].with_context(cls._test_context).create({
|
||||
'name': 'List2',
|
||||
'contact_ids': [
|
||||
(0, 0, {'name': 'Gilberte', 'email': 'gilberte@example.com'}),
|
||||
(0, 0, {'name': 'Gilberte En Mieux', 'email': 'gilberte@example.com'}),
|
||||
(0, 0, {'name': 'Norbert', 'email': 'norbert@example.com'}),
|
||||
(0, 0, {'name': 'Ybrant', 'email': 'ybrant@example.com'}),
|
||||
]
|
||||
})
|
||||
cls.mailing_list_1, cls.mailing_list_2, cls.mailing_list_3, cls.mailing_list_4 = cls.env['mailing.list'].with_context(cls._test_context).create([
|
||||
{
|
||||
'contact_ids': [
|
||||
(0, 0, {'name': 'Déboulonneur', 'email': 'fleurus@example.com'}),
|
||||
(0, 0, {'name': 'Gorramts', 'email': 'gorramts@example.com'}),
|
||||
(0, 0, {'name': 'Ybrant', 'email': 'ybrant@example.com'}),
|
||||
],
|
||||
'name': 'List1',
|
||||
'is_public': True,
|
||||
}, {
|
||||
'contact_ids': [
|
||||
(0, 0, {'name': 'Gilberte', 'email': 'gilberte@example.com'}),
|
||||
(0, 0, {'name': 'Gilberte En Mieux', 'email': 'gilberte@example.com'}),
|
||||
(0, 0, {'name': 'Norbert', 'email': 'norbert@example.com'}),
|
||||
(0, 0, {'name': 'Ybrant', 'email': 'ybrant@example.com'}),
|
||||
],
|
||||
'name': 'List2',
|
||||
'is_public': True,
|
||||
}, {
|
||||
'contact_ids': [
|
||||
(0, 0, {'name': 'Déboulonneur', 'email': 'fleurus@example.com'}),
|
||||
],
|
||||
'name': 'List3',
|
||||
'is_public': True,
|
||||
}, {
|
||||
'name': 'List4',
|
||||
}
|
||||
])
|
||||
cls.mailing_list_3.subscription_ids[0].opt_out = True
|
||||
|
||||
@classmethod
|
||||
def _create_mailing_list_of_x_contacts(cls, contacts_nbr):
|
||||
|
|
@ -325,8 +410,11 @@ class MassMailCase(MailCase, MockLinkTracker):
|
|||
return cls.env['mailing.list'].with_context(cls._test_context).create({
|
||||
'name': 'Test List',
|
||||
'contact_ids': [
|
||||
(0, 0, {'name': 'Contact %s' % i, 'email': 'contact%s@example.com' % i})
|
||||
for i in range(contacts_nbr)
|
||||
(0, 0, {
|
||||
'name': f'Contact %{idx}',
|
||||
'email': f'contact%{idx}@example.com'
|
||||
})
|
||||
for idx in range(contacts_nbr)
|
||||
],
|
||||
})
|
||||
|
||||
|
|
@ -337,10 +425,13 @@ class MassMailCommon(MailCommon, MassMailCase):
|
|||
def setUpClass(cls):
|
||||
super(MassMailCommon, cls).setUpClass()
|
||||
|
||||
cls.user_marketing = mail_new_test_user(
|
||||
cls.env, login='user_marketing',
|
||||
cls.user_marketing, cls.user_marketing_1 = [mail_new_test_user(
|
||||
cls.env,
|
||||
groups='base.group_user,base.group_partner_manager,mass_mailing.group_mass_mailing_user',
|
||||
name='Martial Marketing', signature='--\nMartial')
|
||||
login=f'user_marketing{suffix}',
|
||||
name=f'Martial Marketing{suffix}',
|
||||
signature=f'--\nMartial{suffix}',
|
||||
) for suffix in ('', '_1')]
|
||||
|
||||
cls.email_reply_to = 'MyCompany SomehowAlias <test.alias@test.mycompany.com>'
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from odoo.addons.mass_mailing.tests.common import MassMailCommon
|
||||
from odoo.tests import users, tagged
|
||||
from odoo.tests import Form, users, tagged
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tests.common import Form
|
||||
from odoo import fields
|
||||
|
||||
|
||||
|
|
@ -54,7 +53,7 @@ class TestMailingABTesting(TestMailingABTestingCommon):
|
|||
self.ab_testing_mailing_2.mailing_trace_ids[:15].set_opened()
|
||||
self.ab_testing_mailing_ids.invalidate_recordset()
|
||||
|
||||
self.assertEqual(self.ab_testing_mailing_1.opened_ratio, 66)
|
||||
self.assertEqual(self.ab_testing_mailing_1.opened_ratio, 66.67)
|
||||
self.assertEqual(self.ab_testing_mailing_2.opened_ratio, 50)
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
|
|
@ -86,10 +85,10 @@ class TestMailingABTesting(TestMailingABTestingCommon):
|
|||
self.ab_testing_mailing_2.mailing_trace_ids[:15].set_opened()
|
||||
self.ab_testing_mailing_ids.invalidate_recordset()
|
||||
|
||||
self.assertEqual(self.ab_testing_mailing_1.opened_ratio, 66)
|
||||
self.assertEqual(self.ab_testing_mailing_1.opened_ratio, 66.67)
|
||||
self.assertEqual(self.ab_testing_mailing_2.opened_ratio, 50)
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
with self.mock_mail_gateway(), self.enter_registry_test_mode():
|
||||
self.env.ref('mass_mailing.ir_cron_mass_mailing_ab_testing').sudo().method_direct_trigger()
|
||||
self.ab_testing_mailing_ids.invalidate_recordset()
|
||||
winner_mailing = self.ab_testing_campaign.mailing_mail_ids.filtered(lambda mailing: mailing.ab_testing_pc == 100)
|
||||
|
|
@ -169,7 +168,7 @@ class TestMailingABTesting(TestMailingABTestingCommon):
|
|||
self.ab_testing_mailing_2.mailing_trace_ids[:15].set_opened()
|
||||
self.ab_testing_mailing_ids.invalidate_recordset()
|
||||
|
||||
self.assertEqual(self.ab_testing_mailing_1.opened_ratio, 66)
|
||||
self.assertEqual(self.ab_testing_mailing_1.opened_ratio, 66.67)
|
||||
self.assertEqual(self.ab_testing_mailing_2.opened_ratio, 50)
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
|
|
|
|||
|
|
@ -2,30 +2,52 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from freezegun import freeze_time
|
||||
from markupsafe import Markup
|
||||
from requests import Session, PreparedRequest, Response
|
||||
|
||||
import datetime
|
||||
import werkzeug
|
||||
|
||||
from odoo import tools
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.mass_mailing.tests.common import MassMailCommon
|
||||
from odoo.tests.common import HttpCase
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests import HttpCase, tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('link_tracker')
|
||||
class TestMailingControllers(MassMailCommon, HttpCase):
|
||||
class TestMailingControllersCommon(MassMailCommon, HttpCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailingControllers, cls).setUpClass()
|
||||
super(TestMailingControllersCommon, cls).setUpClass()
|
||||
|
||||
# cleanup lists
|
||||
cls.env['mailing.list'].search([]).unlink()
|
||||
|
||||
cls._create_mailing_list()
|
||||
cls.test_mailing = cls.env['mailing.mailing'].create({
|
||||
cls.test_mailing_on_contacts = cls.env['mailing.mailing'].create({
|
||||
'body_html': '<p>Hello <t t-out="object.name"/><br />Go to <a id="url" href="https://www.example.com/foo/bar?baz=qux">this link</a></p>',
|
||||
'mailing_domain': [],
|
||||
'mailing_model_id': cls.env['ir.model']._get_id('mailing.contact'),
|
||||
'mailing_type': 'mail',
|
||||
'name': 'TestMailing on Contacts',
|
||||
'subject': 'TestMailing on Contacts',
|
||||
})
|
||||
cls.test_mailing_on_documents = cls.env['mailing.mailing'].create({
|
||||
'body_html': '<p>Hello <t t-out="object.name"/><br />Go to <a id="url" href="https://www.example.com/foo/bar?baz=qux">this link</a></p>',
|
||||
'mailing_domain': [],
|
||||
'mailing_model_id': cls.env['ir.model']._get_id('res.partner'),
|
||||
'mailing_type': 'mail',
|
||||
'name': 'TestMailing on Documents',
|
||||
'subject': 'TestMailing on Documents',
|
||||
})
|
||||
cls.test_mailing_on_lists = cls.env['mailing.mailing'].create({
|
||||
'body_html': '<p>Hello <t t-out="object.name"/><br />Go to <a id="url" href="https://www.example.com/foo/bar?baz=qux">this link</a></p>',
|
||||
'contact_list_ids': [(4, cls.mailing_list_1.id), (4, cls.mailing_list_2.id)],
|
||||
'mailing_model_id': cls.env['ir.model']._get('mailing.list').id,
|
||||
'mailing_model_id': cls.env['ir.model']._get_id('mailing.list'),
|
||||
'mailing_type': 'mail',
|
||||
'name': 'TestMailing',
|
||||
'name': 'TestMailing on Lists',
|
||||
'reply_to': cls.email_reply_to,
|
||||
'subject': 'Test',
|
||||
'subject': 'TestMailing on Lists',
|
||||
})
|
||||
|
||||
cls.test_contact = cls.mailing_list_1.contact_ids[0]
|
||||
|
|
@ -33,10 +55,587 @@ class TestMailingControllers(MassMailCommon, HttpCase):
|
|||
# freeze time base value
|
||||
cls._reference_now = datetime.datetime(2022, 6, 14, 10, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def _request_handler(cls, s: Session, r: PreparedRequest, /, **kw):
|
||||
if r.url.startswith('https://www.example.com/foo/bar'):
|
||||
r = Response()
|
||||
r.status_code = 200
|
||||
return r
|
||||
return super()._request_handler(s, r, **kw)
|
||||
|
||||
|
||||
@tagged('mailing_portal', 'post_install', '-at_install')
|
||||
class TestMailingControllers(TestMailingControllersCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.test_email = tools.formataddr(('Déboulonneur', '<fleurus@example.com>'))
|
||||
cls.test_email_normalized = 'fleurus@example.com'
|
||||
|
||||
def test_assert_initial_values(self):
|
||||
""" Ensure test base data to ease test understanding. Globally test_email
|
||||
is member of 2 mailing public lists. """
|
||||
memberships = self.env['mailing.subscription'].search([
|
||||
('contact_id.email_normalized', '=', self.test_email_normalized)]
|
||||
)
|
||||
self.assertEqual(memberships.list_id, self.mailing_list_1 + self.mailing_list_3)
|
||||
self.assertEqual(memberships.mapped('opt_out'), [False, True])
|
||||
|
||||
contact_l1 = self.mailing_list_1.contact_ids.filtered(
|
||||
lambda contact: contact.email == self.test_email_normalized
|
||||
)
|
||||
self.assertTrue(contact_l1)
|
||||
self.assertFalse(contact_l1.is_blacklisted)
|
||||
self.assertFalse(contact_l1.message_ids)
|
||||
subscription_l1 = self.mailing_list_1.subscription_ids.filtered(
|
||||
lambda subscription: subscription.contact_id == contact_l1
|
||||
)
|
||||
self.assertTrue(subscription_l1)
|
||||
self.assertFalse(subscription_l1.is_blacklisted)
|
||||
self.assertFalse(subscription_l1.opt_out)
|
||||
self.assertFalse(subscription_l1.opt_out_datetime)
|
||||
|
||||
contact_l2 = self.mailing_list_2.contact_ids.filtered(
|
||||
lambda contact: contact.email == self.test_email_normalized
|
||||
)
|
||||
self.assertFalse(contact_l2)
|
||||
|
||||
contact_l3 = self.mailing_list_3.contact_ids.filtered(
|
||||
lambda contact: contact.email == self.test_email_normalized
|
||||
)
|
||||
self.assertTrue(contact_l3)
|
||||
self.assertTrue(contact_l3 != contact_l1)
|
||||
self.assertFalse(contact_l3.is_blacklisted)
|
||||
subscription_l3 = self.mailing_list_3.subscription_ids.filtered(
|
||||
lambda subscription: subscription.contact_id == contact_l3
|
||||
)
|
||||
self.assertFalse(subscription_l3.is_blacklisted)
|
||||
self.assertTrue(subscription_l3.opt_out)
|
||||
|
||||
contact_l4 = self.mailing_list_4.contact_ids.filtered(
|
||||
lambda contact: contact.email == self.test_email_normalized
|
||||
)
|
||||
self.assertFalse(contact_l4)
|
||||
|
||||
self.assertFalse(self.env['mail.blacklist'].search([('email', '=', self.test_email_normalized)]))
|
||||
|
||||
@mute_logger('odoo.http', 'odoo.addons.website.models.ir_ui_view')
|
||||
def test_mailing_report_unsubscribe(self):
|
||||
""" Test deactivation of mailing report sending. It requires usage of
|
||||
a hash token. """
|
||||
test_mailing = self.test_mailing_on_lists.with_env(self.env)
|
||||
self.env['ir.config_parameter'].sudo().set_param(
|
||||
'mass_mailing.mass_mailing_reports', True
|
||||
)
|
||||
hash_token = test_mailing._generate_mailing_report_token(self.user_marketing.id)
|
||||
self.authenticate('user_marketing', 'user_marketing')
|
||||
|
||||
# TEST: various invalid cases
|
||||
for test_user_id, test_token, error_code in [
|
||||
(self.user_marketing.id, '', 400), # no token
|
||||
(self.user_marketing.id, 'zboobs', 401), # invalid token
|
||||
(self.env.uid, hash_token, 401), # invalid credentials
|
||||
]:
|
||||
with self.subTest(test_user_id=test_user_id, test_token=test_token):
|
||||
res = self.url_open(
|
||||
tools.urls.urljoin(
|
||||
test_mailing.get_base_url(),
|
||||
f'mailing/report/unsubscribe?user_id={test_user_id}&token={test_token}',
|
||||
)
|
||||
)
|
||||
self.assertEqual(res.status_code, error_code)
|
||||
self.assertTrue(self.env['ir.config_parameter'].sudo().get_param('mass_mailing.mass_mailing_reports'))
|
||||
|
||||
# TEST: not mailing user
|
||||
self.user_marketing.write({
|
||||
'group_ids': [(3, self.env.ref('mass_mailing.group_mass_mailing_user').id)],
|
||||
})
|
||||
res = self.url_open(
|
||||
tools.urls.urljoin(
|
||||
test_mailing.get_base_url(),
|
||||
f'mailing/report/unsubscribe?user_id={self.user_marketing.id}&token={hash_token}',
|
||||
)
|
||||
)
|
||||
self.assertEqual(res.status_code, 401)
|
||||
self.assertTrue(self.env['ir.config_parameter'].sudo().get_param('mass_mailing.mass_mailing_reports'))
|
||||
|
||||
# TEST: finally valid call
|
||||
self.user_marketing.write({
|
||||
'group_ids': [(4, self.env.ref('mass_mailing.group_mass_mailing_user').id)],
|
||||
})
|
||||
res = self.url_open(
|
||||
tools.urls.urljoin(
|
||||
test_mailing.get_base_url(),
|
||||
f'mailing/report/unsubscribe?user_id={self.user_marketing.id}&token={hash_token}',
|
||||
)
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertFalse(self.env['ir.config_parameter'].sudo().get_param('mass_mailing.mass_mailing_reports'))
|
||||
|
||||
def test_mailing_unsubscribe_from_document_tour(self):
|
||||
""" Test portal unsubscribe on mailings performed on documents (not
|
||||
mailing lists or contacts). Primary effect is to automatically exclude
|
||||
the email (see tour).
|
||||
|
||||
Two tests are performed (with and without existing list subscriptions)
|
||||
as it triggers the display of the mailing list part of the UI.
|
||||
|
||||
Tour effects
|
||||
* unsubscribe from mailing based on a document = blocklist;
|
||||
* add feedback (block list): Other reason, with 'My feedback' feedback;
|
||||
* remove email from exclusion list;
|
||||
* re-add email to exclusion list;
|
||||
"""
|
||||
opt_out_reasons = self.env['mailing.subscription.optout'].search([])
|
||||
test_mailing = self.test_mailing_on_documents.with_env(self.env)
|
||||
test_feedback = "My feedback"
|
||||
|
||||
for test_email, tour_name in [
|
||||
('"Not Déboulonneur" <not.fleurus@example.com>', 'mailing_portal_unsubscribe_from_document'),
|
||||
(self.test_email, 'mailing_portal_unsubscribe_from_document_with_lists'),
|
||||
]:
|
||||
with self.subTest(test_email=test_email, tour_name=tour_name):
|
||||
test_partner = self.env['res.partner'].create({
|
||||
'email': test_email,
|
||||
'name': 'Test Déboulonneur'
|
||||
})
|
||||
self.assertFalse(test_partner.is_blacklisted)
|
||||
previous_messages = test_partner.message_ids
|
||||
test_email_normalized = tools.email_normalize(test_email)
|
||||
|
||||
# launch unsubscription tour
|
||||
hash_token = test_mailing._generate_mailing_recipient_token(test_partner.id, test_partner.email_normalized)
|
||||
with freeze_time(self._reference_now):
|
||||
self.start_tour(
|
||||
f"/mailing/{test_mailing.id}/unsubscribe?email={test_partner.email_normalized}&document_id={test_partner.id}&hash_token={hash_token}",
|
||||
tour_name,
|
||||
login=None,
|
||||
)
|
||||
|
||||
# status update check
|
||||
self.assertTrue(test_partner.is_blacklisted)
|
||||
|
||||
# partner (document): new message for blocklist addition with feedback
|
||||
self.assertEqual(len(test_partner.message_ids), len(previous_messages) + 1)
|
||||
msg_fb = test_partner.message_ids[0]
|
||||
self.assertEqual(
|
||||
msg_fb.body,
|
||||
Markup(f'<p>Feedback from {test_email_normalized}<br>{test_feedback}</p>')
|
||||
)
|
||||
|
||||
# posted messages on exclusion list record: activated, feedback, deactivated, activated again
|
||||
bl_record = self.env['mail.blacklist'].search([('email', '=', test_partner.email_normalized)])
|
||||
self.assertEqual(len(bl_record.message_ids), 5)
|
||||
self.assertEqual(bl_record.opt_out_reason_id, opt_out_reasons[-1])
|
||||
msg_bl2, msg_unbl, msg_fb, msg_bl, msg_create = bl_record.message_ids
|
||||
self.assertEqual(
|
||||
msg_bl2.body,
|
||||
Markup(f'<p>Blocklist request from portal of mailing <a href="#" data-oe-model="{test_mailing._name}" '
|
||||
f'data-oe-id="{test_mailing.id}">{test_mailing.subject}</a> (document <a href="#" '
|
||||
f'data-oe-model="{test_partner._name}" data-oe-id="{test_partner.id}">Contact</a>)</p>')
|
||||
)
|
||||
self.assertEqual(
|
||||
msg_unbl.body,
|
||||
Markup(f'<p>Blocklist removal request from portal of mailing <a href="#" data-oe-model="{test_mailing._name}" '
|
||||
f'data-oe-id="{test_mailing.id}">{test_mailing.subject}</a> (document <a href="#" '
|
||||
f'data-oe-model="{test_partner._name}" data-oe-id="{test_partner.id}">Contact</a>)</p>')
|
||||
)
|
||||
self.assertEqual(
|
||||
msg_fb.body,
|
||||
Markup(f'<p>Feedback from {test_email_normalized}<br>{test_feedback}</p>')
|
||||
)
|
||||
self.assertTracking(msg_fb, [('opt_out_reason_id', 'many2one', False, opt_out_reasons[-1])])
|
||||
self.assertEqual(
|
||||
msg_bl.body,
|
||||
Markup(f'<p>Blocklist request from unsubscribe link of mailing <a href="#" data-oe-model="{test_mailing._name}" '
|
||||
f'data-oe-id="{test_mailing.id}">{test_mailing.subject}</a> (document <a href="#" '
|
||||
f'data-oe-model="{test_partner._name}" data-oe-id="{test_partner.id}">Contact</a>)</p>')
|
||||
)
|
||||
self.assertEqual(msg_create.body, Markup('<p>Mail Blacklist created</p>'))
|
||||
|
||||
def test_mailing_unsubscribe_from_document_tour_mailing_user(self):
|
||||
""" Test portal unsubscribe on mailings performed on documents (not
|
||||
mailing lists or contacts) using a generic '/unsubscribe' link allowing
|
||||
mailing users to see and edit unsubcribe page.
|
||||
|
||||
Tour effects
|
||||
* unsubscribe from mailing based on a document = blocklist;
|
||||
* add feedback (block list): Other reason, with 'My feedback' feedback;
|
||||
* remove email from exclusion list;
|
||||
* re-add email to exclusion list;
|
||||
"""
|
||||
# update user to link it with existing mailing contacts and allow the tour
|
||||
# to run; test without and with mailing group
|
||||
self.user_marketing.write({
|
||||
'email': tools.formataddr(("Déboulonneur", "fleurus@example.com")),
|
||||
'group_ids': [(3, self.env.ref('mass_mailing.group_mass_mailing_user').id)],
|
||||
})
|
||||
test_mailing = self.test_mailing_on_documents.with_env(self.env)
|
||||
self.authenticate('user_marketing', 'user_marketing')
|
||||
|
||||
# no group -> no direct access to /unsubscribe
|
||||
res = self.url_open(
|
||||
tools.urls.urljoin(
|
||||
test_mailing.get_base_url(),
|
||||
f'mailing/{test_mailing.id}/unsubscribe',
|
||||
)
|
||||
)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
|
||||
# group -> direct access to /unsubscribe should wokr
|
||||
self.user_marketing.write({
|
||||
'group_ids': [(4, self.env.ref('mass_mailing.group_mass_mailing_user').id)],
|
||||
})
|
||||
# launch unsubscription tour
|
||||
with freeze_time(self._reference_now):
|
||||
self.start_tour(
|
||||
f"/mailing/{test_mailing.id}/unsubscribe",
|
||||
"mailing_portal_unsubscribe_from_document_with_lists",
|
||||
login=self.user_marketing.login,
|
||||
)
|
||||
|
||||
def test_mailing_unsubscribe_from_list_tour(self):
|
||||
""" Test portal unsubscribe on mailings performed on mailing lists. Their
|
||||
effect is to opt-out from the mailing list.
|
||||
|
||||
Tour effects
|
||||
* unsubscribe from mailing based on lists = opt-out from lists;
|
||||
* add feedback (opt-out): Other reason, with 'My feedback' feedback;
|
||||
* add email to exclusion list;
|
||||
"""
|
||||
opt_out_reasons = self.env['mailing.subscription.optout'].search([])
|
||||
test_mailing = self.test_mailing_on_lists.with_env(self.env)
|
||||
test_feedback = "My feedback"
|
||||
|
||||
# fetch contact and its subscription and blacklist status, to see the tour effects
|
||||
contact_l1 = self.mailing_list_1.contact_ids.filtered(
|
||||
lambda contact: contact.email == self.test_email_normalized
|
||||
)
|
||||
subscription_l1 = self.mailing_list_1.subscription_ids.filtered(
|
||||
lambda subscription: subscription.contact_id == contact_l1
|
||||
)
|
||||
|
||||
# launch unsubscribe tour
|
||||
hash_token = test_mailing._generate_mailing_recipient_token(contact_l1.id, contact_l1.email)
|
||||
with freeze_time(self._reference_now):
|
||||
self.start_tour(
|
||||
f"/mailing/{test_mailing.id}/unsubscribe?email={self.test_email_normalized}&document_id={contact_l1.id}&hash_token={hash_token}",
|
||||
"mailing_portal_unsubscribe_from_list",
|
||||
login=None,
|
||||
)
|
||||
|
||||
# status update check on list 1
|
||||
self.assertTrue(subscription_l1.opt_out)
|
||||
self.assertEqual(subscription_l1.opt_out_datetime, self._reference_now)
|
||||
self.assertEqual(subscription_l1.opt_out_reason_id, opt_out_reasons[-1])
|
||||
# status update check on list 2: unmodified (was not member, still not member)
|
||||
contact_l2 = self.mailing_list_2.contact_ids.filtered(
|
||||
lambda contact: contact.email == self.test_email_normalized
|
||||
)
|
||||
self.assertFalse(contact_l2)
|
||||
|
||||
# posted messages on contact record for mailing list 1: feedback, unsubscription
|
||||
message_feedback = contact_l1.message_ids[0]
|
||||
self.assertEqual(
|
||||
message_feedback.body,
|
||||
Markup(f'<p>Feedback from {contact_l1.email_normalized}<br>{test_feedback}</p>')
|
||||
)
|
||||
message_unsub = contact_l1.message_ids[1]
|
||||
self.assertEqual(
|
||||
message_unsub.body,
|
||||
Markup(f'<p>{contact_l1.display_name} unsubscribed from the following mailing list(s)</p><ul><li>{self.mailing_list_1.name}</li></ul>')
|
||||
)
|
||||
|
||||
# posted messages on exclusion list record: activated, deactivated, activated again
|
||||
bl_record = self.env['mail.blacklist'].search([('email', '=', contact_l1.email_normalized)])
|
||||
self.assertEqual(len(bl_record.message_ids), 2)
|
||||
self.assertFalse(bl_record.opt_out_reason_id)
|
||||
msg_bl, msg_create = bl_record.message_ids
|
||||
self.assertEqual(
|
||||
msg_bl.body,
|
||||
Markup(f'<p>Blocklist request from portal of mailing <a href="#" data-oe-model="{test_mailing._name}" '
|
||||
f'data-oe-id="{test_mailing.id}">{test_mailing.subject}</a> (document <a href="#" '
|
||||
f'data-oe-model="{contact_l1._name}" data-oe-id="{contact_l1.id}">Mailing Contact</a>)</p>')
|
||||
)
|
||||
self.assertEqual(msg_create.body, Markup('<p>Mail Blacklist created</p>'))
|
||||
|
||||
def test_mailing_unsubscribe_from_list_with_update_tour(self):
|
||||
""" Test portal unsubscribe on mailings performed on mailing lists. Their
|
||||
effect is to opt-out from the mailing list. Optional exclusion list can
|
||||
be done through interface (see tour).
|
||||
|
||||
Tour effects
|
||||
* unsubscribe from mailing based on lists = opt-out from lists;
|
||||
* add feedback (opt-out): Other reason, with 'My feedback' feedback;
|
||||
* add email to exclusion list;
|
||||
* remove email from exclusion list;
|
||||
* come back to List3;
|
||||
* join List2 (with no feedback, as no opt-out / block list was done);
|
||||
* re-add email to exclusion list;
|
||||
"""
|
||||
opt_out_reasons = self.env['mailing.subscription.optout'].search([])
|
||||
test_mailing = self.test_mailing_on_lists.with_env(self.env)
|
||||
test_feedback = "My feedback"
|
||||
|
||||
# fetch contact and its subscription and blacklist status, to see the tour effects
|
||||
contact_l1 = self.mailing_list_1.contact_ids.filtered(
|
||||
lambda contact: contact.email == self.test_email_normalized
|
||||
)
|
||||
subscription_l1 = self.mailing_list_1.subscription_ids.filtered(
|
||||
lambda subscription: subscription.contact_id == contact_l1
|
||||
)
|
||||
contact_l3 = self.mailing_list_3.contact_ids.filtered(
|
||||
lambda contact: contact.email == self.test_email_normalized
|
||||
)
|
||||
subscription_l3 = self.mailing_list_3.subscription_ids.filtered(
|
||||
lambda subscription: subscription.contact_id == contact_l3
|
||||
)
|
||||
|
||||
# launch unsubscription tour
|
||||
hash_token = test_mailing._generate_mailing_recipient_token(contact_l1.id, contact_l1.email)
|
||||
with freeze_time(self._reference_now):
|
||||
self.start_tour(
|
||||
f"/mailing/{test_mailing.id}/unsubscribe?email={contact_l1.email}&document_id={contact_l1.id}&hash_token={hash_token}",
|
||||
"mailing_portal_unsubscribe_from_list_with_update",
|
||||
login=None,
|
||||
)
|
||||
|
||||
# status update check on list 1
|
||||
self.assertTrue(subscription_l1.opt_out)
|
||||
self.assertEqual(subscription_l1.opt_out_datetime, self._reference_now)
|
||||
self.assertEqual(subscription_l1.opt_out_reason_id, opt_out_reasons[-1])
|
||||
# status update check on list 3 (opt-in during test)
|
||||
self.assertFalse(subscription_l3.opt_out)
|
||||
self.assertFalse(subscription_l3.opt_out_datetime)
|
||||
|
||||
# posted messages on contact record for mailing list 1: subscription update, feedback, unsubscription
|
||||
message_update = contact_l1.message_ids[0]
|
||||
self.assertEqual(
|
||||
message_update.body,
|
||||
Markup(f'<p>{contact_l1.display_name} subscribed to the following mailing list(s)</p>'
|
||||
f'<ul><li>{self.mailing_list_2.name}</li></ul>')
|
||||
)
|
||||
message_feedback = contact_l1.message_ids[1]
|
||||
self.assertEqual(
|
||||
message_feedback.body,
|
||||
Markup(f'<p>Feedback from {contact_l1.email_normalized}<br>{test_feedback}</p>')
|
||||
)
|
||||
message_unsub = contact_l1.message_ids[2]
|
||||
self.assertEqual(
|
||||
message_unsub.body,
|
||||
Markup(f'<p>{contact_l1.display_name} unsubscribed from the following mailing list(s)</p>'
|
||||
f'<ul><li>{self.mailing_list_1.name}</li></ul>')
|
||||
)
|
||||
|
||||
# posted messages on contact record for mailing list 3: subscription
|
||||
message_sub = contact_l3.message_ids[0]
|
||||
self.assertEqual(
|
||||
message_sub.body,
|
||||
Markup(f'<p>{contact_l3.display_name} subscribed to the following mailing list(s)</p>'
|
||||
f'<ul><li>{self.mailing_list_3.name}</li><li>{self.mailing_list_2.name}</li></ul>')
|
||||
)
|
||||
|
||||
# posted messages on exclusion list record: activated, deactivated, activated again, feedback
|
||||
bl_record = self.env['mail.blacklist'].search([('email', '=', contact_l1.email_normalized)])
|
||||
self.assertEqual(bl_record.opt_out_reason_id, opt_out_reasons[0])
|
||||
self.assertEqual(len(bl_record.message_ids), 5)
|
||||
msg_fb, msg_bl2, msg_unbl, msg_bl, msg_create = bl_record.message_ids
|
||||
self.assertTracking(msg_fb, [('opt_out_reason_id', 'many2one', False, opt_out_reasons[0])])
|
||||
self.assertFalse(msg_fb.body)
|
||||
self.assertEqual(
|
||||
msg_bl2.body,
|
||||
Markup(f'<p>Blocklist request from portal of mailing <a href="#" data-oe-model="{test_mailing._name}" '
|
||||
f'data-oe-id="{test_mailing.id}">{test_mailing.subject}</a> (document <a href="#" '
|
||||
f'data-oe-model="{contact_l1._name}" data-oe-id="{contact_l1.id}">Mailing Contact</a>)</p>')
|
||||
)
|
||||
self.assertEqual(
|
||||
msg_unbl.body,
|
||||
Markup(f'<p>Blocklist removal request from portal of mailing <a href="#" data-oe-model="{test_mailing._name}" '
|
||||
f'data-oe-id="{test_mailing.id}">{test_mailing.subject}</a> (document <a href="#" '
|
||||
f'data-oe-model="{contact_l1._name}" data-oe-id="{contact_l1.id}">Mailing Contact</a>)</p>')
|
||||
)
|
||||
self.assertEqual(
|
||||
msg_bl.body,
|
||||
Markup(f'<p>Blocklist request from portal of mailing <a href="#" data-oe-model="{test_mailing._name}" '
|
||||
f'data-oe-id="{test_mailing.id}">{test_mailing.subject}</a> (document <a href="#" '
|
||||
f'data-oe-model="{contact_l1._name}" data-oe-id="{contact_l1.id}">Mailing Contact</a>)</p>')
|
||||
)
|
||||
self.assertEqual(msg_create.body, Markup('<p>Mail Blacklist created</p>'))
|
||||
|
||||
def test_mailing_unsubscribe_from_my(self):
|
||||
""" Test portal unsubscribe using the 'my' mailing-specific portal page.
|
||||
It allows to opt-in / opt-out from mailing lists as well as to manage
|
||||
blocklist (see tour).
|
||||
|
||||
Tour effects
|
||||
* opt-in List3 from opt-out, opt-in List2, opt-out List1;
|
||||
* add feedback (as new opt-out): Other reason, with 'My feedback' feedback;
|
||||
* add email in block list;
|
||||
* add feedback (as block list addition): First reason (hence no feedback);
|
||||
"""
|
||||
test_feedback = "My feedback"
|
||||
portal_user = mail_new_test_user(
|
||||
self.env,
|
||||
email=tools.formataddr(("Déboulonneur", "fleurus@example.com")),
|
||||
groups='base.group_portal',
|
||||
login='user_portal_fleurus',
|
||||
name='Déboulonneur User',
|
||||
signature='--\nDéboulonneur',
|
||||
)
|
||||
_test_email, test_email_normalized = portal_user.email, portal_user.email_normalized
|
||||
opt_out_reasons = self.env['mailing.subscription.optout'].search([])
|
||||
|
||||
# list opted-out and non-public should not be displayed
|
||||
private_list = self.env['mailing.list'].with_context(self._test_context).create({
|
||||
'contact_ids': [
|
||||
(0, 0, {'name': 'Déboulonneur User', 'email': 'fleurus@example.com'}),
|
||||
],
|
||||
'name': 'List5',
|
||||
'is_public': False
|
||||
})
|
||||
private_list.subscription_ids[0].opt_out = True
|
||||
|
||||
# launch 'my' mailing' tour
|
||||
self.authenticate(portal_user.login, portal_user.login)
|
||||
with freeze_time(self._reference_now):
|
||||
self.start_tour(
|
||||
"/mailing/my",
|
||||
"mailing_portal_unsubscribe_from_my",
|
||||
login=portal_user.login,
|
||||
)
|
||||
|
||||
# fetch contact and its subscription and blacklist status, to see the tour effects
|
||||
contact_l1 = self.mailing_list_1.contact_ids.filtered(
|
||||
lambda contact: contact.email == test_email_normalized
|
||||
)
|
||||
subscription_l1 = self.mailing_list_1.subscription_ids.filtered(
|
||||
lambda subscription: subscription.contact_id == contact_l1
|
||||
)
|
||||
contact_l2 = self.mailing_list_2.contact_ids.filtered(
|
||||
lambda contact: contact.email == test_email_normalized
|
||||
)
|
||||
subscription_l2 = self.mailing_list_2.subscription_ids.filtered(
|
||||
lambda subscription: subscription.contact_id == contact_l2
|
||||
)
|
||||
contact_l3 = self.mailing_list_3.contact_ids.filtered(
|
||||
lambda contact: contact.email == test_email_normalized
|
||||
)
|
||||
subscription_l3 = self.mailing_list_3.subscription_ids.filtered(
|
||||
lambda subscription: subscription.contact_id == contact_l3
|
||||
)
|
||||
self.assertEqual(contact_l2, contact_l3,
|
||||
'When creating new membership, should link with first found existing contact')
|
||||
self.assertTrue(contact_l1.is_blacklisted)
|
||||
self.assertTrue(contact_l3.is_blacklisted)
|
||||
self.assertTrue(subscription_l1.opt_out)
|
||||
self.assertEqual(subscription_l1.opt_out_datetime, self._reference_now,
|
||||
'Subscription: opt-outed during test, datetime should have been set')
|
||||
self.assertEqual(subscription_l1.opt_out_reason_id, opt_out_reasons[-1])
|
||||
self.assertFalse(subscription_l2.opt_out)
|
||||
self.assertFalse(subscription_l2.opt_out_datetime)
|
||||
self.assertFalse(subscription_l2.opt_out_reason_id)
|
||||
self.assertFalse(subscription_l3.opt_out)
|
||||
self.assertFalse(subscription_l3.opt_out_datetime,
|
||||
'Subscription: opt-in during test, datetime should have been reset')
|
||||
self.assertFalse(subscription_l3.opt_out_reason_id)
|
||||
# message on contact for list 1: opt-out L1, join L2
|
||||
msg_fb, msg_sub, msg_uns = contact_l1.message_ids
|
||||
self.assertEqual(
|
||||
msg_fb.body,
|
||||
Markup(f'<p>Feedback from {portal_user.name} ({test_email_normalized})<br>{test_feedback}</p>')
|
||||
)
|
||||
self.assertEqual(
|
||||
msg_sub.body,
|
||||
Markup(f'<p>{contact_l1.name} subscribed to the following mailing list(s)</p>'
|
||||
f'<ul><li>{self.mailing_list_2.name}</li></ul>')
|
||||
)
|
||||
self.assertEqual(
|
||||
msg_uns.body,
|
||||
Markup(f'<p>{contact_l1.name} unsubscribed from the following mailing list(s)</p>'
|
||||
f'<ul><li>{self.mailing_list_1.name}</li></ul>')
|
||||
)
|
||||
# message on contact for list 2: opt-in L3 and L2
|
||||
msg_fb, msg_sub = contact_l3.message_ids
|
||||
self.assertEqual(
|
||||
msg_fb.body,
|
||||
Markup(f'<p>Feedback from {portal_user.name} ({test_email_normalized})<br>{test_feedback}</p>')
|
||||
)
|
||||
self.assertEqual(
|
||||
msg_sub.body,
|
||||
Markup(f'<p>{contact_l3.name} subscribed to the following mailing list(s)</p>'
|
||||
f'<ul><li>{self.mailing_list_3.name}</li><li>{self.mailing_list_2.name}</li></ul>')
|
||||
)
|
||||
|
||||
# block list record created, feedback logged
|
||||
bl_record = self.env['mail.blacklist'].search([('email', '=', contact_l1.email_normalized)])
|
||||
self.assertEqual(bl_record.opt_out_reason_id, opt_out_reasons[0])
|
||||
self.assertEqual(len(bl_record.message_ids), 3)
|
||||
msg_fb, msg_bl, _msg_create = bl_record.message_ids
|
||||
self.assertTracking(msg_fb, [('opt_out_reason_id', 'many2one', False, opt_out_reasons[0])])
|
||||
self.assertEqual(msg_bl.body, Markup('<p>Blocklist request from portal</p>'))
|
||||
|
||||
@mute_logger('odoo.http', 'odoo.addons.website.models.ir_ui_view')
|
||||
def test_mailing_view(self):
|
||||
""" Test preview of mailing. It requires either a token, either being
|
||||
mailing user. """
|
||||
test_mailing = self.test_mailing_on_documents.with_env(self.env)
|
||||
shadow_mailing = test_mailing.copy()
|
||||
doc_id, email_normalized = self.user_marketing.partner_id.id, self.user_marketing.email_normalized
|
||||
hash_token = test_mailing._generate_mailing_recipient_token(doc_id, email_normalized)
|
||||
self.user_marketing.write({
|
||||
'group_ids': [(3, self.env.ref('mass_mailing.group_mass_mailing_user').id)],
|
||||
})
|
||||
self.authenticate('user_marketing', 'user_marketing')
|
||||
|
||||
# TEST: various invalid cases
|
||||
for test_mid, test_doc_id, test_email, test_token, error_code in [
|
||||
(test_mailing.id, doc_id, email_normalized, '', 400), # no token
|
||||
(test_mailing.id, doc_id, email_normalized, 'zboobs', 401), # wrong token
|
||||
(test_mailing.id, self.env.user.partner_id.id, email_normalized, hash_token, 401), # mismatch
|
||||
(test_mailing.id, doc_id, 'not.email@example.com', hash_token, 401), # mismatch
|
||||
(shadow_mailing.id, doc_id, email_normalized, hash_token, 401), # valid credentials but wrong mailing_id
|
||||
(0, doc_id, email_normalized, hash_token, 400), # valid credentials but missing mailing_id
|
||||
]:
|
||||
with self.subTest(test_mid=test_mid, test_email=test_email, test_doc_id=test_doc_id, test_token=test_token):
|
||||
res = self.url_open(
|
||||
tools.urls.urljoin(
|
||||
test_mailing.get_base_url(),
|
||||
f'mailing/{test_mid}/view?email={test_email}&document_id={test_doc_id}&hash_token={test_token}',
|
||||
)
|
||||
)
|
||||
self.assertEqual(res.status_code, error_code)
|
||||
|
||||
# TEST: valid call using credentials
|
||||
res = self.url_open(
|
||||
tools.urls.urljoin(
|
||||
test_mailing.get_base_url(),
|
||||
f'mailing/{test_mailing.id}/view?email={email_normalized}&document_id={doc_id}&hash_token={hash_token}',
|
||||
)
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
# TEST: invalid credentials but mailing user
|
||||
self.user_marketing.write({
|
||||
'group_ids': [(4, self.env.ref('mass_mailing.group_mass_mailing_user').id)],
|
||||
})
|
||||
res = self.url_open(
|
||||
tools.urls.urljoin(
|
||||
test_mailing.get_base_url(),
|
||||
f'mailing/{test_mailing.id}/view',
|
||||
)
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
|
||||
@tagged('link_tracker', 'mailing_portal')
|
||||
class TestMailingTracking(TestMailingControllersCommon):
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.addons.mass_mailing.models.mailing')
|
||||
def test_tracking_short_code(self):
|
||||
""" Test opening short code linked to a mailing trace: should set the
|
||||
trace as opened and clicked, create a click record. """
|
||||
mailing = self.test_mailing.with_env(self.env)
|
||||
mailing = self.test_mailing_on_lists.with_env(self.env)
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
|
|
@ -53,43 +652,47 @@ class TestMailingControllers(MassMailCommon, HttpCase):
|
|||
self.assertFalse(mailing_trace.open_datetime)
|
||||
self.assertEqual(mailing_trace.trace_status, 'sent')
|
||||
|
||||
short_link_url = werkzeug.urls.url_join(
|
||||
short_link_url = tools.urls.urljoin(
|
||||
mail.get_base_url(),
|
||||
f'r/{link_tracker_code.code}/m/{mailing_trace.id}'
|
||||
)
|
||||
with freeze_time(self._reference_now):
|
||||
response = self.url_open(short_link_url, allow_redirects=False)
|
||||
self.assertEqual(response.headers['Location'], 'https://www.example.com/foo/bar?baz=qux&utm_source=TestMailing&utm_medium=Email')
|
||||
self.assertTrue(response.headers['Location'].startswith('https://www.example.com/foo/bar?baz=qux'))
|
||||
|
||||
self.assertEqual(link_tracker_code.link_id.count, 1)
|
||||
self.assertEqual(mailing_trace.links_click_datetime, self._reference_now)
|
||||
self.assertEqual(mailing_trace.open_datetime, self._reference_now)
|
||||
self.assertEqual(mailing_trace.trace_status, 'open')
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.addons.mass_mailing.models.mailing')
|
||||
def test_tracking_url_token(self):
|
||||
""" Test tracking of mails linked to a mailing trace: should set the
|
||||
trace as opened. """
|
||||
mailing = self.test_mailing.with_env(self.env)
|
||||
mailing = self.test_mailing_on_lists.with_env(self.env)
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
mail = self._find_mail_mail_wrecord(self.test_contact)
|
||||
mail_id_int = mail.id
|
||||
mail_tracking_url = mail._get_tracking_url()
|
||||
mailing_trace = mail.mailing_trace_ids
|
||||
self.assertEqual(mail.state, 'sent')
|
||||
self.assertEqual(len(mailing_trace), 1)
|
||||
self.assertFalse(mailing_trace.open_datetime)
|
||||
self.assertEqual(mailing_trace.trace_status, 'sent')
|
||||
mail.unlink() # the mail might be removed during the email sending
|
||||
self.env.flush_all()
|
||||
|
||||
with freeze_time(self._reference_now):
|
||||
response = self.url_open(mail._get_tracking_url())
|
||||
response = self.url_open(mail_tracking_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(mail.state, 'sent')
|
||||
self.assertEqual(mailing_trace.open_datetime, self._reference_now)
|
||||
self.assertEqual(mailing_trace.trace_status, 'open')
|
||||
|
||||
track_url = werkzeug.urls.url_join(
|
||||
mail.get_base_url(),
|
||||
'mail/track/%s/fake_token/blank.gif' % mail.id
|
||||
track_url = tools.urls.urljoin(
|
||||
mailing.get_base_url(),
|
||||
f'mail/track/{mail_id_int}/fake_token/blank.gif'
|
||||
)
|
||||
response = self.url_open(track_url)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from odoo.addons.base.tests.test_ir_cron import CronMixinCase
|
|||
from odoo.addons.mass_mailing.tests.common import MassMailCommon
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.sql_db import Cursor
|
||||
from odoo.tests.common import users, Form, HttpCase, tagged
|
||||
from odoo.tests import Form, HttpCase, users, tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
BASE_64_STRING = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII='
|
||||
|
|
@ -51,9 +51,9 @@ class TestMassMailValues(MassMailCommon):
|
|||
return urls
|
||||
else:
|
||||
return []
|
||||
with patch("odoo.addons.mass_mailing.models.mailing.MassMailing._get_image_by_url",
|
||||
with patch("odoo.addons.mass_mailing.models.mailing.MailingMailing._get_image_by_url",
|
||||
new=patched_get_image), \
|
||||
patch("odoo.addons.mass_mailing.models.mailing.MassMailing._create_attachments_from_inline_images",
|
||||
patch("odoo.addons.mass_mailing.models.mailing.MailingMailing._create_attachments_from_inline_images",
|
||||
new=patched_images_to_urls):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'Test',
|
||||
|
|
@ -61,19 +61,19 @@ class TestMassMailValues(MassMailCommon):
|
|||
'state': 'draft',
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
'body_html': """
|
||||
<html>
|
||||
<section>
|
||||
<!--[if mso]>
|
||||
<v:image src="https://www.example.com/image" style="width:100px;height:100px;"/>
|
||||
<![endif]-->
|
||||
</html>
|
||||
</section>
|
||||
""",
|
||||
})
|
||||
self.assertEqual(str(mailing.body_html), f"""
|
||||
<html>
|
||||
self.assertEqual(str(mailing.body_html).strip(), f"""
|
||||
<section>
|
||||
<!--[if mso]>
|
||||
<v:image src="/web/image/{attachment['id']}?access_token={attachment['token']}" style="width:100px;height:100px;"/>
|
||||
<![endif]-->
|
||||
</html>
|
||||
</section>
|
||||
""".strip())
|
||||
|
||||
@users('user_marketing')
|
||||
|
|
@ -94,7 +94,7 @@ class TestMassMailValues(MassMailCommon):
|
|||
'token': attachment_token,
|
||||
})
|
||||
return urls
|
||||
with patch("odoo.addons.mass_mailing.models.mailing.MassMailing._create_attachments_from_inline_images",
|
||||
with patch("odoo.addons.mass_mailing.models.mailing.MailingMailing._create_attachments_from_inline_images",
|
||||
new=patched_images_to_urls):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'Test',
|
||||
|
|
@ -102,7 +102,7 @@ class TestMassMailValues(MassMailCommon):
|
|||
'state': 'draft',
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
'body_html': f"""
|
||||
<html><body>
|
||||
<section>
|
||||
<img src="data:image/png;base64,{BASE_64_STRING}0">
|
||||
<img src="data:image/jpg;base64,{BASE_64_STRING}1">
|
||||
<div style='color: red; background-image:url("data:image/jpg;base64,{BASE_64_STRING}2"); display: block;'/>
|
||||
|
|
@ -124,21 +124,21 @@ class TestMassMailValues(MassMailCommon):
|
|||
<div style="color: red; background-image: url(data:image/jpg;base64,{BASE_64_STRING}16); background: url('data:image/jpg;base64,{BASE_64_STRING}17'); display: block;"/>
|
||||
<![endif]-->
|
||||
<img src="data:image/png;base64,{BASE_64_STRING}0">
|
||||
</body></html>
|
||||
</section>
|
||||
""",
|
||||
})
|
||||
self.assertEqual(len(attachments), 19)
|
||||
self.assertEqual(attachments[0]['id'], attachments[18]['id'])
|
||||
self.assertEqual(str(mailing.body_html), f"""
|
||||
<html><body>
|
||||
<img src="/web/image/{attachments[0]['id']}?access_token={attachments[0]['token']}">
|
||||
<img src="/web/image/{attachments[1]['id']}?access_token={attachments[1]['token']}">
|
||||
<div style='color: red; background-image:url("/web/image/{attachments[2]['id']}?access_token={attachments[2]['token']}"); display: block;'></div>
|
||||
<div style="color: red; background-image:url('/web/image/{attachments[3]['id']}?access_token={attachments[3]['token']}'); display: block;"></div>
|
||||
<div style='color: red; background-image:url("/web/image/{attachments[4]['id']}?access_token={attachments[4]['token']}"); display: block;'></div>
|
||||
<div style='color: red; background-image:url("/web/image/{attachments[5]['id']}?access_token={attachments[5]['token']}"); display: block;'></div>
|
||||
<div style="color: red; background-image:url(/web/image/{attachments[6]['id']}?access_token={attachments[6]['token']}); display: block;"></div>
|
||||
<div style="color: red; background-image: url(/web/image/{attachments[7]['id']}?access_token={attachments[7]['token']}); background: url('/web/image/{attachments[8]['id']}?access_token={attachments[8]['token']}'); display: block;"></div>
|
||||
self.assertEqual(str(mailing.body_html).strip(), f"""
|
||||
<section>
|
||||
<img src="/web/image/{attachments[0]['id']}?access_token={attachments[0]['token']}"/>
|
||||
<img src="/web/image/{attachments[1]['id']}?access_token={attachments[1]['token']}"/>
|
||||
<div style="color: red; background-image:url("/web/image/{attachments[2]['id']}?access_token={attachments[2]['token']}"); display: block;"/>
|
||||
<div style="color: red; background-image:url('/web/image/{attachments[3]['id']}?access_token={attachments[3]['token']}'); display: block;"/>
|
||||
<div style="color: red; background-image:url("/web/image/{attachments[4]['id']}?access_token={attachments[4]['token']}"); display: block;"/>
|
||||
<div style="color: red; background-image:url("/web/image/{attachments[5]['id']}?access_token={attachments[5]['token']}"); display: block;"/>
|
||||
<div style="color: red; background-image:url(/web/image/{attachments[6]['id']}?access_token={attachments[6]['token']}); display: block;"/>
|
||||
<div style="color: red; background-image: url(/web/image/{attachments[7]['id']}?access_token={attachments[7]['token']}); background: url('/web/image/{attachments[8]['id']}?access_token={attachments[8]['token']}'); display: block;"/>
|
||||
<!--[if mso]>
|
||||
<img src="/web/image/{attachments[9]['id']}?access_token={attachments[9]['token']}">Fake url, in text: img src="data:image/png;base64,{BASE_64_STRING}"
|
||||
Fake url, in text: img src="data:image/png;base64,{BASE_64_STRING}"
|
||||
|
|
@ -151,8 +151,8 @@ class TestMassMailValues(MassMailCommon):
|
|||
<div style="color: red; background-image:url(/web/image/{attachments[15]['id']}?access_token={attachments[15]['token']}); display: block;"/>
|
||||
<div style="color: red; background-image: url(/web/image/{attachments[16]['id']}?access_token={attachments[16]['token']}); background: url('/web/image/{attachments[17]['id']}?access_token={attachments[17]['token']}'); display: block;"/>
|
||||
<![endif]-->
|
||||
<img src="/web/image/{attachments[18]['id']}?access_token={attachments[18]['token']}">
|
||||
</body></html>
|
||||
<img src="/web/image/{attachments[18]['id']}?access_token={attachments[18]['token']}"/>
|
||||
</section>
|
||||
""".strip())
|
||||
|
||||
@users('user_marketing')
|
||||
|
|
@ -178,14 +178,14 @@ class TestMassMailValues(MassMailCommon):
|
|||
composer = self.env['mail.compose.message'].with_user(self.user_marketing).with_context({
|
||||
'default_composition_mode': 'mass_mail',
|
||||
'default_model': 'res.partner',
|
||||
'default_res_id': recipient.id,
|
||||
'default_res_ids': recipient.ids,
|
||||
}).create({
|
||||
'subject': 'Mass Mail Responsive',
|
||||
'body': 'I am Responsive body',
|
||||
'mass_mailing_id': mailing.id
|
||||
})
|
||||
|
||||
mail_values = composer.get_mail_values([recipient.id])
|
||||
mail_values = composer._prepare_mail_values([recipient.id])
|
||||
body_html = mail_values[recipient.id]['body_html']
|
||||
|
||||
self.assertIn('<!DOCTYPE html>', body_html)
|
||||
|
|
@ -211,8 +211,7 @@ class TestMassMailValues(MassMailCommon):
|
|||
self.assertEqual(mailing.mailing_model_real, 'res.partner')
|
||||
self.assertEqual(mailing.reply_to_mode, 'new')
|
||||
self.assertEqual(mailing.reply_to, self.user_marketing.email_formatted)
|
||||
# default for partner: remove blacklisted
|
||||
self.assertEqual(literal_eval(mailing.mailing_domain), [('is_blacklisted', '=', False)])
|
||||
self.assertEqual(literal_eval(mailing.mailing_domain), [])
|
||||
# update domain
|
||||
mailing.write({
|
||||
'mailing_domain': [('email', 'ilike', 'test.example.com')]
|
||||
|
|
@ -237,10 +236,10 @@ class TestMassMailValues(MassMailCommon):
|
|||
|
||||
# reset mailing model -> reset domain and reply to mode
|
||||
mailing.write({
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.channel').id,
|
||||
'mailing_model_id': self.env['ir.model']._get('discuss.channel').id,
|
||||
})
|
||||
self.assertEqual(mailing.mailing_model_name, 'mail.channel')
|
||||
self.assertEqual(mailing.mailing_model_real, 'mail.channel')
|
||||
self.assertEqual(mailing.mailing_model_name, 'discuss.channel')
|
||||
self.assertEqual(mailing.mailing_model_real, 'discuss.channel')
|
||||
self.assertEqual(mailing.reply_to_mode, 'update')
|
||||
self.assertFalse(mailing.reply_to)
|
||||
|
||||
|
|
@ -255,14 +254,13 @@ class TestMassMailValues(MassMailCommon):
|
|||
'body_html': '<p>Hello <t t-out="object.name"/></p>',
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
})
|
||||
# default for partner: remove blacklisted
|
||||
self.assertEqual(literal_eval(mailing.mailing_domain), [('is_blacklisted', '=', False)])
|
||||
self.assertEqual(literal_eval(mailing.mailing_domain), [])
|
||||
|
||||
# prepare initial data
|
||||
filter_1, filter_2, filter_3 = self.env['mailing.filter'].create([
|
||||
{'name': 'General channel',
|
||||
'mailing_domain' : [('name', '=', 'general')],
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.channel').id,
|
||||
'mailing_model_id': self.env['ir.model']._get('discuss.channel').id,
|
||||
},
|
||||
{'name': 'LLN City',
|
||||
'mailing_domain' : [('city', 'ilike', 'LLN')],
|
||||
|
|
@ -283,7 +281,7 @@ class TestMassMailValues(MassMailCommon):
|
|||
mailing.mailing_filter_id = filter_1
|
||||
|
||||
# resetting model should reset domain, even if filter was chosen previously
|
||||
mailing.mailing_model_id = self.env['ir.model']._get('mail.channel').id
|
||||
mailing.mailing_model_id = self.env['ir.model']._get('discuss.channel').id
|
||||
self.assertEqual(literal_eval(mailing.mailing_domain), [])
|
||||
|
||||
# changing the filter should update the mailing domain correctly
|
||||
|
|
@ -328,17 +326,17 @@ class TestMassMailValues(MassMailCommon):
|
|||
# for mass mailing. from_filter matches domain of company alias domain
|
||||
# before record creation
|
||||
{
|
||||
'name': 'mass_mailing_test_match_from_filter',
|
||||
'from_filter': self.alias_domain,
|
||||
'smtp_host': 'not_real@smtp.com',
|
||||
'name' : 'mass_mailing_test_match_from_filter',
|
||||
'from_filter' : self.alias_domain,
|
||||
'smtp_host' : 'not_real@smtp.com',
|
||||
},
|
||||
# Case where alias domain is set and there is a default outgoing email server
|
||||
# for mass mailing. from_filter DOES NOT match domain of company alias domain
|
||||
# before record creation
|
||||
{
|
||||
'name': 'mass_mailing_test_from_missmatch',
|
||||
'from_filter': 'notcompanydomain.com',
|
||||
'smtp_host': 'not_real@smtp.com',
|
||||
'name' : 'mass_mailing_test_from_missmatch',
|
||||
'from_filter' : 'test.com',
|
||||
'smtp_host' : 'not_real@smtp.com',
|
||||
},
|
||||
])
|
||||
|
||||
|
|
@ -351,7 +349,7 @@ class TestMassMailValues(MassMailCommon):
|
|||
]
|
||||
expected_from_all = [
|
||||
self.env.user.email_formatted, # default when no server
|
||||
self.env['ir.mail_server']._get_default_from_address(), # matches company alias domain
|
||||
self.env.user.company_id.alias_domain_id.default_from_email, # matches company alias domain
|
||||
self.env.user.email_formatted, # not matching from filter -> back to user from
|
||||
]
|
||||
|
||||
|
|
@ -361,8 +359,8 @@ class TestMassMailValues(MassMailCommon):
|
|||
# settings to designate a dedicated outgoing email server
|
||||
if mail_server:
|
||||
self.env['res.config.settings'].sudo().create({
|
||||
'mass_mailing_mail_server_id': mail_server.id,
|
||||
'mass_mailing_outgoing_mail_server': mail_server,
|
||||
'mass_mailing_mail_server_id' : mail_server.id,
|
||||
'mass_mailing_outgoing_mail_server' : mail_server,
|
||||
}).execute()
|
||||
|
||||
# Create mailing
|
||||
|
|
@ -390,6 +388,35 @@ class TestMassMailValues(MassMailCommon):
|
|||
)
|
||||
self.assertEqual(mailing_form.mailing_model_real, 'res.partner')
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_create_on_send(self):
|
||||
recipient = self.env['res.partner'].create({
|
||||
'name': 'Mass Mail Partner',
|
||||
'email': 'Customer <test.customer@example.com>',
|
||||
})
|
||||
|
||||
mass_mailing_name = "An arbitrary mailing name"
|
||||
|
||||
composer = self.env['mail.compose.message'].with_user(self.user_marketing).with_context({
|
||||
'default_composition_mode': 'mass_mail',
|
||||
'default_model': 'res.partner',
|
||||
'default_res_ids': recipient.ids,
|
||||
}).create({
|
||||
'subject': 'Mass Mail Responsive',
|
||||
'body': 'I am Responsive body',
|
||||
'mass_mailing_name': mass_mailing_name
|
||||
})
|
||||
self.assertFalse(composer.mass_mailing_id, "No mailing should've been created")
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
composer._action_send_mail(recipient.ids)
|
||||
|
||||
self.assertEqual(len(composer.mass_mailing_id.ids), 1, "A mailing should've been created")
|
||||
self.assertEqual(composer.mass_mailing_id.name, mass_mailing_name, f"Mailing name should be: {mass_mailing_name}")
|
||||
|
||||
mail_values = composer._prepare_mail_values(recipient.ids)[recipient.id]
|
||||
self.assertIn(f"Received the mailing <b>{mass_mailing_name}</b>", mail_values["body"], "The composer doesn't use the provided mass_mailing_name")
|
||||
|
||||
@mute_logger('odoo.sql_db')
|
||||
@users('user_marketing')
|
||||
def test_mailing_trace_values(self):
|
||||
|
|
@ -492,8 +519,8 @@ class TestMassMailValues(MassMailCommon):
|
|||
'schedule_date': datetime(2023, 2, 17, 11, 0),
|
||||
})
|
||||
mailing.action_put_in_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing._process_mass_mailing_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.enter_registry_test_mode():
|
||||
self.env.ref('mass_mailing.ir_cron_mass_mailing_queue').sudo().method_direct_trigger()
|
||||
|
||||
self.assertFalse(mailing.body_html)
|
||||
self.assertEqual(mailing.mailing_model_name, 'res.partner')
|
||||
|
|
@ -710,14 +737,19 @@ class TestMassMailFeatures(MassMailCommon, CronMixinCase):
|
|||
'mailing_domain': [('id', 'in', (partner_a | partner_b).ids)],
|
||||
'body_html': 'This is mass mail marketing demo'
|
||||
})
|
||||
self.assertEqual(mailing.user_id, self.user_marketing)
|
||||
mailing.action_put_in_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing._process_mass_mailing_queue()
|
||||
self.assertEqual(mailing.email_from, self.env.user.email_formatted)
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.enter_registry_test_mode():
|
||||
self.env.ref('mass_mailing.ir_cron_mass_mailing_queue').sudo().method_direct_trigger()
|
||||
|
||||
author = self.user_marketing.partner_id
|
||||
email_values = {'email_from': mailing.email_from}
|
||||
self.assertMailTraces(
|
||||
[{'partner': partner_a},
|
||||
{'partner': partner_b, 'trace_status': 'cancel', 'failure_type': 'mail_bl'}],
|
||||
mailing, partner_a + partner_b, check_mail=True
|
||||
[{'partner': partner_a, 'email_values': email_values},
|
||||
{'partner': partner_b, 'trace_status': 'cancel', 'failure_type': 'mail_bl', 'email_values': email_values}],
|
||||
mailing, partner_a + partner_b,
|
||||
check_mail=True, author=author,
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
|
|
@ -745,14 +777,17 @@ Email: <a id="url5" href="mailto:test@odoo.com">test@odoo.com</a></div>""",
|
|||
|
||||
mailing.action_put_in_queue()
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing._process_mass_mailing_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.enter_registry_test_mode():
|
||||
self.env.ref('mass_mailing.ir_cron_mass_mailing_queue').sudo().method_direct_trigger()
|
||||
|
||||
author = self.user_marketing.partner_id
|
||||
email_values = {'email_from': mailing.email_from}
|
||||
self.assertMailTraces(
|
||||
[{'email': 'fleurus@example.com'},
|
||||
{'email': 'gorramts@example.com'},
|
||||
{'email': 'ybrant@example.com'}],
|
||||
mailing, self.mailing_list_1.contact_ids, check_mail=True
|
||||
[{'email': 'fleurus@example.com', 'email_values': email_values},
|
||||
{'email': 'gorramts@example.com', 'email_values': email_values},
|
||||
{'email': 'ybrant@example.com', 'email_values': email_values}],
|
||||
mailing, self.mailing_list_1.contact_ids,
|
||||
check_mail=True, author=author,
|
||||
)
|
||||
|
||||
for contact in self.mailing_list_1.contact_ids:
|
||||
|
|
@ -813,7 +848,7 @@ class TestMailingHeaders(MassMailCommon, HttpCase):
|
|||
|
||||
# check outgoing email headers (those are put into outgoing email
|
||||
# not in the mail.mail record)
|
||||
email = self._find_sent_mail_wemail(contact.email)
|
||||
email = self._find_sent_email_wemail(contact.email)
|
||||
headers = email.get("headers")
|
||||
unsubscribe_oneclick_url = test_mailing._get_unsubscribe_oneclick_url(contact.email, contact.id)
|
||||
self.assertTrue(headers, "Mass mailing emails should have headers for unsubscribe")
|
||||
|
|
@ -826,10 +861,10 @@ class TestMailingHeaders(MassMailCommon, HttpCase):
|
|||
|
||||
# unsubscribe in one-click
|
||||
unsubscribe_oneclick_url = headers["List-Unsubscribe"].strip("<>")
|
||||
self.opener.post(unsubscribe_oneclick_url)
|
||||
self.url_open(unsubscribe_oneclick_url, method='POST')
|
||||
|
||||
# should be unsubscribed
|
||||
self.assertTrue(contact.subscription_list_ids.opt_out)
|
||||
self.assertTrue(contact.subscription_ids.opt_out)
|
||||
|
||||
|
||||
class TestMailingScheduleDateWizard(MassMailCommon):
|
||||
|
|
@ -854,3 +889,27 @@ class TestMailingScheduleDateWizard(MassMailCommon):
|
|||
self.assertEqual(mailing.schedule_date, datetime(2021, 4, 30, 9, 0))
|
||||
self.assertEqual(mailing.schedule_type, 'scheduled')
|
||||
self.assertEqual(mailing.state, 'in_queue')
|
||||
|
||||
|
||||
class TestMassMailingActions(MassMailCommon):
|
||||
def test_mailing_action_open(self):
|
||||
mass_mailings = self.env['mailing.mailing'].create([
|
||||
{'subject': 'First subject'},
|
||||
{'subject': 'Second subject'}
|
||||
])
|
||||
# Create two traces: one linked to the created mass.mailing and one not (action should open only the first)
|
||||
self.env["mailing.trace"].create([{
|
||||
"trace_status": "open",
|
||||
"mass_mailing_id": mass_mailings[0].id,
|
||||
"model": "res.partner",
|
||||
"res_id": self.partner_admin.id,
|
||||
}, {
|
||||
"trace_status": "open",
|
||||
"mass_mailing_id": mass_mailings[1].id,
|
||||
"model": "res.partner",
|
||||
"res_id": self.partner_employee.id,
|
||||
}
|
||||
])
|
||||
results = mass_mailings[0].action_view_opened()
|
||||
results_partner = self.env["res.partner"].search(results['domain'])
|
||||
self.assertEqual(results_partner, self.partner_admin, "Trace leaked from mass_mailing_2 to mass_mailing_1")
|
||||
|
|
|
|||
|
|
@ -1,14 +1,48 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import copy
|
||||
|
||||
from datetime import datetime
|
||||
from freezegun import freeze_time
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo import exceptions
|
||||
from odoo.addons.mass_mailing.tests.common import MassMailCommon
|
||||
from odoo.tests.common import Form, users
|
||||
from odoo.tests import Form, tagged, users
|
||||
|
||||
|
||||
@tagged('mailing_list')
|
||||
class TestMailingContactAccess(MassMailCommon):
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_contact_properties_access(self):
|
||||
# Check that mailing user can edit properties on mailing contact
|
||||
value = [{'type': 'char', 'name': 'test', 'value': 'test', 'definition_changed': True}]
|
||||
contact = self.env['mailing.contact'].create({'properties': copy.deepcopy(value)})
|
||||
self.assertEqual(dict(contact.properties), {'test': 'test'})
|
||||
|
||||
delete_value = [{'type': 'char', 'name': 'test', 'value': 'test', 'definition_deleted': True}]
|
||||
contact = self.env['mailing.contact'].create({'properties': copy.deepcopy(delete_value)})
|
||||
self.assertEqual(dict(contact.properties), {})
|
||||
|
||||
# Sanity check, mailing user can only edit the definition on partner in SUDO
|
||||
with self.assertRaises(exceptions.AccessError):
|
||||
self.env['res.partner'].create({'properties': copy.deepcopy(value), 'name': 'test'})
|
||||
|
||||
partner = self.env['res.partner'].sudo().create({'properties': copy.deepcopy(value), 'name': 'test'})
|
||||
self.assertEqual(dict(partner.properties), {'test': 'test'})
|
||||
|
||||
base_definition = self.env['properties.base.definition']._get_definition_for_property_field('mailing.contact', 'properties')
|
||||
self.assertTrue(base_definition)
|
||||
with self.assertRaises(exceptions.AccessError):
|
||||
base_definition.properties_field_id = self.env["ir.model.fields"].sudo()._get('res.partner', 'properties').id
|
||||
|
||||
partner_base_definition = self.env['properties.base.definition']._get_definition_for_property_field('res.partner', 'properties')
|
||||
with self.assertRaises(exceptions.AccessError):
|
||||
partner_base_definition.unlink()
|
||||
|
||||
|
||||
@tagged('mailing_list')
|
||||
class TestMailingContactToList(MassMailCommon):
|
||||
|
||||
@users('user_marketing')
|
||||
|
|
@ -27,13 +61,17 @@ class TestMailingContactToList(MassMailCommon):
|
|||
|
||||
# create wizard with context values
|
||||
wizard_form = Form(self.env['mailing.contact.to.list'].with_context(default_contact_ids=contacts.ids))
|
||||
self.assertEqual(wizard_form.contact_ids._get_ids(), contacts.ids)
|
||||
self.assertEqual(wizard_form.contact_ids.ids, contacts.ids)
|
||||
|
||||
# set mailing list and add contacts
|
||||
wizard_form.mailing_list_id = mailing
|
||||
wizard = wizard_form.save()
|
||||
action = wizard.action_add_contacts()
|
||||
self.assertEqual(contacts.list_ids, mailing)
|
||||
frozen_time = datetime(2025, 1, 1, 0, 0)
|
||||
with self.mock_datetime_and_now(frozen_time):
|
||||
action = wizard.action_add_contacts()
|
||||
self.assertEqual(contacts.list_ids, mailing)
|
||||
create_dates = contacts.subscription_ids.mapped('create_date')
|
||||
self.assertTrue(all(date == frozen_time for date in create_dates), "All create dates should be equal to frozen datetime")
|
||||
self.assertEqual(action["type"], "ir.actions.client")
|
||||
self.assertTrue(action.get("params", {}).get("next"), "Should return a notification with a next action")
|
||||
subaction = action["params"]["next"]
|
||||
|
|
@ -55,6 +93,7 @@ class TestMailingContactToList(MassMailCommon):
|
|||
self.assertEqual(subaction["context"]["default_contact_list_ids"], [mailing2.id])
|
||||
|
||||
|
||||
@tagged('mailing_list')
|
||||
class TestMailingListMerge(MassMailCommon):
|
||||
|
||||
@classmethod
|
||||
|
|
@ -84,7 +123,7 @@ class TestMailingListMerge(MassMailCommon):
|
|||
new = self.env['mailing.contact'].with_context(default_list_ids=default_list_ids).create([{
|
||||
'name': 'Contact_%d' % x,
|
||||
'email': 'contact_%d@test.example.com' % x,
|
||||
'subscription_list_ids': [(0, 0, {
|
||||
'subscription_ids': [(0, 0, {
|
||||
'list_id': self.mailing_list_1.id,
|
||||
'opt_out': True,
|
||||
}), (0, 0, {
|
||||
|
|
@ -102,18 +141,32 @@ class TestMailingListMerge(MassMailCommon):
|
|||
new = new.with_context(default_list_ids=[list_id])
|
||||
self.assertFalse(any(contact.opt_out for contact in new))
|
||||
|
||||
with freeze_time('2022-01-01 12:00'):
|
||||
with freeze_time('2022-01-01 12:00'), \
|
||||
patch.object(self.env.cr, 'now', lambda: datetime(2022, 1, 1, 12, 0, 0)):
|
||||
contact_form = Form(self.env['mailing.contact'])
|
||||
contact_form.name = 'Contact_test'
|
||||
with contact_form.subscription_list_ids.new() as subscription:
|
||||
with contact_form.subscription_ids.new() as subscription:
|
||||
subscription.list_id = self.mailing_list_1
|
||||
subscription.opt_out = True
|
||||
with contact_form.subscription_list_ids.new() as subscription:
|
||||
with contact_form.subscription_ids.new() as subscription:
|
||||
subscription.list_id = self.mailing_list_2
|
||||
subscription.opt_out = False
|
||||
contact = contact_form.save()
|
||||
self.assertEqual(contact.subscription_list_ids[0].unsubscription_date, datetime(2022, 1, 1, 12, 0, 0))
|
||||
self.assertFalse(contact.subscription_list_ids[1].unsubscription_date)
|
||||
self.assertEqual(contact.subscription_ids.filtered(lambda s: s.list_id == self.mailing_list_1).opt_out_datetime, datetime(2022, 1, 1, 12, 0, 0))
|
||||
self.assertFalse(contact.subscription_ids.filtered(lambda s: s.list_id == self.mailing_list_2).opt_out_datetime)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_list_action_send_mailing(self):
|
||||
mailing_ctx = self.mailing_list_1.action_send_mailing().get('context', {})
|
||||
form = Form(self.env['mailing.mailing'].with_context(mailing_ctx))
|
||||
form.subject = 'Test Mail'
|
||||
mailing = form.save()
|
||||
# Check that mailing model and mailing list are set properly
|
||||
self.assertEqual(
|
||||
mailing.mailing_model_id, self.env['ir.model']._get('mailing.list'),
|
||||
'Should have correct mailing model set')
|
||||
self.assertEqual(mailing.contact_list_ids, self.mailing_list_1, 'Should have correct mailing list set')
|
||||
self.assertEqual(mailing.mailing_type, 'mail', 'Should have correct mailing_type')
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_list_contact_copy_in_context_of_mailing_list(self):
|
||||
|
|
@ -121,7 +174,7 @@ class TestMailingListMerge(MassMailCommon):
|
|||
contact_1 = MailingContact.create({
|
||||
'name': 'Sam',
|
||||
'email': 'gamgee@shire.com',
|
||||
'subscription_list_ids': [(0, 0, {'list_id': self.mailing_list_3.id})],
|
||||
'subscription_ids': [(0, 0, {'list_id': self.mailing_list_3.id})],
|
||||
})
|
||||
# Copy the contact with default_list_ids in context, which should not raise anything
|
||||
contact_2 = contact_1.with_context(default_list_ids=self.mailing_list_3.ids).copy()
|
||||
|
|
@ -175,6 +228,7 @@ class TestMailingListMerge(MassMailCommon):
|
|||
self.assertEqual(merge.dest_list_id, self.mailing_list_3)
|
||||
|
||||
|
||||
@tagged('mailing_list')
|
||||
class TestMailingContactImport(MassMailCommon):
|
||||
"""Test the transient <mailing.contact.import>."""
|
||||
|
||||
|
|
@ -238,6 +292,7 @@ class TestMailingContactImport(MassMailCommon):
|
|||
# Test that the context key "default_list_ids" is ignored (because we manually set list_ids)
|
||||
contact_import.with_context(default_list_ids=(first_list | second_list).ids).action_import()
|
||||
|
||||
self.env['mailing.list'].invalidate_model(['contact_ids'])
|
||||
# Check the contact of the first mailing list
|
||||
contacts = [
|
||||
(contact.name, contact.email)
|
||||
|
|
@ -269,3 +324,49 @@ class TestMailingContactImport(MassMailCommon):
|
|||
|
||||
contact = self.env['mailing.contact'].search([('email', '=', 'already_exists_list_1@example.com')])
|
||||
self.assertEqual(len(contact), 1, 'Should have updated the existing contact instead of creating a new one')
|
||||
|
||||
|
||||
@tagged('mailing_list')
|
||||
class TestSubscriptionManagement(MassMailCommon):
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_update_optout(self):
|
||||
_email_formatted = '"Mireille Labeille" <mireille@test.example.com>'
|
||||
_email_formatted_upd = '"Mireille Oreille-Labeille" <mireille@test.example.com>'
|
||||
_email_normalized = 'mireille@test.example.com'
|
||||
self._create_mailing_list()
|
||||
ml_1, ml_2 = self.mailing_list_1.with_env(self.env), self.mailing_list_2.with_env(self.env)
|
||||
ml_3 = self._create_mailing_list_of_x_contacts(3)
|
||||
self.assertEqual(ml_1.contact_count, 3)
|
||||
self.assertEqual(ml_1.contact_count_blacklisted, 0)
|
||||
self.assertEqual(ml_1.contact_count_email, 3)
|
||||
self.assertEqual(ml_1.contact_count_opt_out, 0)
|
||||
self.assertEqual(ml_2.contact_count, 4)
|
||||
self.assertEqual(ml_2.contact_count_blacklisted, 0)
|
||||
self.assertEqual(ml_2.contact_count_email, 4)
|
||||
self.assertEqual(ml_2.contact_count_opt_out, 0)
|
||||
self.assertEqual(ml_3.contact_count, 3)
|
||||
self.assertEqual(ml_3.contact_count_blacklisted, 0)
|
||||
self.assertEqual(ml_3.contact_count_email, 3)
|
||||
self.assertEqual(ml_3.contact_count_opt_out, 0)
|
||||
|
||||
# create a new test contact
|
||||
contact = self.env['mailing.contact'].browse(
|
||||
self.env['mailing.contact'].name_create(_email_formatted)[0]
|
||||
)
|
||||
self.assertEqual(contact.email, _email_normalized)
|
||||
self.assertEqual(contact.name, 'Mireille Labeille')
|
||||
|
||||
# add new subscriptions (and ensure email_normalized is used)
|
||||
(ml_1 + ml_2)._update_subscription_from_email(_email_formatted_upd, opt_out=False)
|
||||
subs = self.env['mailing.subscription'].search(
|
||||
[('contact_id', '=', contact.id)]
|
||||
)
|
||||
self.assertEqual(subs.list_id, ml_1 + ml_2)
|
||||
|
||||
# opt-out from opted-in mailing list + 1 non opted-in mailing list
|
||||
(ml_2 + ml_3)._update_subscription_from_email(_email_formatted_upd, opt_out=True)
|
||||
subs = self.env['mailing.subscription'].search(
|
||||
[('contact_id', '=', contact.id)]
|
||||
)
|
||||
self.assertEqual(subs.list_id, ml_1 + ml_2)
|
||||
|
|
|
|||
|
|
@ -27,10 +27,14 @@ class TestMailingRetry(MassMailCommon, CronMixinCase):
|
|||
mailing.action_launch()
|
||||
|
||||
# force email sending to fail to test our retry mechanism
|
||||
def patched_mail_mail_send(mail_records, auto_commit=False, raise_exception=False, smtp_session=None):
|
||||
def patched_mail_mail_send(mail_records, auto_commit=False, raise_exception=False, smtp_session=None,
|
||||
alias_domain_id=False, mail_server=False, post_send_callback=None):
|
||||
mail_records.write({'state': 'exception', 'failure_reason': 'forced_failure'})
|
||||
|
||||
with patch('odoo.addons.mail.models.mail_mail.MailMail._send', patched_mail_mail_send):
|
||||
with (
|
||||
patch('odoo.addons.mail.models.mail_mail.MailMail._send', patched_mail_mail_send),
|
||||
self.enter_registry_test_mode(),
|
||||
):
|
||||
self.env.ref('mass_mailing.ir_cron_mass_mailing_queue').sudo().method_direct_trigger()
|
||||
|
||||
with self.capture_triggers('mass_mailing.ir_cron_mass_mailing_queue') as captured_triggers:
|
||||
|
|
|
|||
|
|
@ -1,45 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo.tests
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
|
||||
from odoo.addons.mass_mailing.tests.common import MassMailCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@odoo.tests.tagged('-at_install', 'post_install')
|
||||
class TestUi(HttpCaseWithUserDemo):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user_demo.groups_id |= self.env.ref('mass_mailing.group_mass_mailing_user')
|
||||
self.user_demo.groups_id |= self.env.ref('mail.group_mail_template_editor')
|
||||
self.user_demo.groups_id |= self.env.ref('mass_mailing.group_mass_mailing_campaign')
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestMailingUi(MassMailCommon, HttpCaseWithUserDemo):
|
||||
|
||||
def test_01_mass_mailing_editor_tour(self):
|
||||
self.start_tour("/web", 'mass_mailing_editor_tour', login="demo")
|
||||
mail = self.env['mailing.mailing'].search([('subject', '=', 'Test')])[0]
|
||||
# The tour created and saved an email. The edited version should be
|
||||
# saved in body_arch, and its transpiled version (see convert_inline)
|
||||
# for email client compatibility should be saved in body_html. This
|
||||
# ensures both fields have different values (the mailing body should
|
||||
# have been converted to a table in body_html).
|
||||
self.assertIn('data-snippet="s_title"', mail.body_arch)
|
||||
self.assertTrue(mail.body_arch.startswith('<div'))
|
||||
self.assertIn('data-snippet="s_title"', mail.body_html)
|
||||
self.assertTrue(mail.body_html.startswith('<table'))
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailingUi, cls).setUpClass()
|
||||
|
||||
def test_02_mass_mailing_snippets_menu_tabs(self):
|
||||
self.start_tour("/web", 'mass_mailing_snippets_menu_tabs', login="demo")
|
||||
cls.user_marketing.write({
|
||||
'group_ids': [
|
||||
(4, cls.env.ref('mail.group_mail_template_editor').id),
|
||||
],
|
||||
})
|
||||
cls.user_demo.write({
|
||||
'group_ids': [
|
||||
(4, cls.env.ref('mass_mailing.group_mass_mailing_campaign').id),
|
||||
(4, cls.env.ref('mass_mailing.group_mass_mailing_user').id),
|
||||
],
|
||||
})
|
||||
|
||||
def test_03_mass_mailing_snippets_toolbar_mobile_hide(self):
|
||||
self.start_tour("/web", 'mass_mailing_snippets_menu_toolbar_new_mailing_mobile', login="demo")
|
||||
|
||||
def test_04_mass_mailing_snippets_menu_hide(self):
|
||||
self.start_tour("/web", 'mass_mailing_snippets_menu_toolbar', login="demo")
|
||||
|
||||
def test_05_mass_mailing_basic_theme_toolbar(self):
|
||||
self.start_tour('/web', 'mass_mailing_basic_theme_toolbar', login="demo")
|
||||
|
||||
def test_06_mass_mailing_campaign_new_mailing(self):
|
||||
self.env.ref('base.group_user').write({'implied_ids': [(4, self.env.ref('mass_mailing.group_mass_mailing_campaign').id)]})
|
||||
def test_mailing_campaign_tour(self):
|
||||
# self.env.ref('base.group_user').write({'implied_ids': [(4, self.env.ref('mass_mailing.group_mass_mailing_campaign').id)]})
|
||||
campaign = self.env['utm.campaign'].create({
|
||||
'name': 'Test Newsletter',
|
||||
'user_id': self.env.ref("base.user_admin").id,
|
||||
|
|
@ -54,7 +41,44 @@ class TestUi(HttpCaseWithUserDemo):
|
|||
self.env['mailing.list'].create({
|
||||
'name': 'Test Newsletter',
|
||||
})
|
||||
self.start_tour("/web", 'mass_mailing_campaing_new_mailing', login="demo")
|
||||
self.user_marketing.write({
|
||||
'group_ids': [
|
||||
(4, self.env.ref('mass_mailing.group_mass_mailing_campaign').id),
|
||||
],
|
||||
})
|
||||
self.start_tour("/odoo", 'mailing_campaign', login="user_marketing")
|
||||
|
||||
def test_07_mass_mailing_code_view_tour(self):
|
||||
self.start_tour("/web?debug=tests", 'mass_mailing_code_view_tour', login="demo")
|
||||
def test_mailing_editor_tour(self):
|
||||
mailing = self.env['mailing.mailing'].search([('subject', '=', 'TestFromTour')], limit=1)
|
||||
self.assertFalse(mailing)
|
||||
self.start_tour("/odoo", 'mailing_editor', login="user_marketing")
|
||||
|
||||
# The tour created and saved a mailing. The edited version should be
|
||||
# saved in body_arch, and its transpiled version (see convert_inline)
|
||||
# for email client compatibility should be saved in body_html. This
|
||||
# ensures both fields have different values (the mailing body should
|
||||
# have been converted to a table in body_html).
|
||||
mailing = self.env['mailing.mailing'].search([('subject', '=', 'TestFromTour')], limit=1)
|
||||
self.assertTrue(mailing)
|
||||
self.assertIn('data-snippet="s_title"', mailing.body_arch)
|
||||
self.assertTrue(mailing.body_arch.startswith('<div'))
|
||||
self.assertIn('data-snippet="s_title"', mailing.body_html)
|
||||
self.assertTrue(mailing.body_html.startswith('<table'))
|
||||
|
||||
def test_mailing_editor_theme_tour(self):
|
||||
self.start_tour('/odoo', 'mailing_editor_theme', login="demo")
|
||||
|
||||
def test_snippets_mailing_menu_tabs_tour(self):
|
||||
self.start_tour("/odoo", 'snippets_mailing_menu_tabs', login="demo")
|
||||
|
||||
def test_snippets_mailing_menu_toolbar_tour(self):
|
||||
self.start_tour("/odoo", 'snippets_mailing_menu_toolbar', login="demo")
|
||||
|
||||
def test_snippets_mailing_menu_toolbar_mobile_tour(self):
|
||||
self.start_tour("/odoo", 'snippets_mailing_menu_toolbar_mobile', login="demo")
|
||||
|
||||
def test_mass_mailing_code_view_tour(self):
|
||||
self.start_tour("/odoo?debug=tests", 'mass_mailing_code_view_tour', login="demo")
|
||||
|
||||
def test_mass_mailing_dynamic_placeholder_tour(self):
|
||||
self.start_tour("/odoo", 'mass_mailing_dynamic_placeholder_tour', login="demo")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue