diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/mass_mailing/tests | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/mass_mailing/tests')
| -rw-r--r-- | addons/mass_mailing/tests/__init__.py | 7 | ||||
| -rw-r--r-- | addons/mass_mailing/tests/common.py | 249 | ||||
| -rw-r--r-- | addons/mass_mailing/tests/test_mailing_controllers.py | 22 | ||||
| -rw-r--r-- | addons/mass_mailing/tests/test_mailing_internals.py | 332 | ||||
| -rw-r--r-- | addons/mass_mailing/tests/test_mailing_list.py | 112 |
5 files changed, 722 insertions, 0 deletions
diff --git a/addons/mass_mailing/tests/__init__.py b/addons/mass_mailing/tests/__init__.py new file mode 100644 index 00000000..ddc9e161 --- /dev/null +++ b/addons/mass_mailing/tests/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import common +from . import test_mailing_internals +from . import test_mailing_list +from . import test_mailing_controllers diff --git a/addons/mass_mailing/tests/common.py b/addons/mass_mailing/tests/common.py new file mode 100644 index 00000000..1baf6c7d --- /dev/null +++ b/addons/mass_mailing/tests/common.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import datetime +import random +import re +import werkzeug + +from odoo.addons.link_tracker.tests.common import MockLinkTracker +from odoo.addons.mail.tests.common import MailCase, MailCommon, mail_new_test_user +from odoo import tools + +class MassMailCase(MailCase, MockLinkTracker): + + # ------------------------------------------------------------ + # ASSERTS + # ------------------------------------------------------------ + + def assertMailingStatistics(self, mailing, **kwargs): + """ Helper to assert mailing statistics fields. As we have many of them + it helps lessening test asserts. """ + if not kwargs.get('expected'): + kwargs['expected'] = len(mailing.mailing_trace_ids) + if not kwargs.get('delivered'): + kwargs['delivered'] = len(mailing.mailing_trace_ids) + for fname in ['scheduled', 'expected', 'sent', 'delivered', + 'opened', 'replied', 'clicked', + 'ignored', 'failed', 'bounced']: + self.assertEqual( + mailing[fname], kwargs.get(fname, 0), + 'Mailing %s statistics failed: got %s instead of %s' % (fname, mailing[fname], kwargs.get(fname, 0)) + ) + + def assertMailTraces(self, recipients_info, mailing, records, + check_mail=True, sent_unlink=False, author=None, + mail_links_info=None): + """ Check content of traces. Traces are fetched based on a given mailing + and records. Their content is compared to recipients_info structure that + holds expected information. Links content may be checked, notably to + assert shortening or unsubscribe links. Mail.mail records may optionally + be checked. + + :param recipients_info: list[{ + # TRACE + 'partner': res.partner record (may be empty), + 'email': email used when sending email (may be empty, computed based on partner), + 'state': outgoing / sent / ignored / bounced / exception / opened (sent by default), + 'record: linked record, + # MAIL.MAIL + 'content': optional content that should be present in mail.mail body_html; + 'failure_type': optional failure reason; + }, { ... }] + + :param mailing: a mailing.mailing record from which traces have been + generated; + :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; + :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; + :param author: author of sent mail.mail; + """ + # map trace state to email state + state_mapping = { + 'sent': 'sent', + 'opened': 'sent', # opened implies something has been sent + 'replied': 'sent', # replied implies something has been sent + 'ignored': 'cancel', + 'exception': 'exception', + 'canceled': 'cancel', + 'bounced': 'cancel', + } + + traces = self.env['mailing.trace'].search([ + ('mass_mailing_id', 'in', mailing.ids), + ('res_id', 'in', records.ids) + ]) + + # ensure trace coherency + self.assertTrue(all(s.model == records._name for s in traces)) + self.assertEqual(set(s.res_id for s in traces), set(records.ids)) + + # check each traces + 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): + partner = recipient_info.get('partner', self.env['res.partner']) + email = recipient_info.get('email') + state = recipient_info.get('state', 'sent') + record = record or recipient_info.get('record') + content = recipient_info.get('content') + if email is None and partner: + email = partner.email_normalized + + recipient_trace = traces.filtered( + lambda t: t.email == email and t.state == state and (t.res_id == record.id if record else True) + ) + self.assertTrue( + len(recipient_trace) == 1, + 'MailTrace: email %s (recipient %s, state: %s, record: %s): found %s records (1 expected)' % (email, partner, state, record, len(recipient_trace)) + ) + self.assertTrue(bool(recipient_trace.mail_mail_id_int)) + + if check_mail: + if author is None: + author = self.env.user.partner_id + + fields_values = {'mailing_id': mailing} + if 'failure_type' in recipient_info: + fields_values['failure_type'] = recipient_info['failure_type'] + + # specific for partner: email_formatted is used + if partner: + if state == 'sent' and sent_unlink: + self.assertSentEmail(author, [partner]) + else: + self.assertMailMail(partner, state_mapping[state], author=author, content=content, fields_values=fields_values) + # specific if email is False -> could have troubles finding it if several falsy traces + elif not email and state in ('ignored', 'canceled', 'bounced'): + self.assertMailMailWId(recipient_trace.mail_mail_id_int, state_mapping[state], content=content, fields_values=fields_values) + else: + self.assertMailMailWEmails([email], state_mapping[state], author=author, content=content, fields_values=fields_values) + + if link_info: + 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} + if add_link_params: + link_params.update(**add_link_params) + self.assertLinkShortenedHtml( + trace_mail.body_html, + (anchor_id, url, is_shortened), + link_params=link_params, + ) + + # ------------------------------------------------------------ + # TOOLS + # ------------------------------------------------------------ + + def gateway_mail_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 + to bounce; + :param record: record which should bounce; + :param bounce_base_values: optional values given to routing; + """ + trace = mailing.mailing_trace_ids.filtered(lambda t: t.model == record._name and t.res_id == record.id) + + 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'), + 'bounced_partner': self.env['res.partner'].sudo(), + 'bounced_message': self.env['mail.message'].sudo() + } + 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], + }) + self.env['mail.thread']._routing_handle_bounce(False, parsed_bounce_values) + + def gateway_mail_click(self, mailing, record, click_label): + """ Simulate a click on a sent email. """ + trace = mailing.mailing_trace_ids.filtered(lambda t: t.model == record._name and t.res_id == record.id) + email = self._find_sent_mail_wemail(trace.email) + self.assertTrue(bool(email)) + for (_url_href, link_url, _dummy, label) in re.findall(tools.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('/') + code, trace_id = path_items[2], int(path_items[4]) + self.assertEqual(trace.id, trace_id) + + self.env['link.tracker.click'].sudo().add_click( + code, + ip='100.200.300.%3f' % random.random(), + country_code='BE', + mailing_trace_id=trace.id + ) + break + else: + raise AssertionError('url %s not found in mailing %s for record %s' % (click_label, mailing, record)) + + @classmethod + def _create_bounce_trace(cls, mailing, record, dt=None): + if 'email_normalized' in record: + trace_email = record.email_normalized + elif 'email_from' in record: + trace_email = record.email_from + else: + trace_email = record.email + if dt is None: + dt = datetime.datetime.now() - datetime.timedelta(days=1) + randomized = random.random() + trace = cls.env['mailing.trace'].create({ + 'mass_mailing_id': mailing.id, + 'model': record._name, + 'res_id': record.id, + 'bounced': dt, + # TDE FIXME: improve this with a mail-enabled heuristics + 'email': trace_email, + 'message_id': '<%5f@gilbert.boitempomils>' % randomized, + }) + return trace + + +class MassMailCommon(MailCommon, MassMailCase): + + @classmethod + def setUpClass(cls): + super(MassMailCommon, cls).setUpClass() + + cls.user_marketing = mail_new_test_user( + cls.env, login='user_marketing', + groups='base.group_user,base.group_partner_manager,mass_mailing.group_mass_mailing_user', + name='Martial Marketing', signature='--\nMartial') + + cls.email_reply_to = 'MyCompany SomehowAlias <test.alias@test.mycompany.com>' + + cls.env['base'].flush() + + @classmethod + 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'}), + ] + }) diff --git a/addons/mass_mailing/tests/test_mailing_controllers.py b/addons/mass_mailing/tests/test_mailing_controllers.py new file mode 100644 index 00000000..310664c4 --- /dev/null +++ b/addons/mass_mailing/tests/test_mailing_controllers.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import werkzeug + +from odoo.addons.mass_mailing.tests.common import MassMailCommon +from odoo.tests.common import HttpCase + + +class TestMassMailingControllers(MassMailCommon, HttpCase): + + def test_tracking_url_token(self): + mail_mail = self.env['mail.mail'].create({}) + + response = self.url_open(mail_mail._get_tracking_url()) + self.assertEqual(response.status_code, 200) + + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + url = werkzeug.urls.url_join(base_url, 'mail/track/%s/fake_token/blank.gif' % mail_mail.id) + + response = self.url_open(url) + self.assertEqual(response.status_code, 400) diff --git a/addons/mass_mailing/tests/test_mailing_internals.py b/addons/mass_mailing/tests/test_mailing_internals.py new file mode 100644 index 00000000..5fb3771a --- /dev/null +++ b/addons/mass_mailing/tests/test_mailing_internals.py @@ -0,0 +1,332 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from ast import literal_eval + +from odoo.addons.mass_mailing.tests.common import MassMailCommon +from odoo.tests.common import users, Form +from odoo.tools import formataddr, mute_logger + +class TestMassMailValues(MassMailCommon): + + @classmethod + def setUpClass(cls): + super(TestMassMailValues, cls).setUpClass() + cls._create_mailing_list() + + @users('user_marketing') + def test_mailing_body_responsive(self): + """ Testing mail mailing responsive mail body """ + recipient = self.env['res.partner'].create({ + 'name': 'Mass Mail Partner', + 'email': 'Customer <test.customer@example.com>', + }) + mailing = self.env['mailing.mailing'].create({ + 'name': 'Test', + 'subject': 'Test', + 'state': 'draft', + 'mailing_model_id': self.env['ir.model']._get('res.partner').id, + }) + + 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, + }).create({ + 'subject': 'Mass Mail Responsive', + 'body': 'I am Responsive body', + 'mass_mailing_id': mailing.id + }) + + mail_values = composer.get_mail_values([recipient.id]) + body_html = str(mail_values[recipient.id]['body_html']) + + self.assertIn('<!DOCTYPE html>', body_html) + self.assertIn('<head>', body_html) + self.assertIn('viewport', body_html) + self.assertIn('@media', body_html) + self.assertIn('I am Responsive body', body_html) + + @users('user_marketing') + def test_mailing_computed_fields(self): + # Create on res.partner, with default values for computed fields + mailing = self.env['mailing.mailing'].create({ + 'name': 'TestMailing', + 'subject': 'Test', + 'mailing_type': 'mail', + 'body_html': '<p>Hello ${object.name}</p>', + 'mailing_model_id': self.env['ir.model']._get('res.partner').id, + }) + self.assertEqual(mailing.user_id, self.user_marketing) + self.assertEqual(mailing.medium_id, self.env.ref('utm.utm_medium_email')) + self.assertEqual(mailing.mailing_model_name, 'res.partner') + self.assertEqual(mailing.mailing_model_real, 'res.partner') + self.assertEqual(mailing.reply_to_mode, 'email') + 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)]) + # update domain + mailing.write({ + 'mailing_domain': [('email', 'ilike', 'test.example.com')] + }) + self.assertEqual(literal_eval(mailing.mailing_domain), [('email', 'ilike', 'test.example.com')]) + + # reset mailing model -> reset domain; set reply_to -> keep it + mailing.write({ + 'mailing_model_id': self.env['ir.model']._get('mailing.list').id, + 'reply_to': self.email_reply_to, + }) + self.assertEqual(mailing.mailing_model_name, 'mailing.list') + self.assertEqual(mailing.mailing_model_real, 'mailing.contact') + self.assertEqual(mailing.reply_to_mode, 'email') + self.assertEqual(mailing.reply_to, self.email_reply_to) + # default for mailing list: depends upon contact_list_ids + self.assertEqual(literal_eval(mailing.mailing_domain), []) + mailing.write({ + 'contact_list_ids': [(4, self.mailing_list_1.id), (4, self.mailing_list_2.id)] + }) + self.assertEqual(literal_eval(mailing.mailing_domain), [('list_ids', 'in', (self.mailing_list_1 | self.mailing_list_2).ids)]) + + # reset mailing model -> reset domain and reply to mode + mailing.write({ + 'mailing_model_id': self.env['ir.model']._get('mail.channel').id, + }) + self.assertEqual(mailing.mailing_model_name, 'mail.channel') + self.assertEqual(mailing.mailing_model_real, 'mail.channel') + self.assertEqual(mailing.reply_to_mode, 'thread') + self.assertFalse(mailing.reply_to) + + @users('user_marketing') + def test_mailing_computed_fields_default(self): + mailing = self.env['mailing.mailing'].with_context( + default_mailing_domain=repr([('email', 'ilike', 'test.example.com')]) + ).create({ + 'name': 'TestMailing', + 'subject': 'Test', + 'mailing_type': 'mail', + 'body_html': '<p>Hello ${object.name}</p>', + 'mailing_model_id': self.env['ir.model']._get('res.partner').id, + }) + self.assertEqual(literal_eval(mailing.mailing_domain), [('email', 'ilike', 'test.example.com')]) + + @users('user_marketing') + def test_mailing_computed_fields_form(self): + mailing_form = Form(self.env['mailing.mailing'].with_context( + default_mailing_domain="[('email', 'ilike', 'test.example.com')]", + default_mailing_model_id=self.env['ir.model']._get('res.partner').id, + )) + self.assertEqual( + literal_eval(mailing_form.mailing_domain), + [('email', 'ilike', 'test.example.com')], + ) + self.assertEqual(mailing_form.mailing_model_real, 'res.partner') + + +class TestMassMailFeatures(MassMailCommon): + + @classmethod + def setUpClass(cls): + super(TestMassMailFeatures, cls).setUpClass() + cls._create_mailing_list() + + @users('user_marketing') + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_channel_blacklisted_recipients(self): + """ Posting a message on a channel should send one email to all recipients, except the blacklisted ones """ + def _join_channel(channel, partners): + for partner in partners: + channel.write({'channel_last_seen_partner_ids': [(0, 0, {'partner_id': partner.id})]}) + channel.invalidate_cache() + + test_channel = self.env['mail.channel'].create({ + 'name': 'Test', + 'description': 'Description', + 'alias_name': 'test', + 'public': 'public', + 'email_send': True, + }) + test_partner = self.env['res.partner'].create({ + 'name': 'Test Partner', + 'email': 'test@example.com', + }) + + blacklisted_partner = self.env['res.partner'].create({ + 'name': 'Blacklisted Partner', + 'email': 'test@black.list', + }) + + # Set Blacklist + self.env['mail.blacklist'].create({ + 'email': 'test@black.list', + }) + + _join_channel(test_channel, test_partner) + _join_channel(test_channel, blacklisted_partner) + with self.mock_mail_gateway(): + test_channel.message_post(body="Test", message_type='comment', subtype_xmlid='mail.mt_comment') + + self.assertEqual(len(self._mails), 1, 'Number of mail incorrect. Should be equal to 1.') + for email in self._mails: + self.assertEqual( + set(email['email_to']), + set([formataddr((test_partner.name, test_partner.email))]), + 'email_to incorrect. Should be equal to "%s"' % ( + formataddr((test_partner.name, test_partner.email)))) + + @users('user_marketing') + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_mailing_deletion(self): + """ Test deletion in various use case, depending on reply-to """ + # 1- Keep archives and reply-to set to 'answer = new thread' + mailing = self.env['mailing.mailing'].create({ + 'name': 'TestSource', + 'subject': 'TestDeletion', + 'body_html': "<div>Hello {object.name}</div>", + 'mailing_model_id': self.env['ir.model']._get('mailing.list').id, + 'contact_list_ids': [(6, 0, self.mailing_list_1.ids)], + 'keep_archives': True, + 'reply_to_mode': 'email', + 'reply_to': self.email_reply_to, + }) + self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message']) + + with self.mock_mail_gateway(mail_unlink_sent=True): + mailing.action_send_mail() + + self.assertEqual(len(self._mails), 3) + self.assertEqual(len(self._new_mails.exists()), 3) + self.assertEqual(len(self.mailing_list_1.contact_ids.message_ids), 3) + + # 2- Keep archives and reply-to set to 'answer = update thread' + self.mailing_list_1.contact_ids.message_ids.unlink() + mailing = mailing.copy() + mailing.write({ + 'reply_to_mode': 'thread', + }) + self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message']) + + with self.mock_mail_gateway(mail_unlink_sent=True): + mailing.action_send_mail() + + self.assertEqual(len(self._mails), 3) + self.assertEqual(len(self._new_mails.exists()), 3) + self.assertEqual(len(self.mailing_list_1.contact_ids.message_ids), 3) + + # 3- Remove archives and reply-to set to 'answer = new thread' + self.mailing_list_1.contact_ids.message_ids.unlink() + mailing = mailing.copy() + mailing.write({ + 'keep_archives': False, + 'reply_to_mode': 'email', + 'reply_to': self.email_reply_to, + }) + self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message']) + + with self.mock_mail_gateway(mail_unlink_sent=True): + mailing.action_send_mail() + + self.assertEqual(len(self._mails), 3) + self.assertEqual(len(self._new_mails.exists()), 0) + self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message']) + + # 4- Remove archives and reply-to set to 'answer = update thread' + # Imply keeping mail.message for gateway reply) + self.mailing_list_1.contact_ids.message_ids.unlink() + mailing = mailing.copy() + mailing.write({ + 'keep_archives': False, + 'reply_to_mode': 'thread', + }) + self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message']) + + with self.mock_mail_gateway(mail_unlink_sent=True): + mailing.action_send_mail() + + self.assertEqual(len(self._mails), 3) + self.assertEqual(len(self._new_mails.exists()), 0) + self.assertEqual(len(self.mailing_list_1.contact_ids.message_ids), 3) + + @users('user_marketing') + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_mailing_on_res_partner(self): + """ Test mailing on res.partner model: ensure default recipients are + correctly computed """ + partner_a = self.env['res.partner'].create({ + 'name': 'test email 1', + 'email': 'test1@example.com', + }) + partner_b = self.env['res.partner'].create({ + 'name': 'test email 2', + 'email': 'test2@example.com', + }) + self.env['mail.blacklist'].create({'email': 'Test2@example.com',}) + + mailing = self.env['mailing.mailing'].create({ + 'name': 'One', + 'subject': 'One', + 'mailing_model_id': self.env['ir.model']._get('res.partner').id, + 'mailing_domain': [('id', 'in', (partner_a | partner_b).ids)], + 'body_html': 'This is mass mail marketing demo' + }) + mailing.action_put_in_queue() + with self.mock_mail_gateway(mail_unlink_sent=False): + mailing._process_mass_mailing_queue() + + self.assertMailTraces( + [{'partner': partner_a}, + {'partner': partner_b, 'state': 'ignored'}], + mailing, partner_a + partner_b, check_mail=True + ) + + @users('user_marketing') + @mute_logger('odoo.addons.mail.models.mail_mail') + def test_mailing_shortener(self): + mailing = self.env['mailing.mailing'].create({ + 'name': 'TestSource', + 'subject': 'TestShortener', + 'body_html': """<div> +Hi, +% set url = "www.odoo.com" +% set httpurl = "https://www.odoo.eu" +Website0: <a id="url0" href="https://www.odoo.tz/my/${object.name}">https://www.odoo.tz/my/${object.name}</a> +Website1: <a id="url1" href="https://www.odoo.be">https://www.odoo.be</a> +Website2: <a id="url2" href="https://${url}">https://${url}</a> +Website3: <a id="url3" href="${httpurl}">${httpurl}</a> +External1: <a id="url4" href="https://www.example.com/foo/bar?baz=qux">Youpie</a> +Email: <a id="url5" href="mailto:test@odoo.com">test@odoo.com</a></div>""", + 'mailing_model_id': self.env['ir.model']._get('mailing.list').id, + 'reply_to_mode': 'email', + 'reply_to': self.email_reply_to, + 'contact_list_ids': [(6, 0, self.mailing_list_1.ids)], + 'keep_archives': True, + }) + + mailing.action_put_in_queue() + + with self.mock_mail_gateway(mail_unlink_sent=False): + mailing._process_mass_mailing_queue() + + self.assertMailTraces( + [{'email': 'fleurus@example.com'}, + {'email': 'gorramts@example.com'}, + {'email': 'ybrant@example.com'}], + mailing, self.mailing_list_1.contact_ids, check_mail=True + ) + + for contact in self.mailing_list_1.contact_ids: + new_mail = self._find_mail_mail_wrecord(contact) + for link_info in [('url0', 'https://www.odoo.tz/my/%s' % contact.name, True), + ('url1', 'https://www.odoo.be', True), + ('url2', 'https://www.odoo.com', True), + ('url3', 'https://www.odoo.eu', True), + ('url4', 'https://www.example.com/foo/bar?baz=qux', True), + ('url5', 'mailto:test@odoo.com', False)]: + # TDE FIXME: why going to mail message id ? mail.body_html seems to fail, check + link_params = {'utm_medium': 'Email', 'utm_source': mailing.name} + if link_info[0] == 'url4': + link_params['baz'] = 'qux' + self.assertLinkShortenedHtml( + new_mail.mail_message_id.body, + link_info, + link_params=link_params, + ) diff --git a/addons/mass_mailing/tests/test_mailing_list.py b/addons/mass_mailing/tests/test_mailing_list.py new file mode 100644 index 00000000..5501f944 --- /dev/null +++ b/addons/mass_mailing/tests/test_mailing_list.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import exceptions +from odoo.addons.mass_mailing.tests.common import MassMailCommon +from odoo.tests.common import Form, users + + +class TestMailingListMerge(MassMailCommon): + + @classmethod + def setUpClass(cls): + super(TestMailingListMerge, cls).setUpClass() + cls._create_mailing_list() + + cls.mailing_list_3 = cls.env['mailing.list'].with_context(cls._test_context).create({ + 'name': 'ListC', + 'contact_ids': [ + (0, 0, {'name': 'Norberto', 'email': 'norbert@example.com'}), + ] + }) + + + @users('user_marketing') + def test_mailing_contact_create(self): + default_list_ids = (self.mailing_list_2 | self.mailing_list_3).ids + + # simply set default list in context + 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, + } for x in range(0, 5)]) + self.assertEqual(new.list_ids, (self.mailing_list_2 | self.mailing_list_3)) + + # default list and subscriptions should be merged + 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, { + 'list_id': self.mailing_list_1.id, + 'opt_out': True, + }), (0, 0, { + 'list_id': self.mailing_list_2.id, + 'opt_out': True, + })], + } for x in range(0, 5)]) + self.assertEqual(new.list_ids, (self.mailing_list_1 | self.mailing_list_2 | self.mailing_list_3)) + # should correctly take subscription opt_out value + for list_id in (self.mailing_list_1 | self.mailing_list_2).ids: + new = new.with_context(default_list_ids=[list_id]) + self.assertTrue(all(contact.opt_out for contact in new)) + # not opt_out for new subscription without specific create values + for list_id in self.mailing_list_3.ids: + new = new.with_context(default_list_ids=[list_id]) + self.assertFalse(any(contact.opt_out for contact in new)) + + @users('user_marketing') + def test_mailing_list_contact_copy_in_context_of_mailing_list(self): + MailingContact = self.env['mailing.contact'] + contact_1 = MailingContact.create({ + 'name': 'Sam', + 'email': 'gamgee@shire.com', + 'subscription_list_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() + self.assertEqual(contact_1.list_ids, contact_2.list_ids, 'Should copy the existing mailing list(s)') + + @users('user_marketing') + def test_mailing_list_merge(self): + # TEST CASE: Merge A,B into the existing mailing list C + # The mailing list C contains the same email address than 'Norbert' in list B + # This test ensure that the mailing lists are correctly merged and no + # duplicates are appearing in C + merge_form = Form(self.env['mailing.list.merge'].with_context( + active_ids=[self.mailing_list_1.id, self.mailing_list_2.id], + active_model='mailing.list' + )) + merge_form.new_list_name = False + merge_form.dest_list_id = self.mailing_list_3 + merge_form.merge_options = 'existing' + merge_form.archive_src_lists = False + result_list = merge_form.save().action_mailing_lists_merge() + + # Assert the number of contacts is correct + self.assertEqual( + len(result_list.contact_ids.ids), 5, + 'The number of contacts on the mailing list C is not equal to 5') + + # Assert there's no duplicated email address + self.assertEqual( + len(list(set(result_list.contact_ids.mapped('email')))), 5, + 'Duplicates have been merged into the destination mailing list. Check %s' % (result_list.contact_ids.mapped('email'))) + + @users('user_marketing') + def test_mailing_list_merge_cornercase(self): + """ Check wrong use of merge wizard """ + with self.assertRaises(exceptions.UserError): + merge_form = Form(self.env['mailing.list.merge'].with_context( + active_ids=[self.mailing_list_1.id, self.mailing_list_2.id], + )) + + merge_form = Form(self.env['mailing.list.merge'].with_context( + active_ids=[self.mailing_list_1.id], + active_model='mailing.list', + default_src_list_ids=[self.mailing_list_1.id, self.mailing_list_2.id], + default_dest_list_id=self.mailing_list_3.id, + default_merge_options='existing', + )) + merge = merge_form.save() + self.assertEqual(merge.src_list_ids, self.mailing_list_1 + self.mailing_list_2) + self.assertEqual(merge.dest_list_id, self.mailing_list_3) |
