[IMP] mass_mailing: add tests for unsubscribe mailing portal

Purpose of this test is to add tours and tests about the portal in mass mailing
that allows to unsubscribe from mailing list and to use the exclusion list.

This is done using small tours called from unit tests. Two type of tours are
added. The first one targets mailing done on mailing lists. As it targets
contact model unsubscription click opts-out the contact from the mailing
list. The second one targets mailing done on other documents. When clicking
on unsubscription link the document's primary email is directly added into
the exclusion list.

Some other additional tests are added to cover various use cases and features
of mailing controllers.

Task-2150462 (Mass Mailing: Improve subscription management)

Part-of: odoo/odoo#86084
This commit is contained in:
Thibault Delavallée
2023-10-06 06:13:53 +00:00
parent c855bee515
commit a2872cb981
4 changed files with 598 additions and 30 deletions
@@ -0,0 +1,67 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
/*
* Tour: unsubscribe from a mailing done on documents (aka not on contacts or
* mailing lists). We assume email is not member of any mailing list in this test.
*/
registry.category("web_tour.tours").add('mailing_portal_unsubscribe_from_document', {
test: true,
steps: () => [
{
content: "Confirmation unsubscribe is done",
trigger: "div#subscription_info strong:contains('successfully unsubscribed')",
}, {
content: "No warning should be displayed",
trigger: "div#div_blacklist:not(:has(p:contains('You were still subscribed to those newsletters. You will not receive any news from them anymore')))",
}, {
content: "Revert exclusion list",
trigger: "div#button_remove_blacklist",
}, {
content: "Confirmation exclusion list is removed",
trigger: "div#subscription_info strong:contains('removed from our blacklist')",
}, {
content: "Now exclude me (again)",
trigger: "div#button_add_blacklist",
}, {
content: "Confirmation exclusion is done",
trigger: "div#subscription_info strong:contains('added to our blacklist')",
isCheck: true,
},
],
});
/*
* Tour: unsubscribe from a mailing done on documents (aka not on contacts or
* mailing lists). We assume email is member of mailing lists in this test.
*/
registry.category("web_tour.tours").add('mailing_portal_unsubscribe_from_document_with_lists', {
test: true,
steps: () => [
{
content: "Confirmation unsubscribe is done",
trigger: "div#subscription_info strong:contains('successfully unsubscribed')",
}, {
content: "Display warning about mailing lists",
trigger: "div#div_blacklist p:contains('You were still subscribed to those newsletters. You will not receive any news from them anymore')",
}, {
content: "Warning should contain reference to memberships",
trigger: "div#div_blacklist li strong:contains('List1')",
}, {
content: "Revert exclusion list",
trigger: "div#button_remove_blacklist",
}, {
content: "Confirmation exclusion list is removed",
trigger: "div#subscription_info strong:contains('removed from our blacklist')",
}, {
content: "Now exclude me (again)",
trigger: "div#button_add_blacklist",
}, {
content: "Confirmation exclusion is done",
trigger: "div#subscription_info strong:contains('added to our blacklist')",
isCheck: true,
},
],
});
@@ -0,0 +1,105 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
/*
* Tour: unsubscribe from a mailing done on lists (aka playing with opt-out flag
* instead of directly blocking emails).
*/
registry.category("web_tour.tours").add('mailing_portal_unsubscribe_from_list', {
test: true,
steps: () => [
{
content: "Confirmation unsubscribe is done",
trigger: "div#subscription_info strong:contains('unsubscribed from List1, List2')",
}, {
content: "Write feedback reason",
trigger: "textarea[name='opt_out_feedback']",
run: "text My feedback",
}, {
content: "Hit Send",
trigger: "div#button_feedback",
}, {
content: "Confirmation feedback is sent",
trigger: "div#subscription_info:contains('Your feedback has been sent successfully')",
}, {
content: "Now exclude me",
trigger: "div#button_add_blacklist",
}, {
content: "Confirmation exclusion is done",
trigger: "div#subscription_info strong:contains('added to our blacklist')",
isCheck: true,
},
],
});
/*
* Tour: unsubscribe from a mailing done on lists (aka playing with opt-out flag
* instead of directly blocking emails), then play with list subscriptions and
* blocklist addition / removal. This is mainly an extended version of the tour
* hereabove, easing debug and splitting checks.
*/
registry.category("web_tour.tours").add('mailing_portal_unsubscribe_from_list_with_update', {
test: true,
steps: () => [
{
content: "Confirmation unsubscribe is done",
trigger: "div#subscription_info strong:contains('unsubscribed from List1, List2')",
}, {
content: "List1 is present, just opt-outed",
trigger: "li.list-group-item:contains('List1') span:contains('Unsubscribed')",
}, {
content: "List3 is present, opt-outed (test starting data)",
trigger: "li.list-group-item:contains('List3') span:contains('Unsubscribed')",
}, {
content: "List2 is not present (not member -> not displayed)",
trigger: "ul:not(:has(li.list-group-item:contains('List2') span:contains('Unsubscribed')))",
}, {
content: "Write feedback reason",
trigger: "textarea[name='opt_out_feedback']",
run: "text My feedback",
}, {
content: "Hit Send",
trigger: "div#button_feedback",
}, {
content: "Confirmation feedback is sent",
trigger: "div#subscription_info:contains('Your feedback has been sent successfully')",
}, {
content: "Now exclude me",
trigger: "div#button_add_blacklist",
}, {
content: "Confirmation exclusion is done",
trigger: "div#subscription_info strong:contains('added to our blacklist')",
}, {
content: "This should disable the 'Update my subscriptions' button",
trigger: "button#send_form[disabled]",
isCheck: true,
}, {
content: "Revert exclusion list",
trigger: "div#button_remove_blacklist",
}, {
content: "Confirmation exclusion list is removed",
trigger: "div#subscription_info strong:contains('removed from our blacklist')",
}, {
content: "'Update my subscriptions' button usable again",
trigger: "button#send_form:not([disabled])",
}, {
content: "Choose the mailing list 3 to come back",
trigger: "li.list-group-item:contains('List3') input",
}, {
content: "Update subscription",
trigger: "button#send_form",
}, {
content: "Confirmation changes are done",
trigger: "div#subscription_info:contains('Your changes have been saved')",
}, {
content: "Now exclude me (again)",
trigger: "div#button_add_blacklist",
}, {
content: "Confirmation exclusion is done",
trigger: "div#subscription_info strong:contains('added to our blacklist')",
isCheck: true,
}
],
});
+27 -17
View File
@@ -291,23 +291,33 @@ class MassMailCommon(MailCommon, MassMailCase):
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',
}, {
'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',
}, {
'contact_ids': [
(0, 0, {'name': 'Déboulonneur', 'email': 'fleurus@example.com'}),
],
'name': 'List3',
}, {
'is_public': False,
'name': 'List4',
}
])
cls.mailing_list_3.subscription_ids[0].opt_out = True
@classmethod
def _create_mailing_list_of_x_contacts(cls, contacts_nbr):
@@ -2,32 +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.mass_mailing.tests.common import MassMailCommon
from requests import Session, PreparedRequest, Response
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]
@@ -43,10 +63,375 @@ class TestMailingControllers(MassMailCommon, HttpCase):
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.contact.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.unsubscription_date)
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 in [
(self.user_marketing.id, ''), # no token
(self.user_marketing.id, 'zboobs'), # invalid token
(self.env.uid, hash_token), # invalid credentials
]:
with self.subTest(test_user_id=test_user_id, test_token=test_token):
res = self.url_open(
werkzeug.urls.url_join(
test_mailing.get_base_url(),
f'mailing/report/unsubscribe?user_id={test_user_id}&token={test_token}',
)
)
self.assertEqual(res.status_code, 404)
self.assertTrue(self.env['ir.config_parameter'].sudo().get_param('mass_mailing.mass_mailing_reports'))
# TEST: not mailing user
self.user_marketing.write({
'groups_id': [(3, self.env.ref('mass_mailing.group_mass_mailing_user').id)],
})
res = self.url_open(
werkzeug.urls.url_join(
test_mailing.get_base_url(),
f'mailing/report/unsubscribe?user_id={self.user_marketing.id}&token={hash_token}',
)
)
self.assertEqual(res.status_code, 404)
self.assertTrue(self.env['ir.config_parameter'].sudo().get_param('mass_mailing.mass_mailing_reports'))
# TEST: finally valid call
self.user_marketing.write({
'groups_id': [(4, self.env.ref('mass_mailing.group_mass_mailing_user').id)],
})
res = self.url_open(
werkzeug.urls.url_join(
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;
* remove email from exclusion list;
* re-add email to exclusion list;
"""
test_mailing = self.test_mailing_on_documents.with_env(self.env)
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
# 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}&res_id={test_partner.id}&token={hash_token}",
tour_name,
login=None,
)
# status update check
self.assertTrue(test_partner.is_blacklisted)
# partner (document): no messages added
self.assertEqual(test_partner.message_ids, previous_messages)
# posted messages on exclusion list record: activated, deactivated, activated again
bl_record = self.env['mail.blacklist'].search([('email', '=', test_partner.email_normalized)])
self.assertEqual(len(bl_record.message_ids), 4)
msg_bl2, msg_unbl, 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}" data-oe-id="{test_mailing.id}">{test_mailing.subject}</a> (document <a href="#" 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}" data-oe-id="{test_mailing.id}">{test_mailing.subject}</a> (document <a href="#" data-oe-model="{test_partner._name}" data-oe-id="{test_partner.id}">Contact</a>)</p>')
)
self.assertEqual(
msg_bl.body,
Markup(f'<p>Blocklist request from unsubscribe link of mailing <a href="#" data-oe-model="{test_mailing._name}" data-oe-id="{test_mailing.id}">{test_mailing.subject}</a> (document <a href="#" 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_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) 'My feedback';'
* add email to exclusion list;
"""
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}&res_id={contact_l1.id}&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.unsubscription_date, self._reference_now)
# 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 {self.test_email_normalized}: {test_feedback}</p>')
)
message_unsub = contact_l1.message_ids[1]
self.assertEqual(
message_unsub.body,
Markup(f'<p>The recipient <strong>unsubscribed from {self.mailing_list_1.name}</strong> mailing list(s)</p>')
)
# 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)
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}" data-oe-id="{test_mailing.id}">{test_mailing.subject}</a> (document <a href="#" 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) 'My feedback';'
* add email to exclusion list;
* remove email from exclusion list;
* come back to List3;
* re-add email to exclusion list;
"""
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}&res_id={contact_l1.id}&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.unsubscription_date, self._reference_now)
# status update check on list 3 (opt-in during test)
self.assertFalse(subscription_l3.opt_out)
self.assertFalse(subscription_l3.unsubscription_date)
# 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 {self.test_email_normalized}: {test_feedback}</p>')
)
message_unsub = contact_l1.message_ids[1]
self.assertEqual(
message_unsub.body,
Markup(f'<p>The recipient <strong>unsubscribed from {self.mailing_list_1.name}</strong> mailing list(s)</p>')
)
# posted messages on contact record for mailing list 3: subscription
message_unsub = contact_l3.message_ids[0]
self.assertEqual(
message_unsub.body,
Markup(f'<p>The recipient <strong>subscribed to {self.mailing_list_3.name}</strong> mailing list(s)</p>')
)
# 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), 4)
msg_bl2, msg_unbl, 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}" data-oe-id="{test_mailing.id}">{test_mailing.subject}</a> (document <a href="#" 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}" data-oe-id="{test_mailing.id}">{test_mailing.subject}</a> (document <a href="#" 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}" data-oe-id="{test_mailing.id}">{test_mailing.subject}</a> (document <a href="#" 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>'))
@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)
res_id, email_normalized = self.user_marketing.partner_id.id, self.user_marketing.email_normalized
hash_token = test_mailing._generate_mailing_recipient_token(res_id, email_normalized)
self.user_marketing.write({
'groups_id': [(3, self.env.ref('mass_mailing.group_mass_mailing_user').id)],
})
self.authenticate('user_marketing', 'user_marketing')
# TEST: various invalid cases
for test_res_id, test_email, test_token in [
(res_id, email_normalized, ''), # no token
(res_id, email_normalized, 'zboobs'), # wrong token
(self.env.user.partner_id.id, email_normalized, hash_token), # mismatch
(res_id, 'not.email@example.com', hash_token), # mismatch
]:
with self.subTest(test_email=test_email, test_res_id=test_res_id, test_token=test_token):
res = self.url_open(
werkzeug.urls.url_join(
test_mailing.get_base_url(),
f'mailing/{test_mailing.id}/view?email={test_email}&res_id={test_res_id}&token={test_token}',
)
)
self.assertEqual(res.status_code, 403)
# TEST: valid call using credentials
res = self.url_open(
werkzeug.urls.url_join(
test_mailing.get_base_url(),
f'mailing/{test_mailing.id}/view?email={email_normalized}&res_id={res_id}&token={hash_token}',
)
)
self.assertEqual(res.status_code, 200)
# TEST: invalid credentials but mailing user
self.user_marketing.write({
'groups_id': [(4, self.env.ref('mass_mailing.group_mass_mailing_user').id)],
})
res = self.url_open(
werkzeug.urls.url_join(
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()
@@ -75,10 +460,11 @@ class TestMailingControllers(MassMailCommon, HttpCase):
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()