diff --git a/addons/mass_mailing/static/tests/tours/mailing_portal_unsubscribe_from_document.js b/addons/mass_mailing/static/tests/tours/mailing_portal_unsubscribe_from_document.js new file mode 100644 index 00000000000..1387b264018 --- /dev/null +++ b/addons/mass_mailing/static/tests/tours/mailing_portal_unsubscribe_from_document.js @@ -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, + }, + ], +}); diff --git a/addons/mass_mailing/static/tests/tours/mailing_portal_unsubscribe_from_list.js b/addons/mass_mailing/static/tests/tours/mailing_portal_unsubscribe_from_list.js new file mode 100644 index 00000000000..3ae73876e86 --- /dev/null +++ b/addons/mass_mailing/static/tests/tours/mailing_portal_unsubscribe_from_list.js @@ -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, + } + ], +}); diff --git a/addons/mass_mailing/tests/common.py b/addons/mass_mailing/tests/common.py index a43ccde80ef..b6857d8c10d 100644 --- a/addons/mass_mailing/tests/common.py +++ b/addons/mass_mailing/tests/common.py @@ -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): diff --git a/addons/mass_mailing/tests/test_mailing_controllers.py b/addons/mass_mailing/tests/test_mailing_controllers.py index 49ad26ef02b..7c620d4216c 100644 --- a/addons/mass_mailing/tests/test_mailing_controllers.py +++ b/addons/mass_mailing/tests/test_mailing_controllers.py @@ -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': '

Hello
Go to this link

', + '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': '

Hello
Go to this link

', + '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': '

Hello
Go to this link

', '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', '')) + 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" ', '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'

Blocklist request from portal of mailing {test_mailing.subject} (document Contact)

') + ) + self.assertEqual( + msg_unbl.body, + Markup(f'

Blocklist removal request from portal of mailing {test_mailing.subject} (document Contact)

') + ) + self.assertEqual( + msg_bl.body, + Markup(f'

Blocklist request from unsubscribe link of mailing {test_mailing.subject} (document Contact)

') + ) + self.assertEqual(msg_create.body, Markup('

Mail Blacklist created

')) + + 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'

Feedback from {self.test_email_normalized}: {test_feedback}

') + ) + message_unsub = contact_l1.message_ids[1] + self.assertEqual( + message_unsub.body, + Markup(f'

The recipient unsubscribed from {self.mailing_list_1.name} mailing list(s)

') + ) + + # 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'

Blocklist request from portal of mailing {test_mailing.subject} (document Mailing Contact)

') + ) + self.assertEqual(msg_create.body, Markup('

Mail Blacklist created

')) + + 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'

Feedback from {self.test_email_normalized}: {test_feedback}

') + ) + message_unsub = contact_l1.message_ids[1] + self.assertEqual( + message_unsub.body, + Markup(f'

The recipient unsubscribed from {self.mailing_list_1.name} mailing list(s)

') + ) + + # posted messages on contact record for mailing list 3: subscription + message_unsub = contact_l3.message_ids[0] + self.assertEqual( + message_unsub.body, + Markup(f'

The recipient subscribed to {self.mailing_list_3.name} mailing list(s)

') + ) + + # 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'

Blocklist request from portal of mailing {test_mailing.subject} (document Mailing Contact)

') + ) + self.assertEqual( + msg_unbl.body, + Markup(f'

Blocklist removal request from portal of mailing {test_mailing.subject} (document Mailing Contact)

') + ) + self.assertEqual( + msg_bl.body, + Markup(f'

Blocklist request from portal of mailing {test_mailing.subject} (document Mailing Contact)

') + ) + self.assertEqual(msg_create.body, Markup('

Mail Blacklist created

')) + + @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()